Spring AOP

概念
AOP(Aspect Oriented Programming),即面向切面编程(也叫面向方面编程,面向方法编程)。其主要作用是,在不修改源代码的情况下给某个或者一组操作添加额外的功能。像日志记录,事务处理,权限控制等功能,都可以用AOP来“优雅”地实现,使这些额外功能和真正的业务逻辑分离开来,软件的结构将更加清晰。AOP是OOP的一个强有力的补充。

术语 

AOP的术语不太直观,Spring文档中也没有给一个确切的定义,所以重在理解。

  • Join Point(连接点): 所谓的连接点就是被拦截到的点,spring中,这些点指的就是方法(通俗来讲就是起作用的那个方法)。spring中只支持方法类型的连接点,事实上join point还可以是field或类构造器。

  • Pointcut(切入点):用来指定join point(通俗来讲就是描述(定义)的一组符合某个条件的join point)。通常使用pointcut表达式来限定joint point,

Spring默认使用AspectJ pointcut expression language。Pointcut通过pointcut expression来描述,有若干种限定词。由于Pointcut的定义在Spring文档7.2.3 Declaring a pointcut中写得比较详细,所以在此不再赘述。

eg: execution(* com.tech.service.impl..*.*(..)) 含义:执行([任何]返回类型 包名[..代表所有子包][*.*所有类的所有方法](..[任何参数]))

  • Advice(通知):是指拦截到join point在特定的时刻执行的操作,Advice有以下几种不同类型:(通俗地来讲就是起作用的内容和时间点)
    1. Before advice: 执行在join point之前的advice,但是它不能阻止joint point的执行流程,除非抛出了一个异常(exception)。
    2. After returning advice: 执行在join point这个方法返回之后的advice。
    3. After throwing advice: 执行在join point抛出异常之后的advice。
    4. After(finally) advice: 执行在join point返回之后或者抛出异常之后的advice,通常用来释放所使用的资源。
    5. Around advice: 执行在join point这个方法执行之前与之后的advice。
  • Introduction(引入):在不修改类代码的前提下,Introduction可以在运行期动态的给对象增加方法或者属性。
  • Target object: Advice起作用的那个对象,代理的对象。
  • AOP proxy:为实现AOP所生成的代理。在Spring中有两种方式生成代理:JDK代理和CGLIB代理。
  • Aspect: 组合了Pointcut与Advice,在Spring中有时候也称为Advisor。某些资料说Advisor是一种特殊的Aspect,其区别是Advisor只能包含一对pointcut和advice,但是aspect可以包含多对。AOP中的aspect可以类比于OOP中的class。
  • Weaving:将Advice织入join point的这个过程。 
组织关系图:
Spring AOP
 织入器通过在切面中pointcut(切入点定义)来搜索目标(被代理类)的JoinPoint(切入点),然后把要切入的逻辑(Advice)织入到目标对象里,生成代理类。 

两种代理

Spring AOP是基于代理机制的,Spring AOP通过JDK ProxyCGLIB Proxy两种方法实现代理。

  • 如果target object没有实现任何接口,那么Spring将使用CGLIB来实现代理。CGLIB是一个开源项目,它是一个强大的,高性能,高质量的Code生成类库,它可以在运行期扩展Java类与实现Java接口。
  • 如果target object实现了一个以上的接口,那么Spring将使用JDK Proxy来实现代理,因为Spring默认使用的就是JDK Proxy,并且JDK Proxy是基于接口的。这也是Spring提倡的面向接口编程。当然,你也可以强制使用CGLIB来进行代理,但是这样可能会造成性能上的下降。
Spring AOP
 Spring AOP的应用

我们可以通过三种方式来使用Spring AOP,它们分别是:@Aspect-based(Annotation)Schema-based(XML)以及底层的Spring AOP API

一、@Aspect-based (Annotation)

通过Annotaion(注解)实现AOP是最常用的方式。

1)配置

        首先,我们应该在配置文件中增加对Annotation的支持。

        假设我们的配置文件是classpath下的applicationContext.xml,添加如下片段:

        <aop:aspectj-autoproxy />

2)业务逻辑类

       假设我们有一个UserManager类,这个类负责处理业务逻辑。类的定义如下:

[java] view plaincopy
  1. public class UserManager {  
  2. /*这个方法需要一个参数*/  
  3. public void addUser(String user) {  
  4.       System.out.println("addUser(String str) method is executed!");  
  5. }  
  6.   
  7. public void deleteUser() {  
  8.       System.out.println("deleteUser() method is executed!");  
  9. }  
  10. /*这个方法返回一个字符串*/  
  11. public String getUser() {  
  12.       System.out.println("getUser() method is executed!");  
  13.       return "Hello";  
  14. }  
  15. /*这个方法抛出一个异常*/  
  16. public void editUser() throws Exception {  
  17.       throw new Exception("something is wrong.");  
  18. }   
  19. }  

这是一个很普通的Java对象,看不出任何Spring AOP的痕迹,这也是Spring低侵入式设计的体现。

3)切面(Aspect)类

为了给业务逻辑增加额外功能,我们需要定义一个切面类,切面类里包含了pointcut和advice。假设我们的切面类是ExampleAspect,代码如下:

[java] view plaincopy
  1. @Aspect  
  2. public class ExampleAspect {  
  3.   
  4.     @Pointcut("execution(* com.psjay.example.spring.aop.*.*(..))")  
  5.     public void aPointcut() {  
  6.     }  
  7.   
  8.     @Before("aPointcut()")  
  9.     public void beforeAdvice() {  
  10.         System.out.println("before advice is executed!");  
  11.     }  
  12.   
  13.     @AfterReturning(pointcut = "aPointcut()", returning = "r")  
  14.     public void afterReturningAdvice(String r) {  
  15.         if (r != null)  
  16.             System.out  
  17.                     .println("after returning advice is executed! returning String is : "  
  18.                             + r);  
  19.     }  
  20.   
  21.     @After("aPointcut()")  
  22.     public void AfterAdvice() {  
  23.         System.out.println("after advice is executed!");  
  24.     }  
  25.   
  26.     @After("aPointcut() && args(str)")  
  27.     public void AfterAdviceWithArg(String str) {  
  28.         System.out.println("after advice with arg is executed!arg is : " + str);  
  29.     }  
  30.   
  31.     @AfterThrowing(pointcut = "aPointcut()", throwing = "e")  
  32.     public void afterThrowingAdvice(Exception e) {  
  33.         System.out  
  34.                 .println("after throwing advice is executed!exception msg is : "  
  35.                         + e.getMessage());  
  36.     }  
  37.   
  38. }  

        在基于annotation的Spring AOP中,@Aspect用来标注切面类。@Pointcut标注一个空的方法,用来代表一个pointcut,这个方法必须是public的。@Pointcut注解括号内是pointcut expression,例子中的表达式表示com.psjay.example.spring.aop的所有方法都是join point。而@Before,@After等注解对应着几种不同类型的Advice。被标注的方法就是一个Advice。@Advice注解括号内是一个pointcut。例子中的@afterReturningAdvice(),AfterAdviceWithArg()和afterThrowingAdvice()分别演示了Advice得到join point的返回值,Advice使用join point的参数,Advice使用join point抛出的异常对象几种操作。

不要忘了在Spring配置文件中配置以上两个类的“Bean”,这里就不贴出具体代码了。

4)测试类

测试类相对简单,就是从Spring中拿出bean演示AOP的结果。测试类代码如下:

[java] view plaincopy
  1. public class Test {  
  2.     public static void main(String[] args) {  
  3.         ApplicationContext ctx = new ClassPathXmlApplicationContext(  
  4.                 "applicationContext.xml");  
  5.         UserManager um = ctx.getBean("userManager", UserManager.class);  
  6.         System.out.println("------ Case 1 --------");  
  7.         um.addUser("hey");  
  8.         System.out.println("------ Case 2 --------");  
  9.         try {  
  10.             um.editUser();  
  11.         } catch (Exception e) {  
  12.   
  13.         }  
  14.         System.out.println("------ Case 3 --------");  
  15.         um.getUser();  
  16.     }  
  17. }  

测试结果:

[java] view plaincopy
  1. —— Case 1 ——–  
  2. before advice is executed!  
  3. addUser(String str) method is executed!  
  4. after advice is executed!  
  5. after advice with arg is executed!arg is : hey  
  6. —— Case 2 ——–  
  7. before advice is executed!  
  8. after advice is executed!  
  9. after throwing advice is executed!exception msg is : something is wrong.  
  10. —— Case 3 ——–  
  11. before advice is executed!  
  12. getUser() method is executed!  
  13. after returning advice is executed! returning String is : Hello  
  14. after advice is executed!  

可以看到,Advice已经在对应的join point上起作用了。

 

二、 Schema-based(XML)

除了使用Annotation,我们还可以使用XML来实现Spring AOP。使用XML来实现AOP只是将AOP的配置信息移到XML配置文件里。

1)业务类

[java] view plaincopy
  1. package com.tech.aop.service;  
  2.   
  3. public interface CustomerService {  
  4.     public String getName(Integer id);  
  5.   
  6.     public void save(String name);  
  7.   
  8.     public void update(Integer id, String name);  
  9. }  
[java] view plaincopy
  1. package com.tech.aop.service.impl;  
  2.   
  3. import com.tech.aop.service.CustomerService;  
  4.   
  5. public class CustomerServiceBean implements CustomerService {  
  6.       
  7.     @Override  
  8.     public String getName(Integer id) {  
  9.         System.out.println("这是find方法");  
  10.         return "zhang";  
  11.     }  
  12.   
  13.     @Override  
  14.     public void save(String name) {  
  15.         //throw new RuntimeException("例外通知");  
  16.         System.out.println("这是save方法");  
  17.     }  
  18.   
  19.     @Override  
  20.     public void update(Integer personId, String name) {  
  21.         System.out.println("这是update方法");  
  22.     }  
  23.       
  24. }  

2)切面(Aspect)类

[java] view plaincopy
  1. package com.tech.xml.aop;  
  2.   
  3. import org.aspectj.lang.ProceedingJoinPoint;  
  4.   
  5. /** 
  6.  * 切面 
  7.  *  
  8.  * @author ch 
  9.  *  
  10.  */  
  11. public class MyInterceptor {  
  12.     public void doBefore() {  
  13.         System.out.println("前置通知");  
  14.     }  
  15.   
  16.     public void doAfterReturning() {  
  17.         System.out.println("后置通知");  
  18.     }  
  19.   
  20.     public void doAfter() {  
  21.         System.out.println("最终通知");  
  22.     }  
  23.   
  24.     public void doAfterThrowing() {  
  25.         System.out.println("例外通知");  
  26.     }  
  27.   
  28.     public Object doBasicProfiling(ProceedingJoinPoint  pjp) throws Throwable {  
  29.         System.out.println("进入方法");  
  30.         Object result = pjp.proceed();  
  31.         System.out.println("退出 方法");  
  32.         return result;  
  33.     }  
  34. }  

 3)xml配置aop

[html] view plaincopy
  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" xmlns:p="http://www.springframework.org/schema/p"  
  4.     xmlns:aop="http://www.springframework.org/schema/aop"  
  5.     xsi:schemaLocation="http://www.springframework.org/schema/beans   
  6.     http://www.springframework.org/schema/beans/spring-beans-3.0.xsd  
  7.     http://www.springframework.org/schema/aop   
  8.     http://www.springframework.org/schema/aop/spring-aop-3.0.xsd"  
  9.     default-lazy-init="false">  
  10.     <!-- 添加对 @AspectJ 注解的支持 -->       
  11.     <aop:aspectj-autoproxy />   
  12.     <!-- 业务类定义 -->  
  13.     <bean id="customerService" class="com.tech.aop.service.impl.CustomerServiceBean" />  
  14.       
  15.     <!-- 切面(Aspect)类定义 -->  
  16.     <bean id="myInterceptor" class="com.tech.xml.aop.MyInterceptor"/>  
  17.       
  18.     <!-- AOP配置 -->  
  19.     <aop:config>  
  20.         <!-- 配置切面类 -->   
  21.         <aop:aspect id="apt" ref="myInterceptor">  
  22.             <!-- 定义切入点(通过表达式对指定方法进行拦截) -->  
  23.             <aop:pointcut id="mypCut" expression="execution(* com.tech.aop.service.impl.CustomerServiceBean.*(..))"/>  
  24.             <!-- 定义advice(不同类型的通知) -->  
  25.             <aop:before method="doBefore" pointcut-ref="mypCut"/>  
  26.             <aop:after-returning method="doAfterReturning" pointcut-ref="mypCut"/>  
  27.             <aop:after-throwing method="doAfterThrowing" pointcut-ref="mypCut"/>  
  28.             <aop:after method="doAfter" pointcut-ref="mypCut"/>  
  29.             <aop:around method="doBasicProfiling" pointcut-ref="mypCut"/>  
  30.         </aop:aspect>  
  31.     </aop:config>  
  32.       
  33. </beans>  

4)测试

[java] view plaincopy
  1. package com.tech.xml.test;  
  2.   
  3. import org.springframework.context.ApplicationContext;  
  4. import org.springframework.context.support.ClassPathXmlApplicationContext;  
  5.   
  6. import com.tech.aop.service.CustomerService;  
  7.   
  8. public class TestXmlAop {  
  9.   
  10.     /** 
  11.      * @param args 
  12.      */  
  13.     public static void main(String[] args) {  
  14.         // TODO Auto-generated method stub  
  15.         ApplicationContext ac = new  ClassPathXmlApplicationContext("applicationContext.xml");  
  16.         CustomerService service = (CustomerService)ac.getBean("customerService");  
  17.         service.save("abc");  
  18.     }  
  19. }  

输出结果:

[java] view plaincopy
  1. 前置通知  
  2. 进入方法  
  3. 这是save方法  
  4. 后置通知  
  5. 最终通知  
  6. 退出 方法  


三、Spring AOP API

在Spring1.2中使用底层的Spring AOP API来实现AOP。当然,Spring3也是完全与其兼容的。我们可以借其窥探一下底层实现。在此不做介绍。

需要的jar包

Spring AOP

总结

Spring AOP是基于代理的,是运行时绑定的。合理的运用AOP,将使软件的开发更加便捷,清晰。

Spring AOP的应用主要有以下几个方面:

  • 性能监控,在方法调用前后记录调用时间,方法执行太长或超时报警。
  • 工作流系统,工作流系统需要将业务代码和流程引擎代码混合在一起执行,那么我们可以使用AOP将其分离,并动态挂接业务。
  • 权限验证,方法执行前验证是否有权限执行当前方法,没有则抛出没有权限执行异常,由业务代码捕捉。
  • 缓存代理,缓存某方法的返回值,下次执行该方法时,直接从缓存里获取。
  • 软件**,使用AOP修改软件的验证类的判断逻辑。
  • 记录日志,在方法执行前后记录系统日志。