AOP 实现原理
一、简介
AOP 是 Aspect Oriented Programming 的简写,面向切面编程。主要的作用是以一种统一的方式对程序的逻辑进行修改、增强处理。可以在编译时也可以在运行时实现。编译时处理一般是通过字节码处理技术,运行时进行的一般是通过动态代理技术实现。
二、AOP 核心概念
- Concerns, 关注:有两类,核心关注–是关于业务逻辑的;横切关注–是一些通用的逻辑,比如日志、缓存。
- Joinpoint, 连接点:是执行时的切入点。可以是字段访问也可以是被调用方法。基于动态代理技术实现的一般只支持方法调用的连接点。
- Target, 目标:是一个被切入的地方,一般是一个业务逻辑。比如是对一个业务逻辑的方法的调用。
- Pointcut, 切入点:并不是所有的连接点都需要切入,切入点用于指定哪些连接点需要切入。
- Advice, 建议:定义了 Aspect 的任务和什么时候执行它,是在核心关注之前还是之后。
- Aspect, 方面:Advice 和 pointcut 定义一个方面。Advice 定义 Aspect 的任务和什么时候执行它,而切入点 pointcut 定义在哪里具体地方切入。就是说 Aspect 定义了它是什么东西、什么时候切入和在哪里切入。
- Weaving, 织入:织入是一个把横切方面混合到业务目标对象的过程。可以是在编译时间,也可以是在运行时使用类加载机制,Spring AOP 是在运行时生成 bean 时处理。
下面是在 Sping 的 XML 里定义一个 AOP 的配置,注意其中个元素间的关系:
<!-- 一个 weaving 的定义 -->
<aop:config>
<!-- 定义 aspect, ref 指向 任务的定义 -->
<aop:aspect id="aspectCommonLogHandler" ref="commonLogHandler">
<!-- 定义 pointcut -->
<aop:pointcut id="commonLogPointcut" expression="execution( * net.coderbee.*.controller..*.*(..))" />
<!-- 定义 advice, around 表示在目标的前/后执行 -->
<aop:around method="inceptor" pointcut-ref="commonLogPointcut" />
</aop:aspect>
</aop:config>
三、AOP 实现
1. 各组件定义配置
warnhill 的 AOP 实现没有采用 Spring 那样的配置方式,而是采用 bean 定义的形式组织起来:
<!-- 定义织入逻辑的实现 -->
<bean id="aspectJAutoProxyCreator" class="net.coderbee.warmhill.aop.AspectJAutoProxyCreator" />
<!-- 定义 pointcut -->
<bean id="pointcut" class="net.coderbee.warmhill.aop.AspectJExpressionPointcut" >
<property name="expression" value="execution(* net.coderbee..*.*(..))" />
</bean>
<!-- 定义 任务 -->
<bean id="timerInterceptor" class="net.coderbee.warmhill.aop.TimerInterceptor" />
<bean id="anotherInterceptor" class="net.coderbee.warmhill.aop.AnotherInterceptor" />
<!-- 定义 aspect -->
<bean id="testAdvisor" class="net.coderbee.warmhill.aop.AspectJExpressionPointcutAdvisor">
<property name="advice" ref="timerInterceptor" />
<property name="pointcut" ref="pointcut" />
</bean>
<bean id="testAdvisor2" class="net.coderbee.warmhill.aop.AspectJExpressionPointcutAdvisor">
<property name="advice" ref="anotherInterceptor" />
<property name="pointcut" ref="pointcut" />
</bean>
2. 任务实现
任务的实现类必须实现 org.aopalliance.intercept.MethodInterceptor
接口,至于是在目标关注之前还是之后执行由任务实现类自行决定。
下面是个计算目标方法调用耗时的任务实现:
public class TimerInterceptor implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
long start = System.currentTimeMillis();
System.out.println("start invoke " + invocation.getMethod().getName());
Object result = invocation.proceed();
System.out.println("end invoke " + invocation.getMethod().getName() + " used time millils :" + (System.currentTimeMillis() - start));
return result;
}
}
3. 切入点实现
切入点只支持被调用方法,采用 aspectj 类库实现,用于匹配给定的 Class 是否满足该切入点。
具体实现见类: AspectJExpressionPointcut
。
4. 织入实现
warnhill 基于 IoC 容器的 BeanPostProcessor
机制对生成过程中的 bean 进行加工,织入相关的目标逻辑。
对于同一目标上的多个切面,是按声明的顺序生效的。对于代理机制,最后加入的代理反而是最先生效的。因此,对于多个切面,需要按声明的逆序进行切入,以便让调用时按声明顺序调用任务的逻辑。
对于框架基础设施类、任务类的实现是不能做织入的,避免循环地织入。
AspectJAutoProxyCreator
是 BeanPostProcessor
的一个实现类。下面是织入的核心:
public Object postProcessAfterInit(Object bean, String beanId) {
if (Advice.class.isAssignableFrom(bean.getClass())
|| Advisor.class.isAssignableFrom(bean.getClass())
|| MethodInterceptor.class.isAssignableFrom(bean.getClass())
|| Pointcut.class.isAssignableFrom(bean.getClass())) {
return bean;
}
Class<?> beanClass = bean.getClass();
List<AspectJExpressionPointcutAdvisor> pointcutAdvisors = beanFactory.getBeans(AspectJExpressionPointcutAdvisor.class);
Collections.reverse(pointcutAdvisors);
for (AspectJExpressionPointcutAdvisor pointcutAdvisor : pointcutAdvisors) {
if (pointcutAdvisor.getPointcut().getClassFilter().isMatch(beanClass)) {
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.setMethodMatcher(pointcutAdvisor.getPointcut().getMethodMatcher());
proxyFactory.setInterceptor((MethodInterceptor) pointcutAdvisor.getAdvice());
TargetSource targetSource = new TargetSource(bean, beanClass, beanClass.getInterfaces());
proxyFactory.setTargetSource(targetSource);
bean = proxyFactory.getProxy();
}
}
return bean;
}
注意:上面的逻辑只是对 bean 的类型进行匹配,匹配则生成代理对象,对方法的判断是在调用时进行的。
CglibAopProxy
里面有对方法进行判断:
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
CglibMethodInvocation cglibMethodInvocation = new CglibMethodInvocation(method, advisorSupport
.getTargetSource().getTarget(), objects, methodProxy);
if (advisorSupport.getMethodMatcher() != null && advisorSupport.getMethodMatcher().matches(method,
advisorSupport.getTargetSource().getTargetType())){
return advisorSupport.getInterceptor().invoke(cglibMethodInvocation);
}
return cglibMethodInvocation.proceed();
}
欢迎关注我的微信公众号: coderbee笔记,可以更及时回复你的讨论。