An overview,

Long warning, in fact, I do not want to write too long article, one side is too redundant, on the one hand, the reader is easy to tired, but as long as it is related to the source level, it is certainly not short, because too short certainly does not make sense and cannot explain clearly, but I believe that the patience to read this article will be harvested!

Recently there are a lot of readers to the interview was asked about the Spring l3 cache solutions, many readers after the setback in the interview, try to read the source code, with breakpoints and try to find a set of a layer, for a moment his meng, I sums up these days, in order to be able to let the reader more to learn about Spring to solve the problem of circular dependencies, I decided to talk about it from the following four aspects:

  1. What are circular dependencies
  2. What if you don’t rely on Spring to solve the loop dependency itself?
  3. What are the drawbacks of the self-implementation approach?
  4. How are loop dependencies addressed in Spring?

What are cyclic dependencies

Loop dependency is straightforward: you refer to me, I refer to your state, as shown here:

3. How to solve the loop dependency without relying on Spring itself

For example, let’s say we can create the AService, put it in a cache, and then inject properties! Each time a property is injected, the required value of the property is fetched from the cache. As shown in the figure:

To summarize the process above:

  1. AServiceOnce created, add yourself to the level 2 cache and start injecting properties
  2. foundAServiceRely onBServiceIf there is no data in level 1 cache, then query level 2 cache, return if there is, and create if there is no data in level 1 cacheBService
  3. None in cache, start instantiatingBServiceAnd then inject the internal properties!
  4. Dependencies found when internal properties are injectedAServiceIf level 1 cache does not have data, it queries level 2 cache. If level 1 cache does not have data, it returns it. If level 2 cache does not have data, it creates it. So it fetched it from level 2 cacheAServiceInjected into theBService.
  5. BServiceAfter creation, move itself from level 2 cache to Level 1 cache and return.
  6. AServiceAccess to theBServiceReturns a level 1 cache that is injected into its own properties and removed from level 2 cacheAService!
  7. At this point, the loop dependency is created!

So with that in mind, how do we implement our logic in code?

How to solve the loop dependency without relying on Spring itself

First of all, we must define something like@AutowiredSuch a note is what we call here@MyAutowired

package simulation.annotations;

import java.lang.annotation.*;

/** * Custom injection annotations are equivalent to Spring's@Autowired
 * @author huangfu
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@Inherited
public @interface MyAutowired {
}
Copy the code

However, we need to simulate a circular reference

package simulation.service;

import simulation.annotations.MyAutowired;

public class AService {
    @MyAutowired
    private BService bService;
}
Copy the code
package simulation.service;

import simulation.annotations.MyAutowired;

public class BService {

    @MyAutowired
    private AService aService;
}
Copy the code

Above, we define a circular reference, AService refers to BService; And BService refers to AService, the standard circular reference

Then, according to the idea of (3), we go to the code to solve

package simulation;

import simulation.annotations.MyAutowired;
import simulation.service.AService;

import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/** * Emulates Spring to solve the problem of loop dependency *@author huangfu
 */
public class DebugTest {

    /** ** already fully created */
    private final Map<String,Object> singletonObject = new HashMap<>(8);
    /** * creates half of */ without attribute injection
    private final Map<String,Object> earlySingletonObjects = new HashMap<>(8);

    public static void main(String[] args) throws IllegalAccessException, InstantiationException {
        DebugTest debugTest = new DebugTest();
        AService bean = debugTest.getBean(AService.class);
        System.out.println(bean);
    }

    /** * get a bean object *@param tClass
     * @return* /
    public <T> T getBean(Class<T> tClass) throws InstantiationException, IllegalAccessException {
        // Check whether level 1 cache has data
        String beanName = getBeanName(tClass);
        Object object = singletonObject.get(beanName);
        // Level 1 cache does not query level 2 cache for data
        if(object == null){
            object = earlySingletonObjects.get(beanName);
            if(object == null) {
            	// Create a class without either cacheobject = createBean(tClass,beanName); }}return (T)object;
    }

    /** * Create a bean *@param tClass
     * @param beanName
     * @return* /
    public Object createBean(Class
        tClass,String beanName) throws IllegalAccessException, InstantiationException {
        // reflection creates objects
        Object newInstance = tClass.newInstance();
        // instantiate it to level 2 cache
        earlySingletonObjects.put(beanName,newInstance);
        // Start filling in the properties
        populateBean(newInstance);
        // Move from the creation collection to the completion collection
        earlySingletonObjects.remove(beanName);
        singletonObject.put(beanName,newInstance);
        return newInstance;
    }

    /** * fill the property */
    public void populateBean(Object object) throws InstantiationException, IllegalAccessException {
    	// Get all attributes with @myAutoWired annotated
        List<Field> autowiredFields = getAutowiredField(object.getClass());
        for (Field field : autowiredFields) {
        	// Start injectiondoPopulateBean(object, field); }}/** * start to inject object *@param object
     * @param field
     */
    public void doPopulateBean(Object object, Field field) throws IllegalAccessException, InstantiationException {
    	// call the fetch logic again
        Object target = getBean(field.getType());
        field.setAccessible(true);
        // Reflection injection
        field.set(object,target);
    }

    /** * gets the attribute * that is automatically injected by the identity@param tClass
     * @return* /
    private List<Field> getAutowiredField(Class
        tClass){
        Field[] declaredFields = tClass.getDeclaredFields();
        returnArrays.stream(declaredFields).filter(field -> ield.isAnnotationPresent(MyAutowired.class)).collect(Collectors.toList());  }/** * get the class name *@param tClass
     * @return* /
    public String getBeanName(Class
        tClass){
        returntClass.getSimpleName(); }}Copy the code

The results of

In fact, Spring’s solution is very similar to the one we wrote, but as an ecosystem, it is very well designed and coded. We write this similar to the original idea of Spring, but what are the problems?

Five, what are the defects of their own implementation?

Now, we are injected directly into the class of objects, assuming we changed a logic, if we inject the target object, is a need to be the agent of objects (such as the method by AOP proxies), we have this kind of writing is powerless, of course we can create again to determine whether need to increase the agent, of course this is a kind of solution, However, Spring’s original intention is to remove AOP in the last few steps of the bean life cycle, and then inject the object’s proxy logic, which is clearly not in line with its design philosophy. So how does Spring solve this problem?

How are loop dependencies addressed in Spring?

First, we need to find out where the class is instantiated, because only then will the injected logic be executed!

Entry method:

org.springframework.context.support.AbstractApplicationContext#finishBeanFactoryInitialization

org.springframework.beans.factory.support.DefaultListableBeanFactory#preInstantiateSingletons

	@Override
	public void preInstantiateSingletons(a) throws BeansException {
		// Iterate over a copy to allow init methods, which in turn register new bean definitions.
		// Although this may not be part of the regular factory boot program, it works fine.
		// Get all bean names here
		List<String> beanNames = new ArrayList<>(this.beanDefinitionNames);

		// Trigger the initialization of all non-lazy singleton beans...
		for (String beanName : beanNames) {
			// Get a detailed definition of the class
			RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
			// Instantiation conditions are not abstract classes, singleton classes, and lazy classes
			if(! bd.isAbstract() && bd.isSingleton() && ! bd.isLazyInit()) {// When a bean is integrated with a Beanname, it is created without instantiation of the bean lifecycle
				if(isFactoryBean(beanName)) { .... Ignore unnecessary code, normal bean initialization does not go here... }else {
					// This is where the Spring bean entity object is recreated. This is also the most important logic we exploregetBean(beanName); }}}... Ignore ignore..... }}Copy the code

This step is called getBean. If you want to use Spring naming conventions to create a Bean, why should you call it getBean? He must have a reason to do this. He named it so because, during property injection, he found that a dependency on a property is not created immediately, but will call the method to fetch it again and not create it again! Don’t understand it’s okay, you remember this place, look down! Method goes to getBean –> doGetBean

	protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
			@Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {

		final String beanName = transformedBeanName(name);
		Object bean;

		// Check if the singleton cache has a manually registered singleton.
		// Check if there are objects for the singleton bean in the level 1 cache
		// If the level 1 cache is absent and the current bean is in the creation state (instantiated but not initialized), check the level 2 cache object and return if it exists
		// If the level 2 cache does not check the level 3 cache, call the callback method of the anonymous inner class of the level 3 cache to get the bean object, place it in the level 2 cache, delete the data of the level 3 cache and return the current bean
		// The reason for caching from level 3 is that if the class is a dependent class and the proxy is set, then the method will fetch the proxy object inside the method, ensuring that the first fetch is a proxy object when injected
		// In fact, if it is a circular reference, the factory object of the level 3 cache will be used when the referenced object is re-injected into the property
		Object sharedInstance = getSingleton(beanName);
		// When a cyclic dependency occurs, the first query returns null
		if(sharedInstance ! =null && args == null) {... Ignore unnecessary code.... }else{... Ignore unnecessary code....try {
				finalRootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName); . Ignore unnecessary code and make some judgments, such as instantiation dependencies (@dependsOn), etc...// Create the bean instance. This is a real method singleton pool fetch to create the bean instance, otherwise add the bean to the callback that is being created and then go create the bean
				if (mbd.isSingleton()) {
                    // This method is important because it does several things inside:
					//1. Check whether there are beans in the current level 1 cache
					Create java8 callback method (createBean), add it to level 1 cache, return bean
					//3. Return the bean directly if level 1 cache exists
					sharedInstance = getSingleton(beanName, () -> {
						try {
							The {@link #getSingleton} method calls back the object to perform the actual bean creation logic
							return createBean(beanName, mbd, args);
						}
						catch (BeansException ex) {
							// Explicitly remove the instance from the singleton cache: it may already be there
							// Rush through the creation process to allow circular reference resolution.
							// Also delete all beans that received temporary references to the bean.
							destroySingleton(beanName);
							throwex; }}); bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd); }else if(mbd.isPrototype()) { .... Ignore unnecessary code.... }else{... Ignore unnecessary code.... }}catch (BeansException ex) {
				cleanupAfterBeanCreationFailure(beanName);
				throwex; }}if(requiredType ! =null&&! requiredType.isInstance(bean)) { .... Ignore unnecessary code.... }return (T) bean;
	}
Copy the code
  • I wrote the code to solve the loop dependency. Is this familiar? This is to find the corresponding bean in the cache, return it when there is one, and create it when there is none! I believe smart you must be thoughtful! This is extremely important. Let’s go increateBeaninside
protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
                                                                                throws BeanCreationException {... Ignore unnecessary code....try {
      // The real work comes in the ha-ha-ha reflection create bean
      Object beanInstance = doCreateBean(beanName, mbdToUse, args);
      / /... Ignore unnecessary code....
      return beanInstance;
   }
   catch (BeanCreationException | ImplicitlyAppearedSingletonException ex) {
      // Previously detected exceptions with the correct bean creation context,
      / / or illegal singleton state, most can give DefaultSingletonBeanRegistry convey.
      throw ex;
   }
   catch (Throwable ex) {
      throw new BeanCreationException(
            mbdToUse.getResourceDescription(), beanName, "Unexpected exception during bean creation", ex); }}Copy the code
  • Into thedoCreateBeaninside
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
			throws BeanCreationException {

		// Instantiate the bean.
		BeanWrapper instanceWrapper = null;
		if (mbd.isSingleton()) {
			instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
		}
		if (instanceWrapper == null) {
			// Start creating the bean logic where the class is actually instantiated but returns a wrapper object that contains the instantiated object
			instanceWrapper = createBeanInstance(beanName, mbd, args);
		}
		// Get the previously created bean
		finalObject bean = instanceWrapper.getWrappedInstance(); . Ignore unnecessary code....// Determine if the current object is a singleton, supports circular references, and is being created before being placed in level 3 cache
		boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences 
                                          &&isSingletonCurrentlyInCreation(beanName));
		if(earlySingletonExposure) { .... Ignore unnecessary code....BeanName -> beanName -> beanName -> beanName -> beanName -> beanName -> bean
			// The purpose of this method is to expect Spring to perform operations on the proxy object after the bean is instantiated, not to determine whether it is a proxy object at re-creation time
			// But in practice, if a circular reference occurs, the dependent class is created ahead of time and injected into the target class to ensure that an actual proxy object is injected
            // So Spring has changed his job
			// The factory method can be used to perform proxy operations in the factory. After the proxy operation is performed, the object is returned
			// This conforms to Spring design expectations to ensure that the packaging of proxy objects is implemented later in the Spring Bean lifecycle
			// This step also deletes the data in the secondary cache
			addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
		}

		// Initialize the bean instance.
		Object exposedObject = bean;
		try {
			// Fill in the inner attributes
			// This step solves the problem of loop dependency, where automatic injection logic occurs
			populateBean(beanName, mbd, instanceWrapper);
			// Perform initialization logic and lifecycle callbacks
			exposedObject = initializeBean(beanName, exposedObject, mbd);
		}
		catch(Throwable ex) { .... Ignore unnecessary code.... }if(earlySingletonExposure) { .... Ignore unnecessary code.... }... Ignore unnecessary code....return exposedObject;
	}
Copy the code
  • Into thepopulateBeanMethod, which performs property injection and also resolves loop dependencies!
protected void populateBean(String beanName, RootBeanDefinition mbd, @Nullable BeanWrapper bw) {... Ignore unnecessary code..../ / to any InstantiationAwareBeanPostProcessors modify opportunity,
		// Set the state of the Bean before the property. For example, you can use it
		// Support field injection mode.
		boolean continueWithPropertyPopulation = true;

		if(! mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {for(BeanPostProcessor bp : getBeanPostProcessors()) { .... Ignore unnecessary code.... }}if(! continueWithPropertyPopulation) {return; }... Ignore unnecessary code....if (hasInstAwareBpps) {
			if (pvs == null) {
				pvs = mbd.getPropertyValues();
			}
			for (BeanPostProcessor bp : getBeanPostProcessors()) {
				if (bp instanceof InstantiationAwareBeanPostProcessor) {
					InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;
					// This is done automatically using the @autowired annotation
					/ / so Spring will use AutowiredAnnotationBeanPostProcessor postProcessProperties to deal with automatic injection
                    // In fact, this step will do the injection processing, which is also the focus of our observationPropertyValues pvsToUse = ibp.postProcessProperties(pvs, bw.getWrappedInstance(), beanName); . Ignore unnecessary code.... }}}if(needsDepCheck) { .... Ignore unnecessary code.... }if(pvs ! =null) {
			// Start setting the property value MBD is the dependent beanapplyPropertyValues(beanName, mbd, bw, pvs); }}Copy the code
  • Into theAutowiredAnnotationBeanPostProcessor.postProcessProperties
	@Override
	public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {... Ignore unnecessary code....try {
			// Inject logic
			metadata.inject(bean, beanName, pvs);
		} catch (BeanCreationException ex) {
			throw ex;
		} catch (Throwable ex) {
			throw new BeanCreationException(beanName, "Injection of autowired dependencies failed", ex);
		}
		return pvs;
	}
Copy the code
  • Into theinject
public void inject(Object target, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
    Collection<InjectedElement> checkedElements = this.checkedElements; Collection<InjectedElement> elementsToIterate = (checkedElements ! =null ? checkedElements : this.injectedElements);
    if(! elementsToIterate.isEmpty()) {for(InjectedElement element : elementsToIterate) { .... Ignore unnecessary code....The actual code / / injection logic Because is attribute injection, so use AutowiredFieldElement. Injectelement.inject(target, beanName, pvs); }}}Copy the code
  • Into theorg.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.AutowiredFieldElement#inject
protected void inject(Object bean, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
    // Get the property object to be injected
    Field field = (Field) this.member; Object value; . Ignore unnecessary code......else{... Ignore unnecessary code......try {
            // To really resolve dependencies, find dependencies to create dependencies code
            value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter);
        }
        catch (BeansException ex) {
            throw new UnsatisfiedDependencyException(null, beanName, newInjectionPoint(field), ex); }... Ignore unnecessary code...... }if(value ! =null) {
        // Reflection injection logicReflectionUtils.makeAccessible(field); field.set(bean, value); }}Copy the code
  • I’d like to say something about all of you at this pointwo caoThere’s hope at last. Here we gobeanFactory.resolveDependencyGet the object to be injected, and then inject it into the object via reflection, we just need to knowbeanFactory.resolveDependencySo the logic in there tells us what the problem is with circular dependencies, right? We went in and found it wasn’t over:
public Object resolveDependency(DependencyDescriptor descriptor, @Nullable String requestingBeanName,
			@Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {... Ignore unnecessary code......if (result == null) {
				// This is the way to do the actual work
				result = doResolveDependency(descriptor, requestingBeanName, autowiredBeanNames, typeConverter);
			}
			returnresult; }}Copy the code

Enter the doResolveDependency

@Nullable
	public Object doResolveDependency(DependencyDescriptor descriptor, @Nullable String beanName,
			@Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {... Ignore unnecessary code......// Query the bean data by type and nameMap<String, Object> matchingBeans = findAutowireCandidates(beanName, type, descriptor); . Ignore unnecessary code......// This step is to actually create a class that calls the getBean method to follow the logic of creating the bean above
			if (instanceCandidate instanceof Class) {
				instanceCandidate = descriptor.resolveCandidate(autowiredBeanName, type, this); }... Ignore unnecessary code......returnresult; }... Ignore unnecessary code...... }Copy the code
  • Congratulations on making it. We’re indescriptor.resolveCandidate(autowiredBeanName, type, this)Methods:

org.springframework.beans.factory.config.DependencyDescriptor#resolveCandidate

public Object resolveCandidate(String beanName, Class
        requiredType, BeanFactory beanFactory)
			throws BeansException {

    return beanFactory.getBean(beanName);
}
Copy the code

Oh ho, gentBean, I believe you must have lost your memory, haven’t you seen it somewhere? If you think about the place I told you to remember above, is it also a getBean? Yes, it’s the same method. You’ll see that the properties that need to be injected will also go through the same logic to create and retrieve the property object, thus completing the dependency loop! To summarize the whole logic of dealing with level 3 caching, borrow a picture from YourBatman

Seven,

Readers can refer to the above source logic to see a deeper impression!

This time we first custom implementation of a solution to the cycle dependency, and then analyze the defects, and then compare the Spring source solution, I believe, read here, the screen before you must have harvest! Come on!


Well, today’s article is over here, the author of leisure time to sort out a material, you can pay attention to the public number [JAVA program dog] reply [OMG] get next!