스프링 설정 클래스와 컴포넌트 스캔
@Configuration이란
-
@Configuration: 스프링 설정 클래스에 설정하는 어노테이션 -
스프링 설정 클래스: 스프링 컨테이너가 등록할 빈 정보와 의존관계 정보를 담는 클래스
-
@Bean메서드를 작성해 어떤 객체를 스프링 빈으로 등록할지 지정
@Configuration
public class AppConfig {
@Bean
public OrderService orderService() {
return new OrderService(orderRepository());
}
@Bean
public OrderRepository orderRepository() {
return new JdbcOrderRepository();
}
}
-
위 코드에서
orderService()와orderRepository()는 스프링 빈을 등록하는 메서드 -
스프링 컨테이너는 이 설정 정보를 읽고, 메서드가 반환하는 객체를 빈으로 관리
-
@Configuration클래스도 스프링 빈으로 등록됨 -
스프링은 기본적으로 설정 클래스를 프록시로 감싸서
@Bean메서드 호출을 가로챔 -
설정 클래스 안에서
@Bean메서드를 직접 호출하더라도 새 객체를 만들지 않고, 컨테이너가 관리하는 빈을 반환함 -
따라서 싱글톤 스코프의 빈은 설정 클래스 내부에서 여러 번 호출되더라도 컨테이너가 관리하는 동일한 인스턴스로 유지됨
@Bean을 이용한 빈 수동 등록

@Bean: 메서드가 생성하는(반환하는) 객체를 스프링 빈으로 등록하는 어노테이션- 주로 설정 클래스 안의
@Bean메서드를 통해 빈 등록
@Configuration
public class AppConfig {
@Bean
public OrderRepository orderRepository() {
return new JdbcOrderRepository();
}
}
- 위 코드에서
orderRepository()메서드가 반환하는JdbcOrderRepository객체는 스프링 빈으로 등록됨 - 기본 빈 이름은 메서드 이름인
orderRepository가 됨
@Configuration
public class AppConfig {
@Bean(name = "jdbcOrderRepository")
public OrderRepository orderRepository() {
return new JdbcOrderRepository();
}
}
name속성으로 빈 이름을 직접 지정 가능
- 외부 라이브러리 클래스처럼 직접
@Component를 붙일 수 없는 객체는@Bean메서드로 생성해 스프링 빈으로 등록할 수 있음
@Configuration
public class ExternalConfig {
@Bean
public ObjectMapper objectMapper() {
return new ObjectMapper();
}
}
@Bean을 사용하면 같은 타입의 구현체 중 어떤 객체를 빈으로 등록할지 설정 코드에서 직접 결정할 수 있음- 생성자에 전달할 값이나 객체를 만들 때 필요한 설정들도 함께 작성할 수 있어, 객체 생성 방식이 코드에 명시적으로 드러남
- 단, 등록해야 할 빈이 많아지면 설정 코드가 비대해짐
컴포넌트 스캔

- 컴포넌트 스캔: 스프링이 지정된 패키지 범위에서 빈으로 등록할 클래스를 찾아 스프링 빈으로 등록하는 방식
@Component또는@Component를 포함한 어노테이션이 붙은 클래스는 컴포넌트 스캔 후보가 됨
@Component
public class OrderService {
private final OrderRepository orderRepository;
public OrderService(OrderRepository orderRepository) {
this.orderRepository = orderRepository;
}
}
-
위 클래스가 컴포넌트 스캔 범위에 포함되어 있다면, 스프링은
OrderService를 빈으로 등록함 -
생성자에
OrderRepository가 필요하므로, 컨테이너에서 주입 가능한OrderRepository빈을 찾아 생성자에 전달함 -
컴포넌트 스캔을 사용하면 개발자가 설정 클래스에 모든 빈을 작성하지 않아도 됨
-
애플리케이션 내부에서 작성한 서비스, 레포지토리, 컨트롤러는 보통
@Service,@Repository,@Controller같은 어노테이션을 붙여 컴포넌트 스캔으로 등록함
@SpringBootApplication
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
}
}
- 스프링 부트에서는
@SpringBootApplication안에@ComponentScan이 포함됨 - 따라서 메인 애플리케이션 클래스가 있는 패키지와 그 하위 패키지는 기본적으로 스캔 대상이 됨
@ComponentScan 동작 원리
@ComponentScan: 지정한 패키지 범위에서 빈으로 등록할 클래스를 탐색하는 어노테이션
@Configuration
@ComponentScan(basePackages = "com.example.order")
public class AppConfig {
}
-
basePackages: 컴포넌트 스캔을 시작할 패키지 범위를 지정하는 옵션basePackages옵션이 없다면@ComponentScan이 붙은 클래스의 패키지가 스캔 시작점이 됨
-
위 예제에서는
com.example.order패키지와 그 하위 패키지를 스캔 대상으로 지정함 -
일반적으로
@ComponentScan클래스는 스캔해야 할 컴포넌트들을 포함하는 공통 상위 패키지에 두는 것이 좋음 -
컴포넌트 스캔 대상이 되는 어노테이션:
@Component,@Service,@Repository,@Controller
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Service {
@AliasFor(annotation = Component.class)
String value() default "";
}
@Service,@Repository,@Controller는 내부적으로@Component를 포함
@Service("orderServiceV1")
public class OrderService {
}
-
어노테이션의
value옵션으로 빈 이름을 직접 지정할 수 있음 -
컴포넌트 스캔 범위가 넓어질수록 의도하지 않은 클래스가 빈으로 등록될 수 있음
-
스프링 부트에서는 보통 메인 애플리케이션 클래스를 프로젝트의 공통 상위 패키지에 두고, 그 하위 패키지 안에 컴포넌트를 배치함
@Component, @Service, @Repository, @Controller
-
@Component: 컴포넌트 스캔으로 스프링 빈 등록 대상이 되도록 표시하는 어노테이션 -
@Component는 일반 컴포넌트 클래스에 사용하고,@Service는 서비스 계층,@Repository는 데이터 접근 계층,@Controller는 웹 계층의 클래스에 사용함 -
@Service,@Repository,@Controller를 사용하는 이유는 클래스의 역할을 코드에 드러내기 위해서임 -
세 어노테이션은 모두 내부적으로
@Component를 포함하므로, 컴포넌트 스캔으로 빈 등록 대상이 될 수 있음 -
@Repository는 데이터 접근 예외 변환과도 관련 있음 -
스프링은
@Repository가 붙은 클래스에서 발생한 데이터 접근 예외를DataAccessException계층으로 변환할 수 있음
@Component
public class AppBeans {
@Bean
public OrderRepository orderRepository() {
return new JdbcOrderRepository();
}
}
@Component가 붙은 클래스 안에도@Bean메서드를 작성할 수 있음- 위 코드에서
AppBeans는@Component가 붙어 있으므로 컴포넌트 스캔으로 스프링 빈이 됨 - 스프링은
AppBeans빈을 처리하면서 그 안의@Bean메서드도 읽음 orderRepository()메서드가 반환하는 객체를 별도의 스프링 빈으로 등록함- 즉,
AppBeans도 빈이고,orderRepository()가 생성한JdbcOrderRepository도 빈이 됨
@Component
public class AppBeans {
@Bean
public OrderRepository orderRepository() {
return new JdbcOrderRepository();
}
@Bean
public OrderService orderService() {
return new OrderService(orderRepository());
}
}

- 위 코드에서
AppBeans는 컴포넌트 스캔으로 빈이 되고,orderRepository()와orderService()가 반환하는 객체도 각각 빈으로 등록될 수 있음 - 단,
AppBeans는@Configuration클래스가 아니라@Component클래스이므로,@Bean메서드 간 호출이 프록시로 가로채지지 않음 - 즉,
orderService()안에서 호출한orderRepository()는 컨테이너에서orderRepository빈을 조회하는 호출이 아니라, 일반적인 메서드 호출로 동작함 - 이 경우
orderService()가 실행될 때new JdbcOrderRepository()가 직접 실행되어, 컨테이너가 관리하는orderRepository빈과 별개의 객체가 만들어질 수 있음
@Component
public class AppBeans {
@Bean
public OrderRepository orderRepository() {
return new JdbcOrderRepository();
}
@Bean
public OrderService orderService(OrderRepository orderRepository) {
return new OrderService(orderRepository);
}
}
- 이전과 같은 문제를 피하려면 다른
@Bean메서드를 직접 호출하지 말고, 필요한 빈을 메서드 파라미터로 전달받는 방식으로 작성하는 것이 좋음 - 즉,
@Component클래스에서도@Bean메시드 선언은 가능하지만@Bean메서드끼리 직접 호출하는 구조는 피하는 것이 좋음
댓글