본문 바로가기
SPRING

스프링 공부 - 4

by HUNIIIIII 2022. 9. 1.

● 회원 서비스 개발

 

리포지토리에 생성해놨던 메소드들을 이용해 아래와 같이 service 패키지 생성  후 서비스를 설계한다.

특히 Optional 을 이해했다면 쉽게 활용하여 예외처리를 해줄 수가 있다.

(이름 중복 확인하는 validateDuplicateMember 메소드 참조 )

package hello.hellospring.service;

import java.util.List;
import java.util.Optional;

import hello.hellospring.domain.Member;
import hello.hellospring.repository.MemberRepository;
import hello.hellospring.repository.MemoryMemberRepository;

public class MemberService {
	
	//서비스의 경우 비지니스적인 용어를 사용/ 비지니스에 의존적으로 설계
	//리포지토리의 경우 단순하게 기계적인 네이밍 룰 선택
	
	private final MemberRepository memberRepository = new MemoryMemberRepository();
	
	
	/*  
	 * 회원 가입
	 * */    
	public Long join(Member member) {
		//같은 이름이 있는 중복 회원 X
		validateDuplicateMember(member);
		memberRepository.save(member);
		return member.getId();
	}
	
	private void validateDuplicateMember(Member member) {
		memberRepository.findByName(member.getName())
		.ifPresent(m -> {
				throw new IllegalStateException("이미 존재하는 회원입니다.");
		});
	}
	
	
	/* 
	 * 전체 회원 조회
	 *  */
	public List<Member> findMembers(){
		
		return memberRepository.findAll();
	}
	
	public Optional<Member> findOne(Long memberId){
		return memberRepository.findById(memberId);
	}

}

 

 

 

이렇게 생성이 되었으면 리포지토리를 테스트해보았을때와 마찬가지로 테스트를 진행해보아야한다.


● 회원 서비스 테스트

 

회원 서비스 클래스를 생성해 메소드도 똑같이 생성해준다.

테스트를 할때 정상적인 회원가입 테스트도 중요하지만, 예외상황이 잘 발생하는지에 대해 테스트하는 것이 더욱 중요하다. JUnit 의 Asserttions 클래스의 assertThrows 메소드를 활용하여 아래와 같이 제대로된 Exception 이 발생하였는지 확인가능하다.

 

추가적으로 MemberService 와 MemberServiceTest 에서  같은 MemoryMemberRepository 를 사용해야 다른 메서드에서 접근하더라고 같은 곳에 저장이 되므로 아래와 같이 MemberService 에서 매개변수를 받는 생성자를 통해서 생성되게 하고 MemberServiceTest 에서 BeforeEach 를 활용해 테스트 시작 전 MemoryMemberRepository 객체를 생성하고 MemberService 를 매개변수로 넣어 같은 MemoryMemberRepository 를 받을 수 있도록 생성한다.

 

이런 방식을 Dependency injection ( DI ) 라고 한다.

//MemberService.java

private final MemberRepository memberRepository;
	
	
	public MemberService(MemberRepository memberRepository) {
		this.memberRepository = memberRepository;
	}

 

package hello.hellospring.service;

import static org.assertj.core.api.Assertions.fail;
import static org.junit.jupiter.api.Assertions.fail;

import java.util.List;
import java.util.Optional;

import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import hello.hellospring.domain.Member;
import hello.hellospring.repository.MemoryMemberRepository;

public class MemberServiceTest {
	
	MemberService memberService;
	MemoryMemberRepository memberRepository;

	@BeforeEach
	public void beforeEach() {
		memberRepository  = new MemoryMemberRepository();
		memberService = new MemberService(memberRepository);
	}
	
	@AfterEach
	public void afterEach() {
		memberRepository.clearStore();
	}
	
	@Test
	void 회원가입() {
		//given 무언가 주어졌을때
		Member member  = new Member();
		member.setName("spring");
		
		//when 이걸 실행하였을때 
		Long saveId = memberService.join(member);
		
		//then 결과가 이렇게 나와야해
		Member findMember = memberService.findOne(saveId).get();
		Assertions.assertThat(member.getName()).isEqualTo(findMember.getName());
	}
	
	@Test
	public void 중복_회원예외() {
		//given
		Member member1 = new Member();
		member1.setName("spring");
		
		Member member2 = new Member();
		member2.setName("spring");
		
		
		//when
		memberService.join(member1);
		IllegalStateException e = org.junit.jupiter.api.Assertions.assertThrows(IllegalStateException.class, () -> memberService.join(member2));
		
		Assertions.assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다.");
		
//		try {
//			memberService.join(member2);
//			fail();
//		}catch (IllegalStateException e) {
//			Assertions.assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다.");
//		}
		
		
		//then
	}
	
	@Test
	void findMembers(){
		
	}
	
	@Test
	void findOne(){
		
	}

}

● 컴포넌트 스캔과 자동 의존관계 설정

 

회원 컨트롤러가 회원서비스와 회원 리포지토리를 사용할 수 있게 의존관계를 준비해보자

우리가 보통 컨트롤러에서 new 연산자를 통해 new MemberService 하여 서비스의 기능들을 가져다 쓸 수 있지만

다른 컨트롤러에서도 MemberService를 사용하는 일이 많을 것이다. 이럴때 여러군데에서 new 를 통해 새로 생성하기보단

스프링의 의존성 주입 기능을 통해 객체 하나로 간편하게 관리하는 것이 좋다. 자세한 내용을 알아보자.

 

아래와 같이 컨트롤러 생성시 @Controller 어노테이션을 이용해 스프링 빈에 MemberController 를 등록해준다.

그리고 자주 쓸 서비스를 생성해주고 MemberController 호출 시 받아오도록 한다. 이때 어디서 받아오느냐가 문제인데

@Autowired 를 통해 스프링 빈에 MemberService 가 등록되어있는지 확인 후 MemberService 를 가져와 의존성을 주입해준다. 이것이 Dependency Injection ( 의존성 주입 ) 이다. 

단, MemberService 가 @Service 어노테이션으로 서비스 등록이 되어있어야한다.

 

마찬가지로 서비스에서도 멤버 리포지토리를 사용할텐데 @Autowired 어노테이션을 통해 의존성을 주입해준다.

 

하기와 같이 controller-service-repository 의존성이 만들어지게 된다. 

 


자바 코드로 직접 스프링 빈 등록하기

 

두번째 방법은 직접 스프링 빈을 등록하는 방법이 있다.

예전에는 XML로 설정하는 방식도 있지만 최근에는 잘 사용하지 않으므로 생략한다.

 

아래와 같이 config 클래스 생성 후 @Configuration 어노테이션을 이용해 spring 이 시작될 때 스캔되도록 설정한다.

@Bean 어노테이션을 이용해 빈을 등록할 메소드에 붙여주고 빈에 등록할 객체를 반환값에 작성한다.

 return 값에 객체를 반환해주면 끝이 난다.

package hello.hellospring;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import hello.hellospring.repository.MemberRepository;
import hello.hellospring.repository.MemoryMemberRepository;
import hello.hellospring.service.MemberService;

@Configuration
public class SpringConfig {
	
	@Bean
	public MemberService memberService() {
		return new MemberService(memberRepository());
	}
	
	@Bean
	public MemberRepository memberRepository() {
		return new MemoryMemberRepository();
	}

}

 

직접 작성하는 것과 컴포넌트 스캔을 통한 방법 중 편리한 것은 당연히 한줄만 작성해주면 되는 컴포넌트 스캔 방법이다.

하지만 각각의 장단점이 존재한다. 


DI에는 필드 주입, setter 주입, 생성자 주입 이렇게 3가지 방법이 있다.

의존관계가 실행중에 동적으로 변하는 경우는 거의 없으므로 생성자 주입을 권장한다

 

 

*필드 주입 방식

@Autowired private MemberService memberService;

 

*setter 주입 방식

- setter 를 사용하면 다른 곳에서도 사용을 할 수 있기 때문에 좋은 방법은 아니다.

private MemberService  memberService;

@Autowired
public void setMemberService(MemberService memberService){
	this.memberService = memberService;
}

 

*생성자를 통한 주입 방식 ( 요즘 사용하는 방식 )

- 생성자를 통해 주입하게 되면 생성하는 시점에 넣고 막도록 하는 것이 포인트

//final 을 사용할 수 있다.
private final MemberService memberService;

@Autowired
public MemberController(MemberService memberService) {
	this.memberService = memberService;
}

참조 글 https://soobarkbar.tistory.com/230

 


컴포넌트 스캔과 직접 스프링 빈 등록하는 방법은 언제언제 사용해야하는가? 

실무에서는 주로 정형화된 컨트롤러, 서비스, 리포지토리 같은 코드는 컴포넌트 스캔을 사용한다.

그리고 정형화 되지 않거나, 상황에 따라 구현 클래스를 변경해야 하면 설정을 통해 스프링 빈으로 등록한다.

 

 

'SPRING' 카테고리의 다른 글

스프링 공부 - 6  (0) 2022.09.14
스프링 공부 - 5  (0) 2022.09.03
스프링 공부 - 3  (1) 2022.08.27
스프링 공부 - 2  (0) 2022.08.27
스프링 공부 - 1  (0) 2022.08.24