Detailed explanation of the difference and principle between @Configuration and @Component in Spring

1.Background

With Spring Bootthe popularity of , annotation configuration development has been favored by everyone, and we have said goodbye to the Springcumbersome configuration based on development XML. Let’s first briefly understand Springthe internal definition of configuration annotations. @Component、@Configuration、@Bean、@ImportFunctionally speaking, these annotations are indeed responsible for different functions, but in essence, Springthey are all processed internally as configuration annotations.

For a mature framework, simple and diverse configuration is crucial, and Springso is it. From the Springperspective of the configuration development process, the overall configuration method has gone from a relatively "original" stage to a very "smart" one now. stage, the efforts made during this period were very huge, from XML to automatic assembly, from Spring to Spring Boot, from @Component to @Configuration and @Conditional, Spring has developed to this day, and while it is becoming more and more easy to use , SpringIt also hides many details for us, so today let us explore @Component and @Configuration together.

In our usual Spring development work, we basically use configuration annotations, especially @Component and @Configuration. Of course, other annotations can also be used in Spring to mark a class as a configuration class. This is the concept of a configuration class in a broad sense, but Here we only discuss @Component and @Configuration, because they are closely related to our development work. Then we will discuss the next question first, which is  what is the difference between @Component and @Configuration?

2. Use of @Component and @Configuration

2.1 Annotation definition

Before discussing the difference between the two, let’s take a look at the definitions of the two annotations:

@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;
​
}
复制代码

From the definition, the @Configuration annotation is essentially @Component, so @ComponentScan can scan the @Configuration annotated class.

2.2 Annotation usage

Next, let’s take a look at the use of the two in our daily development: use these two annotations to mark a class as a configuration class

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

For the above program, Spring will treat it as a configuration class for processing, but there is a concept that needs to be clarified, that is, in Spring, configuration classes are actually classified, and they can be roughly divided into two categories . It is called LITE mode, and the other type is called FULL mode . Corresponding to the above annotations, @Component is the LITE type, and @Configuration is the FULL type. How to understand these two configuration types? Let's look at this program first.

When we use @Componentthe implementation configuration class:

@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);
  }
}
复制代码

The execution results are as follows:

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

It can be seen from the results that foo()the method is executed twice, once by the bean method and once eoo()by calling, so the objects generated twice fooare different. It is in line with everyone's expectations, but when we use @Configurationthe annotation configuration class, the execution results are as follows:

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

Here you can see that foo()the method is only executed once, and the foo object generated by eoo()the method call is the same. foo()This is the difference phenomenon between @Componentand @Configuration, so why is there such a phenomenon? Let’s consider a problem, that is, the foo() method is called in the eoo() method. Obviously, the foo() method will form a new Object, assuming that the foo() method we call is not the original foo() method, is it possible that a new object will not be formed? If we go to the container to get the bean foo when calling the foo() method, can we achieve this effect? So how can we achieve such an effect? There is a way, proxy ! In other words, the eoo() and foo() methods we called, including AppConfig, are all proxied by Spring. So here we understand the most fundamental difference between @Component and @Configuration, that is, classes marked with @Configuration will be proxied by Spring. , in fact, this description is not very rigorous. To be more precise, if there is a Full configuration attribute in the BeanDefinition Attribute of a class, then this class will be proxied by Spring.

3.How Spring implements FULL configured proxy

If you want to understand this, you also need to clarify the premise, which is when Spring will convert these configuration classes into FULL mode or LITE mode. Next, we will introduce a class that I personally think is very important in Spring, ConfigurationClassPostProcessor.

3.1What is ConfigurationClassPostProcessor

First, let’s take a brief look at the definition of this class:

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

From this class definition, we can see that the type of this class is BeanDefinitionRegistryPostProcessor, and it implements many of Spring's built-in Aware interfaces. If you understand Beanfactory's post-processor, you should know the execution timing of ConfigurationClassPostProcessor. Of course, there is no problem if you don't understand it. We will explain it later. To explain the entire process clearly, what you need to know now is when was the ConfigurationClassPostProcessor class instantiated?

3.2When is ConfigurationClassPostProcessor instantiated?

To answer this question, we need to clarify a premise first, which is when the BeanDefinition corresponding to the ConfigurationClassPostProcessor class was registered in the Spring container. Because Spring's instantiation is quite special and is mainly processed based on the BeanDefinition, then this question now It can be converted into ConfigurationClassPostProcessor. When was this class registered as a Beanddefinition? The answer to this can be found in the source code, specifically when initializing the Spring container.

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

As can be seen from here, ConfigurationClassPostProcessor has been registered as a BeanDefinition. As we said above, Spring forms a Bean by parsing, processing, instantiating, filling, initializing, and numerous callbacks on the BeanDefinition. So now ConfigurationClassPostProcessor A BeanDefinition has been formed.

3.3 Implementation differences between @Component and @Configuration

The above ConfigurationClassPostProcessor has been registered in the BeanDefinition registration center, which means that Spring will process it into a Bean at a certain point in time. The specific point in time is the processing of all post-processors in the BeanFactory.

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

This method of processing BeanFactory's post-processor is relatively complicated. Simply put, it mainly processes all classes that implement BeanFactoryPostProcessor and BeanDefinitionRegistryPostProcessor. Of course, ConfigurationClassPostProcessor is one of them, so let's take a look at the implementation method:

  @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));
  }
复制代码

These two methods should be the most critical of ConfigurationClassPostProcessor. Let’s briefly summarize it here. The first method mainly completes the processing of internal classes, @Component, @ComponentScan, @Bean, @Configuration, @Import and other annotations. , and then generate the corresponding BeanDefinition. Another method is to use CGLIB to enhance @Configuration. Let's first look at where Spring distinguishes the configured LITE mode and FULL mode? In the first method there is a checkConfigurationClassCandidate method:

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;
        }
        // ...      
      }
复制代码

According to the judgment of the program, if a class is annotated with @Configuration and the proxy mode is true, then the BeanDefinition corresponding to this class will have a FULL configuration mode attribute added by Spring. Some students may not understand this "attribute" very well. Here To put it simply, in fact, this "attribute" has a specific interface in Spring, which is AttributeAccessor. BeanDefinition inherits this interface. How to understand this AttributeAccessor? It's actually very simple. Think about it, what does BeanDefinition mainly do? This is mainly used to describe Class objects, such as whether this Class is abstract, what its scope is, whether it is lazy loaded, etc. If a Class object has an "attribute" that cannot be described by BeanDefinition, then this must How to deal with it? Then this interface AttributeAccessor comes in handy again. You can store any data you define in it. It can be understood as a map. Do you now understand the meaning of the attributes of BeanDefinition?

@Configuration(proxyBeanMethods = false)You can also see the same effect here @Component, both in LITE mode

The first step here is to determine whether the class is in FULL mode or LITE mode. Then the next step is to start parsing the annotations of the configuration class. There is a processConfigurationClass method in the ConfigurationClassParser class, and there is a doProcessConfigurationClass method inside. Here is The process of parsing the @Component and other annotations listed above. After the parsing is completed, there is a loadBeanDefinitions method in the processConfigBeanDefinitions method of the ConfigurationClassPostProcessor class. This method is to register all the successfully parsed annotation data as BeanDefinition. This is the ConfigurationClassPostProcessor class. The tasks accomplished by the first method, and this method is very briefly described here. In fact, this method is very complicated and needs to be studied slowly.

Next, let’s talk about enhanceConfigurationClassesthe method of ConfigurationClassPostProcessor class. This method mainly completes the enhancement of the class marked with @Configuration annotation and performs CGLIB proxy. The code is as follows:

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);
            }
        }
    }
复制代码

Some knowledge of CGLIB is required here. I will simply summarize it here. This method finds the BeanDefinition whose attribute is FULL mode from all BeanDefinitions, then performs proxy enhancement on it, and sets the beanClass of the BeanDefinition. Then there are some details that need to be clarified during enhancement, that is, the methods in our ordinary class, such as eoo(), foo() and other methods, will be intercepted by MethodInterceptor, and the call of this method will be proxied by BeanMethodInterceptor. At this point we should all have a little clarity on when ConfigurationClassPostProcessor is instantiated, when the annotation configuration is parsed, and when configuration enhancements are performed. If you don’t understand what you see here, you are welcome to discuss it with me.

4. Summary

@Component represents the configuration annotation in LITE mode in Spring. The annotations in this mode will not be proxied by Spring. It is a standard class. If there are methods marked with @Bean in this class, then the methods can call each other. In fact, it is a call to a method of an ordinary Java class.

@Configuration is a configuration annotation representing FULL mode in Spring. Classes in this mode will be proxied by Spring. Then the mutual calls of @Bean methods in this class are equivalent to calling the proxy method. Then in the proxy method It will be judged whether to call the getBean method or the invokeSuper method. This is the most fundamental difference between these two annotations.

In one sentence,  @Configuration all annotated  @Bean methods will be dynamically proxied, so calling this method will return the same instance. 

Guess you like

Origin blog.csdn.net/2301_76607156/article/details/130526647