【Spring实战】----解析Spring AOP

来源:互联网 发布:黄河商品交易软件 编辑:程序博客网 时间:2024/06/10 14:20

上篇文章说要解析下spring的事务管理,再说Spring的事务管理之前,先说下AOP,因为Spring的声明式事务管理是基于AOP的。

一、什么是AOP

AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

二、AOP术语

遗憾的是,大多数用于描述AOP功能的术语不太直观,但是它们已经是AOP的组成部分了,我们必须了解它们,为了理解AOP。

1)通知(Advice)

切面的工作称为通知,通知除了描述切面要完成的工作,还解决了何时执行这个工作的问题,它应该应用于某个方法被调用之前?之后?之前和之后?还是只有方法抛出异常时?

Spring切面可以应用5种类型的通知:

  • Before:在方法被调用之前调用通知
  • After:在方法调用之后,调用通知,无论方法是否执行成功
  • After-returning:在方法成功调用之后调用通知
  • After-throwing:在方法抛出异常后调用通知
  • Around:通知包裹了被通知的方法,在被通知的方法调用之前和调用通知

2)连接点(Joinpoint)

连接点是在应用执行过程中能够插入切面的一个点,这个点可以是调用方法时、抛出异常时、甚至是修改一个字段时。

3)切点(Pointcut)

如果说通知定义切面的“什么”和“何时”,那么切点就定义了“何处”。切点的定义会匹配通知所要织入的一个或多个连接点。我们通常会使用明确的类和方法名称来指定这些切点,或是利用正则表达式定义匹配的类和方法名称模式来指定这些切点。

4)切面(Aspect)

切面是通知和切点的结合,通知和切点共同定义了关于切面的全部内容----它是什么,在何时和何处完成其功能

5)引入(Introduction)

引入允许我们为现有的类添加新方法或属性

6)织入(Weaving)

织入是将切面应用到目标对象来创建新的代理对象的过程。切面在指定的连接点被织入到目标对象中,在目标对象的生命周期里有多个点可以进行织入:

  • 编译期:切面在目标类编译时被织入,需要特殊的编译器,例如AspectJ
  • 类加载期:切面在目标类被加载到JVM时被织入,需要特殊的加载器AspectJ 5的LTW
  • 运行期:切面在应用运行的某个时刻被织入,一般情况下,在织入切面时,AOP容器会为目标对象动态地创建一个代理对象,Spring AOP就是以这种方式织入切面的。

三、切面配置

从上面概念可知,AOP最关键的就是切面的配置,其中Spring AOP的配置有如下几种:

  • 基于代理的经典AOP
  • @AspectJ注解驱动的切面
  • 纯POJO切面
  • 注入式AspectJ切面(适合Spring各版本)

前三种都是Spring基于代理的AOP变体,因此Spring对AOP的支持局限于方法拦截。如果超过了方法级(比如构造器或属性拦截),那么应该考虑注入式AspectJ切面。

常用的切面配置有两种:XML配置及基于@AspectJ注解的配置,下面对基于@AspectJ在该项目中的应用进行说明,XML中的配置及更多AOP相关可参考《Spring in action》。

1)配置<aop:aspectj-autoproxy>启用@AspectJ注解,还是常用的xml配置,当然可以用Java配置

[html] view plain copy 在CODE上查看代码片派生到我的代码片
  1. <?xml version="1.0" encoding="UTF-8"?>  
  2. <beans xmlns="http://www.springframework.org/schema/beans"  
  3.     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
  4.     xmlns:aop="http://www.springframework.org/schema/aop"   
  5.     xsi:schemaLocation="  
  6.        http://www.springframework.org/schema/beans  
  7.        http://www.springframework.org/schema/beans/spring-beans.xsd  
  8.        http://www.springframework.org/schema/aop   
  9.        http://www.springframework.org/schema/aop/spring-aop.xsd" >  
  10.   
  11.     <!-- 启用@AspectJ注解驱动的切面 -->  
  12.     <aop:aspectj-autoproxy></aop:aspectj-autoproxy>  
  13.       
  14.       
  15. </beans>  

2)切面类

[java] view plain copy 在CODE上查看代码片派生到我的代码片
  1. /** 
  2.  *  
  3.  */  
  4. package com.mango.jtt.springAspect;  
  5.   
  6. import org.aspectj.lang.annotation.AfterReturning;  
  7. import org.aspectj.lang.annotation.Aspect;  
  8. import org.springframework.stereotype.Component;  
  9.   
  10. import com.mango.jtt.po.Order;  
  11. import com.mango.jtt.util.LogUtil;  
  12.   
  13. /** 
  14.  * @author HHL 
  15.  * 
  16.  * @date 2016年10月25日 
  17.  */  
  18. @Component  
  19. @Aspect  
  20. public class LogAspect {  
  21.   
  22.     @AfterReturning("execution(* com.mango.jtt.springTask.TaskJob.job1(..))")  
  23.     public void logTaskJob() {  
  24.         LogUtil.printInfoLog(getClass(), "任务进行中ing......");  
  25.     }  
  26.       
  27.     @AfterReturning(pointcut = "execution(* com.mango.jtt.service.OrderService.saveOrder(..)) && args(order)")  
  28.     public void saveOrder(Order order) {  
  29.         LogUtil.printInfoLog(getClass(), "保存订单,订单号为:" + order.getOrderId());  
  30.     }  
  31.   
  32. }  

当然切面类也是普通类,需要加到Spring容器中,采用@Component注解加入到其中。

@Aspect注解表明该类为切面类,其后的方法上注解,表明了连接点和通知,通知的类型在术语中已经说过有5种,分别对应的注解为@Before,@After,@AfterReturning,@AfterThrowing,@Around。


3)展示下用xml配置的aop

applicationContext-aspect.xml

[html] view plain copy 在CODE上查看代码片派生到我的代码片
  1. <?xml version="1.0" encoding="UTF-8"?>  
  2. <beans xmlns="http://www.springframework.org/schema/beans"  
  3.     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
  4.     xmlns:aop="http://www.springframework.org/schema/aop"   
  5.     xsi:schemaLocation="  
  6.        http://www.springframework.org/schema/beans  
  7.        http://www.springframework.org/schema/beans/spring-beans.xsd  
  8.        http://www.springframework.org/schema/aop   
  9.        http://www.springframework.org/schema/aop/spring-aop.xsd" >  
  10.     <!-- 基于xml的aop配置 -->  
  11.     <bean id="xmlAspect" class="com.mango.jtt.springAspect.XmlAspect"></bean>  
  12.     <aop:config>  
  13.         <aop:aspect ref="xmlAspect">  
  14.             <aop:pointcut expression="execution(* com.mango.jtt.service.ProductServiceImpl.getProductById(..))"   
  15.                           id="getProductById"/>  
  16.             <aop:before method="beforeGet" pointcut-ref="getProductById"/>  
  17.             <aop:after method="afterGet" pointcut-ref="getProductById"/>  
  18.             <aop:around method="aroundGet" pointcut-ref="getProductById"/>  
  19.             <aop:after-returning method="afterReturningGet"  
  20.                                  pointcut="execution(* com.mango.jtt.service.ProductServiceImpl.getProductById(String))   
  21.                                            and args(productId)"  
  22.                                  arg-names="productId"/>  
  23.         </aop:aspect>  
  24.     </aop:config>  
  25.       
  26.     <!-- 基于@AspectJ注解的aop配置 @see com.mango.jtt.springAspect.LogAspect -->  
  27.     <!-- 启用@AspectJ注解驱动的切面 -->  
  28.     <aop:aspectj-autoproxy></aop:aspectj-autoproxy>  
  29.       
  30.       
  31. </beans>  

4) aspect类

[java] view plain copy 在CODE上查看代码片派生到我的代码片
  1. /** 
  2.  *  
  3.  */  
  4. package com.mango.jtt.springAspect;  
  5.   
  6. import org.aspectj.lang.ProceedingJoinPoint;  
  7.   
  8. import com.mango.jtt.util.LogUtil;  
  9.   
  10. /** 
  11.  * 基于xml的aop配置 
  12.  *  
  13.  * @author HHL 
  14.  *  
  15.  * @date 2016年12月6日 
  16.  */  
  17. public class XmlAspect {  
  18.     public void beforeGet() {  
  19.         LogUtil.printInfoLog(getClass(), "beforeGet");  
  20.     }  
  21.   
  22.     public void afterGet() {  
  23.         LogUtil.printInfoLog(getClass(), "afterGet");  
  24.     }  
  25.   
  26.     public Object aroundGet(ProceedingJoinPoint joinPoint) {  
  27.         LogUtil.printInfoLog(getClass(), "beforeGet--around");  
  28.         Object obj = null;  
  29.         try {  
  30.             obj = joinPoint.proceed();  
  31.         } catch (Throwable e) {  
  32.             e.printStackTrace();  
  33.         }  
  34.         LogUtil.printInfoLog(getClass(), "afterGet--around");  
  35.         return obj;  
  36.     }  
  37.   
  38.     public void afterReturningGet(String productId) {  
  39.         LogUtil.printInfoLog(getClass(), "afterReturningGet----productId:"  
  40.                 + productId);  
  41.     }  
  42. }  


[html] view plain copy 在CODE上查看代码片派生到我的代码片
  1.   
而且使用环绕通知时,需注意,要调用proceed方法,才能执行被通知的方法,并且如果被通知的方法有返回值的话,也需要将proceed的方法的返回值返回,否则controller无法收到service的返回值。


5)执行结果:可以看出执行顺序
[html] view plain copy 在CODE上查看代码片派生到我的代码片
  1. 2016-12-06 09:25:55.757 [http-apr-9080-exec-13] INFO  com.mango.jtt.util.LogUtil.printInfoLog(LogUtil.java:31) beforeGet  
  2. 2016-12-06 09:25:55.757 [http-apr-9080-exec-13] INFO  com.mango.jtt.util.LogUtil.printInfoLog(LogUtil.java:31) beforeGet--around  
  3. Hibernate:   
  4.     select  
  5.         product0_.productId as productI1_1_0_,  
  6.         product0_.picture as picture2_1_0_,  
  7.         product0_.productName as productN3_1_0_,  
  8.         product0_.quantity as quantity4_1_0_,  
  9.         product0_.unit as unit5_1_0_,  
  10.         product0_.unitPrice as unitPric6_1_0_   
  11.     from  
  12.         product product0_   
  13.     where  
  14.         product0_.productId=?  
  15. 2016-12-06 09:25:55.782 [http-apr-9080-exec-13] INFO  com.mango.jtt.util.LogUtil.printInfoLog(LogUtil.java:31) afterReturningGet----productId:123456  
  16. 2016-12-06 09:25:55.782 [http-apr-9080-exec-13] INFO  com.mango.jtt.util.LogUtil.printInfoLog(LogUtil.java:31) afterGet--around  
  17. 2016-12-06 09:25:55.783 [http-apr-9080-exec-13] INFO  com.mango.jtt.util.LogUtil.printInfoLog(LogUtil.java:31) afterGet  
6)切点配置说明

7)切面优先级就,数字越小,优先级越高
当有多个切面时,可以用Spring中的Ordered接口或者@Order注解定义优先级
[java] view plain copy 在CODE上查看代码片派生到我的代码片
  1. public class XmlAspect implements Ordered {  
  2.   
  3.     /* 
  4.      * 配置该切面的的优先级 
  5.      */  
  6.     @Override  
  7.     public int getOrder() {  
  8.         return 0;  
  9.     }  
  10. }  
[java] view plain copy 在CODE上查看代码片派生到我的代码片
  1. @org.springframework.core.annotation.Order(1)  
  2. public class LogAspect { 
0 0