Here’s what you can learn about Spring:

  • What does the getBean in refreshContext do
  • Call timing for multiple extension points in BeanPostProcessor
  • Dynamic proxy entry
  • Mechanisms for resolving circular dependencies
  • Dealing with both circular dependencies and multiple dynamic proxies

Cut into the code of the problem

To simulate cyclic dependencies and multiple dynamic proxies, using the following code, you can see that DevController and TestController are interdependent and simulate multiple dynamic proxies through @Transactional and @async

@Transactional
@RestController
public class DevController {
    @Autowired
    private TestController testController;
    @GetMapping("dev")
    @Async
    public String dev(a) {
        return "dev"; }}Copy the code
@Transactional
@RestController
public class TestController {
    @Autowired
    private DevController devController;
    @GetMapping("test")
    @Async
    public String test(a) {
        return "test"; }}Copy the code

What does the getBean in refreshContext do

Spring, as an IOC container, looks simple at first glance. It simply initializes objects and places them in a map, fetching them directly from the map as needed. The general idea of Spring is true, but the details, dealing with Factory classes, Advisor classes, circular dependencies, dynamic proxies, and so on, lead to a level 3 cache to handle these special cases.

  • SingletonObjects: A level 1 cache that holds initialized singleton bean instances
  • EarlySingletonObjects: Hold singleton bean instances that were exposed earlier
  • SingletonFactories: The factory function object that holds the singleton bean

GetBean involves all levels of caching, as shown in the figure above

  • CreateBeanInstance: create a new instance using the constructor
  • PopulateBean: inject individual properties into the instance
  • InitializeBean: Performs some subsequent initialization of the instance

Some of the detailed logic is indicated in the figure above and will not be repeated here.

2. Call timing of multiple extension points in BeanPostProcessor

Spring provides several BeanPostProcessor interfaces (BPP). BPP provides the opportunity for users to customize beans at different points in time. Most of the BPP interfaces are highlighted in yellow bubbles above.

  • PostProcessMergedBeanDefinition: to BeanDefinition add additional custom configurations
  • GetEarlyBeanReference: Returns an earlier exposed bean reference. A typical example is when a dynamic proxy is available for circular dependencies, where the proxy instance needs to be returned first
  • PostProcessAfterInstantiation: before populateBean users can manually inject some properties
  • PostProcessProperties: Inject properties, such as configuration file encryption information after this decryption
  • PostProcessBeforeInitialization: properties after injection of some additional operations
  • PostProcessAfterInitialization: instance finish to create the final step, here is some BPP the timing of the agent

3. Dynamic proxy entry

Spring’s dynamic proxy class is implemented through BPP. AbstractAutoProxyCreator is a very typical automatic proxy class. It implements the getEarlyBeanReference and postProcessAfterInitialization two interfaces are agent logic, through earlyProxyReferences cache to avoid agent twice for the same instance.

@Override
public Object getEarlyBeanReference(Object bean, String beanName) {
    Object cacheKey = getCacheKey(bean.getClass(), beanName);
    // Put the instance in the cache to show that it has been proxied
    this.earlyProxyReferences.put(cacheKey, bean);
    // wrapIfNecessary proxys the bean instance according to the Advisor
    return wrapIfNecessary(bean, beanName, cacheKey);
}
@Override
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
    if(bean ! =null) {
        Object cacheKey = getCacheKey(bean.getClass(), beanName);
        // If the instance has not already been brokered, then brokered
        if (this.earlyProxyReferences.remove(cacheKey) ! = bean) {returnwrapIfNecessary(bean, beanName, cacheKey); }}// The instance has been proxied and returns directly
    return bean;
}
Copy the code

4. Mechanism to solve circular dependency

In conjunction with the getBean workflow, let’s look at how DevController and TestController in question resolve circular dependencies (assuming DevController was constructed before TestController)

@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    // First look up from the level 1 cache
    Object singletonObject = this.singletonObjects.get(beanName);
    // If the bean instance is being created, look it up from the level-2 cache
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
        synchronized (this.singletonObjects) {
            singletonObject = this.earlySingletonObjects.get(beanName);
            // If the level-2 cache cannot find the reference and allows early exposure, the reference is looked up from the level-2 cache
            if (singletonObject == null&& allowEarlyReference) { ObjectFactory<? > singletonFactory =this.singletonFactories.get(beanName);
                // Level 3 cache found
                if(singletonFactory ! =null) {
                    // Call its factory method to construct the instance and move it from the level 3 cache to the level 2 cache
                    singletonObject = singletonFactory.getObject();
                    this.earlySingletonObjects.put(beanName, singletonObject);
                    this.singletonFactories.remove(beanName); }}}}return singletonObject;
}
Copy the code

5. Dealing with circular dependencies and multiple dynamic proxies

Many articles on the Web have mentioned this level 3 cache as a solution to circular dependencies, but if you think about it, in the case of circular dependencies, it is enough to put the bean in the level 2 cache. Why do you need level 3 cache? I think level 3 cache is used to solve the problem of both circular dependencies and dynamic proxies.

As you can see, 4. The mechanism for resolving circular dependencies explains how the level-level cache singletonFactories are used with circular dependencies, while the level-level cache earlySingletonObjects are used with multiple dynamic proxies

The code is at the end of the doCreateBean, and I think that once you understand this code, you’ve pretty much understood the most obscure part of Spring IOC

if (earlySingletonExposure) {
    // Look up the bean instance from the level-1 and level-2 caches. Since the last parameter is false, the level-2 caches are not looked up
    Object earlySingletonReference = getSingleton(beanName, false);
    DevController is dynamically represented by @Transactional and is exposed to the second-level cache via AbstractAutoProxyCreator under the above circular dependency
    // The branch is entered
    if(earlySingletonReference ! =null) {
        // If the DevController is not represented in the initializeBean and this condition is met, the represented DevController is returned
        if (exposedObject == bean) {
            exposedObject = earlySingletonReference;
        }
        / / if in initializeBean DevController was agent (such as @ Async AbstractBeanFactoryAwareAdvisingPostProcessor)
        // So exposedObject! = bean, the following judgment will be attempted
        else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
            // If DevController has dependencies on other beans, in this case TestController, then there are two DevControllers
            / / @ Async and @ Transactional proxy, return which one is correct, so throw an exception
            String[] dependentBeans = getDependentBeans(beanName);
            Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
            for (String dependentBean : dependentBeans) {
                if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
                    actualDependentBeans.add(dependentBean);
                }
            }
            if(! actualDependentBeans.isEmpty()) {throw new BeanCurrentlyInCreationException(beanName,
                    "Bean with name '" + beanName + "' has been injected into other beans [" +
                    StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +
                    "] in its raw version as part of a circular reference, but has eventually been " +
                    "wrapped. This means that said other beans do not use the final version of the " +
                    "bean. This is often the result of over-eager type matching - consider using " +
                    "'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example."); }}}}Copy the code

conclusion

  • Spring provides a variety of BeanPostprocessors for users to intervene in the instantiation process, and Spring itself uses these BPPS for dynamic proxy and other work. If you are developing some framework functionality, you can get a lot more done with BPP
  • The instantiation of a common isolated bean is very simple, after the createBeanInstance, populateBean, initializeBean and so on
  • Other dependent beans are injected in the populateBean phase, and if the bean is not already created, it is instantiated first
  • If there are cyclic dependencies, the level 3 cache is used to expose them in advance; if there are no cyclic dependencies, they are not exposed
  • If circular dependencies and multiple dynamic proxies exist at the same time, you can use methods such as @lazy to delay instantiation