“This is the 10th day of my participation in the First Challenge 2022. For details: First Challenge 2022.”


Continuing on from the last article, let’s first look at the following example, where the previous two serviceA and serviceB remain unchanged, and we add a BeanPostProcessor:

@Component
public class MyPostProcessor implements BeanPostProcessor {
   @Override
   public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
       if (beanName.equals("serviceA")){
           System.out.println("create new ServiceA");
           return new ServiceA();
       }
       returnbean; }}Copy the code

Error:

Exception in thread "main" org.springframework.beans.factory.BeanCurrentlyInCreationException: 
Error creating bean with name 'serviceA': Bean with name 'serviceA' 
has been injected into other beans [serviceB] 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

Before analyzing the errors, let’s review the normal cycle dependency process:

1. Initialize the native serviceA object and place it in the level 3 cache

2. The serviceA populates the properties, finds the dependency serviceB, and creates the dependency object

3. Create the serviceB, find the fill property based on the serviceA, and find the fill from the tertiary cache

4. Execute the serviceB’s post-handler and callback methods into the singleton pool

5. Execute the serviceA’s post-processor and callback methods into the singleton pool

In the loop dependency, we injected serviceA into the serviceB, but then wrapped the serviceA in the back processor, resulting in an inconsistency between the serviceA injected and the serviceA generated.

But those of you who are familiar with AOP should know that the bottom layer of AOP is implemented using a post-processor, so why can AOP be executed properly? We add a getServiceB method for cross-cutting serviceA:

@Component
@Aspect
public class MyAspect {
    @Around("execution(* com.hydra.service.ServiceA.getServiceB())")
    public void invoke(ProceedingJoinPoint pjp){
        try{
            System.out.println("execute aop around method");
            pjp.proceed();
        }catch(Throwable e){ e.printStackTrace(); }}}Copy the code

Regardless of the results, the code can be executed without exception, so how is AOP implemented?

Using the same process as without AOP, we run to where serviceB needs to inject serviceA and call getSingleton to get the singletonFactory stored in serviceA from the level 3 cache. Call the getEarlyBeanReference method. Traverse in the method perform SmartInstantiationAwareBeanPostProcessor post processor getEarlyBeanReference method:

Take a look at which classes implement this method:

In spring, it is this AbstractAutoProxyCreator that implements aop, going into the getEarlyBeanReference method:

public Object getEarlyBeanReference(Object bean, String beanName) throws BeansException {
    //beanName
    Object cacheKey = getCacheKey(bean.getClass(), beanName);
    this.earlyProxyReferences.put(cacheKey, bean); 
    // Generate a proxy object
    return wrapIfNecessary(bean, beanName, cacheKey); 
}
Copy the code

EarlyProxyReferences is a Map that caches the bean’s original object, that is, the bean before aop is executed. It is very important and will be used later:

Map<Object, Object> earlyProxyReferences = new ConcurrentHashMap<>(16);
Copy the code

Remember the following wrapIfNecessary method, which is really responsible for generating proxy objects:

First parses and retrieves all the facets, then calls createProxy to create the proxy object and return it. Then go back to the getSingleton method and add the serviceA to the level 2 cache and remove it from the level 3 cache.

As you can see, the serviceA in the level 2 cache is already a proxy object proxied by Cglib, although the serviceA is still not populated with attribute values.

So there’s another problem. As we’ve seen before, once you’ve filled in the properties, you call the methods in the post-processor, and those methods are based on the original object, not the proxy object.

As we explained in the previous article, the post-processor is executed in the initializeBean method, and aop is normally done here. Then we face a problem if we avoid repeating the aop process. In the initializeBean method:

if (mbd == null| |! mbd.isSynthetic()) { wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName); }Copy the code

Call applyBeanPostProcessorsAfterInitialization, perform all the post processor after methods:

Perform AbstractAutoProxyCreator postProcessAfterInitialization method:

public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) throws BeansException {
  if(bean ! =null) {
    Object cacheKey = getCacheKey(bean.getClass(), beanName);
    if (this.earlyProxyReferences.remove(cacheKey) ! = bean) {returnwrapIfNecessary(bean, beanName, cacheKey); }}return bean;
}
Copy the code

EarlyProxyReferences we mentioned earlier is very important because it caches the original Object before aop, and the Object passed in here is also the original Object. So the judgment statement that performs the remove operation here returns false, does not execute the statement in if, and does not execute aop’s procedure again.

To recalibrate, we need to implement the AbstractautoXyCreator getEarlyBeanReference method in order to implement the AOP process and cache the native object in earlyProxyReferences. So in the case of loop dependence, the equation is true and we just return. In the normal case without cyclic dependencies, earlyProxyReferences execution returns null, the equation is not established, and the normal implementation of AOP process.

Note that this method still returns the original object, not the post-AOP proxy object. At this point, let’s take a look at the nested state:

The exposed serviceA is the original object, and the dependent serviceB has already been injected. The serviceA that the serviceB relies on is a proxy object that has not yet been injected.

Execute down:

Get the serviceA again via getSingleton:

This time we can retrieve the previous AOP proxy object through the level 2 cache, so we don’t need to find the level 3 cache to return the proxy object directly, and finally add the proxy object to the level 1 cache singleton pool.

Here’s a summary of what level 3 caching can do:

SingletonObjects: a singleton pool that caches beans through their full life cycle

EarlySingletonObjects: Caches raw pre-exposed objects. Note that the objects are not beans yet. The objects are propped up by AOP, but there are no execution properties populated or post-processor methods executed

SingletonFactories: Caches ObjectFactory, which is used to generate proxy objects from the original object after aop. A factory is cached here ahead of time during the generation of each bean. If there are no cyclic dependencies that depend on the bean, then the factory does not work, executes as normal, and puts the bean directly into the level 1 cache. If a circular dependency is present that depends on the bean, the original object is returned directly in the absence of AOP, and the proxy object is returned in the presence of AOP.

At the end of the process, look at the result:

We found that in the cglib proxy object of the generated serviceA, the value of the serviceB attribute was not populated, only the serviceA attribute in the serviceB was successfully populated.

You can see that with cglib, the target of the proxy object wraps a primitive object whose properties are populated.

So what if instead of using the Cglib proxy, you use the JDK dynamic proxy? Let’s modify the previous code and add two interfaces:

public interface IServiceA {
    public IServiceB getServiceB(a);
}
public interface IServiceB {
    public IServiceA getServiceA(a);
}
Copy the code

Modify two Service classes:

@Component
public class ServiceA implements IServiceA{
    @Autowired
    private IServiceB serviceB;

    public IServiceB getServiceB(a) {
        System.out.println("get ServiceB");
        return this.serviceB; }}@Component
public class ServiceB implements IServiceB{
    @Autowired
    private IServiceA serviceA;

    public IServiceA getServiceA(a) {
        System.out.println("get ServiceA");
        returnserviceA; }}Copy the code

Execution Result:

Take a look at the serviceA details:

It also wraps the native object in target and injects an instance of serviceB into the native object.

When we execute serviceA’s getServiceB method, the bean object is not retrievable, and both methods return a null value. So what if you have to get the serviceB directly?

We can first look at the cglib proxy case by way of reflection:

ServiceA serviceA= (ServiceA) context.getBean("serviceA");
Field h = serviceA.getClass().getDeclaredField("CGLIB$CALLBACK_0");
h.setAccessible(true);
Object dynamicAdvisedInterceptor = h.get(serviceA);
Field advised = dynamicAdvisedInterceptor.getClass().getDeclaredField("advised");
advised.setAccessible(true);
Object target = ((AdvisedSupport)advised.get(dynamicAdvisedInterceptor)).getTargetSource().getTarget();
ServiceA serviceA1= (ServiceA) target;
System.out.println(serviceA1.getServiceB());
Copy the code

Let’s look at the JDK dynamic proxy case:

IServiceA serviceA = (IServiceA) context.getBean("serviceA");
Field h=serviceA.getClass().getSuperclass().getDeclaredField("h");
h.setAccessible(true);
AopProxy aopProxy = (AopProxy) h.get(serviceA);
Field advised = aopProxy.getClass().getDeclaredField("advised");
advised.setAccessible(true);
Object target = ((AdvisedSupport)advised.get(aopProxy)).getTargetSource().getTarget();
ServiceA serviceA1= (ServiceA) target;
System.out.println(serviceA1.getServiceB());
Copy the code

The serviceB instance can be obtained as a result:

To recap, Spring provides a special solution for handling aop’s cyclic dependencies, but both JDK dynamic proxies and Cglib proxies wrap the original object inside the proxy object, where the properties of the dependencies reside. Furthermore, if we had used a post-processor to wrap the bean, the problem of loop dependency would not have been solved.

conclusion

Finally, summarize the key points of this paper:

1. Spring implements cyclic dependencies with the help of tertiary caching. In this process, it is necessary to know what specific roles tertiary caching plays in what scenarios

2. In the case of aop generation, call the post-processor and expose the generated proxy object ahead of time, with an additional cache to avoid repeated execution of AOP

Level 2 and level 3 caches only really work if they create cyclic dependencies

4. Furthermore, with the exception of injecting dependencies through attributes mentioned in this article, you may wonder if you can implement circular dependencies using constructors, but you can’t. The specific call process here is no longer said, interested students can be compared with the source code to comb.

The last

If you feel helpful, you can click a “like” ah, thank you very much ~

Nongcanshang, an interesting, in-depth and direct public account that loves sharing, will talk to you about technology. Welcome to Hydra as a “like” friend.