文章目录
- aop:aspectj-autoproxy
- 属性
- proxy-target-class
- expose-proxy
- 栗子
- 切面
- 被代理类
- 配置
- 解析
- 原理
- 总结
- 拾遗
- AOP切面的坑
- 总结
aop:aspectj-autoproxy
此标签用以开启对于@AspectJ注解风格AOP的支持。
属性
proxy-target-class
你懂的。
expose-proxy
是否应该把代理对象暴露给AopContext,默认false。
栗子
切面
代码语言:javascript复制@Aspect
public class AspectDemo {
@Pointcut("execution(void base.aop.AopDemo.send(..))")
public void beforeSend() {}
@Before("beforeSend()")
public void before() {
System.out.println("send之前");
}
}
被代理类
代码语言:javascript复制public class AopDemo implements AopDemoInter {
public void send() {
System.out.println("send from aopdemo");
}
public void receive() {
System.out.println("receive from aopdemo");
}
@Override
public void inter() {
System.out.println("inter");
}
}
配置
代码语言:javascript复制<aop:aspectj-autoproxy proxy-target-class="true" />
<bean class="base.aop.AopDemo" />
<bean class="base.aop.annotation.AspectDemo" />
因为AopDemo实现了AopDemoInter接口,但做实验的send方法又不在此接口里定义,所以只能用cglib的方式代理。
可以看出,即使标注了@Aspect注解,仍然需要将切面自己配置到Spring容器中。
解析
AspectJAutoProxyBeanDefinitionParser.parse:
代码语言:javascript复制@Override
public BeanDefinition parse(Element element, ParserContext parserContext) {
AopNamespaceUtils.
registerAspectJAnnotationAutoProxyCreatorIfNecessary(parserContext, element);
extendBeanDefinition(element, parserContext);
return null;
}
注册最终在AopConfigUtils.registerOrEscalateApcAsRequired方法中完成,创建器实际上是一个AnnotationAwareAspectJAutoProxyCreator类的对象,此类是前面AspectJAwareAdvisorAutoProxyCreator的子类。
原理
既然是AspectJAwareAdvisorAutoProxyCreator的子类,那么其代理子类的创建等核心逻辑自然是一样的。这里所需要关注的地方自然是所不一样的地方: 即是如何体现其注解的特性的。
前面说过,AspectJAwareAdvisorAutoProxyCreator通过findCandidateAdvisors方法来找到适用于bean的Advisor,所以注解的特性也是通过重写此方法来体现。
AnnotationAwareAspectJAutoProxyCreator.findCandidateAdvisors:
代码语言:javascript复制@Override
protected List<Advisor> findCandidateAdvisors() {
List<Advisor> advisors = super.findCandidateAdvisors();
//这里
advisors.addAll(this.aspectJAdvisorsBuilder.buildAspectJAdvisors());
return advisors;
}
buildAspectJAdvisors方法所做的便是从容器中得到所有的bean,逐一判断是不是一个Aspect。那么判断Aspect的依据是什么?
AbstractAspectJAdvisorFactory.isAspect:
代码语言:javascript复制@Override
public boolean isAspect(Class<?> clazz) {
return (hasAspectAnnotation(clazz) && !compiledByAjc(clazz));
}
至于其它的实现细节不再探究。
总结
Spring对于AspectJ风格AOP的支持停留在外表(注解)上面,内部的实现仍然是自己的东西。
拾遗
AOP切面的坑
- 定义在private方法上的切面不会被执行,这个很容易理解,毕竟子类不能覆盖父类的私有方法。
- 同一个代理子类内部的方法相互调用不会再次执行切面。
这里以Cglib为例对第二点进行说明,cglib的相关核心组件可以参考前面CallbackFilter & Callback部分。对于配置了一个切面的典型场景,Spring内部的执行流程可总结如下图:
核心便是对目标方法的调用上,这里由CglibMethodInvocation的invokeJoinpoint实现:
代码语言:javascript复制@Override
protected Object invokeJoinpoint() throws Throwable {
if (this.publicMethod) {
return this.methodProxy.invoke(this.target, this.arguments);
} else {
return super.invokeJoinpoint();
}
}
如果是非public方法,那么Spring将使用反射的方法对其进行调用,因为反射将其可访问性设为true。MethodProxy是Cglib对方法代理的抽象,这里的关键是方法调用的对象(目标)是我们的原生类对象,而不是Cglib代理子类的对象,这就从根本上决定了对同类方法的调用不会再次经过切面。
总结
前面aop:aspectj-autoproxy-属性-expose-proxy一节提到了,Spring允许我们将代理子类暴露出来,可以进行如下配置:
代码语言:javascript复制<aop:config expose-proxy="true">
<aop:advisor advice-ref="simpleMethodInterceptor" pointcut="execution(* aop.SimpleAopBean.*(..))" />
</aop:config>
当我们需要在一个被代理方法中调用同类的方法时(此方法也需要经过切面),可以这样调用:
代码语言:javascript复制public void testB() {
System.out.println("testB执行");
((SimpleAopBean) AopContext.currentProxy()).testC();
}
这里其实是一个ThreadLocal,当Cglib代理子类创建调用链之间便会将代理类设置到其中,DynamicAdvisedInterceptor.intercept相关源码:
代码语言:javascript复制if (this.advised.exposeProxy) {
// Make invocation available if necessary.
oldProxy = AopContext.setCurrentProxy(proxy);
setProxyContext = true;
}