Spring AOP小结

来源:互联网 发布:两台台式机共享网络 编辑:程序博客网 时间:2024/06/06 00:27

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 stublong 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 stubSystem.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 stubSystem.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);}}@Overridepublic void get(int position) {// TODO Auto-generated method stubSystem.out.println("array get:"+list.get(position));}@Overridepublic void insert(int position, int value) {// TODO Auto-generated method stubSystem.out.println("array insert:");list.add(position, value);}@Overridepublic void remove(int position) {// TODO Auto-generated method stubSystem.out.println("array remove:");list.remove(position);}@Overridepublic void removeEven() {// TODO Auto-generated method stubSystem.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);}}@Overridepublic void get(int position) {// TODO Auto-generated method stubSystem.out.println("list get:"+list.get(position));}@Overridepublic void insert(int position, int value) {// TODO Auto-generated method stubSystem.out.println("list insert:");list.add(position, value);}@Overridepublic void remove(int position) {// TODO Auto-generated method stubSystem.out.println("list remove:");list.remove(position);}@Overridepublic void removeEven() {// TODO Auto-generated method stubSystem.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;}@Overridepublic 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;@Beforepublic 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);}//可以看到真正执行时,只需要关注业务执行逻辑即可,不需要再增加冗余的性能检测逻辑。@Testpublic 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:耗时:0mslist insert:耗时:0msarray remove:耗时:1mslist remove:耗时:1msarray remove even:耗时:52890mslist remove even:耗时:17msarray get:180003耗时:0mslist 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{@Overridepublic void greetTo(String clientName) {System.out.println("greet to "+clientName);}@Overridepublic void serveTo(String clientName) {System.out.println("serve to "+clientName);}}

//前置增强public class GreetingBeforeAdvice implements MethodBeforeAdvice{@Overridepublic 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;@Beforepublic void prepare(){ac = new ClassPathXmlApplicationContext("beans.xml");//自动视为在classpath中}@Testpublic void process(){//aop增强测试Waiter waiter = (Waiter)ac.getBean("waiter");waiter.greetTo("caiwuxin");}}


How are you Mr.caiwuxingreet to caiwuxin



在项目使用中,虽然Spring AOP能解决很多应用的切面需要,但是和AspectJ相比,仍有许多不足之处。因此我更推荐以下的AOP实现,Spring联合AspectJ,更简单的配置,更丰富的实现。需要添加AspectJ相关依赖。

//定义为切面@Aspectpublic class PreGreetingAspect {@Pointcut("execution(* greetTo(..))")//定义切点public void greet(){}@Before("greet()")//使用定义的切点 并定义执行方位,等同于MethodBeforeAdvicepublic 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 yougreet to Johnserve to John
很明显可以看出对greetTo方法的增强已经实现。




0 0