@Configuration
어노테이션은 스프링에게 해당 클래스가 애플리케이션의 구성(configuration)을 정의하는 클래스임을 나타낸다. 해당 클래스 내부에는 Bean 정의를 나타내는 @Bean
어노테이션이 적용된 메서드가 존재한다.
@Configuration
public class AppConfig {
@Bean
public Choco getChoco() {
return new Choco();
}
@Bean
public Chocochip getChocochip() {
return new Chocochip(getChoco());
}
}
이때 @Bean
을 메서드 사용하여 수동으로 등록한다. Bean을 등록해줄 때에는 메소드 이름으로 Bean 이름이 결정된다.
@Configuration
을 활용한 수동 Bean 등록의 경우, 다음 두 가지 경우에 대해 사용하는 것이 좋다.
애플리케이션의 로직은 비즈니즈 로직과 인프라 로직으로 나눌 수 있다.
비즈니즈 로직 관련 Bean은 Component Scan 기능을 적극 활용하는 것이 좋다. 문제가 발생해도, 어떤 곳에서 문제가 발생했는지 명확하게 파악하기 쉽기 때문이다.
하지만 인프라 로직 Bean은 비즈니즈 로직 Bean에 비해 그 수가 매우 적으면서도 애플리케이션 전반에 걸쳐 광범위하게 영향을 미친다.
그러나 문제가 발생 했을 때 문제의 위치를 파악하기 어렵고, 애플리케이션에 적용이 잘 되고 있는지 아닌지 파악하기 어려운 경우가 많다. 따라서 가급적 @Configuration
을 활용한 수동 Bean 등록을 사용하여 문제들이 명확하게 드러날 수 있도록 하는 것이 좋다.
즉, 애플리케이션에 광범위하게 영향을 미치는 인프라 로직 관련 객체는 수동 Bean으로 등록하여 설정 정보에 명확히 나타나게 하는 것이 유지 보수하기에 좋다.
@Configuration
을 활용한 수동 Bean 등록과 Component Scan의 경우 모두 최대한 인터페이스의 구현 Bean들을 따로 모아 특정 패키지에 모아두어, Bean의 이름을 쉽게 파악할 수 있도록 할 수 있다.
다음과 같이 Piece의 종류 중 하나인 King과 Queen 클래스가 있다.
public interface Piece {
void move();
}
public class King implements Piece {
@Override
public void move() {
System.out.println("King Moved");
}
}
public class Queen implements Piece{
@Override
public void move() {
System.out.println("Queen Moved");
}
}
List, Map을 통해 같은 자동적으로 Piece타입 내 모든 Bean들을 주입 받아, 클라이언트의 요청에 따라 동적으로 구현 객체를 선택하도록 만든 PieceService 에 대하여, @Configuration
을 통해 별도의 설정 정보 클래스인 PieceConfig 클래스를 만들어 수동으로 Bean을 등록하는 방식으로 변경하면 다음과 같다.
@Configuration
public class PieceConfig {
@Bean
public Piece king() {
return new King();
}
@Bean
public Piece queen() {
return new Queen();
}
}
위와 같이 Piece의 구현 Bean들만 따로 모아 특정 패키지에 모아두면, PieceService가 의존 관계 자동 주입으로 Map<String, Piece>에 주입을 받는 상황에서, 어떤 Bean들이 조회되어 주입되는지, 각 Bean들의 이름이 무엇인지 쉽게 파악할 수 있게 된다.
결론부터 말하자면 등록된 클래스가 proxy로 등록되는지 아닌지의 차이이다.
아래 클래스에 @Configuration
와 @Component
를 적용시켜보자.
public class AppConfig {
@Bean
public Choco getChoco() {
return new Choco();
}
@Bean
public Chocochip getChocochip() {
return new Chocochip(getChoco());
}
}
@Configuration
를 적용했을 때에는 AppConfig
클래스에 CGLIB 프록시가 등록된 것이다.
반면에 @Component
를 적용했을 때에는 실제 AppConfig 객체가 Bean으로 등록된다.
그럼 왜 @Configuration
을 적용한 클래스는 Proxy로 등록되는 것일까? 이는 완전한 싱글톤을 보장하기 위함이다. @Configuration
을 적용하는 경우는 수동으로 불가피하게 Bean으로 등록해야 하는 상황일 것이다. 예를 들어 개발자가 직접 제어가 불가능한 라이브러리를 Bean으로 등록할 때 불가피하게 사용하는 경우가 있을 것이다.
이러한 상황에서 실수로 Bean을 생성하는 라이브러리의 메소드를 여러 번 호출하였다면 불필요하게 여러 개의 Bean이 생성이 된다. 스프링은 이러한 문제를 방지하고자 @Configuration
이 있는 클래스를 객체로 생성할 때 CGLib 라이브러리를 사용해 프록시 패턴을 적용한다.
그래서 @Bean
이 있는 메소드를 여러 번 호출하여도 항상 동일한 객체를 반환하여 싱글톤을 보장한다.