Spring의 @Configuration과 @Component의 차이점과 원리에 대한 자세한 설명

1.배경

의 인기로 인해 주석 구성 개발이 모두에게 선호되었으며 개발 기반의 번거로운 구성 Spring Boot에 작별을 고했습니다 . 먼저 구성 주석의 내부 정의를 간단히 이해해 보겠습니다. 기능적으로 말하면 이러한 주석은 실제로 다양한 기능을 담당하지만 본질적으로 모두 구성 주석으로 내부적으로 처리됩니다.SpringXMLSpring@Component、@Configuration、@Bean、@ImportSpring

성숙한 프레임워크를 위해서는 간단하고 다양한 구성이 중요하며, 구성 개발 과정의 관점 Spring에서 볼 때 Spring전체적인 구성 방법은 이제 상대적으로 "원래" 단계에서 매우 "스마트한" 단계로 바뀌었습니다. XML에서 자동 어셈블리, Spring에서 Spring Boot, @Component에서 @Configuration 및 @Conditional까지 이 기간 동안의 노력은 매우 컸습니다. Spring은 오늘날까지 발전했으며 점점 더 사용하기 쉬워지고 있습니다. , Spring또한 많은 세부 정보를 숨기므로 오늘은 @Component 및 @Configuration을 함께 살펴보겠습니다.

일반적인 Spring 개발 작업에서는 기본적으로 구성 주석(특히 @Component 및 ) @Configuration을 사용합니다. 물론 Spring에서는 다른 주석을 사용하여 클래스를 구성 클래스로 표시할 수도 있습니다. 이는 넓은 의미의 구성 클래스 개념입니다. 하지만 여기서는 개발 작업과 밀접한 관련이 있는 @Component 및 @Configuration에 대해서만 논의하고, @Component와 @Configuration의 차이점이 무엇인지에 대한 다음 질문을 먼저 논의하겠습니다. 

2. @Component 및 @Configuration 사용

2.1 주석 정의

둘 사이의 차이점을 논의하기 전에 두 주석의 정의를 살펴보겠습니다.

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Indexed
public @interface Component {
  /**
   * 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)
   */
  String value() default "";
}
复制代码
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {
  @AliasFor(annotation = Component.class)
  String value() default "";
  boolean proxyBeanMethods() default true;
}
复制代码

정의에서 @Configuration 주석은 기본적으로 @Component이므로 @ComponentScan은 @Configuration 주석이 달린 클래스를 검색할 수 있습니다.

2.2 주석 사용법

다음으로 일상적인 개발에서 두 가지 주석을 사용하는 방법을 살펴보겠습니다. 이 두 가지 주석을 사용하여 클래스를 구성 클래스로 표시합니다.

@Configuration
public class AppConfig {
}
@Component
public class AppConfig {
}
复制代码

위 프로그램에 대해 Spring에서는 처리를 위한 구성 클래스로 취급하지만 명확히 해야 할 개념이 있는데, 즉 Spring에서는 구성 클래스가 실제로 분류되며 크게 두 가지 범주로 나눌 수 있다는 것이다 . LITE 모드, 다른 유형을 FULL 모드라고 하는데 , 위의 주석에 따라 @Component 는 LITE 유형, @Configuration 은 FULL 유형인데 이 두 가지 구성 유형을 어떻게 이해해야 할까요? 먼저 이 프로그램을 살펴보겠습니다.

@Component구현 구성 클래스를 사용하는 경우 :

@Component
public class AppConfig {
  @Bean
  public Foo foo() {
    System.out.println("foo() invoked...");
    Foo foo = new Foo();
    System.out.println("foo() 方法的 foo hashcode: " + foo.hashCode());
    return foo;
  }
  @Bean
  public Eoo eoo() {
    System.out.println("eoo() invoked...");
    Foo foo = foo();
    System.out.println("eoo() 方法的 foo hashcode: "+ foo.hashCode());
    return new Eoo();
  }
  public static void main(String[] args) {
    AnnotationConfigApplicationContext context =
        new AnnotationConfigApplicationContext(AppConfig.class);
  }
}
复制代码

실행 결과는 다음과 같습니다.

foo() invoked...
foo() 方法的 foo hashcode: 815992954
eoo() invoked...
foo() invoked...
foo() 方法的 foo hashcode: 868737467
eoo() 方法的 foo hashcode: 868737467
复制代码

결과를 보면 foo()Bean 메소드에 의해 한 번, eoo()호출에 의해 한 번, 두 번 메소드가 실행되어 두 번 생성되는 객체가 foo다르다는 것을 알 수 있습니다. 모두의 기대에 부합하지만 @ConfigurationAnnotation 구성 클래스를 사용할 경우 실행 결과는 다음과 같습니다.

foo() invoked...
foo() 方法的 foo hashcode: 849373393
eoo() invoked...
eoo() 方法的 foo hashcode: 849373393
复制代码

foo()여기서는 메소드가 한 번만 실행되고 eoo()메소드 호출 foo()에 의해 생성된 foo 객체가 동일하다는 것을 알 수 있습니다 . 이것이 @Component@Configuration의 차이현상인데 왜 이런 현상이 나타나는지 문제를 생각해보자면, 즉 eoo() 메소드에서 foo() 메소드가 호출된다는 점이다. 당연히 foo() 메소드는 새로운 Object를 형성하게 된다. , 우리가 호출하는 foo() 메서드가 원래 foo() 메서드가 아니라고 가정하면 새 객체가 형성되지 않을 가능성이 있습니까? foo() 메소드를 호출할 때 foo 빈을 얻기 위해 컨테이너로 이동하면 이러한 효과를 얻을 수 있습니까? 그렇다면 어떻게 그러한 효과를 얻을 수 있습니까? 방법이 있습니다, 프록시 ! 즉, AppConfig를 포함하여 우리가 호출한 eoo() 및 foo() 메소드는 모두 Spring에 의해 프록시됩니다. 따라서 여기서 우리는 @Component와 @Configuration의 가장 근본적인 차이점을 이해합니다. Spring에 의해 프록시됨. 사실 이 설명은 그다지 엄격하지 않습니다. 더 정확하게 말하면 클래스의 BeanDefinition 속성에 Full 구성 속성이 있으면 이 클래스는 Spring에 의해 프록시됩니다.

3. Spring이 전체 구성된 프록시를 구현하는 방법

이를 이해하려면 Spring이 이러한 구성 클래스를 FULL 모드 또는 LITE 모드로 변환한다는 전제도 명확히 해야 합니다.다음으로 개인적으로 Spring에서 매우 중요하다고 생각하는 클래스인 ConfigurationClassPostProcessor를 소개하겠습니다.

3.1ConfigurationClassPostProcessor란 무엇입니까?

먼저 이 클래스의 정의를 간략하게 살펴보겠습니다.

public class ConfigurationClassPostProcessor implements 
                                              BeanDefinitionRegistryPostProcessor,
                                               PriorityOrdered, 
                                               ResourceLoaderAware, 
                                               BeanClassLoaderAware, 
                                               EnvironmentAware {}
复制代码

이 클래스 정의를 보면 이 클래스의 타입이 BeanDefinitionRegistryPostProcessor이고 Spring에 내장된 많은 Aware 인터페이스를 구현하고 있음을 알 수 있다.Beanfactory의 후처리기를 이해한다면 ConfigurationClassPostProcessor의 실행 시점을 알아야 한다.물론 거기에는 이해하지 못해도 문제 없습니다. 나중에 설명하겠습니다. 전체 과정을 명확하게 설명하기 위해 지금 알아야 할 것은 ConfigurationClassPostProcessor 클래스가 언제 인스턴스화되었는지입니다.

3.2ConfigurationClassPostProcessor는 언제 인스턴스화되나요?

이 질문에 답하기 위해서는 먼저 ConfigurationClassPostProcessor 클래스에 해당하는 BeanDefinition이 Spring 컨테이너에 등록되었다는 전제를 명확히 할 필요가 있는데, Spring의 인스턴스화는 상당히 특수하고 주로 BeanDefinition을 기반으로 처리되기 때문에 이제 이 질문은 다음과 같다. ConfigurationClassPostProcessor로 변환될 수 있습니다. 이 클래스는 언제 Beanddefinition으로 등록되었습니까? 이에 대한 답은 소스 코드, 특히 Spring 컨테이너를 초기화할 때 찾을 수 있습니다.

new AnnotationConfigApplicationContext(ConfigClass.class)
  -> new AnnotatedBeanDefinitionReader(this);
    -> AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
      -> new RootBeanDefinition(ConfigurationClassPostProcessor.class);
        -> registerPostProcessor(BeanDefinitionRegistry registry, RootBeanDefinition definition, String beanName)
复制代码

여기에서 볼 수 있듯이 ConfigurationClassPostProcessor가 BeanDefinition으로 등록되어 있고, 앞서 말했듯이 Spring은 BeanDefinition에 대해 파싱, 처리, 인스턴스화, 채우기, 초기화 및 수많은 콜백을 거쳐 Bean을 형성한다. .

3.3 @Component와 @Configuration의 구현 차이점

위의 ConfigurationClassPostProcessor는 BeanDefinition 등록센터에 등록되어 있는데, 이는 Spring이 특정 시점에 이를 Bean으로 처리한다는 의미이며, 특정 시점은 BeanFactory의 모든 후처리 프로세서가 처리되는 시점이다.

AbstractApplicationContext
  -> refresh()
    -> invokeBeanFactoryPostProcessors(beanFactory);
      -> PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());
复制代码

BeanFactory의 후처리기를 처리하는 이 방법은 비교적 복잡하며 간단히 말하면 BeanFactoryPostProcessor와 BeanDefinitionRegistryPostProcessor를 구현하는 모든 클래스를 주로 처리한다.물론 ConfigurationClassPostProcessor도 그 중 하나이므로 구현 방법을 살펴보자.

  @Override
  public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
    int registryId = System.identityHashCode(registry);
    if (this.registriesPostProcessed.contains(registryId)) {
      throw new IllegalStateException(
          "postProcessBeanDefinitionRegistry already called on this post-processor against " + registry);
    }
    if (this.factoriesPostProcessed.contains(registryId)) {
      throw new IllegalStateException(
          "postProcessBeanFactory already called on this post-processor against " + registry);
    }
    this.registriesPostProcessed.add(registryId);
    processConfigBeanDefinitions(registry);
  }
  
  @Override
  public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
    int factoryId = System.identityHashCode(beanFactory);
    if (this.factoriesPostProcessed.contains(factoryId)) {
      throw new IllegalStateException(
          "postProcessBeanFactory already called on this post-processor against " + beanFactory);
    }
    this.factoriesPostProcessed.add(factoryId);
    if (!this.registriesPostProcessed.contains(factoryId)) {
      // BeanDefinitionRegistryPostProcessor hook apparently not supported...
      // Simply call processConfigurationClasses lazily at this point then.
      processConfigBeanDefinitions((BeanDefinitionRegistry) beanFactory);
    }
    enhanceConfigurationClasses(beanFactory);
    beanFactory.addBeanPostProcessor(new ImportAwareBeanPostProcessor(beanFactory));
  }
复制代码

이 두 가지 메소드는 ConfigurationClassPostProcessor에서 가장 핵심적이어야 합니다. 여기서 간단히 요약하자면 첫 번째 메소드는 주로 내부 클래스인 @Component, @ComponentScan, @Bean, @Configuration, @Import 및 기타 주석 처리를 완료한 다음 생성합니다. 또 다른 방법은 @Configuration을 강화하기 위해 CGLIB를 사용하는 것이다.먼저 Spring이 구성된 LITE 모드와 FULL 모드를 어떻게 구별하는지 살펴보자. 첫 번째 방법에는 checkConfigurationClassCandidate 방법이 있습니다.

public static boolean checkConfigurationClassCandidate(
      BeanDefinition beanDef, MetadataReaderFactory metadataReaderFactory) {
      
        // ...
        
      Map<String, Object> config = metadata.getAnnotationAttributes(Configuration.class.getName());
        if (config != null && !Boolean.FALSE.equals(config.get("proxyBeanMethods"))) {
          beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_FULL);
        }
        else if (config != null || isConfigurationCandidate(metadata)) {
          beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_LITE);
        }
        else {
          return false;
        }
        // ...      
      }
复制代码

프로그램의 판단에 따르면 클래스에 @Configuration 주석이 달렸고 프록시 모드가 true인 경우 이 클래스에 해당하는 BeanDefinition은 Spring에서 추가한 FULL 구성 모드 속성을 갖게 됩니다. 일부 학생은 이 "속성"을 이해하지 못할 수도 있습니다. 아주 좋습니다. 간단히 말해서 사실 이 "속성"은 Spring의 특정 인터페이스인 AttributeAccessor를 가지고 있습니다. BeanDefinition은 이 인터페이스를 상속합니다. 이 AttributeAccessor를 이해하는 방법은 무엇입니까? 사실 아주 간단한데 BeanDefinition이 주로 하는 일이 무엇인지 생각해 보세요. 이것은 주로 이 클래스가 추상적인지, 그 범위가 무엇인지, 게으른 로드인지 등과 같은 클래스 객체를 설명하는 데 사용됩니다. 클래스 객체가 BeanDefinition으로 설명할 수 없는 "속성"을 갖고 있는 경우 다음은 어떻게 해야 합니까? 그것을 처리하기 위해? 그러면 이 인터페이스 AttributeAccessor가 다시 유용하게 사용됩니다. 정의한 모든 데이터를 저장할 수 있습니다. 이는 맵으로 이해될 수 있습니다. 이제 BeanDefinition 속성의 의미를 이해하셨습니까?

LITE 모드에서도 동일한 @Configuration(proxyBeanMethods = false)효과 를 볼 수 있습니다.@Component

여기서 첫 번째 단계는 클래스가 FULL 모드인지 LITE 모드인지 확인하는 것입니다. 그런 다음 다음 단계는 구성 클래스의 주석을 구문 분석하는 것입니다. ConfigurationClassParser 클래스에는 processConfigurationClass 메소드가 있고 내부에는 doProcessConfigurationClass 메소드가 있습니다. 위에 나열된 @Component 및 기타 Annotation을 파싱하는 과정은 다음과 같다. 파싱이 완료되면 ConfigurationClassPostProcessor 클래스의 processConfigBeanDefinitions 메소드에 loadBeanDefinitions 메소드가 있는데, 이 메소드는 성공적으로 파싱된 모든 Annotation 데이터를 BeanDefinition으로 등록하는 것이다. 이것이 ConfigurationClassPostProcessor 클래스입니다. 첫 번째 방법으로 수행되는 작업과 이 방법은 여기에서 매우 간략하게 설명됩니다. 사실 이 방법은 매우 복잡하므로 천천히 공부해야 합니다.

enhanceConfigurationClasses다음 으로 ConfigurationClassPostProcessor 클래스의 메소드에 대해 이야기해 보겠습니다 . 이 메소드는 주로 @Configuration 주석으로 표시된 클래스의 개선을 완료하고 CGLIB 프록시를 수행합니다. 코드는 다음과 같습니다.

public void enhanceConfigurationClasses(ConfigurableListableBeanFactory beanFactory) {
        Map<String, AbstractBeanDefinition> configBeanDefs = new LinkedHashMap<String, AbstractBeanDefinition>();
        for (String beanName : beanFactory.getBeanDefinitionNames()) {
            BeanDefinition beanDef = beanFactory.getBeanDefinition(beanName);
            if (ConfigurationClassUtils.isFullConfigurationClass(beanDef)) {//判断是否被@Configuration标注
                if (!(beanDef instanceof AbstractBeanDefinition)) {
                    throw new BeanDefinitionStoreException("Cannot enhance @Configuration bean definition '" +
                            beanName + "' since it is not stored in an AbstractBeanDefinition subclass");
                }
                else if (logger.isWarnEnabled() && beanFactory.containsSingleton(beanName)) {
                    logger.warn("Cannot enhance @Configuration bean definition '" + beanName +
                            "' since its singleton instance has been created too early. The typical cause " +
                            "is a non-static @Bean method with a BeanDefinitionRegistryPostProcessor " +
                            "return type: Consider declaring such methods as 'static'.");
                }
                configBeanDefs.put(beanName, (AbstractBeanDefinition) beanDef);
            }
        }
        if (configBeanDefs.isEmpty()) {
            // nothing to enhance -> return immediately
            return;
        }
        ConfigurationClassEnhancer enhancer = new ConfigurationClassEnhancer();
        for (Map.Entry<String, AbstractBeanDefinition> entry : configBeanDefs.entrySet()) {
            AbstractBeanDefinition beanDef = entry.getValue();
            // If a @Configuration class gets proxied, always proxy the target class
            beanDef.setAttribute(AutoProxyUtils.PRESERVE_TARGET_CLASS_ATTRIBUTE, Boolean.TRUE);
            try {
                // Set enhanced subclass of the user-specified bean class
                Class<?> configClass = beanDef.resolveBeanClass(this.beanClassLoader);
                Class<?> enhancedClass = enhancer.enhance(configClass, this.beanClassLoader);//生成代理的class
                if (configClass != enhancedClass) {
                    if (logger.isDebugEnabled()) {
                        logger.debug(String.format("Replacing bean definition '%s' existing class '%s' with " +
                                "enhanced class '%s'", entry.getKey(), configClass.getName(), enhancedClass.getName()));
                    }
                    //替换class,将原来的替换为CGLIB代理的class
                    beanDef.setBeanClass(enhancedClass);
                }
            }
            catch (Throwable ex) {
                throw new IllegalStateException("Cannot load configuration class: " + beanDef.getBeanClassName(), ex);
            }
        }
    }
复制代码

여기서는 CGLIB에 대한 지식이 필요하므로 간단하게 요약하면 모든 BeanDefinition들 중에서 속성이 FULL 모드인 BeanDefinition을 찾아 Proxy Enhanced를 수행하고 BeanDefinition의 BeanClass를 설정하는 메소드이다. 그런 다음 향상 중에 명확히 해야 할 몇 가지 세부 사항이 있습니다. 즉, eoo(), foo() 및 기타 메서드와 같은 일반 클래스의 메서드는 MethodInterceptor에 의해 차단되고 이 메서드의 호출은 다음과 같습니다. BeanMethodInterceptor에 의해 프록시됨 이 시점에서 우리는 ConfigurationClassPostProcessor가 인스턴스화되는 시기, 주석 구성이 구문 분석되는 시기, 구성 개선이 수행되는 시기를 약간 명확하게 알아야 합니다. 여기 보이는 내용이 이해가 안 되시면 저와 함께 토론해 보세요.

4. 요약

@Component는 Spring의 LITE 모드에서 설정 Annotation을 나타내며, 이 모드의 Annotation은 Spring에 의해 프록시되지 않으며 표준 클래스이다. 이 클래스에 @Bean으로 표시된 메소드가 있으면 메소드는 서로 호출할 수 있다. 실제로 이는 일반 Java 클래스의 메소드에 대한 호출입니다.

@Configuration은 Spring의 FULL 모드를 나타내는 구성 주석으로, 이 모드의 클래스는 Spring에 의해 프록시화되며, 이 클래스의 @Bean 메소드의 상호 호출은 프록시 메소드를 호출하는 것과 동일합니다.그런 다음 프록시 메소드에서 판단됩니다. getBean 메소드를 호출할지, InvokeSuper 메소드를 호출할지 여부 이것이 이 두 주석의 가장 근본적인 차이점입니다.

한 문장에서 주석  @Configuration 이 달린 모든  @Bean 메서드는 동적으로 프록시되므로 이 메서드를 호출하면 동일한 인스턴스가 반환됩니다. 

Supongo que te gusta

Origin blog.csdn.net/2301_76607156/article/details/130526647
Recomendado
Clasificación