본문 바로가기

Spring/스프링 입문

03. 스프링 빈과 의존관계 - (2) 자바 코드로 직접 스프링 빈 등록하기

💡 본 게시글은 김영한님의 인프런(Inflearn) 강의 스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술에 대해 공부하고, 정리한 내용입니다.

1) 코드로 직접 등록하기 전 상태 되돌리기

 이번에는 컴포넌트 스캔을 사용하지 않고, 자바 코드로 직접 `Service`와 `Repository`를 Spring Bean에 등록해보도록 하겠습니다.

그러기 위해서는 먼저 기존에 컴포넌트 스캔을 위해 적용했던 어노테이션들을 제거해야 합니다.

(1) `MemberService`와 `MemberRepository` 클래스에서 `@Service`, `@Repository` 그리고 `@Autowired` 어노테이션을 제거합니다.

 

(2) 이때, `MemberController`는 어노테이션을 제거하지 않습니다. 이 클래스는 여전히 Spring 컨테이너에 등록되어야 하므로 `@Controller` 어노테이션을 유지합니다.

 

 이 상태에서 코드를 실행하면 컴포넌트 스캔이 되지 않아 `MemberService`가 Spring Bean에 등록되지 않습니다.

따라서 `MemberService`를 찾을 수 없어 오류가 발생하게 됩니다.

 

이제 이 상태에서 자바 코드를 작성하여 직접 Spring Bean을 등록할 준비가 된 것입니다. 다음 단계에서는 `MemberService`와 `MemberRepository`를 직접 Spring Bean으로 등록하는 방법을 알아보겠습니다.


2) 코드로 직접 등록하기 전 상태 되돌리기

 먼저, service 패키지에 SpringConfig 클래스를 생성하고, @Configuration 어노테이션을 추가합니다. 이 어노테이션은 설정 파일을 만들거나 Bean을 등록하기 위한 것입니다. @Bean을 사용하는 클래스의 경우 반드시 @Configuration을 같이 사용해야 합니다.

@Configuration
public class SpringConfig {

}

(1) MemberService를 spring bean에 등록하기

SpringConfig 클래스에 다음과 같은 코드를 작성합니다.

@Bean
public MemberService memberService() {
    return new MemberService();
}

 

 @Bean 어노테이션은 Spring Bean으로 등록하겠다는 의미입니다. 이렇게 하면 Spring이 시작될 때, @Configuration에서 @Bean을 읽고, "어? Spring Bean에 등록하라는 뜻이네?"하고 인식을 할 것입니다. 그리고 반환된 MemberService를 Spring Bean에 등록해줍니다.


(2) MemberRepository를 spring bean에 등록하기

MemberRepository도 Spring Bean에 등록하겠습니다.

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

 

 MemberService와 마찬가지로 @Bean을 통해 Spring Bean에 등록합니다. 생성자에 매개 변수를 받지 않으므로 오류가 발생하지 않습니다.


(3) MemberService 생성자 보완하기

@Bean
public MemberService memberService() {
    return new MemberService(memberRepository());
}

 

MemoryMemberRepository를 반환하는 memberService 메서드를 생성했으므로, MemberService의 생성자로 전달합니다.


(4) 전체 코드 정리 및 마무리

@Configuration
public class SpringConfig {

    @Bean
    public MemberService memberService() {
        return new MemberService(memberRepository());
    }

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

 

 이렇게 하면 Spring이 시작될 때, @Bean 어노테이션을 보고 MemberService와 MemberRepository를 Spring Bean에 등록합니다. 동시에 memberRepository() 메서드를 호출하여, Spring Bean에 등록되어 있는 MemberRepository를 연결해줍니다. 이는 @Autowired와 유사한 작업입니다.

 

이렇게 하면 Controller, Service, Repository가 서로 연결됩니다. Controller는 Spring이 관리하기 때문에, 컴포넌트 스캔을 통해 Spring 컨테이너에 등록되고, 대부분의 경우 @Autowired로 의존성을 주입받습니다.

 

이상으로 자바 코드로 직접 Spring Bean을 등록하는 방법에 대해 알아보았습니다.


3) DI의 세 가지 방법(Field, Setter, Constructor)

 DI(의존성 주입) 방법에는 필드 주입, 세터 주입, 생성자 주입 총 세 가지가 있습니다.

 

(1) 필드 주입:

  • 멤버 변수 앞에 @Autowired 어노테이션을 붙이는 방식입니다. 하지만 이 방식은 IntelliJ에서도 비추천하며, Spring이 처음 실행될 때만 Spring 컨테이너에서 값을 가져와서 넣어주고, 그 이후로는 값을 변경할 수 없습니다.
@Autowired private MemberService memberService;

 

(2) 세터 주입:

  • 생성 후에 호출되어 Spring 컨테이너에서 값을 가져오는 방식입니다. 단점은 세터가 public으로 설정되어 있어, 누군가가 의도치 않게 값을 수정할 수 있다는 점입니다.
@Controller
public class MemberController {
    private MemberService memberService;

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

 

(3) 생성자 주입:

  • 최근에는 이 방법을 권장하며, 애플리케이션이 조립될 때 생성자를 통해 한 번만 값이 할당되고 끝납니다. 이 방법은 public으로 설정된 세터보다 안전합니다.
@Controller
public class MemberController {
    private final MemberService memberService;

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

 

실무에서는 주로 정형화된 Controller, Service, Repository 등의 코드에는 컴포넌트 스캔을 사용하며, 상황에 따라 구현 클래스를 변경해야 할 경우에는 설정(SpringConfig)을 통해 Spring bean으로 등록합니다. 

 

이럴 경우, 설정 파일을 수정하고 서버를 다시 시작하는 것이 바람직합니다.

 

예를 들어, 기억체로 구현된 MemberRepository를 DB로 변경해야 하는 상황이라면, SpringConfig 클래스에서 return 값을 DbMemberRepository로 변경하면 됩니다. 이런 방식이 코드로 직접 빈을 등록할 때의 장점입니다.

@Configuration
public class SpringConfig {

    @Bean
    public MemberService memberService() {
        return new MemberService(memberRepository());
    }

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