The source code analyzes the difference between @ Configuration and @ Component, as well as the Full and Lite modes of @ Configuration

difference

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

// First code
@Configuration
// Second code
//@Component
public class MyTestConfig {

    // Second code correction
	// @Autowired
	// Car car;

	@Bean
	public Driver driver() {
		Driver driver = new Driver();
		driver.setId(1);
		driver.setName("driver");
		driver.setCar(car());
		return driver;
	}

	@Bean
	public Car car() {
		Car car = new Car();
		car.setId(1);
		car.setName("car");
		return car;
	}

}

The above two codes are the same except for the different annotations on the MyTestConfig class, but Spring handles them in a completely different way.

  • The first piece of code will work as we expected, because driver() is the driver The setcar (car()) method will be executed by the Spring proxy, If Spring finds that the Bean requested by the method is already in the container, it will directly return the Bean in the container. Therefore, there is only one instance of Car object globally.
  • The second piece of code is driver. When executing driver() Setcar (car()) will not be proxied by Spring, but will directly call the car() method to obtain a brand-new Car object instance, so there will be multiple Car object instances globally

The reasons for this difference are as follows:

The generalization is that all methods annotated with @ Bean in @ Configuration will be dynamically proxied, so the same instance will be returned when calling this method.

Its working principle is: if the method is called for the first time, the original method will be executed and the result object will be registered in the Spring context. After that, all calls to the method only retrieve the object from the Spring context and return it to the caller.

In the second code above, driver Setcar (car ()) is only a call in pure JAVA. Calling this method many times returns different object instances.

To fix the problem in the second code, use @ Autowired

In a word, all methods annotated with @ Bean in @ Configuration will be dynamically proxied, so the same instance will be returned by calling this method.

Implementation details

@Configuration notes:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {
    String value() default "";
}

From the definition, the @ Configuration annotation is essentially @ Component, so < context: Component scan / > or @ ComponentScan can handle the classes of @ Configuration annotation.

@The class of Configuration tag must meet the following requirements:

  • The configuration class must be provided in the form of a class (not an instance returned by a factory method), allowing runtime enhancement (cglib dynamic proxy) by generating subclasses.
  • The configuration class cannot be final (it cannot be dynamically represented).
  • Configuration annotation is usually used to generate Spring container managed classes through @ Bean annotation,
  • The configuration class must be non local (that is, it cannot be declared in a method or private).
  • Any nested configuration class must be declared static.
  • @The bean method may not create further Configuration classes in reverse (that is, if the returned bean has @ Configuration, it will not be specially processed and will only be used as an ordinary bean).

Loading process

When the Spring container starts, it will load some default postprocessors, including ConfigurationClassPostProcessor. This PostPRocessor is specially used to process classes with @ Configuration annotation. This program will process after the Bean definition is loaded and before Bean initialization. The main processing process is to use cglib dynamic proxy to enhance the class, and process the methods with @ Bean annotation.

The following method is invoked in the postProcessBeanFactory method in ConfigurationClassPostProcessor:

/**
 * Post-processes a BeanFactory in search of Configuration class BeanDefinitions;
 * any candidates are then enhanced by a {@link ConfigurationClassEnhancer}.
 * Candidate status is determined by BeanDefinition attribute metadata.
 * @see ConfigurationClassEnhancer
 */
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)) {
            //Omit some codes
            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);
            if (configClass != enhancedClass) {
                //Omit some codes
                beanDef.setBeanClass(enhancedClass);
            }
        }
        catch (Throwable ex) {
            throw new IllegalStateException(
              "Cannot load configuration class: " + beanDef.getBeanClassName(), ex);
        }
    }
}

In the first loop of the method, find all bean definitions with @ Configuration annotation, and then in the second for loop, enhance the class through the following methods:

Class<?> enhancedClass = enhancer.enhance(configClass, this.beanClassLoader);

Then replace the original beanClass with the enhanced class:

beanDef.setBeanClass(enhancedClass);

So by this time, all bean s with @ Configuration annotation have become enhanced classes.

Let's focus on the enhance ment method above. Follow one more step to see the following methods:

/**
 * Creates a new CGLIB {@link Enhancer} instance.
 */
private Enhancer newEnhancer(Class<?> superclass, ClassLoader classLoader) {
    Enhancer enhancer = new Enhancer();
    enhancer.setSuperclass(superclass);
    enhancer.setInterfaces(new Class<?>[] {EnhancedConfiguration.class});
    enhancer.setUseFactory(false);
    enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
    enhancer.setStrategy(new BeanFactoryAwareGeneratorStrategy(classLoader));
    enhancer.setCallbackFilter(CALLBACK_FILTER);
    enhancer.setCallbackTypes(CALLBACK_FILTER.getCallbackTypes());
    return enhancer;
}

When a class proxied by cglib calls a method, it will call it through CallbackFilter. Here is CALLBACK_FILTER is as follows:

// The callbacks to use. Note that these callbacks must be stateless.
private static final Callback[] CALLBACKS = new Callback[] {
        new BeanMethodInterceptor(),
        new BeanFactoryAwareMethodInterceptor(),
        NoOp.INSTANCE
};

private static final ConditionalCallbackFilter CALLBACK_FILTER = 
        new ConditionalCallbackFilter(CALLBACKS);

The matching method of BeanMethodInterceptor is as follows:

@Override
public boolean isMatch(Method candidateMethod) {
    return BeanAnnotationHelper.isBeanAnnotated(candidateMethod);
}

//BeanAnnotationHelper
public static boolean isBeanAnnotated(Method method) {
    return AnnotatedElementUtils.hasAnnotation(method, Bean.class);
}

That is, when the method has @ Bean annotation, the callback method will be executed.

The matching method of another BeanFactoryAwareMethodInterceptor is as follows:

@Override
public boolean isMatch(Method candidateMethod) {
    return (candidateMethod.getName().equals("setBeanFactory") &&
            candidateMethod.getParameterTypes().length == 1 &&
            BeanFactory.class == candidateMethod.getParameterTypes()[0] &&
            BeanFactoryAware.class.isAssignableFrom(candidateMethod.getDeclaringClass()));
}

The current class also needs to implement the BeanFactoryAware interface, and the isMatch above is the method matching this interface.

@Bean annotation method execution strategy Let's start with a simple example code:

@Configuration
public class MyBeanConfig {

    @Bean
    public Country country(){
        return new Country();
    }

    @Bean
    public UserInfo userInfo(){
        return new UserInfo(country());
    }

}

It is believed that when most people first see country() in userInfo (above), it is assumed that the Country returned here and the Country returned by the above @Bean method may not be the same object, so it may be replaced by the following way:

@Autowired 
private Country country;

In fact, you don't need to do this (the scenarios to do this will be given later). Calling the country() method directly returns the same instance.

Let's look at the logic when calling the country() and userInfo() methods.

Now that we know how the @ Configuration annotated class is handled, now focus on the beanmethodic interceptor above to see the logic of the method execution with @ Bean annotation. Let's break down the intercept method.

//First, get beanFactory from the enhanced Configuration annotation class through reflection
ConfigurableBeanFactory beanFactory = getBeanFactory(enhancedConfigInstance);

//Then obtain the beanName through the method. The default is the method name, which can be specified through the @ Bean annotation
String beanName = BeanAnnotationHelper.determineBeanNameFor(beanMethod);

//Determines whether the bean specifies the scope of the proxy
//By default, the following if condition false will not be executed
Scope scope = AnnotatedElementUtils.findMergedAnnotation(beanMethod, Scope.class);
if (scope != null && scope.proxyMode() != ScopedProxyMode.NO) {
    String scopedBeanName = ScopedProxyCreator.getTargetBeanName(beanName);
    if (beanFactory.isCurrentlyInCreation(scopedBeanName)) {
        beanName = scopedBeanName;
    }
}

//Skip a piece of code related to Factorybean in the middle

//Judge whether the currently executed method is the executing @ Bean method
//Because the country() method exists in the userInfo() method.
//If country() also has @ Bean annotation, the return value is false
if (isCurrentlyInvokedFactoryMethod(beanMethod)) {
    // Judge the return value type. If it is beanfactorypost processor, write a warning log
    if (logger.isWarnEnabled() &&
            BeanFactoryPostProcessor.class.isAssignableFrom(beanMethod.getReturnType())) {
        logger.warn(String.format(
            "@Bean method %s.%s is non-static and returns an object " +
            "assignable to Spring's BeanFactoryPostProcessor interface. This will " +
            "result in a failure to process annotations such as @Autowired, " +
            "@Resource and @PostConstruct within the method's declaring " +
            "@Configuration class. Add the 'static' modifier to this method to avoid " +
            "these container lifecycle issues; see @Bean javadoc for complete details.",
            beanMethod.getDeclaringClass().getSimpleName(), beanMethod.getName()));
    }
    //Directly call the original method to create a bean
    return cglibMethodProxy.invokeSuper(enhancedConfigInstance, beanMethodArgs);
}
//If you do not satisfy the above if, that is, the country() method invoked in userInfo().
return obtainBeanInstanceFromFactory(beanMethod, beanMethodArgs, beanFactory, beanName);

About iscurrently invokedfactorymethod method

You can refer to the instance method in SimpleInstantiationStrategy. The calling method set here first:

currentlyInvokedFactoryMethod.set(factoryMethod);
return factoryMethod.invoke(factoryBean, args);

When the country() method is called directly through the method, the above logic is not followed, and the proxy method is directly entered, that is, the current intercept method. Therefore, the current factory method and the executed method are different.

The obtainBeanInstanceFromFactory method is relatively simple, that is, through beanfactory GetBean gets the Country. If it has been created, it will be returned directly. If it has not been executed, it will be executed for the first time through invokeSuper.

Therefore, we can directly call the method in the bean method defined by the @ Configuration annotation, which does not need to be used after @ Autowired injection.

@Component note

@The Component annotation does not proxy the call of @ Bean method through cglib, so when configured as follows, there are two different countries.

@Component
public class MyBeanConfig {

    @Bean
    public Country country(){
        return new Country();
    }

    @Bean
    public UserInfo userInfo(){
        return new UserInfo(country());
    }

}

In some special cases, we do not want MyBeanConfig to be proxy (after proxy, it will become WebMvcConfig)

EnhancerBySpringCGLIB

8bef3235293), you have to use @ Component. In this case, the above writing method needs to be changed to the following:

@Component
public class MyBeanConfig {

    @Autowired
    private Country country;

    @Bean
    public Country country(){
        return new Country();
    }

    @Bean
    public UserInfo userInfo(){
        return new UserInfo(country);
    }

}

This method can ensure that the same Country instance is used.

Full mode and Lite mode

Both Full mode and Lite mode are for Spring configuration classes and have nothing to do with xml configuration files. It is worth noting that the premise of judging whether it is Full mode or Lite mode is that you must first be a container component. As for how an instance is "promoted" into a container component, it can be annotated or not. This article will not discuss it, which belongs to the basic knowledge of Spring.

Lite mode

When @ bean methods are declared in a class that does not use the @ Configuration annotation, they are referred to as processing in Lite mode. It includes: the @ bean method declared in @ Component, or even the bean method declared in a very common class, are considered as the Configuration class of lite version@ Bean method is a general factory method mechanism.

Unlike @ Configuration in Full mode, the @ Bean method in Lite mode cannot declare dependencies between beans. Therefore, such @ Bean methods should not call other @ Bean methods. Each such method is actually just a factory method referenced by a specific Bean, without any special runtime semantics

When is Lite mode

The official definition is: if there is a @ Bean method in a class without @ Configuration, it is called the Configuration of Lite mode. Looking at the source code, this definition is not completely correct, but should be a Configuration class with the following case s considered as Lite mode:

  1. Class is annotated with @ Component annotation
  2. Class is annotated with @ ComponentScan annotation
  3. Class is marked with @ Import annotation
  4. Class is marked with @ ImportResource annotation
  5. If there are no annotations on the class, but there are @ Bean methods in the class

The premise of the above cases is that @ Configuration is not marked on the class. A new case is added after Spring 5.2, which is also counted as Lite mode:

  1. It is marked with @ Configuration(proxyBeanMethods = false). Note: this value is true by default. It needs to be changed to false to be regarded as Lite mode

Careful, you will find that since spring 5 2 (corresponding to Spring Boot 2.2.0), almost all built-in @ Configuration configuration classes have been modified to @ Configuration(proxyBeanMethods = false). What is the purpose? A: this will reduce the startup time and prepare Cloud Native to continue.

advantage:

  • It is no longer necessary to generate CGLIB subclasses for the corresponding classes at runtime, which improves the running performance and reduces the startup time
  • The @ Bean and @ final methods can be configured as a common class

Disadvantages:

  • You cannot declare dependencies between @ beans, that is, you cannot rely on other beans through method calls
  • (in fact, this disadvantage is good. It is easy to "make up" by other methods, such as putting dependent beans into methods and parameters)

Code example

Main configuration class:

@ComponentScan("com.yourbatman.fullliteconfig.liteconfig")

@Configuration

public class AppConfig {

}

Prepare a Lite mode configuration:

@Component
// @Configuration(proxyBeanMethods = false) / / this is also the Lite mode
public class LiteConfig {
 
    @Bean
    public User user() {
        User user = new User();
        user.setName("A brother-lite");
        user.setAge(18);
        return user;
    }
 
 
    @Bean
    private final User user2() {
        User user = new User();
        user.setName("A brother-lite2");
        user.setAge(18);
 
        // The simulation depends on the user instance to see if it is the same instance
        System.out.println(System.identityHashCode(user()));
        System.out.println(System.identityHashCode(user()));
 
        return user;
    }
 
    public static class InnerConfig {
 
        @Bean
        // private final User userInner() {/ / it works only in lite mode
        public User userInner() {
            User user = new User();
            user.setName("A brother-lite-inner");
            user.setAge(18);
            return user;
        }
    }
}

Test case:

public class Application {
 
    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
 
        // Configuration class
        System.out.println(context.getBean(LiteConfig.class).getClass());
        System.out.println(context.getBean(LiteConfig.InnerConfig.class).getClass());
 
        String[] beanNames = context.getBeanNamesForType(User.class);
        for (String beanName : beanNames) {
            User user = context.getBean(beanName, User.class);
            System.out.println("beanName:" + beanName);
            System.out.println(user.getClass());
            System.out.println(user);
            System.out.println("------------------------");
        }
    }
}

Result output:

1100767002
313540687
class com.yourbatman.fullliteconfig.liteconfig.LiteConfig
class com.yourbatman.fullliteconfig.liteconfig.LiteConfig$InnerConfig
beanName:userInner
class com.yourbatman.fullliteconfig.User
User{name='A brother-lite-inner', age=18}
------------------------
beanName:user
class com.yourbatman.fullliteconfig.User
User{name='A brother-lite', age=18}
------------------------
beanName:user2
class com.yourbatman.fullliteconfig.User
User{name='A brother-lite2', age=18}
------------------------

Lite summary

  • In this mode, the configuration class itself will not be enhanced by CGLIB, and what is put into the IoC container is benzun
  • In this mode, there are no restrictions on internal classes: it can be Full mode or Lite mode
  • In this mode, dependencies cannot be handled through method calls within the configuration class, otherwise a new instance will be generated each time instead of a single instance in the IoC container
  • In this mode, the configuration class is a common class, so the @ Bean method can be modified with private/final and so on (static is also a common class naturally)

Full mode

In common scenarios, the @ Bean method will be declared in the class marked with @ Configuration to ensure that the "Full mode" is always used. In this way, the cross method reference will be redirected to the lifecycle management of the container, so it is more convenient to manage Bean dependencies

When is Full mode

The class marked with @ Configuration annotation is called the Configuration class of full mode. From spring 5 I think it is more accurate to change the sentence after 2 into the following:

  • Classes marked with @ Configuration or @ Configuration(proxyBeanMethods = true) are called Configuration classes in Full mode
  • (of course, the default value of proxyBeanMethods property is true, so we generally need Full mode. We only need to mark a comment.)

advantage:

  • It can support calling the @ Bean method of the same class through conventional Java to ensure that it is a Bean in the container, which effectively avoids subtle errors that are difficult to track when operating in "Lite mode". Especially for new programmers, this feature is very meaningful

Disadvantages:

  • The runtime will generate a CGLIB subclass for this class and put it into the container, which has certain performance and time overhead (this overhead can not be ignored in the case of Spring Boot with a large number of configuration classes, which is the most direct reason why proxyBeanMethods attribute is added in Spring 5.2)
  • Because it is proxied, the @ Bean method cannot be private or final

Code example

Master configuration:

@ComponentScan("com.yourbatman.fullliteconfig.fullconfig")
@Configuration
public class AppConfig {
}

Prepare a Full mode configuration:

@Configuration
public class FullConfig {
 
    @Bean
    public User user() {
        User user = new User();
        user.setName("A brother-lite");
        user.setAge(18);
        return user;
    }
 
 
    @Bean
    protected User user2() {
        User user = new User();
        user.setName("A brother-lite2");
        user.setAge(18);
 
        // The simulation depends on the user instance to see if it is the same instance
        System.out.println(System.identityHashCode(user()));
        System.out.println(System.identityHashCode(user()));
 
        return user;
    }
 
    public static class InnerConfig {
 
        @Bean
        // private final User userInner() {/ / it works only in lite mode
        public User userInner() {
            User user = new User();
            user.setName("A brother-lite-inner");
            user.setAge(18);
            return user;
        }
    }
}

Test case:

public class Application {
 
    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
 
        // Configuration class
        System.out.println(context.getBean(FullConfig.class).getClass());
        System.out.println(context.getBean(FullConfig.InnerConfig.class).getClass());
 
        String[] beanNames = context.getBeanNamesForType(User.class);
        for (String beanName : beanNames) {
            User user = context.getBean(beanName, User.class);
            System.out.println("beanName:" + beanName);
            System.out.println(user.getClass());
            System.out.println(user);
            System.out.println("------------------------");
        }
    }
}

Result output:

550668305

550668305

class com.yourbatman.fullliteconfig.fullconfig.FullConfig$$EnhancerBySpringCGLIB$$70a94a63

class com.yourbatman.fullliteconfig.fullconfig.FullConfig$InnerConfig

beanName:userInner

class com.yourbatman.fullliteconfig.User

User{name='A brother-lite-inner', age=18}

------------------------

beanName:user

class com.yourbatman.fullliteconfig.User

User{name='A brother-lite', age=18}

------------------------

beanName:user2

class com.yourbatman.fullliteconfig.User

User{name='A brother-lite2', age=18}

------------------------

Full summary

  • In this mode, the configuration class will be enhanced by CGLIB (generate proxy object), and the proxy will be placed in the IoC container
  • In this mode, there are no restrictions on internal classes: it can be Full mode or Lite mode
  • In this mode, the dependency can be handled through method calls inside the configuration class, and it can be guaranteed that it is the same instance and all point to the single instance in IoC
  • In this mode, the @ Bean method cannot be modified by private/final (very simple, because the method needs to be copied, so it cannot be private or final. Default / protected / public can be used). Otherwise, an error is reported when starting (in fact, the IDEA compiler can prompt you when prompted by the compiler):
Exception in thread "main" org.springframework.beans.factory.parsing.BeanDefinitionParsingException: Configuration problem: @Bean method 'user2' must not be private or final; change the method's modifiers to continue
Offending resource: class path resource [com/yourbatman/fullliteconfig/fullconfig/FullConfig.class]
	at org.springframework.beans.factory.parsing.FailFastProblemReporter.error(FailFastProblemReporter.java:72)
	at org.springframework.context.annotation.BeanMethod.validate(BeanMethod.java:50)
	at org.springframework.context.annotation.ConfigurationClass.validate(ConfigurationClass.java:220)
	at org.springframework.context.annotation.ConfigurationClassParser.validate(ConfigurationClassParser.java:211)
	at org.springframework.context.annotation.ConfigurationClassPostProcessor.processConfigBeanDefinitions(ConfigurationClassPostProcessor.java:326)
	at org.springframework.context.annotation.ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry(ConfigurationClassPostProcessor.java:242)
	at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanDefinitionRegistryPostProcessors(PostProcessorRegistrationDelegate.java:275)
	at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:95)
	at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:706)
	at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:532)
	at org.springframework.context.annotation.AnnotationConfigApplicationContext.<init>(AnnotationConfigApplicationContext.java:89)
	at com.yourbatman.fullliteconfig.Application.main(Application.java:11)

Use suggestions

After understanding the Full mode and Lite mode of Spring configuration class, how can I use them in my work? Here, brother A gives suggestions for use for reference only:

  • If you are developing on the company's business functions / services, use the Full mode
  • If you are a container developer, or you are developing middleware and general components, using Lite mode is a more recommended way, which is more friendly to Cloud Native

Thinking questions?

Will the class directly put in through new AnnotationConfigApplicationContext(AppConfig.class) become an IoC component? If so, is it Full mode or Lite mode? Is it a fixed result or is it also related to its annotation?

reference resources:

difference: https://www.cnblogs.com/gmhappy/p/13457045.html

Parsing source code: https://blog.csdn.net/isea533/article/details/78072133

@Configuration class - Full and Lite modes: https://blog.csdn.net/demon7552003/article/details/107988310

Added by jyushinx on Wed, 09 Mar 2022 07:48:30 +0200