- preface
- BeanDefinition
- Introduction to BeanFactory
- Web container startup process
- bean loading
- FactoryBean
- Cyclic dependence
- bean lifecycle
- official account
preface
Many articles analyze xml configuration, but now Spring Boot development is mostly based on annotations. This paper analyzes the source code of Spring IOC container from the perspective of annotation.
edition:
- Spring Boot: 2.1.6.RELEASE
- Spring FrameWork: 5.1.8.RELEASE
- Java 8
Part of the article is from: https://www.javadoop.com/post/spring-ioc
BeanDefinition
The BeanDefinition interface defines a bean instance that contains properties, constructor parameters, and other specific information.
public interface BeanDefinition extends AttributeAccessor, BeanMetadataElement { // There are only two types of ConfigurableBeanFactory: singleton and prototype. // request, session, etc. are Web-based extensions String SCOPE_SINGLETON = ConfigurableBeanFactory.SCOPE_SINGLETON; String SCOPE_PROTOTYPE = ConfigurableBeanFactory.SCOPE_PROTOTYPE; // unimportance int ROLE_APPLICATION = 0; int ROLE_SUPPORT = 1; int ROLE_INFRASTRUCTURE = 2; // Set the information of the parent Bean (inherit the configuration information of the parent Bean) void setParentName(@Nullable String parentName); @Nullable String getParentName(); // Set the class name of the Bean and generate an instance through reflection void setBeanClassName(@Nullable String beanClassName); // Returns the class name of the current Bean String getBeanClassName(); void setScope(@Nullable String scope); @Nullable String getScope(); // Delay initialization void setLazyInit(boolean lazyInit); boolean isLazyInit(); // Set all beans that this Bean depends on, not those marked with @ Autowire void setDependsOn(@Nullable String... dependsOn); @Nullable String[] getDependsOn(); // Set whether this Bean can be injected into other beans, which is only valid for injection according to type, // If you inject by name, even if false is set here, it is OK void setAutowireCandidate(boolean autowireCandidate); boolean isAutowireCandidate(); // For multiple implementations of the same interface, if you do not specify a name, Spring will give priority to bean s with primary set to true void setPrimary(boolean primary); boolean isPrimary(); // If the Bean is generated by the factory method, specify the factory name; Otherwise, it is generated by reflection void setFactoryBeanName(@Nullable String factoryBeanName); @Nullable String getFactoryBeanName(); // Specifies the name of the factory method in the factory class void setFactoryMethodName(@Nullable String factoryMethodName); @Nullable String getFactoryMethodName(); // Returns the constructor parameters of the bean ConstructorArgumentValues getConstructorArgumentValues(); default boolean hasConstructorArgumentValues() { return !getConstructorArgumentValues().isEmpty(); } // The attribute value in the Bean and the returned instance will be changed during Bean factory post processing MutablePropertyValues getPropertyValues(); default boolean hasPropertyValues() { return !getPropertyValues().isEmpty(); } void setInitMethodName(@Nullable String initMethodName); @Nullable String getInitMethodName(); void setDestroyMethodName(@Nullable String destroyMethodName); @Nullable String getDestroyMethodName(); void setRole(int role); int getRole(); void setDescription(@Nullable String description); @Nullable String getDescription(); // Read-only attributes boolean isSingleton(); boolean isPrototype(); boolean isAbstract(); @Nullable String getResourceDescription(); @Nullable BeanDefinition getOriginatingBeanDefinition(); }
AnnotationConfigUtils#processCommonDefinitionAnnotations(...)
static void processCommonDefinitionAnnotations(AnnotatedBeanDefinition abd, AnnotatedTypeMetadata metadata) { AnnotationAttributes lazy = attributesFor(metadata, Lazy.class); if (lazy != null) { abd.setLazyInit(lazy.getBoolean("value")); } else if (abd.getMetadata() != metadata) { lazy = attributesFor(abd.getMetadata(), Lazy.class); if (lazy != null) { abd.setLazyInit(lazy.getBoolean("value")); } } if (metadata.isAnnotated(Primary.class.getName())) { abd.setPrimary(true); } AnnotationAttributes dependsOn = attributesFor(metadata, DependsOn.class); if (dependsOn != null) { abd.setDependsOn(dependsOn.getStringArray("value")); } AnnotationAttributes role = attributesFor(metadata, Role.class); if (role != null) { abd.setRole(role.getNumber("value").intValue()); } AnnotationAttributes description = attributesFor(metadata, Description.class); if (description != null) { abd.setDescription(description.getString("value")); } }
You can see that the processCommonDefinitionAnnotations method will fill in the AnnotatedBeanDefinition according to the annotations, which are:
- Lazy
- Primary
- DependsOn
- Role
- Description
Look up at the call and find that it will be registered as a bean definition in ConfigurationClassBeanDefinitionReader#registerBeanDefinitionForImportedConfigurationClass.
private void registerBeanDefinitionForImportedConfigurationClass(ConfigurationClass configClass) { AnnotationMetadata metadata = configClass.getMetadata(); AnnotatedGenericBeanDefinition configBeanDef = new AnnotatedGenericBeanDefinition(metadata); ScopeMetadata scopeMetadata = scopeMetadataResolver.resolveScopeMetadata(configBeanDef); configBeanDef.setScope(scopeMetadata.getScopeName()); String configBeanName = this.importBeanNameGenerator.generateBeanName(configBeanDef, this.registry); // 1. Fill in configBeanDef with annotations AnnotationConfigUtils.processCommonDefinitionAnnotations(configBeanDef, metadata); BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(configBeanDef, configBeanName); definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry); // 2. Register the bean definition in the registry this.registry.registerBeanDefinition(definitionHolder.getBeanName(), definitionHolder.getBeanDefinition()); configClass.setBeanName(configBeanName); if (logger.isTraceEnabled()) { logger.trace("Registered bean definition for imported class '" + configBeanName + "'"); } }
It will eventually be called by the invokebeanfactoryprocessors (beanfactory) method of AbstractApplicationContext#refresh.
@Override public void refresh() throws BeansException, IllegalStateException { synchronized (this.startupShutdownMonitor) { // Prepare this context for refreshing. prepareRefresh(); // Tell the subclass to refresh the internal bean factory. ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); // Prepare the bean factory for use in this context. prepareBeanFactory(beanFactory); try { // Allows post-processing of the bean factory in context subclasses. postProcessBeanFactory(beanFactory); // Invoke factory processors registered as beans in the context. invokeBeanFactoryPostProcessors(beanFactory); // Register bean processors that intercept bean creation. registerBeanPostProcessors(beanFactory); // Initialize message source for this context. initMessageSource(); // Initialize event multicaster for this context. initApplicationEventMulticaster(); // Initialize other special beans in specific context subclasses. onRefresh(); // Check for listener beans and register them. registerListeners(); // Instantiate all remaining (non-lazy-init) singletons. finishBeanFactoryInitialization(beanFactory); // Last step: publish corresponding event. finishRefresh(); } ... } }
Introduction to BeanFactory
BeanFactory is a factory that produces beans. It is responsible for producing and managing various bean instances. As can be seen from the figure below, ApplicationContext is also a BeanFactory. If BeanFactory is the heart of Spring, then ApplicationContext is the complete body.
ApplicationContext is a general interface that provides configuration information when an application runs. ApplicationContext cannot be changed when the program is running, but the implementation class can re-enter the configuration information.
There are many implementation classes of ApplicationContext, such as AnnotationConfigApplicationContext, AnnotationConfigWebApplicationContext, ClassPathXmlApplicationContext, FileSystemXmlApplicationContext, XmlWebApplicationContext, etc. What we analyzed above is the AnnotationConfigApplicationContext, which provides configuration information in the form of annotation, so we don't need to write XML configuration files, which is very concise.
Web container startup process
This article is developed using Spring Boot, and its startup code is:
@SpringBootApplication @EnableScheduling @EnableAspectJAutoProxy public class AppApplication { public static void main(String[] args) { SpringApplication.run(AppApplication.class, args); } }
The core point is this sentence:
SpringApplication.run(AppApplication.class, args);
The code of SpringApplication will not be analyzed. It is clear that the purpose of looking at the source code this time is to analyze the container source code. The startup process of Spring Boot and other information are ignored, because the Spring code is really complex. After analyzing the run method above, we will eventually trace it to Spring application #run (...) method.
public ConfigurableApplicationContext run(String... args) { StopWatch stopWatch = new StopWatch(); stopWatch.start(); ConfigurableApplicationContext context = null; Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>(); configureHeadlessProperty(); SpringApplicationRunListeners listeners = getRunListeners(args); listeners.starting(); try { ApplicationArguments applicationArguments = new DefaultApplicationArguments(args); ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments); configureIgnoreBeanInfo(environment); Banner printedBanner = printBanner(environment); context = createApplicationContext(); exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[] { ConfigurableApplicationContext.class }, context); prepareContext(context, environment, listeners, applicationArguments, printedBanner); refreshContext(context); afterRefresh(context, applicationArguments); stopWatch.stop(); if (this.logStartupInfo) { new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch); } listeners.started(context); callRunners(context, applicationArguments); } catch (Throwable ex) { handleRunFailure(context, ex, exceptionReporters, listeners); throw new IllegalStateException(ex); } try { listeners.running(context); } catch (Throwable ex) { handleRunFailure(context, ex, exceptionReporters, null); throw new IllegalStateException(ex); } return context; }
Related to context are the following three codes:
prepareContext(context, environment, listeners, applicationArguments, printedBanner); refreshContext(context); afterRefresh(context, applicationArguments);
The refreshContext method refreshes the given context:
private void refreshContext(ConfigurableApplicationContext context) { refresh(context); if (this.registerShutdownHook) { try { context.registerShutdownHook(); } catch (AccessControlException ex) { // Not allowed in some environments. } } }
protected void refresh(ApplicationContext applicationContext) { Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext); ((AbstractApplicationContext) applicationContext).refresh(); }
You will find that the AbstractApplicationContext#refresh method is finally called. Note reference from: https://www.javadoop.com/post/spring-ioc
@Override public void refresh() throws BeansException, IllegalStateException { // A lock, or else refresh() is not over, and you start or destroy the container again. That's a mess synchronized (this.startupShutdownMonitor) { // Prepare the work, record the start time of the container, mark the "started" status, and handle the placeholder in the configuration file prepareRefresh(); // This is a key step. After this step is completed, the configuration file will be parsed into Bean definitions and registered in BeanFactory, // Of course, the Bean mentioned here has not been initialized, but the configuration information has been extracted, // Registration only saves these information to the registration center (in the final analysis, the core is a beanname - > beandefinition map) ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); // Set the class loader of BeanFactory, add several beanpostprocessors, and manually register several special beans prepareBeanFactory(beanFactory); try { // [here you need to know the knowledge of beanfactoryprocessor. If a Bean implements this interface, // After the container is initialized, Spring will be responsible for calling the postProcessBeanFactory method inside.] // Here is the extension point provided to subclasses. When you get here, all beans have been loaded and registered, but they have not been initialized // For specific subclasses, you can add some special beanfactoryprocessor implementation classes or do something at this step postProcessBeanFactory(beanFactory); // Call the postProcessBeanFactory(factory) method of each implementation class of beanfactoryprocessor invokeBeanFactoryPostProcessors(beanFactory); // Register the implementation class of BeanPostProcessor. Pay attention to the difference between BeanPostProcessor and BeanFactoryPostProcessor // This interface has two methods: postProcessBeforeInitialization and postProcessAfterInitialization // The two methods are executed before and after Bean initialization. Note that the Bean has not yet been initialized registerBeanPostProcessors(beanFactory); // Initialize the MessageSource of the current ApplicationContext. Internationalization will not be expanded here, otherwise it will be endless initMessageSource(); // Initialize the event broadcaster of the current ApplicationContext, which will not be expanded here initApplicationEventMulticaster(); // You can know from the method name that a typical template method (hook method), // Specific subclasses can initialize some special beans here (before initializing singleton beans) onRefresh(); // Register the event listener. The listener needs to implement the ApplicationListener interface. This is not our focus, too registerListeners(); // Focus, focus, focus // Initialize all singleton beans //(except for lazy init) finishBeanFactoryInitialization(beanFactory); // Finally, broadcast the event and the ApplicationContext is initialized finishRefresh(); } catch (BeansException ex) { if (logger.isWarnEnabled()) { logger.warn("Exception encountered during context initialization - " + "cancelling refresh attempt: " + ex); } // Destroy already created singletons to avoid dangling resources. // Destroy the initialized singleton Beans to prevent some Beans from occupying resources all the time destroyBeans(); // Reset 'active' flag. cancelRefresh(ex); throw ex; } finally { // Reset common introspection caches in Spring's core, since we // might not ever need metadata for singleton beans anymore... resetCommonCaches(); } } }
The core process is the content in the try code block. We should understand the overall principle. This article cannot analyze it line by line and sentence by sentence. If you do that, it will completely become a dictionary
bean loading
bean loaded calling function: org springframework. beans. factory. support. AbstractBeanFactory#doGetBean
protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType, @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException { // Extract the name of the corresponding bean final String beanName = transformedBeanName(name); Object bean; // 1. Important, important, important! // Create a singleton bean to avoid circular dependency and try to get it from the cache Object sharedInstance = getSingleton(beanName); if (sharedInstance != null && args == null) { if (logger.isTraceEnabled()) { if (isSingletonCurrentlyInCreation(beanName)) { logger.trace("Returning eagerly cached instance of singleton bean '" + beanName + "' that is not fully initialized yet - a consequence of a circular reference"); } else { logger.trace("Returning cached instance of singleton bean '" + beanName + "'"); } } bean = getObjectForBeanInstance(sharedInstance, name, beanName, null); } else { // Circular dependency exists if (isPrototypeCurrentlyInCreation(beanName)) { // Prototype mode throws exceptions directly (circular dependency can only be solved in singleton mode) throw new BeanCurrentlyInCreationException(beanName); } // Check if bean definition exists in this factory. BeanFactory parentBeanFactory = getParentBeanFactory(); if (parentBeanFactory != null && !containsBeanDefinition(beanName)) { // Not found -> check parent. String nameToLookup = originalBeanName(name); if (parentBeanFactory instanceof AbstractBeanFactory) { return ((AbstractBeanFactory) parentBeanFactory).doGetBean( nameToLookup, requiredType, args, typeCheckOnly); } else if (args != null) { // Delegation to parent with explicit args. return (T) parentBeanFactory.getBean(nameToLookup, args); } else if (requiredType != null) { // No args -> delegate to standard getBean method. return parentBeanFactory.getBean(nameToLookup, requiredType); } else { return (T) parentBeanFactory.getBean(nameToLookup); } } // If you don't just do type checking, you need to create bean s and make records if (!typeCheckOnly) { markBeanAsCreated(beanName); } try { // Get the RootBeanDefinition. If the specified beanName is a child bean, the parent class attribute needs to be merged final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName); checkMergedBeanDefinition(mbd, beanName, args); // If there are dependencies, you need to recursively instantiate the dependent bean s String[] dependsOn = mbd.getDependsOn(); if (dependsOn != null) { for (String dep : dependsOn) { if (isDependent(beanName, dep)) { throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Circular depends-on relationship between '" + beanName + "' and '" + dep + "'"); } registerDependentBean(dep, beanName); try { getBean(dep); } catch (NoSuchBeanDefinitionException ex) { throw new BeanCreationException(mbd.getResourceDescription(), beanName, "'" + beanName + "' depends on missing bean '" + dep + "'", ex); } } } // Create bean instance // Creation of Singleton pattern if (mbd.isSingleton()) { sharedInstance = getSingleton(beanName, () -> { 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); } // Prototype schema creation else if (mbd.isPrototype()) { Object prototypeInstance = null; try { beforePrototypeCreation(beanName); prototypeInstance = createBean(beanName, mbd, args); } finally { afterPrototypeCreation(beanName); } bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd); } else { String scopeName = mbd.getScope(); final Scope scope = this.scopes.get(scopeName); if (scope == null) { throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'"); } try { Object scopedInstance = scope.get(beanName, () -> { beforePrototypeCreation(beanName); try { return createBean(beanName, mbd, args); } finally { afterPrototypeCreation(beanName); } }); bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd); } catch (IllegalStateException ex) { throw new BeanCreationException(beanName, "Scope '" + scopeName + "' is not active for the current thread; consider " + "defining a scoped proxy for this bean if you intend to refer to it from a singleton", ex); } } } catch (BeansException ex) { cleanupAfterBeanCreationFailure(beanName); throw ex; } } // Check whether the requiredType is the actual type of the bean. If it is not, it will be converted. If it is not successful, an exception will be thrown if (requiredType != null && !requiredType.isInstance(bean)) { try { T convertedBean = getTypeConverter().convertIfNecessary(bean, requiredType); if (convertedBean == null) { throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass()); } return convertedBean; } catch (TypeMismatchException ex) { if (logger.isTraceEnabled()) { logger.trace("Failed to convert bean '" + name + "' to required type '" + ClassUtils.getQualifiedName(requiredType) + "'", ex); } throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass()); } } return (T) bean; }
You can see that the loading of bean s is quite complex. The loading steps are as follows:
- Transform corresponding beanName
- Try loading a singleton from the cache
- bean instantiation
- Dependency checking of prototype patterns
- Detect parentBeanFactory
- Convert configuration file to RootBeanDefinition
- Looking for dependencies
- Create bean s for different scope s
- Type conversion
FactoryBean
BeanFactory mentioned earlier, and here comes another FactoryBean... It is said that Spring provides the implementation of more than 70 factorybeans, which shows its position in the Spring framework. They hide the details of instantiating complex beans and bring convenience to the upper application.
public interface FactoryBean<T> { // Return the bean instance created by FactoryBean. If isSingleton() returns true, the instance will be placed in the singleton cache pool of Spring container @Nullable T getObject() throws Exception; // Returns the bean type created by FactoryBean @Nullable Class<?> getObjectType(); default boolean isSingleton() { return true; } }
Cyclic dependence
Circular dependency is a circular reference. Two or more bean s hold each other. So how does Spring solve circular dependency?
There are three cases of circular dependency in Spring:
- Constructor loop dependency
- setter cyclic dependency
- Dependency handling of prototype scope
The constructor loop dependency cannot be solved, because a bean must first pass through the constructor when it is created, but the constructor is interdependent, which is equivalent to multi-threaded deadlock in Java.
The dependency caused by setter injection is completed by exposing the beans that have just completed the constructor injection but have not completed other steps (such as setter injection) in advance by the Spring container, and can only solve the bean circular dependency of the singleton scope. By exposing a singleton factory method in advance, other beans can reference the bean. The code is as follows:
@Nullable protected Object getSingleton(String beanName, boolean allowEarlyReference) { Object singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) { synchronized (this.singletonObjects) { singletonObject = this.earlySingletonObjects.get(beanName); if (singletonObject == null && allowEarlyReference) { ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName); if (singletonFactory != null) { singletonObject = singletonFactory.getObject(); this.earlySingletonObjects.put(beanName, singletonObject); this.singletonFactories.remove(beanName); } } } } return singletonObject; }
The definition of earlySingletonObjects is:
/** Cache of early singleton objects: bean name to bean instance. */ private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
For prototype scoped beans, the Spring container cannot complete dependency injection because the Spring container does not cache prototype scoped beans.
bean lifecycle
During the interview, the core of Spring is here, but just remember the general process.
official account
coding notes and notes, and later articles will be synchronized to the official account (Coding Insight).
Code and mind mapping in GitHub project Welcome to star!