devoong2
devoong2
devoong2
전체 방문자
오늘
어제

인기 글

최근 글

  • Category (35)
    • Java (4)
    • Spring (13)
    • JPA (4)
    • DesignPattern (1)
    • 동시성 (Concurrency) (4)
    • 회고 (1)
    • Redis (1)
    • Network (3)
    • Kafka (2)
    • Spring Batch (2)

최근 댓글

반응형
hELLO · Designed By 정상우.
devoong2

devoong2

[Spring] @Component vs @Configuration
Spring

[Spring] @Component vs @Configuration

2024. 5. 21. 22:20
반응형

예제 및 테스트 코드는 github 에서 확인 가능합니다.

 

@Component VS @Configuration

안녕하세요. 스프링을 사용하면서 위의 두 어노테이션을 자주 사용하고 계실텐데요.
저도 마찬가지로 두 어노테이션을 자주 사용하지만 Bean 으로 선언하거나 생성할 수 있다는 것만 알고있었지
둘 사이에 어떤 차이가 있는지 그리고 어떤 상황에 사용해야 할지에 대해서는 잘 알고있지 못했습니다.

그래서 둘 사이의 용도 그리고 동작방식에 어떤 차이가 있는지 좀 더 알아보기 위해 글을 작성하게 되었습니다.

 

@Component

먼저 @Component 에 대해 간단하게 어떤 어노테이션인지 알아보겠습니다.
스프링 공식문서에서는 @Component 를 다음과 같이 이야기 합니다.

Indicates that the annotated class is a component.
Such classes are considered as candidates for auto-detection when using annotation-based configuration and classpath scanning.

...(중략)

 

ApplicationContext 로딩시 @Component 가 달린 클래스에 대해 Bean 으로 사용될 수 있도록 탐지한다는 의미로 볼 수 있습니다.
즉, @Component 가 달린 클래스에 대해서는 스프링에서 Bean 으로 사용하겠다는 의미입니다.

간단하게 @Component 를 빈으로 등록하는 과정을 살펴보겠습니다.

 

 

@Component 가 정의된 클래스를 찾을때는 @ComponentScan 어노테이션을 이용하여 찾게됩니다.
@ComponentScan 의 basePackageClasses or basePackages 를 지정하여 찾게됩니다.
만약 지정된 패키지가 없다면 @ComponentScan 가 정의된 패키지에서 부터 스캔이 이루어집니다.

 

ComponentScanAnnotationParser

 

그리고 ComponentScanAnnotationParser 클래스의 parse 메소드에서
@ComponentScan 의 설정을 파싱하게 됩니다.
이때 @Component 를 스캔하기 위한 basePackages, excludeFilters, includeFilters 등등 여러 정보들을 파싱합니다.

 

ClassPathBeanDefinitionScanner

 

A bean definition scanner that detects bean candidates on the classpath, registering corresponding bean definitions with a given registry (BeanFactory or ApplicationContext).
Candidate classes are detected through configurable type filters. The default filters include classes that are annotated with Spring's @Component, @Repository, @Service, or @Controller stereotype.

 

BeanFactory 의 ClassPath 에서 Bean 후보를 탐지하고 해당 클래스를
BeanFactory 또는 ApplicationContext 에 등록하는 Scanner 이고

Bean 등록 후보 클래스는 구성 가능한 필터를 통해 감지되는데 Spring의 @Component, @Repository, @Service, @Controller 어노테이션이 포함된다고 이야기 하고 있습니다.

 

즉, ClassPathBeanDefinitionScanner 클래스에서 @Component 가 정의된
클래스를 스캔하고 Bean 으로 등록하는데요.

 

  • Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
    • Bean 등록 후보 클래스 조회
  • String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
    • Bean 이름 생성
  • registerBeanDefinition(definitionHolder, this.registry);:
    • Bean 등록

다음과 같이 Bean 등록이 되는것을 볼 수 있습니다.

 

@Configuration

다음은 @Configuration 에 대한 스프링 공식문서에서 설명하는 내용입니다.

Indicates that a class declares one or more @Bean methods and may be processed by the Spring container to generate bean definitions and service requests for those beans at runtime, for example:

@Configuration
 public class AppConfig {

     @Bean
     public MyBean myBean() {
         // instantiate, configure and return bean ...
     }
 }

 

@Configuration 이 선언된 클래스는 하나 이상의 @Bean 메소드를 선언하고 해당 @Bean 메소드는 스프링 컨테이너를 통해 처리된다고 이야기하고 있습니다.

 

그리고 @Configuration 내부에 @Component 를 가지고 있기에
@Configuration 이 선언된 클래스도 Bean으로 등록됩니다.

 

 

@Configuration

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

    @AliasFor(annotation = Component.class)
    String value() default "";

    boolean proxyBeanMethods() default true;

    boolean enforceUniqueMethods() default true;
}

 

또한, @Bean 메소드는 @Configuration 내부에서 선언해야 Singleton Scope 를 가질 수 있습니다.
바로 @Configuration 의 proxyBeanMethods 설정때문입니다.

 

proxyBeanMethods

Specify whether @Bean methods should get proxied in order to enforce bean lifecycle behavior, e.g. to return shared singleton bean instances even in case of direct @Bean method calls in user code. This feature requires method interception, implemented through a runtime-generated CGLIB subclass which comes with limitations such as the configuration class and its methods not being allowed to declare final.

 

Bean 메서드를 직접 호출하는 경우에도 싱글턴 Bean Instance 를 적용하기 위한
@Bean 메서드의 프록시 여부를 지정한다고 이야기합니다.

즉, proxyBeanMethods 이 true 이면 CGLib 프록시 패턴을 적용해 Bean 이 싱글톤으로 생성됩니다. false 라면 매번 호출시마다 새로운 객체가 반환됩니다.

 

예제코드

ExamConfiguration.java

@Configuration
public class ExamConfiguration {

}

 

ExamComponent.java

@Component
public class ExamComponent {

}

 

다음과 같이 @Configuration 이 선언된 클래스는 CGLIB Proxy 로 등록된것을 확인할 수 있습니다.

 

그래서 @Component, @Configuration 둘의 차이는 ??

사용 용도

@Component 의 경우 일반적으로 직접 컨트롤 가능한 Class에 대해 선언합니다.
자신이 작성한 Class 에 대해 @ComponentScan 을 이용한 Bean 등록도 편리하고
또한 @Component 어노테이션은 클래스 레벨에 선언할 수 있습니다.

 

@Component

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Indexed
public @interface Component {
}

 

@Configuration 의 경우에는 Bean 으로 등록하기 위해서는 @Bean 이 선언된 메서드 내부에서 생성한 객체를
Bean 으로 등록할 수 있는데요.

 

@Bean

@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Bean {
}

다음과 같이 클래스 레벨에는 선언할 수 없습니다.
외부 라이브러리를 Bean 으로 등록해서 사용하려는 경우 해당 코드를 직접 수정할 수 없기에 @Component 는 사용할 수 없습니다.

대신 @Configuration 의 @Bean Method 을 이용해서 Bean 으로 등록해서 사용하게 됩니다.

@Configuration
public class ExampleConfiguration {

 @Bean
 public ObjectMapper objectMapper() {
   return new ObjectMapper();
 }
}

 

이렇듯, @Configuration 은 외부 라이브러리같은 개발자가 제어할 수 없는 코드에 대해 Bean 으로 등록하거나 특정 Bean 들을 한곳에서 관리하고 싶을때 사용할 수 있습니다.

 

Bean LifeCycle 예제코드

위에서 이야기했든 @Configuration 에서 Bean Method 를 선언하면
proxyBeanMethods 옵션에 따라 싱글턴으로 Bean 이 등록될 수 있다고 했습니다.

그렇다면 @Component 에서는 Bean Method 를 선언했을때
@Configuration 와 어떻게 다른지 코드로 한번 살펴보겠습니다.

 

Exam.java

public class Exam {
}

 

ExamComponent.java

@Component
public class ExamComponent {

  @Bean(name = "exam2")
  public Exam exam() {
    return new Exam2();
  }
}

 

ExamConfiguration.java

@Configuration
public class ExamConfiguration {

  @Bean
  public Exam exam() {
    return new Exam();
  }

}

 

ExamTest.java

@SpringBootTest
public class ExamTest {

  @Autowired
  private ExamConfiguration examConfiguration;

  @Test
  void configuration_test() {
    Exam exam = examConfiguration.exam();
    Exam exam2 = examConfiguration.exam();
    Exam exam3 = examConfiguration.exam();
    Exam exam4 = examConfiguration.exam();
    Exam exam5 = examConfiguration.exam();

    System.out.println("exam: " + exam);
    System.out.println("exam2: " + exam2);
    System.out.println("exam3: " + exam3);
    System.out.println("exam4: " + exam4);
    System.out.println("exam5: " + exam5);
  }
}    

 

 

@Configuration 이 싱글턴으로 Bean 을 생성했다면 해당 테스트 코드에서 Exam 객체의 모든 값은 동일해야 합니다.
위와 같이 모든 객체의 값이 동일한 것을 보아 싱글턴으로 Bean 이 등록된것을 알 수 있습니다.

 

ExamTest.java

@SpringBootTest
public class ExamTest2 {

  @Autowired
  private ExamComponent examComponent;

  @Test
  void configuration_test() {
    Exam exam = examComponent.exam();
    Exam exam2 = examComponent.exam();
    Exam exam3 = examComponent.exam();
    Exam exam4 = examComponent.exam();
    Exam exam5 = examComponent.exam();

    System.out.println("exam: " + exam);
    System.out.println("exam2: " + exam2);
    System.out.println("exam3: " + exam3);
    System.out.println("exam4: " + exam4);
    System.out.println("exam5: " + exam5);
  }
}    

@Component 는 생성된 모든 Bean 객체의 값이 상이한걸 확인할 수 있습니다.
이렇듯 @Component 내에서 Bean Method 를 선언하게 되면 싱글턴으로 생성되는것이 아니라 호출시마다
새로운 Bean 이 생성되는것을 확인할 수 있습니다.

 

 

reference

  • http://dimafeng.com/2015/08/29/spring-configuration_vs_component/
  • https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/stereotype/Component.html
  • https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/context/annotation/Configuration.html
  • https://mangkyu.tistory.com/75
반응형

'Spring' 카테고리의 다른 글

[Spring] Spring Event 를 이용한 비동기 이벤트 처리  (0) 2024.06.24
[Spring] ContextCaching 으로 Test 성능 개선하기 (@MockBean, @SpyBean)  (0) 2024.06.02
[Spring] Transactional REQUIRES_NEW 옵션에서 예외 및 Rollback  (2) 2024.01.10
[Spring] 서로 다른 테스트 클래스에서 테스트 데이터를 공유하는 방법  (0) 2023.11.05
SpEL(Spring Expression Langauge) 사용법 + 어노테이션에 SpEL로 값 전달하기  (1) 2023.05.06
    'Spring' 카테고리의 다른 글
    • [Spring] Spring Event 를 이용한 비동기 이벤트 처리
    • [Spring] ContextCaching 으로 Test 성능 개선하기 (@MockBean, @SpyBean)
    • [Spring] Transactional REQUIRES_NEW 옵션에서 예외 및 Rollback
    • [Spring] 서로 다른 테스트 클래스에서 테스트 데이터를 공유하는 방법
    devoong2
    devoong2
    github 주소: https://github.com/limwoobin

    티스토리툴바