Published on

Spring Boot service, repository bean 등록 방법

Authors

일반적인 웹 애플리케이션(Backend)은 5가지 구성요소로 구분된다.

  • 컨트롤러: 웹 MVC의 컨트롤러 역할
  • 서비스: 핵심 비즈니스 로직 구현
  • 리포지토리: 데이터베이스의 접근, 도메인 객체를 DB에 저장하고 관리
  • 도메인: 비즈니스 도메인 객체
  • DB: 실제 데이터를 저장하는 별도의 프로세스(Spring의 범위 밖)

Spring에서는 서비스나, 리포지토리와 같은 요소를 Bean이라는 객체로 관리하여, 필요한 곳 필요한 시점에 자동으로 주입해준다.(이를 Dependency Injection 라고 한다) 해당 요소들을 Spring에서 관리하게 하는 이유에 대해서는 다음 포스팅에서 자세히 다뤄보겠다. Spring에서 Bean으로 관리되도록 하기 위해서 필요한 최소한의 Annotation은 @Component 이지만, 일반적으로service의 경우 @Service를 아래처럼 달아준다.(@Service 어노테이션은 내부적으로 @Component 를 포함하고 있다.)이는 Component Scan 방식을 이용한 Bean 등록방식이고, annotation이 아닌 Java Code로 명시적으로 Spring Bean을 등록하는 방법도 있다.

Component Scan 방식으로 자동 의존관계 설정

MemberService 등록

예를들어 MemberService라는 서비스를 등록한다면, 아래처럼 @Service annotation을 붙여준다. @AutoWired를 생성자에 붙여준 이유는 Spring에서 관리하는 Bean중 memberRepository를 자동으로 주입하게끔 하기 위함이다.

@Service
public class MemberService {
    private final MemberRepository memberRepository;

    @Autowired
    public MemberService(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }

    /*
            회원 가입
         */
    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);
    }
}

MemoryMemberRepository 등록

Repository의 경우 @Repository annotation을 붙여준다.

MemoryMemberRepository.java
@Repository
public class MemoryMemberRepository implements MemberRepository{
    private static Map<Long, Member> store  = new HashMap<>();
    private static long sequence = 0L;
    @Override
    public Member save(Member member) {
        member.setId(++sequence);
        store.put(member.getId(), member);
        return member;
    }

    @Override
    public Optional<Member> findById(Long id) {
        return Optional.ofNullable(store.get(id));
    }

    @Override
    public Optional<Member> findByName(String name) {
        return store.values().stream()
                .filter((member) -> member.getName().equals(name))
                .findAny();
    }

    @Override
    public List<Member> findAll() {
        return new ArrayList<>(store.values());
    }

    public void clearStore(){
        store.clear();
    }
}

@Repository Annotation 또한 @Component를 가지고 있다.

Repository.java
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Repository {

	/**
	 * The value may indicate a suggestion for a logical component name,
	 * to be turned into a Spring bean in case of an autodetected component.
	 * @return the suggested component name, if any (or empty String otherwise)
	 */
	@AliasFor(annotation = Component.class)
	String value() default "";

}

Note 해당 어노테이션들은 모든 패키지에서 동작하는 것은 아니고, @SpringBootApplication 어노테이션이 붙어있는 패키지와 그 하위 패키지들에만 적용 된다. Spring Bean은 싱글톤(SingleTon)으로 생성되며, 1개만 생성되어 관리된다.

Java 코드로 Spring Bean 등록

@service, @Repository, @AutoWired 어노테이션을 사용하지 않고, Spring Bean을 등록하는 방법을 알아보겠습니다. 기존에 Bean 주입을 위해서 달아두었던 위의 3개의 어노테이션을 지우고 SpringConfig.java라는 파일을 생성하여 아래와 같이 작성해줍니다. @Configuration 어노테이션을 통해 Spring과 관련된 파일임을 등록하고 @Bean어노테이션을 붙여 Bean에 등록할 객체들을 생성하여 Return하는 함수를 작성합니다.

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

    @Bean
    public MemberRepository memberRepository(){
        return new MemoryMemberRepository();
    }
}

DI에는 생성자 주입, 필드 주입, 세터주입 3가지 방식이 있다. 필드주입의 경우, 생성할 때를 제외해서는 필드를 변경할 수 없다는 단점이 있고, 세터 주입의 경우 setter가 Public으로 열려있어야 하기 떄문에 노출이 된다는 점에서 좋지 않다. 제일 권장되는 방식은 생성자를 통해서 주입되는 방식이다. 실무에서는 컴포넌트 스캔 방식으로 개발이 진행되지만, 상황에 따라 구현클래스를 다른 Bean으로 변경해야 할 경우 기존 코드를 건드리지 않고 변경하기 위해서 Java code로 Bean을 설정하는 방식도 사용된다.

References