本随笔内容要点如下:
- 什么是AOP
- AOP术语解释
- Spring中AOP的xml实现
一、什么是AOP
AOP(Aspect Oriented Programming),即面向切面编程。那什么是面向切面编程呢?切面又是什么呢?
如下图,本来存在ServiceA、ServiceB、ServiceC的,一刀把它们给切了。那这个面就是切面,切面切面,就是切开的面。
面向切面编程呢,就是在这个大切面上插入代码,使得ServiceA、ServiceB、ServiceC都能够实现插入的逻辑。在编码中,经常会在一个类中抽取公共代码形成一个独立的方法。而面向切面编程呢,则是抽取多个类中多个方法的相同代码形成一个独立的模块,而这些相同的代码就是横切关注点。例如事务,我们可能会在这些Service中的方法添加事务,而这些事务又与具体的业务逻辑无关,不属于Service的职责,那我们可以将它抽取出来形成一个类,在通过AOP编程来实现原本的逻辑。如下图:
二、AOP术语解释
- 通知(Advice):要被织入到指定位置的代码,上图的抽取出来的事务就是通知。通知除了定义了切面做什么还定义了这些它什么时候被使用。是在执行方法之前还是执行完方法还是抛出异常后。
- 连接点(Join point):切面代码被织入的位置。上图事务代码要被织入的方法就是连接点了
- 切点(Pointcut):用来描述拥有哪些连接点,描述了一个连接点的集合
- 切面(Aspect):通知和切点的结合
- 织入(Weaving):把切面应用到指定对象并产生代理对象的过程,抽象地理解为把切面代码插入到指定对象的指定位置
对于织入,有一下几种形式:
- 编译器织入:在编译前真真正正地代码插入到指定位置。但这种需要特定的编译器
- 类加载器织入:当类加载器加载目标类时再织入,即使用字节码来增强。需要特定的类加载器
- 运行期织入:在运行期间动态地织入,通过代理技术生成代理对象,而代理对象是织入完代码后的对象
Spring只支持运行期织入,AspectJ三种都支持
三、Spring中AOP的实现
本随笔使用xml实现,下篇随笔再使用注解实现。
Spring中有两种方式来实现代理。第一种是Java自带的代理,它要求被代理类必须实现接口。第二种是使用了CGLib,即使被代理类没有实现接口也能被代理。除了引入基础的spring jar包之外,还需要引入aspectjweaver包。我使用的spring版本时4.3.2.RELEASE,当使用aspectjweaver1.8.9时会报错,降到1.8.6就可以正常使用了。
首先定义一个接口IService以及其实现类ServiceImpl
public interface IService {
public void doSomething(int num);
} public class ServiceImpl implements IService {
@Override
public void doSomething(int num) {
System.out.println("doSomething --" + num);
}
}
把公共代码提取出来作为一个单独的模块即切面,那就需要定义一个切面,切面也是一个普通的bean,如下:
public class Aspect {
public void before() {
System.out.println("before");
}
}
接着写配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.2.xsd
"> <bean id="aspect" class="cn.powerfully.aspect.Aspect" /> <bean id="service" class="cn.powerfully.service.ServiceImpl" /> <aop:config>
<aop:aspect ref="aspect">
<aop:pointcut expression="execution(* cn.powerfully.service.*.*(..))"
id="p" />
<aop:before method="before" pointcut-ref="p" />
</aop:aspect>
</aop:config> </beans>
首先先使用<bean>标签定义两个类,接着使用<aop:config>标签,关于切面的都要放在里面。
定义切面使用<aop:aspect>,切面呢,是由通知和切点组成的,所以定义通知和切点。<aop:before>用来定义一个前置通知,即在执行代码前执行。
除了前置通知,spring还提供了一下几种通知:
- 后置通知(after)
- 返回通知(after-returning)
- 异常通知(after-throwing)
- 环绕通知(around)
除了环绕通知外,其他通知的使用都类似前置通知。环绕通知的使用如下:
切面类中:
public Object around(ProceedingJoinPoint pjp) throws Throwable {
long time = System.currentTimeMillis();
Object obj = null;
obj = pjp.proceed();
System.out.println(System.currentTimeMillis() - time);
return obj;
}
该方法有类型ProceedingJoinPoint的参数,通过调用该对象中的proceed()方法可以继续执行被代理对象的逻辑代码。xml配置代码类似前置通知配置。