A preface.

“This is the first day of my participation in the Gwen Challenge in November. Check out the details: The last Gwen Challenge in 2021”

Hello, everyone.

Spring’s cyclic dependencies are a common problem in job interviews. Spring uses level 3 caching to resolve cycle dependencies. In addition to your familiarity with the Spring framework, the interviewer wants to know how you think about spring loop dependencies.

You come up and say that Spring uses level 3 caching to eliminate circular dependencies, and you’re going home.

A few days ago, when writing requirements, integrating several method logic, encountered a loop-dependent bug.

Using this bug check idea to tell you about the spring loop dependency several small holes.

This article does not focus on the source code interpretation of Spring loop dependencies. By default, you have a brief understanding of Spring loop dependencies.

Let me post A post about how Spring uses “level 3 caching” to solve the problem of Bean cycle dependency

2. The bug

Bloggers need to invoke logic from several existing interfaces when developing a requirement, but their original methods are private and several logic are defined at the Controller layer. .

For method reuse, I stripped and synchronized the corresponding generic logic from the Controller to the corresponding service, and injected some beans of the related dependencies.

Then the code structure becomes, serviceA injects serviceB, and serviceB injects serviceA

Then the project started with an error

Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'appManagerServiceImpl': Bean with name 'appManagerServiceImpl' has been injected into other beans [deviceManagerServiceImpl] 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 'getBeanNamesForType' with the 'allowEagerInit' flag turned off, for example. at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFac tory.java:623) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFacto ry.java:516) at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:324) at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:23 4) at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:322) at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202) at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:276) at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java :1307) at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1 227) at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.resolveFieldValu e(AutowiredAnnotationBeanPostProcessor.java:657) ... 26 common frames omittedCopy the code

In this article 4.3, I mentioned that using @async to inject beans into each other would cause cyclic dependencies.

But A global search for @aysnc in both beans also failed.

Then you don’t see the constructor injection scenario either.

So, it seems that we can only adjust the source code.

3. The bug

Seeing this bug, directly locate the error line reported on the stack

The object is not the same object as the bean associated with it.

exposedObject ! = bean

Discover that the bean is the original object and exposedObject is the proxy object.

Let me borrow brother A’s picture

Spring resolves loop dependencies when beanB goes to fetch beanA, beanA will be called when beanB associates beanA with beanA if beanA is handled in section

protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) { Object exposedObject = bean; if (! mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) { for (BeanPostProcessor bp : getBeanPostProcessors()) { if (bp instanceof SmartInstantiationAwareBeanPostProcessor) { SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp; exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName); } } } return exposedObject; }Copy the code

Generate proxy objects, delete beanA from level 3 cache, and place generated proxy objects in level 2 cache.

But because getEarlyBeanReference method only the type of agent processing SmartInstantiationAwareBeanPostProcessor post processor. If it is any other type of BeanPostProcessor, it will not be enhanced here.

Ok, let’s go back to the above flowchart. The final logic of bean loading is in

exposedObject = initializeBean(beanName, exposedObject, mbd);
Copy the code

This line ends with the bean logic

protected Object initializeBean(String beanName, Object bean, @Nullable RootBeanDefinition mbd) { if (System.getSecurityManager() ! = null) { AccessController.doPrivileged((PrivilegedAction<Object>) () -> { invokeAwareMethods(beanName, bean); return null; }, getAccessControlContext()); } else { invokeAwareMethods(beanName, bean); } Object wrappedBean = bean; / / post processor pre-processing the if (MBD = = null | |! mbd.isSynthetic()) { wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName); } try { invokeInitMethods(beanName, wrappedBean, mbd); } catch (Throwable ex) { throw new BeanCreationException( (mbd ! = null ? mbd.getResourceDescription() : null), beanName, "Invocation of init method failed", ex); } / / post processor rear handle the if (MBD = = null | |! mbd.isSynthetic()) { wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName); } return wrappedBean; }Copy the code

The point of interruption is here

You can see that these two wrappedBean objects are different, one is the original object and the other is the proxy object.

See the light, go back in

@Override
public Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName)
      throws BeansException {
​
   Object result = existingBean;
   for (BeanPostProcessor processor : getBeanPostProcessors()) {
      Object current = processor.postProcessAfterInitialization(result, beanName);
      if (current == null) {
         return result;
      }
      result = current;
   }
   return result;
}
Copy the code

Breakpoint a dozen

Each screen

Found that bean was MethodValidationPostProcessor enhancement processing!!!!!!!!!!

Click on the class, and any class marked @Validated will be enhanced by the proxy.

4. The bug is solved

The “@” parameter is used on the service to verify the validity of the method.

I’m just going to write the checksum logic in a single method.

In live.

In fact, the investigation thinking of this article is exactly the same as that of the @async article, but I have added a lot of thinking during the investigation.

1. Spring itself helps us solve the circular dependency of property injection. But if the circular dependencies bean, in addition to the rear SmartInstantiationAwareBeanPostProcessor agent to the processor, so will produce an error of circular dependencies.

2. Spring cannot solve the constructor loop dependency for us because the initial operation of the level 3 cache is to put bean instantiations into the level 3 cache.

3. It is possible to use @lazy to solve bugs like this one. For example, B wants to rely on the final proxy object to come in, so B can add, but A does not need to add. But in fact, the reference holding A in B in this case is not the same as A in the Spring container. In the eyes of patients with obsessive-compulsive disorder, treating the symptoms rather than the root causes

Level 2 cache can also solve injection cycle dependency, but why use level 3 cache? Spring still expects the bean declaration cycle to be consistent with Spring’s design specification, similar to the way the early exposure of the second-level cache generates proxies in advance, for system robustness.

5. Use caution: allowRawInjectionDespiteWrapping, after this set to true for cyclic bean not in check, but the agency will be failed.

Six. Contact me

If there is an incorrect place in the article, welcome to correct, writing the article is not easy, point a thumbs-up, yao yao da ~

Nailing: louyanfeng25

WeChat: baiyan_lou

Public account: Uncle Baiyan