本文讲述使用AspectJ框架实现Spring AOP。
再重复一下Spring AOP中的三个概念,
- Advice:向程序内部注入的代码。
- Pointcut:注入Advice的位置,切入点,一般为某方法。
- Advisor:Advice和Pointcut的结合单元,以便将Advice和Pointcut分开实现灵活配置。
AspectJ是基于注释(Annotation)的,所以需要JDK5.0以上的支持。
AspectJ支持的注释类型如下:
- @Before
- @After
- @AfterReturning
- @AfterThrowing
- @Around
首先定义一个简单的bean,CustomerBo实现了接口ICustomerBo
ICustomerBo.java如下:
package com.lei.demo.aop.aspectj; public interface ICustomerBo { void addCustomer(); void deleteCustomer(); String AddCustomerReturnValue(); void addCustomerThrowException() throws Exception; void addCustomerAround(String name); }
CustomerBo.java如下:
package com.lei.demo.aop.aspectj; public class CustomerBo implements ICustomerBo { public void addCustomer() { System.out.println("addCustomer() is running ..."); } public void deleteCustomer() { System.out.println("deleteCustomer() is running ..."); } public String AddCustomerReturnValue() { System.out.println("AddCustomerReturnValue() is running ..."); return "abc"; } public void addCustomerThrowException() throws Exception { System.out.println("addCustomerThrowException() is running ..."); throw new Exception("Generic Error"); } public void addCustomerAround(String name) { System.out.println("addCustomerAround() is running ,args:"+name); } }
一、 简单的AspectJ,Advice和Pointcut结合在一起
首先没有引入Pointcut之前,Advice和Pointcut是混在一起的
步骤,只需要两步,如下:
- 创建一个Aspect类
- 配置Spring配置文件
第一步,创建Aspect类
LoggingAspect.java如下:
package com.lei.demo.aop.aspectj; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; @Aspect public class LoggingAspect { @Before("execution(public * com.lei.demo.aop.aspectj.CustomerBo.addCustomer(..))") public void logBefore(JoinPoint joinPoint){ System.out.println("logBefore() is running ..."); System.out.println("hijacked:"+joinPoint.getSignature().getName()); System.out.println("**********"); } @After("execution(public * com.lei.demo.aop.aspectj.CustomerBo.deleteCustomer(..))") public void logAfter(JoinPoint joinPoint){ System.out.println("logAfter() is running ..."); System.out.println("hijacked:"+joinPoint.getSignature().getName()); System.out.println("**********"); } }
解释:
1. 必须使用@Aspect在LoggingAspect声明之前注释,以便被框架扫描到
2. 此例Advice和Pointcut结合在一起,类中的具体方法logBefore和logAfter即为Advice,是要注入的代码,Advice方法上的表达式为Pointcut表达式,即定义了切入点,上例中@Before注释的表达式代表执行CustomerBo.addCustomer方法时注入logBefore代码。
3. 在LoggingAspect方法上加入@Before或者@After等注释
4. "execution(public * com.lei.demo.aop.aspectj.CustomerBo.addCustomer(..))"是Aspect的切入点表达式,其中,*代表返回类型,后边的就要定义要拦截的方法名,这里写的的是com.lei.demo.aop.aspectj.CustomerBo.addCustomer表示拦截CustomerBo中的addCustomer方法,(..)代表参数匹配,此处表示匹配任意数量的参数,可以是0个也可以是多个,如果你确定这个方法不需要使用参数可以直接用(),还可以使用(*)来匹配一个任意类型的参数,还可以使用 (* , String),这样代表匹配两个参数,第二个参数必须是String 类型的参数
5. AspectJ表达式,可以对整个包定义,例如,execution(* com.lei.service..*.*(..))表示切入点是com.lei.sevice包中的任意一个类的任意方法,具体的表达式请自行百度。
第二步,配置Spring配置文件,
配置Spring-AOP-AspectJ.xml文件,如下:
<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-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd"> <aop:aspectj-autoproxy/> <bean id="customerBo" class="com.lei.demo.aop.aspectj.CustomerBo"/> <bean id="logAspect" class="com.lei.demo.aop.aspectj.LoggingAspect" /> </beans>
解释:
1. <aop:aspectj-autoproxy/>启动AspectJ支持,这样Spring会自动寻找用@Aspect注释过的类,其他的配置与spring普通bean配置一样。
测试:
执行App.java如下:
package com.lei.demo.aop.aspectj; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class App { public static void main(String[] args) { ApplicationContext appContext = new ClassPathXmlApplicationContext( new String[] { "Spring-AOP-AspectJ.xml" }); ICustomerBo customer=(ICustomerBo)appContext.getBean("customerBo"); customer.addCustomer(); System.out.println("-------------------------------------------"); customer.deleteCustomer(); } }
结果:
logBefore() is running ...
hijacked:addCustomer
**********
addCustomer() is running ...
-------------------------------------------
deleteCustomer() is running ...
logAfter() is running ...
hijacked:deleteCustomer
**********
二、 将Advice和Pointcut分开
需要三步,
- 创建Pointcut
- 创建Advice
- 配置Spring的配置文件
第一步,PointcutsDefinition.java定义了Pointcut,如下:
package com.lei.demo.aop.aspectj; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; @Aspect public class PointcutsDefinition { @Pointcut("execution(* com.lei.demo.aop.aspectj.CustomerBo.*(..))") public void customerLog() { } }
解释:
1. 类声明前加入@Aspect注释,以便被框架扫描到。
2. @Pointcut是切入点声明,指定需要注入的代码的位置,如上例中指定切入点为CustomerBo类中的所有方法,在实际业务中往往是指定切入点到一个逻辑层,例如 execution (* com.lei.business.service.*.*(..)),表示aop切入点为service包中所有类的所有方法,具体的表达式后边会有介绍。
3. 方法customerLog是一个签名,在Advice中可以用此签名代替切入点表达式,所以不需要在方法体内编写实际代码,只起到助记功能,例如此处代表操作CustomerBo类时需要的切入点。
第二步,创建Advice类
LoggingAspect.java如下:
package com.lei.demo.aop.aspectj; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; @Aspect public class LoggingAspect { @Before("com.lei.demo.aop.aspectj.PointcutsDefinition.customerLog()") public void logBefore(JoinPoint joinPoint){ System.out.println("logBefore() is running ..."); System.out.println("hijacked:"+joinPoint.getSignature().getName()); System.out.println("**********"); } @After("com.lei.demo.aop.aspectj.PointcutsDefinition.customerLog()") public void logAfter(JoinPoint joinPoint){ System.out.println("logAfter() is running ..."); System.out.println("hijacked:"+joinPoint.getSignature().getName()); System.out.println("**********"); } }
注释:
1. @Before和@After使用PointcutsDefinition中的方法签名代替Pointcut表达式找到相应的切入点,即通过签名找到PointcutsDefinition中customerLog签名上的Pointcut表达式,表达式指定切入点为CustomerBo类中的所有方法。所以此例中Advice类LoggingAdvice,为CustomerBo中的所有方法都加入了@Before和@After两种类型的两种操作。
2. 对于PointcutsDefinition来说,主要职责是定义Pointcut,可以在其中第一多个切入点,并且可以用便于记忆的方法签名进行定义。
3. 单独定义Pointcut的好处是,一是通过使用有意义的方法名,而不是难读的Pointcut表达式,使代码更加直观;二是Pointcut可以实现共享,被多个Advice直接调用。若有多个Advice调用某个Pointcut,而这个Pointcut的表达式在将来有改变时,只需修改一个地方,维护更加方便。
第三步,配置Spring配置文件,配置文件并没有改变
配置Spring-AOP-AspectJ.xml文件,如下:
<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-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd"> <aop:aspectj-autoproxy/> <bean id="customerBo" class="com.lei.demo.aop.aspectj.CustomerBo"/> <bean id="logAspect" class="com.lei.demo.aop.aspectj.LoggingAspect" /> </beans>
App.java不变,运行测试代码App.java
输出结果:
logBefore() is running ...
hijacked:addCustomer
**********
addCustomer() is running ...
logAfter() is running ...
hijacked:addCustomer
**********
-------------------------------------------
logBefore() is running ...
hijacked:deleteCustomer
**********
deleteCustomer() is running ...
logAfter() is running ...
hijacked:deleteCustomer
**********