Spring作为一种web开发最常用的框架之一,其简化开发,松耦合都是我们选择它的原因,而作为Spring的核心---Spring AOP(Aspect Oriented Porgraming)面向切面编程更是重中之重。因此本文将对AOP以本人的理解重新阐述一遍,以便自己和读者理解。如有差错,万望指出。
一、 AOP到底是什么?作用是什么?
按照软件重构的思想,如果多个类中出现相同的代码,那么应该考虑定义一个抽象类,将相同部分抽象出来。例如Horse,Pig,Dog类都用相同的eat(),run()。此时可以定义一个animal抽象类,将eat和run方法实现,而Horse,Pig,Dog可以继承animal类复用eat(),run()方法,从而减少重复代码。但是事情并不只是这么简单。看如下代码:
public void get(int position) {
// TODO Auto-generated method stub
long startTime = System.currentTimeMillis();
System.out.println("list get:"+list.get(position));
long endTime = System.currentTimeMillis();
System.out.println("耗时:"+(endTime-startTime)+"ms");
}
public void insert(int position, int value) {
// TODO Auto-generated method stub
System.out.println("list insert:");
long startTime = System.currentTimeMillis();
list.add(position, value);
long endTime = System.currentTimeMillis();
System.out.println("耗时:"+(endTime-startTime)+"ms");
}
public void remove(int position) {
// TODO Auto-generated method stub
System.out.println("list remove:");
long startTime = System.currentTimeMillis();
list.remove(position);
long endTime = System.currentTimeMillis();
System.out.println("耗时:"+(endTime-startTime)+"ms");
}
业务场景是对arraylist和linkedlist的get,add,remove方法的性能检测。通过方法的执行时间判断方法性能的优劣。
毫无疑问,可以定义一个抽象类。AbstractList,对get,add,remove方法都做如下处理
然后再复用抽象类方法即可。虽然在纵向上减少子类的实现。但是在同一个类中,get,add,remove三个方法都重复了方法执行计时操作。这并不是一种好的代码实现,然后继承并不能解决这种横向的代码冗余。那么能不能用一种方法,将计时操作都提取出来,不需要冗余的实现呢?这就是AOP实现的意义了。
二、 AOP的术语解释
为了学习交流的方便,了解AOP中的术语也是相当有必要的:
1、 连接点(Joinpoint)
程序执行的某个特定位置:如类初始化前,类初始化后,方法调用前,方法调用后,方法抛出异常后。即类与程序代码执行时具有边界性质的特定点。对于Spring来说:仅支持方法调用前,方法调用后,方法抛出异常和方法调用前后。所以,AOP要嵌入额外的代码只能在以上的几个点上。而连接点就是AOP可以嵌入代码的选点。以上例为例,get,add,remove三个方法的调用前,后,抛出异常均是连接点。
连接点位置由两个信息确定:一是执行点,即get或者add方法。二是执行方位,即方法调用前或者方法调用后。这个由增强类型决定。
2、 切点(PointCut)
由上可知,一个类有多个连接点。而切点就是我们所感兴趣的,需要嵌入增强的连接点。在Spring中,使用类和方法的查询条件作为切点。而满足条件查询出来的方法就是执行点,通过配置何种增强共同组成连接点。
3、 增强(Advice)
增强就是织入连接点的额外业务逻辑。在上例中就是方法执行计时的代码。因为切点+执行方位才组成连接点。因此Spring中的增强都是带方位的。如BeforeAdvice,AfterReturningAdvice等。
4、 目标对象(Target)
增强织入的目标类。即连接点所在的类。
5、 引介(Introduction)
引介是一种特殊的增强,为类添加额外的属性和方法。通过引介功能,可以动态为业务类添加接口的实现逻辑。
6、 织入(Weaving)
织入很容易望文生义,将增强逻辑添加到连接点上的过程。
AOP有三中织入方式:
1) 编译期织入,要求特殊的Java编译器。
2) 类装载期织入,要求特殊的类装载器
3) 动态代理织入,在运行期为目标类添加增强生成子类。
Spring采用的是动态代理,而AspectJ采用编译器织入和类装载期织入。
7、 代理(Proxy)
一个类被AOP织入增强后,会生成一个代理类。它融合了目标类和增强逻辑。根据代理方式不同,可能生成同接口的类(JDK动态代理),也可能生成目标类的子类(Cglib动态代理)(有关两种代理方式的不同,可参照本博客关于代理方式的总结)
8、 切面(Adpect)
由切点和增强组成,即包括切点的定义,增强逻辑定义和增强执行方位。
三、 AOP的实现原理
再以上例为例,介绍如何通过JDK动态代理实现AOP:
ps:许多方法没有复用实现是因为JDK动态代理必须依赖于实现接口。用Cglib代理可以解决这个问题。
/**
* 定义接口
* @author cai.wuxin
*
*/
public interface ListApi {
public void get(int position);
public void insert(int position,int value);
public void remove(int position);
public void removeEven();
}
public class ArrayListImpl implements ListApi{
private List<Integer> list = null;
public ArrayListImpl(int initialSize) {
list = new ArrayList<>();
for(int i = 0; i<initialSize;i++){
list.add(i);
}
}
@Override
public void get(int position) {
// TODO Auto-generated method stub
System.out.println("array get:"+list.get(position));
}
@Override
public void insert(int position, int value) {
// TODO Auto-generated method stub
System.out.println("array insert:");
list.add(position, value);
}
@Override
public void remove(int position) {
// TODO Auto-generated method stub
System.out.println("array remove:");
list.remove(position);
}
@Override
public void removeEven() {
// TODO Auto-generated method stub
System.out.println("array remove even:");
Iterator<Integer> e = list.iterator();
while (e.hasNext()) {
if(e.next()%2==0){
e.remove();
}
}
}
}
public class LinkedListImpl implements ListApi{
private List<Integer> list = null;
public LinkedListImpl(int initialSize) {
list = new LinkedList<>();
for(int i = 0; i<initialSize;i++){
list.add(i);
}
}
@Override
public void get(int position) {
// TODO Auto-generated method stub
System.out.println("list get:"+list.get(position));
}
@Override
public void insert(int position, int value) {
// TODO Auto-generated method stub
System.out.println("list insert:");
list.add(position, value);
}
@Override
public void remove(int position) {
// TODO Auto-generated method stub
System.out.println("list remove:");
list.remove(position);
}
@Override
public void removeEven() {
// TODO Auto-generated method stub
System.out.println("list remove even:");
Iterator<Integer> e = list.iterator();
while (e.hasNext()) {
if(e.next()%2==0){
e.remove();
}
}
}
}
/**
* 定义代理实现
* @author cai.wuxin
*
*/
public class TimeHandler implements InvocationHandler{
private ListApi list;
public ListApi getProxy(ListApi list){
this.list = list;
ListApi proxy = (ListApi)Proxy.newProxyInstance(
list.getClass().getClassLoader(),
list.getClass().getInterfaces(),
this);
return proxy;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// TODO Auto-generated method stub
//将方法执行时间计时逻辑移到此处。
long startTime = System.currentTimeMillis();
Object result = method.invoke(list, args);
long endTime = System.currentTimeMillis();
System.out.println("耗时:"+(endTime-startTime)+"ms");
return result;
}
}
public class PerformanceTest {
ListApi array;
ListApi list;
int initialSize = 1000000;
int insertPostion = 1;
int insertValue = 2;
int removePostion = 90000;
int getPostion = 90000;
@Before
public void prepare(){
ListApi arrayImpl = new ArrayListImpl(initialSize);
ListApi listImpl = new LinkedListImpl(initialSize);
TimeHandler t1 = new TimeHandler();
TimeHandler t2 = new TimeHandler();
array = t1.getProxy(arrayImpl);
list = t2.getProxy(listImpl);
}
//可以看到真正执行时,只需要关注业务执行逻辑即可,不需要再增加冗余的性能检测逻辑。
@Test
public void test(){
array.insert(insertPostion, insertValue);
list.insert(insertPostion, insertValue);
array.remove(removePostion);
list.remove(removePostion);
array.removeEven();
list.removeEven();
array.get(getPostion);
list.get(getPostion);
}
}
执行结果:
array insert:
耗时:0ms
list insert:
耗时:0ms
array remove:
耗时:1ms
list remove:
耗时:1ms
array remove even:
耗时:52890ms
list remove even:
耗时:17ms
array get:180003
耗时:0ms
list get:180003
耗时:3ms
四、 AOP的使用
通过上文的介绍,很容易可以知道,实现AOP最关键的无非是切点和增强。因此以下只介绍项目中如何使用Spring AOP而不再讨论各种不同的增强类型,切面类型。首先介绍一下最基础的Spring AOP增强实现。
public interface Waiter {
public void greetTo(String clientName);
public void serveTo(String clientName);
}
/*
* 目标增强类
*/
public class NaiveWaiter implements Waiter{
@Override
public void greetTo(String clientName) {
System.out.println("greet to "+clientName);
}
@Override
public void serveTo(String clientName) {
System.out.println("serve to "+clientName);
}
}
//前置增强
public class GreetingBeforeAdvice implements MethodBeforeAdvice{
@Override
public void before(Method method, Object[] args, Object obj)
throws Throwable {
String clientName = (String)args[0];
System.out.println("How are you Mr "+clientName);
}
}
<!-- XML配置-->
<bean id="greetingAdvice" class="com.test.paditang.aop.adivce.GreetingBeforeAdvice"/>
<bean id="target" class="com.test.paditang.aop.adivce.NaiveWaiter"/>
<bean id="waiter" class="org.springframework.aop.framework.ProxyFactoryBean"
p:proxyInterfaces="com.test.paditang.aop.adivce.Waiter"//定义代理接口
p:interceptorNames="greetingAdvice"//增强方法
p:target-ref="target"/>//目标对象
public class ApplicationContextTest {
private ApplicationContext ac;
@Before
public void prepare(){
ac = new ClassPathXmlApplicationContext("beans.xml");//自动视为在classpath中
}
@Test
public void process(){
//aop增强测试
Waiter waiter = (Waiter)ac.getBean("waiter");
waiter.greetTo("caiwuxin");
}
}
How are you Mr.caiwuxin
greet to caiwuxin
在项目使用中,虽然Spring AOP能解决很多应用的切面需要,但是和AspectJ相比,仍有许多不足之处。因此我更推荐以下的AOP实现,Spring联合AspectJ,更简单的配置,更丰富的实现。需要添加AspectJ相关依赖。
//定义为切面
@Aspect
public class PreGreetingAspect {
@Pointcut("execution(* greetTo(..))")//定义切点
public void greet(){}
@Before("greet()")//使用定义的切点 并定义执行方位,等同于MethodBeforeAdvice
public void beforeGreeting(){
System.out.println("How are you");//定义增强逻辑
}
}
XML中只需要添加配置
<!-- 自动织入加强,XML配置 -->
<aop:aspectj-autoproxy/>
<bean class="com.test.paditang.aspectj.aspectj.PreGreetingAspect"/>
public class AspectJTest {
public static void main(String []args){
Waiter target = new NaiveWaiter();
AspectJProxyFactory factory = new AspectJProxyFactory();
factory.setTarget(target);
factory.addAspect(PreGreetingAspect.class);
Waiter proxy = factory.getProxy();
proxy.greetTo("John");
proxy.serveTo("John");
}
}
How are you
greet to John
serve to John
很明显可以看出对greetTo方法的增强已经实现。