AspectJ 是基于注释(Annotation)的,所以需要 JDK5.0 以上的支持。
AspectJ 支持的注解类型如下:
- @Before
- @After
- @AfterReturning
- @AfterThrowing
- @Around
准备工作
首先定义一个简单的 bean,CustomerBo 实现了接口 ICustomerBo。ICustomerBo.java 如下:
package com.shiyanlou.spring.aop.aspectj;
public interface ICustomerBo {
void addCustomer();
void deleteCustomer();
String AddCustomerReturnValue();
void addCustomerThrowException() throws Exception;
void addCustomerAround(String name);
}
CustomerBo.java 如下:
package com.shiyanlou.spring.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 结合在一起
首先没有引入 Aspect 之前,Advice 和 Pointcut 是混在一起的,步骤如下:
- 创建一个 Aspect 类
- 配置 Spring 配置文件
由于接下来要使用 aspectj 的 jar 包,首先添加 maven 依赖。需要在 pom.xml 添加:
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.2</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjtools</artifactId>
<version>1.9.2</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.9.2</version>
</dependency>
注:我们在新建项目时就已经添加了这些依赖,这里写出来只是让同学们知道这些包的作用
创建 AspectJ 类,LoggingAspect.java 如下:
package com.shiyanlou.spring.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.shiyanlou.spring.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.shiyanlou.spring.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.shiyanlou.spring.aop.aspectj.CustomerBo.addCustomer(..)) 是 Aspect 的切入点表达式,其中,* 代表返回类型,后边的就要定义要拦截的方法名。这里写的的是 com.shiyanlou.spring.aop.aspectj.CustomerBo.addCustomer 表示拦截 CustomerBo 中的 addCustomer 方法,(..) 代表参数匹配,此处表示匹配任意数量的参数,可以是 0 个也可以是多个,如果你确定这个方法不需要使用参数可以直接用 (),还可以使用 () 来匹配一个任意类型的参数,还可以使用 ( , String ),这样代表匹配两个参数,第二个参数必须是 String 类型的参数。
5、AspectJ 表达式,可以对整个包定义,例如 execution ( * com.shiyanlou.spring.aop.aspectj..(..)) 表示切入点是 com.shiyanlou.spring.aop.aspectj 包中的任意一个类的任意方法,具体的表达式请自行搜索。
配置 SpringAopAspectj.xml 文件,如下:
<?xml version = "1.0" encoding = "UTF-8"?>
<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.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<aop:aspectj-autoproxy/>
<bean id = "customerBo" class = "com.shiyanlou.spring.aop.aspectj.CustomerBo"/>
<bean id = "logAspect" class = "com.shiyanlou.spring.aop.aspectj.LoggingAspect" />
</beans>
<aop:aspectj-autoproxy/>
启动 AspectJ 支持,这样 Spring 会自动寻找用 @Aspect 注释过的类,其他的配置与 Spring 普通 bean 配置一样。
执行 App.java 如下:
package com.shiyanlou.spring.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[] { "SpringAOPAspectj.xml" });
ICustomerBo customer = (ICustomerBo)appContext.getBean("customerBo");
customer.addCustomer();
System.out.println("-------------------------------------------");
customer.deleteCustomer();
}
}
控制台输入命令:
mvn clean compile
mvn exec:java -Dexec.mainClass="com.shiyanlou.spring.aop.aspectj.App"
实验结果如下:
将 Advice 和 Pointcut 分开
需要三步:
1、创建 Pointcut
2、创建 Advice
3、配置 Spring 的配置文件
定义 Pointcut,创建 PointcutsDefinition.java,如下:
package com.shiyanlou.spring.aop.aspectj;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
@Aspect
public class PointcutsDefinition {
@Pointcut("execution(* com.shiyanlou.spring.aop.aspectj.CustomerBo.*(..))")
public void customerLog() {
}
}
解释:
1、类声明前加入 @Aspect 注释,以便被框架扫描到。
2、@Pointcut 是切入点声明,指定需要注入的代码的位置,如上例中指定切入点为 CustomerBo 类中的所有方法,在实际业务中往往是指定切入点到一个逻辑层,例如 execution (* com.shiyanlou.spring.aop.aspectj..(..)),表示 aop 切入点为 aspectj 包中所有类的所有方法,具体的表达式后边会有介绍。
3、方法 customerLog 是一个签名,在 Advice 中可以用此签名代替切入点表达式,所以不需要在方法体内编写实际代码,只起到助记功能,例如此处代表操作 CustomerBo 类时需要的切入点。
创建 LoggingAspect.java:
package com.shiyanlou.spring.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.shiyanlou.spring.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.shiyanlou.spring.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 类 LoggingAspect,为 CustomerBo 中的所有方法都加入了 @Before 和 @After 两种类型的两种操作。
2、对于 PointcutsDefinition 来说,主要职责是定义 Pointcut,可以在其中定义多个切入点,并且可以用便于记忆的方法签名进行定义。
3、单独定义 Pointcut 的好处是,一是通过使用有意义的方法名,而不是难读的 Pointcut 表达式,使代码更加直观;二是 Pointcut 可以实现共享,被多个 Advice 直接调用。若有多个 Advice 调用某个 Pointcut,而这个 Pointcut 的表达式在将来有改变时,只需修改一个地方,维护更加方便。
配置 Spring 配置文件。
配置 SpringAOPAspectJ.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.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<aop:aspectj-autoproxy/>
<bean id = "customerBo" class = "com.shiyanlou.spring.aop.aspectj.CustomerBo"/>
<bean id = "logAspect" class = "com.shiyanlou.spring.aop.aspectj.LoggingAspect" />
</beans>
App.java 不变,运行测试代码 App.java:
mvn clean compile
mvn exec:java -Dexec.mainClass="com.shiyanlou.spring.aop.aspectj.App"
结果如下: