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 processFirst, 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 callSo 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.