Why can the @Lazy annotation break the infinite loop?

The following content is based on Spring6.0.4.

In the last article, Brother Song talked with you that not all circular dependencies can be resolved in Spring, and some circular dependencies cannot be resolved by Spring by default. Friends who are not familiar with it can read the previous article first .

Take the case in the first section of the above as an example. Injecting each other’s beans in the construction method is completely an endless loop at this time. Is there really a solution to this endless loop?

Spring provides a way to solve it, but it seems that it has not been solved. Why do you say that, you will understand after reading this article.

1. @Lazy

As shown in the title of this article, the three circular dependencies mentioned in the previous article that cannot be automatically resolved can all be resolved by adding the @Lazy annotation.

If it is constructor injection, as follows:

@Service
public class AService {

    BService bService;

    @Lazy
    public AService(BService bService) {
        this.bService = bService;
    }

    public BService getbService() {
        return bService;
    }
}
@Service
public class BService {
    AService aService;
    
    @Lazy
    public BService(AService aService) {
        this.aService = aService;
    }

    public AService getaService() {
        return aService;
    }
}

The @Lazy annotation can be added to the constructor of AService or BService, or both.

After adding it, we will start the project again, and there will be no error. It seems that the problem has been solved, but it is still almost meaningless. Friends, take a look at my startup code:

ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("aop.xml");
AService aService = ctx.getBean(AService.class);
BService bService = ctx.getBean(BService.class);
System.out.println("aService.getClass() = " + aService.getClass());
System.out.println("bService.getClass() = " + bService.getClass());
System.out.println("aService.getbService().getClass() = " + aService.getbService().getClass());
System.out.println("bService.getaService().getClass() = " + bService.getaService().getClass());

The final print result is as follows:

Friends can see that the beans we get from AService and BService are all normal unproxied objects. In fact, our original code does not need to be proxied. However, the BService in AService and the AService in BService are both proxy objects. It stands to reason that the BService in AService should be consistent with the BService we obtained from the Spring container, and the AService in BService should also be consistent with the AService obtained in the Spring container, but in fact, the two are not the same.

But this is easy to understand. Why Spring can untie a dead knot is because the beans injected by AService and BService are not original beans, but are proxy beans. The BService injected into AService is a proxy object. Similarly, the AService injected into BService is also a proxy object.

This is why I said at the beginning that this problem was solved by Spring but not solved.

In fact, this is the working principle of the @Lazy annotation. Look at the name, the object with this annotation will be loaded lazily. In fact, the object marked with this annotation will automatically generate a proxy object.

The other two problems mentioned in the previous article can also be solved by @Lazy annotation, the code is as follows:

@Service
@Scope("prototype")
public class AService {
    @Lazy
    @Autowired
    BService bService;

}
@Service
@Scope("prototype")
public class BService {
    @Lazy
    @Autowired
    AService aService;
}

Here @Lazy only needs one to solve the problem, or you can add both.

For the case of @Async annotation, it can also be solved by @Lazy annotation:

@Service
public class AService {
    @Autowired
    @Lazy
    BService bService;

    @Async
    public void hello() {
        bService.hello();
    }

    public BService getbService() {
        return bService;
    }
}
@Service
public class BService {
    @Autowired
    AService aService;

    public void hello() {
        System.out.println("xxx");
    }

    public AService getaService() {
        return aService;
    }
}

In this way, the circular dependency can be broken!

In a word, the @Lazy annotation breaks circular dependencies by establishing an intermediate proxy layer.

2. Principle analysis

Next, let's analyze the source code of @Lazy annotation processing.

I will not analyze the source code of this piece from scratch, because how did @Autowired inject variables into the previous part of the entire processing flow and the previous article? The content introduced is the same. For those who are not familiar with it, it is recommended to read @Autowired first. How does variable injection come in? one article. Here I will borrow the summary of this article to lead my friends to review the process of attribute injection:

  1. When creating a bean, after the original bean is created, the populateBean method will be called to fill the properties of the bean.
  2. Next, call the postProcessAfterInstantiation method to determine whether the post-processor needs to be executed. If not, it returns directly.
  3. Call the postProcessProperties method to trigger the execution of various post-processors.

  1. In the method of step 3, call findAutowiringMetadata, which will further trigger the buildAutorwiringMetadata method to find properties or methods that contain @Autowired, @Value and @Inject annotations, and encapsulate them as InjectedElement to return.
  2. Call the InjectedElement#inject method for attribute injection.

  1. Next, execute the resolvedCachedArgument method to try to find the required Bean object from the cache.
  2. If it does not exist in the cache, call the resolveFieldValue method to find the Bean in the container.
  3. Finally, call the makeAccessible and set methods to complete the attribute assignment.

In step 7, call the resolveFieldValue method to resolve the Bean, and the relevant logic of the @Lazy annotation is processed in this method (corresponding to how does @Autowired inject variables into it? Section 3.2 of the article).

The resolveFieldValue method will eventually execute to the resolveDependency method:

@Nullable
public Object resolveDependency(DependencyDescriptor descriptor, @Nullable String requestingBeanName,
		@Nullable Set<string> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {
	descriptor.initParameterNameDiscovery(getParameterNameDiscoverer());
	if (Optional.class == descriptor.getDependencyType()) {
		return createOptionalDependency(descriptor, requestingBeanName);
	}
	else if (ObjectFactory.class == descriptor.getDependencyType() ||
			ObjectProvider.class == descriptor.getDependencyType()) {
		return new DependencyObjectProvider(descriptor, requestingBeanName);
	}
	else if (javaxInjectProviderClass == descriptor.getDependencyType()) {
		return new Jsr330Factory().createDependencyProvider(descriptor, requestingBeanName);
	}
	else {
		Object result = getAutowireCandidateResolver().getLazyResolutionProxyIfNecessary(
				descriptor, requestingBeanName);
		if (result == null) {
			result = doResolveDependency(descriptor, requestingBeanName, autowiredBeanNames, typeConverter);
		}
		return result;
	}
}

In this method, it will first determine whether the injected property type is Optional, ObjectFactory or annotation in JSR-330. We are not here, so take the last branch.

In the last else, first call the getAutowireCandidateResolver().getLazyResolutionProxyIfNecessary method to see if the Bean object needs to be lazy loaded, and the @Lazy annotation is processed here. If lazy loading is possible, then the return value of this method is not null, and it can be returned directly, and there is no need to execute the doResolveDependency method.

ContextAnnotationAutowireCandidateResolver#getLazyResolutionProxyIfNecessary:

@Override
@Nullable
public Object getLazyResolutionProxyIfNecessary(DependencyDescriptor descriptor, @Nullable String beanName) {
	return (isLazy(descriptor) ? buildLazyResolutionProxy(descriptor, beanName) : null);
}

Let's take a look, this method will first call isLazy to determine whether lazy loading is required, if necessary, call the buildLazyResolutionProxy method to build a lazy loaded object; if not, just return a null directly.

protected boolean isLazy(DependencyDescriptor descriptor) {
	for (Annotation ann : descriptor.getAnnotations()) {
		Lazy lazy = AnnotationUtils.getAnnotation(ann, Lazy.class);
		if (lazy != null &amp;&amp; lazy.value()) {
			return true;
		}
	}
	MethodParameter methodParam = descriptor.getMethodParameter();
	if (methodParam != null) {
		Method method = methodParam.getMethod();
		if (method == null || void.class == method.getReturnType()) {
			Lazy lazy = AnnotationUtils.getAnnotation(methodParam.getAnnotatedElement(), Lazy.class);
			if (lazy != null &amp;&amp; lazy.value()) {
				return true;
			}
		}
	}
	return false;
}

This judgment method is mainly to check whether the various parameters in the current class contain the @Lazy annotation, whether the method, attribute, and class name contain the @Lazy annotation. If there is, it returns true, otherwise it returns false.

Let's look at the buildLazyResolutionProxy method:

private Object buildLazyResolutionProxy(
		final DependencyDescriptor descriptor, final @Nullable String beanName, boolean classOnly) {
	BeanFactory beanFactory = getBeanFactory();
	final DefaultListableBeanFactory dlbf = (DefaultListableBeanFactory) beanFactory;
	TargetSource ts = new TargetSource() {
		@Override
		public Class<!--?--> getTargetClass() {
			return descriptor.getDependencyType();
		}
		@Override
		public boolean isStatic() {
			return false;
		}
		@Override
		public Object getTarget() {
			Set<string> autowiredBeanNames = (beanName != null ? new LinkedHashSet&lt;&gt;(1) : null);
			Object target = dlbf.doResolveDependency(descriptor, beanName, autowiredBeanNames, null);
			if (target == null) {
				Class<!--?--> type = getTargetClass();
				if (Map.class == type) {
					return Collections.emptyMap();
				}
				else if (List.class == type) {
					return Collections.emptyList();
				}
				else if (Set.class == type || Collection.class == type) {
					return Collections.emptySet();
				}
				throw new NoSuchBeanDefinitionException(descriptor.getResolvableType(),
						"Optional dependency not present for lazy injection point");
			}
			if (autowiredBeanNames != null) {
				for (String autowiredBeanName : autowiredBeanNames) {
					if (dlbf.containsBean(autowiredBeanName)) {
						dlbf.registerDependentBean(autowiredBeanName, beanName);
					}
				}
			}
			return target;
		}
		@Override
		public void releaseTarget(Object target) {
		}
	};
	ProxyFactory pf = new ProxyFactory();
	pf.setTargetSource(ts);
	Class<!--?--> dependencyType = descriptor.getDependencyType();
	if (dependencyType.isInterface()) {
		pf.addInterface(dependencyType);
	}
	ClassLoader classLoader = dlbf.getBeanClassLoader();
	return (classOnly ? pf.getProxyClass(classLoader) : pf.getProxy(classLoader));
}

This method is used to generate proxy objects. The proxy object TargetSource is constructed here. In its getTarget method, doResolveDependency will be executed to obtain the proxied object (for the acquisition logic of doResolveDependency, please refer to How did @Autowired inject variables? Article), and the getTarget method will only be called when needed . Therefore, what the @Lazy annotation does is to find the injected object in the Spring container when injecting values ​​into each property in the Bean. Now, instead of looking for it, first put a proxy object on it, and then go to the Spring container to find it when needed.

I won't say more about the follow-up logic. Friends, please refer to @Autowired how to inject variables into it? Just one article.

Well, now my friends understand how the @Lazy annotation solves Spring's circular dependency~ Although it is solved, we still have to avoid it in our daily development if we can avoid it~ </string></string>

RustDesk 1.2: Using Flutter to rewrite the desktop version, supporting Wayland accused of deepin V23 successfully adapting to WSL 8 programming languages ​​​​with the most demand in 2023: PHP is strong, C/C++ demand slows down React is experiencing the moment of Angular.js? CentOS project claims to be "open to everyone" MySQL 8.1 and MySQL 8.0.34 are officially released Rust 1.71.0 stable version is released
{{o.name}}
{{m.name}}

Guess you like

Origin my.oschina.net/lenve/blog/10089610