一些使用小细节就是在不断的源码探索中逐步发现的,今天就来和大家研究一下通过 beanName 的设置,可以让一个 bean 拒绝被代理的问题!
- 代码实践 {#1-代码实践}
假设我有如下一个切面:
@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 + " 方法开始执行了...");
}
}
这个切面要拦截的方法是 org.javaboy.demo.service
包下的所有类的所有方法,现在,这个包下有一个 BookService
类,内容如下:
@Service("org.javaboy.demo.service.BookService.ORIGINAL")
public class BookService {
public void hello() {
System.out.println("hello bs");
}
}
这个 BookService
的 beanName 我没有使用默认的 beanName,而是自己配置了一个 beanName,这个 beanName 的配置方式是 类名的完整路径 + .ORIGINAL
。
当我们按照这样的规则给 bean 取名之后,那么即使当前 bean 已经包含在切点所定义的范围内,这个 bean 也不会被代理了。
这是 Spring5.1 开始的新玩法。
这种写法的原理是什么呢?
- 原理分析 {#2-原理分析}
在 Spring 创建 Bean 的时候,大家都知道,bean 创建完成之后会去各种后置处理器(BeanPostProcessor
)中走一圈,所以一般我们认为 BeanPostProcessor
是在 bean 实例创建完成之后执行。但是,BeanPostProcessor
中有一个特例 InstantiationAwareBeanPostProcessor
,这个接口继承自 BeanPostProcessor
,但是在 BeanPostProcessor
的基础之上,增加了额外的能力:
- 在 bean 实例化之前先做一些预处理,例如直接创建代理对象,代替后续的 bean 生成。
- 在 bean 实例化之后但是属性填充之前,可以自定义一些属性注入策略。
大致上就是这两方面的能力。
具体到代码上,就是在创建 bean 的 createBean
方法中:
@Override
protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
throws BeanCreationException {
//省略。。。
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;
}
//省略。。。
}
这里的 resolveBeforeInstantiation
方法就是给 BeanPostProcessor
一个返回代理对象的机会,在这个方法中,最终就会触发到 InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation
方法,我们来看看这里涉及到的跟 AOP 相关的 AbstractAutoProxyCreator#postProcessBeforeInstantiation
方法:
@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;
}
这个方法实际上干了两件事:
-
检查当前 bean 是否需要代理,如果不需要代理,那么就存入到一个 map 集合
advisedBeans
中,key 是 bean 的名字,value 如果为true
则表示这个 bean 是需要代理的,value 为false
,则表示这个 bean 是不需要代理的。 -
如果有我们有自定义的
TargetSource
,则根据自定义的TargetSource
去创建代理对象。
这里我要和大家说的是第一点。
在判断一个 bean 是否需要代理的时候,主要依据两个方法:
isInfrastructureClass
:这个方法主要是检查当前 bean 是否是 Advice/Advisor/Pointcut 等类型,或者这个类上是否有@Aspect
注解。shouldSkip
:如果isInfrastructureClass
方法返回false
,那么就要执行shouldSkip
了,我们来仔细看下shouldSkip
方法。
@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 &&
pointcutAdvisor.getAspectName().equals(beanName)) {
return true;
}
}
return super.shouldSkip(beanClass, beanName);
}
这里首先找到系统中所有的切面,找到之后挨个遍历,遍历的时候判断如果当前要创建的 bean 刚好就是切面,那切面肯定是不需要代理的,直接返回 true。否则就会去调用父类的 shouldSkip
方法,我们再来看一下父类的 shouldSkip
方法:
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()) &&
beanName.endsWith(AutowireCapableBeanFactory.ORIGINAL_INSTANCE_SUFFIX));
}
父类 shouldSkip
方法主要是调用了一个静态工具方法 isOriginalInstance
来判断当前 bean 是否是一个不需要代理的 bean,这个具体的判断逻辑就是检查这个 beanName 是否按照 类名的完整路径 + .ORIGINAL
的方式命名的,如果是则返回 true
。
当 shouldSkip
方法返回 true 的时候,就会进入到 postProcessBeforeInstantiation
方法的 if 分支中,该分支将当前 beanName 存入到 advisedBeans
集合中,存储的 key 就是 beanName,value 则是 false
,然后将方法 return
。
当 bean 创建完成之后,再进入到 AbstractAutoProxyCreator#postProcessAfterInitialization
方法中处理的时候,就会发现这个 bean 已经存入到 advisedBeans
集合中,并且 value 是 false
,这就意味着这个 bean 不需要代理,那么就针对该 bean 就不会进行 AOP 处理了,直接 return 即可。
参考:https://mp.weixin.qq.com/s?__biz=MzI1NDY0MTkzNQ==&mid=2247505764&idx=1&sn=4b38f6d2579930f3c16d513baf887ee8