目录
[1. AOP的演化过程](#1. AOP的演化过程)
[1. 代理模式](#1. 代理模式)
[2. 动态代理](#2. 动态代理)
[2.1 JDK动态代理](#2.1 JDK动态代理)
[2.2 Cglib动态代理](#2.2 Cglib动态代理)
[3. Spring模式](#3. Spring模式)
[3.1 ProxyFactory](#3.1 ProxyFactory)
[3.2 ProxyFactoryBean](#3.2 ProxyFactoryBean)
[3.3 AbstractAutoProxyCreator](#3.3 AbstractAutoProxyCreator)
[2. Spring AOP抽象](#2. Spring AOP抽象)
[1. 核心术语](#1. 核心术语)
[1.1 连接点(JoinPoint)](#1.1 连接点(JoinPoint))
[1.2 切点(Pointcut)](#1.2 切点(Pointcut))
[1.3 增强(Advice)](#1.3 增强(Advice))
[1.4 切面(Aspect)](#1.4 切面(Aspect))
[1.5 目标对象(Target)](#1.5 目标对象(Target))
[1.6 代理对象(Proxy)](#1.6 代理对象(Proxy))
[2. 核心组件](#2. 核心组件)
[2.1 AutoProxyCreator工作原理](#2.1 AutoProxyCreator工作原理)
[2.2 @EnableAspectJAutoProxy工作原理](#2.2 @EnableAspectJAutoProxy工作原理)
[3. Spring AOP案例](#3. Spring AOP案例)
1. AOP的演化过程
编程中我们经常会遇到这样的场景,有一段通用逻辑横跨多个业务,不能用继承来解决,比如方法耗时、数据库事务、通用日志等等。一个典型的DAO方法调用分为下面3个步骤:
每一次都手工的开启和提交事务,即显得啰嗦,又影响业务的可读性。AOP就十分擅长解决这类问题,它把开启和提交事务的逻辑抽取到Advice当中,在任务DAO上复用。那么AOP到底是怎么实现的呢?
1. 代理模式
熟悉设计模式的人这时候会想到代理模式,通过新增一个代理类实现,负责处理事务的开启和提交。假设我们要处理的是下面的类图,Staff是我们要处理的实体类,StaffDao是接口,定义两个方法employ、paySalary,StaffDaoImpl是实际的DAO实现,StaffDaoProxy是代理类,负责事务开启和提交。
Dao的操作用伪代码实现,StaffDaoImpl和代理类,核心代码如下
java
public class StaffDaoImpl implements StaffDao {
public void employ(Staff p) {
System.out.println("employ staff: " + p);
}
public void paySalary(Staff p) {
System.out.println("pay salary :" + p);
}
}
public class StaffDaoProxy implements StaffDao {
private StaffDao staffDao;
public StaffDaoProxy(StaffDao staffDao) {
this.staffDao = staffDao;
}
public void employ(Staff p) {
System.out.println("start transaction...");
staffDao.employ(p);
System.out.println("commit transaction...");
}
public void paySalary(Staff p) {
System.out.println("start transaction...");
staffDao.paySalary(p);
System.out.println("commit transaction...");
}
}
通过如下代码进行测试,StaffDaoImpl只是完成了数据操作,StaffDaoProxy会额外事务开启和提交操作
java
Staff staff = new Staff("zhangsan",18);
StaffDao staffDao = new StaffDaoImpl(); // 直接调用
staffDao.employ(staff);
staffDao.paySalary(staff);
StaffDaoProxy proxy = new StaffDaoProxy(staffDao); // 通过代理类使用
proxy.employ(staff);
proxy.paySalary(staff);
2. 动态代理
代理模式确实达成了我们想要的效果,如果每一个需要横切逻辑的类都需要通过代理模式类实现的话,人力成本过高,而且也不便于维护,后续每一次修改都需要改大量的代理类。通过JDK提供的动态代理,或者字节码操作的第三方库能解决这个问题。
2.1 JDK动态代理
我们来看一个通过JDK的Proxy类实现动态代理的示例,首先要定义一个InvocationHandler的实现
java
public class TransactionInvocationHandler implements InvocationHandler {
private StaffDao staffDao;
public TransactionInvocationHandler(StaffDao staffDao) {
this.staffDao = staffDao;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("start transaction...");
Object result = method.invoke(staffDao,args);
System.out.println("commit transaction...");
return result;
}
}
接着就可以通过Proxy.newInstance来创建代理类实例,并测试,其中staff和staffDao的实例和代理模式里的创建方式一样,通过proxy.employ和proxy.paySalary调用输出也同之前的案例。
java
InvocationHandler invocationHandler = new TransactionInvocationHandler(staffDao);
StaffDao proxy = (StaffDao) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),new Class[]{StaffDao.class},invocationHandler);
proxy.employ(staff);
proxy.paySalary(staff);
2.2 Cglib动态代理
JDK动态代理存在的一个问题是,生成代理对象必须基于接口,如果一个类没有实现接口就无法创建代理。好在还有大量的第三方库支持无接口的类生成动态代理。我们来看一个cglib的示例。和JDK代理类似,首先我们要实现一个回调类,不过cglib里回调类接口是MethodInterceptor。
java
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class TransactionMethodInterceptor implements MethodInterceptor {
private StaffDao staffDao;
public TransactionMethodInterceptor(StaffDao staffDao) {
this.staffDao = staffDao;
}
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("start transaction...");
Object result = method.invoke(staffDao, args);
System.out.println("commit transaction...");
return result;
}
}
紧接着使用Enhancer类创建代理类对象,并使用测试,也能得到同样的测试结果。
java
TransactionMethodInterceptor interceptor = new TransactionMethodInterceptor(staffDao);
Enhancer enhancer = new Enhancer();
enhancer.setCallback(interceptor);
enhancer.setSuperclass(StaffDao.class);
StaffDao proxy = (StaffDao) enhancer.create();
3. Spring模式
动态代理解决代理模式带来的部分问题,我不需要再为每个类手动创建子类,所有横切逻辑都集中到InvocationHandler或MethodInterceptor中了。但是每次使用的时候都需要手工创建代理对象,还是比较麻烦。Spring基于自己的能力对这个问题做了简化
3.1 ProxyFactory
Spring提供了ProxyFactory来简化代理对象的创建,频闭通过JDK或Cglib创建代理对象的差异(涉及类如下图),除此以外并没有其他改善。
同样需要实现一个回调类,实现aopaliance提供的MethodInterceptor接口,具体代码如下
java
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
public class SpringTransactionMethodInterceptor implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("start transaction...");
Object result = invocation.proceed();
System.out.println("commit transaction...");
return result;
}
}
然后使用ProxyFactory来创建代理对象,并进行测试,测试结果同之前的示例
java
ProxyFactory factory = new ProxyFactory();
factory.setTarget(staffDao);
factory.addAdvice(new SpringTransactionMethodInterceptor());
StaffDao proxy = (StaffDao) factory.getProxy();
Staff staff = new Staff("zhangsan", 18);
proxy.employ(staff);
proxy.paySalary(staff);
3.2 ProxyFactoryBean
到目前为止我们依然需要手动创建代理类对象,通过Spring本身的FactoryBean机制,Spring AOP为我们提供了ProxyFactoryBean实现。首先通过xml配置Bean实现
XML
<bean id="staffDao" class="com.lws.designPattern.StaffDaoImpl"/>
<bean id="springTransaction" class="com.lws.spring.SpringTransactionMethodInterceptor"/>
<bean id="staffDaoProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target" ref="staffDao"/>
<property name="interceptorNames">
<list>
<value>springTransaction</value>
</list>
</property>
</bean>
接着通过创建ApplicationContext,获取Bean实例,测试代码如下
java
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
StaffDao proxy = context.getBean("staffDaoProxy", StaffDao.class);
proxy.employ(staff);
proxy.paySalary(staff);
3.3 AbstractAutoProxyCreator
ProxyFactoryBean已经基本可以使用了,只是每一个Bean要做代理时都需要手工配置一个ProxyFactoryBean,使用起来还是略显繁琐。Spring提供了AbstractAutoProxyCreator来完成AOP代理的自动创建。这里我们以BeanNameAutoProxyCreator为例,看看AutoProxyCreator是如何使用,它是怎么工作的。同样先基于xml配置BeanNameAutoProxyFactory,使用相同interceptorNames的所有Bean都可以通过这一个配置自动创建代理,而且staffDao不需要和之前那样创建staffDao、staffDaoProxy两个Bean。
XML
<bean id="staffDao" class="com.lws.designPattern.StaffDaoImpl"/>
<bean id="springTransaction" class="com.lws.spring.SpringTransactionMethodInterceptor"/>
<bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
<property name="beanNames">
<list>
<value>staffDao</value>
</list>
</property>
<property name="interceptorNames" value="springTransaction"/>
</bean>
接着只需要正常的获取staffDao的Bean实例,并调用测试即可
java
Staff staff = new Staff("zhangsan", 18);
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
StaffDao proxy = context.getBean("staffDao", StaffDao.class);
proxy.employ(staff);
proxy.paySalary(staff);
应该承认BeanNameAutoProxyCreator的使用已经比较简单了,对使用相同interceptorNames的AOP场景,只需要把Bean实例名称添加到beanNames就行了。再进一步简化就是通过AnnotationAwareAspectJAutoProxyCreator来实现了,它会自动判断Bean是否匹配AspectJ表达式,对符号条件的Bean完成代理。后面的代码中我们会进行源码级解释,现在我们先来讲讲Spring对AOP做的抽象,以及它实现AOP的核心组件。
2. Spring AOP抽象
1. 核心术语
1.1 连接点(JoinPoint)
连接点是程序中客观存在的特定位置,比如类初始化前、类初始化后、方法执行前、方法执行后等等。Spring AOP中只支持方法的连接点。连接到有两组信息组成,一个是程序的执行点,比如哪个类的哪个方法;一个是相对位置,比如方法执行前、方法返回后等等。Spring使用切点(Pointcut)表示执行点,用增强(Advice)表示相对位置。
1.2 切点(Pointcut)
连接点是程序中客观存在的事务,比如一个程序内有2个方法,方法执行前、方法执行后、抛出异常时,连接点个数就已经确定了,如果只看这3类相对位置的话,连接点就是6个。切点用来表示我们关心的连接点。Spring中使用Pointcut类表示切点。下图是Pointcut类的定义,它有两个成员变量,分别是ClassFilter实例、MethodMatcher实例。通过如下的UML图中的方法定义可以看到,ClassFilter用来表示哪些类是满足当前切点要求,MethodMatcher用来确定哪些方法满足当前切点要求。
Spring内部的核心实现类有下面这些,后面我们会挑几个看一下如何使用。
1.3 增强(Advice)
在介绍连接点的时候我们提供到过,连接点相对位置是通过增强(Advice)来表示的,此外Advice还包含相对位置要执行的++代码++。下图是Spring提供的Advice的核心类。我们日常使用的较多的就是在图的中心位置的4个类:
- MethodBeforeAdvice,方法执行前增强
- MethodInterceptor,方法环绕增强
- AfterReturningAdvice,方法返回后增强
- ThrowsAdvice,方法抛异常是的正常,这里需要特别注意ThrowsAdvice是一个标记接口,Spring通过反射调用,查看类JavaDoc有描述对方法签名的限制
1.4 切面(Aspect)
从之前的定义中可知,Pointcut和Advice已经完整的定义了在哪里执行什么增强逻辑。切面有切点(Pointcut)和增强(引介)组成,即包括横切逻辑的定义,也包括连接点的定义。
1.5 目标对象(Target)
要进行增强逻辑的目标类,在我们前面的例子里,StaffDao类的实例就是目标对象。
1.6 代理对象(Proxy)
对目标对象进行增强后生成的对象,就称为代理对象,这个对象已经包含增强的执行逻辑。
2. 核心组件
这张图里给出了Spring AOP中的核心流程涉及的组件,包括怎么定义切点(Pointcut),增强(Advice),并且通过组合Pointcut和Advice获得Advisor的定义。拿到Advisor后,我们可以使用ProxyFactory将Advisor和目标对象(target)组合,生成代对象。Spring默认支持两种生成代理对象的实现,一种是基于JDK的实现,JdkDynamicAopProxy,一种是基于Cglib的实现,ObjenesisCglibAopProxy。
每一个组件都有大量的实现类,这里不进行展开。接下来我们对几个关键点做详细的说明
2.1 AutoProxyCreator工作原理
BeanNameAutoProxyCreator继承自AbstractAutoProxyCreator,实现了Bean生命周期里的InstantiationAwareBeanFactoryProcessor、BeanPostProcessor,覆写的是两个方法: postProcessBeforeInstantiation、postProcessAfterInitialization,核心流程图如下:
对于定义了customTargetSource的AbstractAutoProxyCreator,Bean不会走正常的实例化流程,在postProcessBeforeInstantiation就通过TargetSource实例化并创建代理返回。日常开发中我们极少使用TargetSource,TargetSource适用于aop时期望目标对象(target)支持池化或者热替换的场景,更多TargetSource信息可以阅读Spring官方文档中关于++Using TargetSources++ 章节。
BeanNameAutoProxy主要覆写了AbstractAutoProxyCreator以下几个方法:
- isSupportedBeanName,基于配置的beanNames判断当前bean是否支持自动创建代理对象
- getAdvicesAndAdvisorsForBean,支持的时候返回空数组,不支持返回null
BeanNameAutoProxyCreator的Advice和Advisor对象的注入主要时通过interceptorNames做为bean名称从容器中查找的,具体实现看AbstractorAutoProxyCreator的buildAdvisors实现
AnnotationAwareAspectJAutoProxyCreator基于AbstractAutoProxyCreator实现,不适用interceptorNames,核心逻辑在于查找bean匹配的Advisor,通过findCandidateAdvisors查找候选Advisor。
以下是AbstractAutoProxyCreator的骨干代码,实现类基于下面的模板修改部分逻辑。
java
@Override
public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) {
...
TargetSource targetSource = getCustomTargetSource(beanClass, beanName); // 自定义customTargetSourceCreator的Bean
if (targetSource != null) {
if (StringUtils.hasLength(beanName)) {
this.targetSourcedBeans.add(beanName);
}
Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(beanClass, beanName, targetSource); // 通过自定义TargetSource直接返回Bean
Object proxy = createProxy(beanClass, beanName, specificInterceptors, targetSource);
this.proxyTypes.put(cacheKey, proxy.getClass());
return proxy;
}
...
}
@Override
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
if (bean != null) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
if (this.earlyBeanReferences.remove(cacheKey) != bean) {
return wrapIfNecessary(bean, beanName, cacheKey);
}
}
return bean;
}
2.2 @EnableAspectJAutoProxy工作原理
EnableAspectJAutoProxy通过在自身注解@Import引入ImportBeanDefinitionRegistrar实现,ImportBeanDefinitionRegistrar接口允许用户通过代码注册自己的BeanDefinition。
java
@Import({AspectJAutoProxyRegistrar.class})
public @interface EnableAspectJAutoProxy
AspectJAutoProxyRegistrar的核心代码如下:
java
class AspectJAutoProxyRegistrar implements ImportBeanDefinitionRegistrar {
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);
AnnotationAttributes enableAspectJAutoProxy = AnnotationConfigUtils.attributesFor(importingClassMetadata, EnableAspectJAutoProxy.class);
...
}
}
进一步往AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary下面追踪,我们发现自动注册了AnnotationAwareAspectJAutoProxyCreator,结合上面我们对AutoProxyCreator的了解,整个基于注解的配置AOP的流程就通顺了。
3. Spring AOP案例
基于上面对原理的探究,我们能理解怎么从Advice、Pointcut开始创建Advisor,怎么利用ProxyFactoryBean创建代理,通过AutoProxyCreator让Spring自动创建代理,Spring对所有这些元素的抽象,以及相互之间如何协同。然而在显示工作中,仅仅知道这些是不够的,我们不可能每次都从最原始的MethodBeforceAdvice来创建我们的Advice,不可能从ClassFilter、MethodMatcher开始定义Pointcut,这样的话使用成本太高了,Spring提供了大量的内置类型,降低我们使用Spring AOP的成本。下一篇中我们会专注提供实战案例,包括但不限于如下主题
1.使用Advice
2.使用Advisor
3.使用BeanNameAutoProxyCreator
4.使用AnnotationAwareAspectJAutoProxyCreator
5.使用aop名称空间
6.使用@Aspect注解