The name of Spring Bean has a hidden secret, so that the name will not be proxied.

Some small usage details are gradually discovered through continuous source code exploration. Today, let’s talk to my friends about how to prevent a bean from being proxied through the setting of beanName!

1. Code practice

Suppose I have the following aspect:

@Aspect
@EnableAspectJAutoProxy
@Component
public class LogAspect {<!-- -->
    @Pointcut("execution(* org.javaboy.demo.service.*.*(..))")
    public void pc() {<!-- -->

    }

    @Before("pc()")
    public void before(JoinPoint jp) {<!-- -->
        String name = jp.getSignature().getName();
        System.out.println(name + "The method started executing...");
    }
}

The methods to be intercepted by this aspect are all methods of all classes under the org.javaboy.demo.service package. Now, there is a BookService class under this package, with the following content:

@Service("org.javaboy.demo.service.BookService.ORIGINAL")
public class BookService {<!-- -->

    public void hello() {<!-- -->
        System.out.println("hello bs");
    }
}

I did not use the default beanName for this BookService beanName, but configured a beanName myself. The configuration method of this beanName is full path of class name + .ORIGINAL.

When we name a bean according to such rules, even if the current bean is already included in the scope defined by the pointcut, the bean will not be proxied.

This is the new gameplay starting with Spring 5.1.

What is the principle of this writing method?

2. Principle analysis

When creating a bean in Spring, everyone knows that after the bean is created, it will go through various post-processors (BeanPostProcessor), so generally we think that the BeanPostProcessor is executed after the bean instance is created. However, there is a special case InstantiationAwareBeanPostProcessor in BeanPostProcessor. This interface inherits from BeanPostProcessor, but based on BeanPostProcessor, additional capabilities are added:

  1. Do some preprocessing before bean instantiation, such as directly creating a proxy object instead of subsequent bean generation.
  2. Some property injection strategies can be customized after the bean is instantiated but before the properties are populated.

Roughly speaking, these are the abilities in these two areas.

Specific to the code, it is in the createBean method of creating a bean:

@Override
protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
throws BeanCreationException {<!-- -->
//Omitted. . .
try {<!-- -->
// Give BeanPostProcessors a chance to return a proxy instead of the target bean instance.
Object bean = resolveBeforeInstantiation(beanName, mbdToUse);
if (bean != null) {<!-- -->
return bean;
}
}
catch (Throwable ex) {<!-- -->
throw new BeanCreationException(mbdToUse.getResourceDescription(), beanName,
"BeanPostProcessor before instantiation of bean failed", ex);
}
try {<!-- -->
Object beanInstance = doCreateBean(beanName, mbdToUse, args);
if (logger.isTraceEnabled()) {<!-- -->
logger.trace("Finished creating instance of bean '" + beanName + "'");
}
return beanInstance;
}
    //Omitted. . .
}

Friends, the resolveBeforeInstantiation method here is to give BeanPostProcessor a chance to return the proxy object. In this method, the InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation method will eventually be triggered. Let’s take a look at the AbstractAutoProxyCreator#postProcessBeforeInstantiation method related to AOP involved here. :

@Override
public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) {<!-- -->
Object cacheKey = getCacheKey(beanClass, beanName);
if (!StringUtils.hasLength(beanName) || !this.targetSourcedBeans.contains(beanName)) {<!-- -->
if (this.advisedBeans.containsKey(cacheKey)) {<!-- -->
return null;
}
if (isInfrastructureClass(beanClass) || shouldSkip(beanClass, beanName)) {<!-- -->
this.advisedBeans.put(cacheKey, Boolean.FALSE);
return null;
}
}
// Create proxy here if we have a custom TargetSource.
// Suppresses unnecessary default instantiation of the target bean:
// The TargetSource will handle target instances in a custom fashion.
TargetSource targetSource = getCustomTargetSource(beanClass, beanName);
if (targetSource != null) {<!-- -->
if (StringUtils.hasLength(beanName)) {<!-- -->
this.targetSourcedBeans.add(beanName);
}
Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(beanClass, beanName, targetSource);
Object proxy = createProxy(beanClass, beanName, specificInterceptors, targetSource);
this.proxyTypes.put(cacheKey, proxy.getClass());
return proxy;
}
return null;
}

This method actually does two things:

  1. Check whether the current bean requires a proxy. If no proxy is required, then store it in a map collection advisedBeans. The key is the name of the bean. If the value is true, it means that the bean needs a proxy. If the value is false, it means that the bean needs a proxy. No agent is needed.
  2. If we have a custom TargetSource, create a proxy object based on the custom TargetSource.

What I want to tell you here is the first point.

When judging whether a bean requires a proxy, there are two main methods:

  1. isInfrastructureClass: This method mainly checks whether the current bean is of type Advice/Advisor/Pointcut, etc., or whether there is an @Aspect annotation on this class. Brother Song actually introduced it to everyone in his previous article: I heard about the creation of Spring Bean. Is there a shortcut? .
  2. shouldSkip: If the isInfrastructureClass method returns false, then shouldSkip must be executed. Let’s take a closer look at the shouldSkip method.
@Override
protected boolean shouldSkip(Class<?> beanClass, String beanName) {<!-- -->
// TODO: Consider optimization by caching the list of the aspect names
List<Advisor> candidateAdvisors = findCandidateAdvisors();
for (Advisor advisor : candidateAdvisors) {<!-- -->
if (advisor instanceof AspectJPointcutAdvisor pointcutAdvisor & amp; & amp;
pointcutAdvisor.getAspectName().equals(beanName)) {<!-- -->
return true;
}
}
return super.shouldSkip(beanClass, beanName);
}

Here we first find all the aspects in the system, and then traverse them one by one. During the traversal, it is judged that if the bean to be created currently happens to be an aspect, then the aspect definitely does not require a proxy, and true is returned directly. Otherwise, the shouldSkip method of the parent class will be called. Let’s take a look at the shouldSkip method of the parent class:

protected boolean shouldSkip(Class<?> beanClass, String beanName) {<!-- -->
return AutoProxyUtils.isOriginalInstance(beanName, beanClass);
}
static boolean isOriginalInstance(String beanName, Class<?> beanClass) {<!-- -->
if (!StringUtils.hasLength(beanName) || beanName.length() !=
beanClass.getName().length() + AutowireCapableBeanFactory.ORIGINAL_INSTANCE_SUFFIX.length()) {<!-- -->
return false;
}
return (beanName.startsWith(beanClass.getName()) & amp; & amp;
beanName.endsWith(AutowireCapableBeanFactory.ORIGINAL_INSTANCE_SUFFIX));
}

The shouldSkip method of the parent class mainly calls a static tool method isOriginalInstance to determine whether the current bean is a bean that does not require a proxy. The specific judgment logic is to check whether the beanName follows the full path of the class name + .ORIGINAL, returns true if it is.

When the shouldSkip method returns true, it will enter the if branch of the postProcessBeforeInstantiation method. This branch stores the current beanName into the advisedBeans collection. The stored key is beanName, the value is false, and then the method returns.

After the bean is created and processed in the AbstractAutoProxyCreator#postProcessAfterInitialization method, you will find that the bean has been stored in the advisedBeans collection, and the value is false, which means that the bean does not require a proxy, so target the bean. The bean will not be processed by AOP and can be returned directly.

Okay, a small detail to deepen everyone’s understanding of Spring AOP. Interested friends can try it~