Initialization of Spring @Bean instance

brief introduction

We all know that Spring has many ways to create beans, including using @ Component, @Service and other annotations, including implementing the ImportSelector or ImportBeanDefinitionRegistrar interface, calling AnnotationConfigApplicationContext#register to manually register beans, or defining beans in the @ Configuration class. So today we want to talk about the principle of @ Bean instantiation in the @ Configuration configuration class.

First, let's take a look at an example of configuring @ beans in the @ Configuration class:

@Configuration
@EnableCaching
public class CachingConfig {

    @Bean
    public RedisCacheManager redisCacheManager(RedisTemplate<Object, Object> redisTemplate) {
        RedisTemplate<Object, Object> newRedisTemplate = new RedisTemplate<>();
        newRedisTemplate.setConnectionFactory(redisTemplate.getConnectionFactory());

        RedisCacheManager redisCacheManager = new RedisCacheManager(newRedisTemplate);
        redisCacheManager.setUsePrefix(true);
        redisCacheManager.setDefaultExpiration(600);
        return redisCacheManager;
    }
}

The @ Configuration annotation here marks that this class is a Configuration class. When Spring starts, it will read the bean definition in this class and generate an instance.

So where does it load bean s and instantiate them?

Introduction to SpringBoot startup process

First, let's sort out a startup process of SpringBoot.

The entry is in the main method of the main class:

public static void main(String[] args) {
    SpringApplication.run(Abc.class, args);
}

Create environment

Then we get to the SpringApplication#run method

The first is to create an environment instance, where some initial configurations and which profile to use will be loaded

ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);

Secondly, in the prepareEnvironment method, the initialization of the context of Spring Cloud will be cut in, and the ancestinitializer initializer will be added for the SpringApplication instance. In this way, when the context of SpringBoot is initialized, the context of SpringCoud will be set as its own parent container.

So where did it connect?

Originally, the ApplicationEnvironmentPreparedEvent event will be published in the prepareEnvironment method, and the spring cloud will load a listener called bootstrap applicationlistener during startup, where the spring cloud context will be initialized.

private ConfigurableEnvironment prepareEnvironment(
      SpringApplicationRunListeners listeners,
      ApplicationArguments applicationArguments) {
   // Create and configure the environment
   ConfigurableEnvironment environment = getOrCreateEnvironment();
   configureEnvironment(environment, applicationArguments.getSourceArgs());
   // Here is the point
   listeners.environmentPrepared(environment);
   if (!this.webEnvironment) {
      environment = new EnvironmentConverter(getClassLoader())
            .convertToStandardEnvironmentIfNecessary(environment);
   }
   return environment;
}

There is such a line in the spring.factories file of spring cloud context, so this listener will be loaded into the container.

org.springframework.context.ApplicationListener=\
org.springframework.cloud.bootstrap.BootstrapApplicationListener

Create context

Secondly, create ApplicationContext, and then do some basic filling work

context = createApplicationContext();
prepareContext(context, environment, listeners, applicationArguments, printedBanner);

When creating ApplicationContext, some Spring built-in bean s will be loaded, such as its important ConfigurationClassPostProcessor.

When creating a web application context, the context class that will be created is org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext.

The constructor of this class is as follows

public AnnotationConfigEmbeddedWebApplicationContext() {
   this.reader = new AnnotatedBeanDefinitionReader(this);
   this.scanner = new ClassPathBeanDefinitionScanner(this);
}

It will create an AnnotatedBeanDefinitionReader instance. The constructor of this class is as follows:

public AnnotatedBeanDefinitionReader(BeanDefinitionRegistry registry, Environment environment) {
   Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
   Assert.notNull(environment, "Environment must not be null");
   this.registry = registry;
   this.conditionEvaluator = new ConditionEvaluator(registry, environment, null);
   AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
}

The most important is the last line, which will load some built-in classes. The final method called is registerAnnotationConfigProcessors. The method is a little long, and only the most important line will be posted. bean ConfigurationClassPostProcessor will be registered here, which is the basis of bean loading and parsing later.

public static Set<BeanDefinitionHolder> registerAnnotationConfigProcessors(
      BeanDefinitionRegistry registry, Object source) {
    // ... omitted
    if (!registry.containsBeanDefinition(CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME)) {
       RootBeanDefinition def = new RootBeanDefinition(ConfigurationClassPostProcessor.class);
       def.setSource(source);
       beanDefs.add(registerPostProcessor(registry, def, CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME));
    }
    // ... omitted
}

Load main class

private void prepareContext(ConfigurableApplicationContext context,
      ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
      ApplicationArguments applicationArguments, Banner printedBanner) {

   // Apply the initializer, such as setting the SpringCloud context as the parent container of the current context
   applyInitializers(context);

   // Get the main class and load it into registry
   Set<Object> sources = getSources();
   Assert.notEmpty(sources, "Sources must not be empty");
   load(context, sources.toArray(new Object[sources.size()]));
   listeners.contextLoaded(context);
}

Refresh context

After preparing the context, the context will be refreshed

refreshContext(context);

In fact, refreshing the context is mainly divided into two steps: one is to load the Bean definition and the other is to instantiate the Bean (except for lazy loaded beans, they are instantiated in advance). In order to highlight the key points here, we will load the @ Bean annotation tag into the registry and parse it according to the main path.

Then it will enter the AbstractApplicationContext#refresh method, and then enter the invokebeanfactoryprocessors method.

public void refresh() throws BeansException, IllegalStateException {

 // Invoke factory processors registered as beans in the context.
 invokeBeanFactoryPostProcessors(beanFactory);
}

Finally, it will call the method postprocessor registrationdelegate #invokebeanfactorypostprocessors (configurablelistablebeanfactory, Java. Util. List < beanfactoryprocessor > beanfactoryprocessors)

public static void invokeBeanFactoryPostProcessors(
      ConfigurableListableBeanFactory beanFactory, List<BeanFactoryPostProcessor> beanFactoryPostProcessors) {

   // Invoke BeanDefinitionRegistryPostProcessors first, if any.
   Set<String> processedBeans = new HashSet<String>();

   if (beanFactory instanceof BeanDefinitionRegistry) {
      BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;
      List<BeanFactoryPostProcessor> regularPostProcessors = new LinkedList<BeanFactoryPostProcessor>();
      List<BeanDefinitionRegistryPostProcessor> registryProcessors = new LinkedList<BeanDefinitionRegistryPostProcessor>();

      for (BeanFactoryPostProcessor postProcessor : beanFactoryPostProcessors) {
         if (postProcessor instanceof BeanDefinitionRegistryPostProcessor) {
            BeanDefinitionRegistryPostProcessor registryProcessor =
                  (BeanDefinitionRegistryPostProcessor) postProcessor;
            registryProcessor.postProcessBeanDefinitionRegistry(registry);
            registryProcessors.add(registryProcessor);
         }
         else {
            regularPostProcessors.add(postProcessor);
         }
      }

      // The ConfigurationClassPostProcessor will be called here
      invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);
      currentRegistryProcessors.clear();
}

    // Call beanfactoryprocessor
}

Therefore, the actual call is invokeBeanDefinitionRegistryPostProcessors

ConfigurationClassPostProcessor resolves the main class

Here you will enter the method ConfigurationClassPostProcessor#processConfigBeanDefinitions

First, you will get the configuration class. Because the main class was loaded before, and the main class is also a configuration class, there will be a main class in configCandidates.

for (String beanName : candidateNames) {
   BeanDefinition beanDef = registry.getBeanDefinition(beanName);
   if (ConfigurationClassUtils.isFullConfigurationClass(beanDef) ||
         ConfigurationClassUtils.isLiteConfigurationClass(beanDef)) {
      if (logger.isDebugEnabled()) {
         logger.debug("Bean definition has already been processed as a configuration class: " + beanDef);
      }
   }
   else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
      configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
   }
}

Then create a Configuration class parser and parse it. Here, all classes under the class path will be loaded

ConfigurationClassParser parser = new ConfigurationClassParser(
      this.metadataReaderFactory, this.problemReporter, this.environment,
      this.resourceLoader, this.componentScanBeanNameGenerator, registry);
      
Set<BeanDefinitionHolder> candidates = new LinkedHashSet<BeanDefinitionHolder>(configCandidates);
parser.parse(candidates);  

For each class, a ConfigurationClass object will be created, and if there is a method marked with @ Bean, it will be added to the property beanMethods of ConfigurationClass.

final class ConfigurationClass {
   private final Set<BeanMethod> beanMethods = new LinkedHashSet<BeanMethod>();
}   

Then, the configuration class is processed through the ConfigurationClassBeanDefinitionReader. Here, all the parsed classes will be put into the configClasses and read.

Set<ConfigurationClass> configClasses = new LinkedHashSet<ConfigurationClass>(parser.getConfigurationClasses());
configClasses.removeAll(alreadyParsed);

// Read the model and create bean definitions based on its content
if (this.reader == null) {
   this.reader = new ConfigurationClassBeanDefinitionReader(
         registry, this.sourceExtractor, this.resourceLoader, this.environment,
         this.importBeanNameGenerator, parser.getImportRegistry());
}
this.reader.loadBeanDefinitions(configClasses);

Load the bean definition of the configuration class

Next, enter the bean definition reading of Configuration class

ConfigurationClassBeanDefinitionReader

public void loadBeanDefinitions(Set<ConfigurationClass> configurationModel) {
   TrackedConditionEvaluator trackedConditionEvaluator = new TrackedConditionEvaluator();
   for (ConfigurationClass configClass : configurationModel) {
      loadBeanDefinitionsForConfigurationClass(configClass, trackedConditionEvaluator);
   }
}


private void loadBeanDefinitionsForConfigurationClass(
      ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator) {
   // ellipsis       
   // Here is the focus, the method of handling the @ Bean tag of the configuration class
   for (BeanMethod beanMethod : configClass.getBeanMethods()) {
      loadBeanDefinitionsForBeanMethod(beanMethod);
   }

    // ellipsis
}

The loadBeanDefinitionsForBeanMethod is defined as follows. Here, a bean definition container of type ConfigurationClassBeanDefinition will be created for each method marked with @ bean annotation and added to the registry.

private void loadBeanDefinitionsForBeanMethod(BeanMethod beanMethod) {

   // For each method marked by @ Bean, create a ConfigurationClassBeanDefinition object
   ConfigurationClassBeanDefinition beanDef = new ConfigurationClassBeanDefinition(configClass, metadata);
   beanDef.setResource(configClass.getResource());
   beanDef.setSource(this.sourceExtractor.extractSource(metadata, configClass.getResource()));

   if (metadata.isStatic()) {
      // static @Bean method
      beanDef.setBeanClassName(configClass.getMetadata().getClassName());
      beanDef.setFactoryMethodName(methodName);
   }
   else {
      // Take this branch  
      // instance @Bean method
      beanDef.setFactoryBeanName(configClass.getBeanName());
      beanDef.setUniqueFactoryMethodName(methodName);
   }
   beanDef.setAutowireMode(RootBeanDefinition.AUTOWIRE_CONSTRUCTOR);
   beanDef.setAttribute(RequiredAnnotationBeanPostProcessor.SKIP_REQUIRED_CHECK_ATTRIBUTE, Boolean.TRUE);

   AnnotationConfigUtils.processCommonDefinitionAnnotations(beanDef, metadata);
 
   // Omit some codes
   // Register the bean defined by the @ bean method
   this.registry.registerBeanDefinition(beanName, beanDefToRegister);
}

The class marked by this @ Bean annotation is put into the registry.

@Bean tag method call

So what is the process of calling initialization?

First, when we all know that the Bean is instantiated, we call the AbstractBeanFactory#getBean method.

Note that this may be because the bean itself is instantiated in advance, or it may be instantiated because it is dependent on other beans.

AbstractBeanFactory

public Object getBean(String name) throws BeansException {
   return doGetBean(name, null, null, false);
}

AbstractBeanFactory

protected <T> T doGetBean(
      final String name, final Class<T> requiredType, final Object[] args, boolean typeCheckOnly)
      throws BeansException {
    // Omit      
    if (mbd.isSingleton()) {
       sharedInstance = getSingleton(beanName, new ObjectFactory<Object>() {
          @Override
          public Object getObject() throws BeansException {
             try {
                return createBean(beanName, mbd, args);
             }
             catch (BeansException ex) {
                // Explicitly remove instance from singleton cache: It might have been put there
                // eagerly by the creation process, to allow for circular reference resolution.
                // Also remove any beans that received a temporary reference to the bean.
                destroySingleton(beanName);
                throw ex;
             }
          }
       });
       bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
    }
    // Omit
} 

Then call the createBean method.

AbstractAutowireCapableBeanFactory#createBean

protected Object createBean(String beanName, RootBeanDefinition mbd, Object[] args) throws BeanCreationException {
   // Omit 
   Object beanInstance = doCreateBean(beanName, mbdToUse, args);
   if (logger.isDebugEnabled()) {
      logger.debug("Finished creating instance of bean '" + beanName + "'");
   }
   return beanInstance;
}

doCreateBean

protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final Object[] args)
      throws BeanCreationException {

   // Instantiate the bean.
   BeanWrapper instanceWrapper = null;
   if (mbd.isSingleton()) {
      instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
   }
   if (instanceWrapper == null) {
      // Call specific methods to create bean s 
      instanceWrapper = createBeanInstance(beanName, mbd, args);
   }
   // ...
} 

Here's the key point. Remember to set the factoryMethod of the bean as the corresponding method of the Configuration class. The reflection call will be made here. The specific method is instantiateUsingFactoryMethod.

protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd, Object[] args) {
   // Make sure bean class is actually resolved at this point.
   Class<?> beanClass = resolveBeanClass(mbd, beanName);

   if (beanClass != null && !Modifier.isPublic(beanClass.getModifiers()) && !mbd.isNonPublicAccessAllowed()) {
      throw new BeanCreationException(mbd.getResourceDescription(), beanName,
            "Bean class isn't public, and non-public access not allowed: " + beanClass.getName());
   }

   // Come here
   if (mbd.getFactoryMethodName() != null)  {
      return instantiateUsingFactoryMethod(beanName, mbd, args);
   }
   // ...
}   


protected BeanWrapper instantiateUsingFactoryMethod(
      String beanName, RootBeanDefinition mbd, Object[] explicitArgs) {

   return new ConstructorResolver(this).instantiateUsingFactoryMethod(beanName, mbd, explicitArgs);
}

ConstructorResolver#instantiateUsingFactoryMethod

   // Find the specific method factoryMethodToUse
   Object beanInstance;

   if (System.getSecurityManager() != null) {
      final Object fb = factoryBean;
      final Method factoryMethod = factoryMethodToUse;
      final Object[] args = argsToUse;
      beanInstance = AccessController.doPrivileged(new PrivilegedAction<Object>() {
         @Override
         public Object run() {
            return beanFactory.getInstantiationStrategy().instantiate(
                  mbd, beanName, beanFactory, fb, factoryMethod, args);
         }
      }, beanFactory.getAccessControlContext());
   }
   else {
      // Come here 
      beanInstance = this.beanFactory.getInstantiationStrategy().instantiate(
            mbd, beanName, this.beanFactory, factoryBean, factoryMethodToUse, argsToUse);
   }

   if (beanInstance == null) {
      return null;
   }
   bw.setBeanInstance(beanInstance);
   return bw;

Final reflection call method

SimpleInstantiationStrategy

@Override
public Object instantiate(RootBeanDefinition bd, String beanName, BeanFactory owner,
      Object factoryBean, final Method factoryMethod, Object... args) {

   try {
      if (System.getSecurityManager() != null) {
         AccessController.doPrivileged(new PrivilegedAction<Object>() {
            @Override
            public Object run() {
               ReflectionUtils.makeAccessible(factoryMethod);
               return null;
            }
         });
      }
      else {
         ReflectionUtils.makeAccessible(factoryMethod);
      }

      Method priorInvokedFactoryMethod = currentlyInvokedFactoryMethod.get();
      try {
         currentlyInvokedFactoryMethod.set(factoryMethod);
         // Here you make a method call and return
         return factoryMethod.invoke(factoryBean, args);
      }
      finally {
         if (priorInvokedFactoryMethod != null) {
            currentlyInvokedFactoryMethod.set(priorInvokedFactoryMethod);
         }
         else {
            currentlyInvokedFactoryMethod.remove();
         }
      }
   }
   catch (IllegalArgumentException ex) {
      throw new BeanInstantiationException(factoryMethod,
            "Illegal arguments to factory method '" + factoryMethod.getName() + "'; " +
            "args: " + StringUtils.arrayToCommaDelimitedString(args), ex);
   }
   catch (IllegalAccessException ex) {
      throw new BeanInstantiationException(factoryMethod,
            "Cannot access factory method '" + factoryMethod.getName() + "'; is it public?", ex);
   }
   catch (InvocationTargetException ex) {
      String msg = "Factory method '" + factoryMethod.getName() + "' threw exception";
      if (bd.getFactoryBeanName() != null && owner instanceof ConfigurableBeanFactory &&
            ((ConfigurableBeanFactory) owner).isCurrentlyInCreation(bd.getFactoryBeanName())) {
         msg = "Circular reference involving containing bean '" + bd.getFactoryBeanName() + "' - consider " +
               "declaring the factory method as static for independence from its containing instance. " + msg;
      }
      throw new BeanInstantiationException(factoryMethod, msg, ex.getTargetException());
   }
}

So here, the complete bean definition loading and instantiation are completed, and some other Spring routine processing will be carried out later.

Tags: Java Spring Spring Boot

Posted on Fri, 26 Nov 2021 23:35:38 -0500 by starnol