Spring基础知识(9)- Spring 集成 AspectJ
Spring AOP 是一个简化版的 AOP 实现,并没有提供完整版的 AOP 功能。通常情况下,Spring AOP 是能够满足我们日常开发过程中的大多数场景的,但在某些情况下,我们可能需要使用 Spring AOP 范围外的某些 AOP 功能。
Spring AOP 仅支持执行公共(public)非静态方法的调用作为连接点,如果我们需要向受保护的(protected)或私有的(private)的方法进行增强,此时就需要使用功能更加全面的 AOP 框架来实现,其中使用最多的就是 AspectJ。
AspectJ 是一个基于 Java 语言的全功能的 AOP 框架,它并不是 Spring 组成部分,是一款独立的 AOP 框架。
AspectJ 支持通过 Spring 配置 AspectJ 切面,因此它是 Spring AOP 的完美补充,通常情况下,我们都是将 AspectJ 和 Spirng 框架一起使用,简化 AOP 操作。
本文在 “Spring基础知识(2)- 创建 Spring 程序” 的基础上,导入 AspectJ 框架。
AspectJ:http://www.eclipse.org/aspectj/
1. 导入 AspectJ 依赖包
访问 http://www.mvnrepository.com/,查询 spring-aop, spring-aspects, aspectjweaver
本文选择了 Spring 4.3.9.RELEASE、aspectjweaver 1.9.6。
修改 pom.xml,添加依赖包:
<project ... >
...
<dependencies>
...
<!-- https://mvnrepository.com/artifact/org.springframework/spring-aop -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>4.3.9.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-aspects -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>4.3.9.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.6</version>
</dependency>
...
</dependencies>
...
</project>
在IDE中项目列表 -> 点击鼠标右键 -> Maven -> Reload Project
2. 基于XML配置实现 AspectJ 的 AOP 开发
Spring 提供了基于 XML 的 AOP 支持,并提供了一个名为 “aop” 的命名空间,该命名空间提供了一个 <aop:config> 元素。
标签和含义:
(1) <aop:config> 表示一个 aop 配置;
(2) <aop:pointcut> 表示切入点;
(3) <aop:advisor> 表示通知(Advice)和切入点(Pointcut) 的组合;
(4) <aop:aspect> 表示切面,它的内部也是通知(Advice)和切入点(Pointcut) 的组合;
<aop:advisor> 和 <aop:aspect> 区别:
(1) 在面向切面编程时(比如用于日志、缓存等),我们一般会用 <aop:aspect>,<aop:aspect> 定义切面(包括通知和切入点);
(2) 在进行事务管理时,我们一般会用<aop:advisor>,<aop:advisor> 定义通知器 (通知器跟切面一样,也包括通知和切入点);
(3) 如果用<aop:advisor>配置切面的话也可以配置,但切面类跟 <aop:aspect> 有所不同,需要实现接口;
配置规则:
(1) 在 Spring 配置中,所有的切面信息(切面、切点、通知)都必须定义在 <aop:config> 元素中;
(2) 在 Spring 配置中,可以使用多个 <aop:config>;
(3) 每一个 <aop:config> 元素内可以包含 3 个子元素:<aop:pointcut>、<aop:advisor> 和 <aop:aspect> ,这些子元素必须按照这个顺序进行声明;
1) 引入 aop 命名空间
需要在 Spring 配置文件中导入 Spring aop 命名空间,如下所示。
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 xmlns:context="http://www.springframework.org/schema/context" 6 xsi:schemaLocation="http://www.springframework.org/schema/beans 7 http://www.springframework.org/schema/beans/spring-beans-4.0.xsd 8 http://www.springframework.org/schema/context 9 http://www.springframework.org/schema/context/spring-context-4.0.xsd 10 http://www.springframework.org/schema/aop 11 http://www.springframework.org/schema/aop/spring-aop-4.0.xsd"> 12 </beans>
2) 定义切面 <aop:aspect>
在 Spring 配置文件中,使用 <aop:aspect> 元素定义切面。该元素可以将定义好的 Bean 转换为切面 Bean,所以使用 <aop:aspect> 之前需要先定义一个普通的 Spring Bean。
格式如下:
<aop:config>
<aop:aspect id="myAspect" ref="com.example.MyAspect">
...
</aop:aspect>
</aop:config>
其中,id 用来定义该切面的唯一标识名称,ref 用于引用Spring Bean。
3) 定义切入点 <aop:pointcut>
<aop:pointcut> 用来定义一个切入点,用来表示对哪个类中的那个方法进行增强。它既可以在 <aop:config> 元素中使用,也可以在 <aop:aspect> 元素下使用。
a) 作为 <aop:config> 元素的子元素定义时,表示该切入点是全局切入点,它可被多个切面所共享;
b) 作为 <aop:aspect> 元素的子元素时,表示该切入点只对当前切面有效;
(1) 使用格式如下:
<aop:config>
<aop:pointcut id="beforePointCut" expression="execution(* com.example.UserDao.*.*(..))"/>
</aop:config>
其中,id 用于指定切入点的唯一标识名称,execution 用于指定切入点关联的切入点表达式。
(2) execution 的用法
execution 的语法格式:
execution([权限修饰符] [返回值类型] [类的完全限定名] [方法名称]([参数列表])
语法说明:
a) 返回值类型、方法名、参数列表是必选项,而其它参数则为可选项。
b) 返回值类型:*表示可以为任何返回值。如果返回值为对象,则需指定全路径的类名。
c) 类的完全限定名:指定包名.类名。
d) 方法名称:*代表所有方法,set* 代表以 set 开头的所有方法。
e) 参数列表:(..)代表所有参数;(*)代表只有一个参数,参数类型为任意类型;(*,String) 代表有两个参数,第一个参数可以为任何值,第二个为 String 类型的值。
示例:
a) 对 com.example 包下 UserDao 类中的 add() 方法进行增强,配置如下:
execution(* com.example.UserDao.add(..))
b) 对 com.example 包下 UserDao 类中的所有方法进行增强,配置如下:
execution(* com.example.UserDao.*(..))
c) 对 com.example 包下所有类中的所有方法进行增强,配置如下:
execution(* com.example.*.*(..))
4) 定义通知
AspectJ 支持 5 种类型的 advice (通知/增强),格式如下:
<aop:aspect ref="myAspect">
<!-- 前置通知 -->
<aop:before method="before" pointcut-ref="beforePointCut"></aop:before>
<!-- 后置通知 -->
<aop:after method="after" pointcut-ref="afterPointCut"></aop:after>
<!-- 后置返回通知 -->
<aop:after-returning method="afterReturning" pointcut-ref="afterReturnPointCut"
returning="returnValue"></aop:after-returning>
<!-- 异常通知 -->
<aop:after-throwing method="afterThrowing" pointcut-ref="throwPointCut"
throwing="exception"></aop:after-throwing>
<!-- 环绕通知 -->
<aop:around method="around" pointcut-ref="beforePointCut"></aop:around>
</aop:aspect>
5) Spring 配置文件(spring-beans.xml)
配置格式如下:
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 xmlns:context="http://www.springframework.org/schema/context" 6 xsi:schemaLocation="http://www.springframework.org/schema/beans 7 http://www.springframework.org/schema/beans/spring-beans-4.0.xsd 8 http://www.springframework.org/schema/context 9 http://www.springframework.org/schema/context/spring-context-4.0.xsd 10 http://www.springframework.org/schema/aop 11 http://www.springframework.org/schema/aop/spring-aop-4.0.xsd"> 12 13 <!-- 定义 Bean --> 14 <bean id="userDao" class="com.example.UserDaoImpl"></bean> 15 <!-- 定义切面 --> 16 <bean id="myAspect" class="com.example.MyAspect"></bean> 17 18 <aop:config> 19 <aop:pointcut id="beforePointCut" expression="execution(* com.example.UserDao.test1(..))"/> 20 <aop:pointcut id="afterPointCut" expression="execution(* com.example.UserDao.test2(..))"/> 21 <aop:pointcut id="afterReturnPointCut" expression="execution(* com.example.UserDao.test3(..))"/> 22 <aop:pointcut id="aroundPointCut" expression="execution(* com.example.UserDao.test4(..))"/> 23 <aop:pointcut id="throwPointCut" expression="execution(* com.example.UserDao.test5(..))"/> 24 <aop:aspect ref="myAspect"> 25 <!-- 前置增知 --> 26 <aop:before method="before" pointcut-ref="beforePointCut"></aop:before> 27 <!-- 后置通知 --> 28 <aop:after method="after" pointcut-ref="afterPointCut"></aop:after> 29 <!-- 后置返回知 --> 30 <aop:after-returning method="afterReturning" pointcut-ref="afterReturnPointCut" 31 returning="returnValue"></aop:after-returning> 32 <!-- 环绕通知 --> 33 <aop:around method="around" pointcut-ref="aroundPointCut"></aop:around> 34 <!-- 异常通知 --> 35 <aop:after-throwing method="afterThrowing" pointcut-ref="throwPointCut" 36 throwing="exception"></aop:after-throwing> 37 </aop:aspect> 38 </aop:config> 39 40 </beans>
示例:
1 package com.example; 2 3 import org.aspectj.lang.ProceedingJoinPoint; 4 import org.springframework.context.support.ClassPathXmlApplicationContext; 5 6 public class App { 7 public static void main( String[] args ) { 8 9 ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring-beans.xml"); 10 UserDao userDao = context.getBean("userDao", UserDao.class); 11 userDao.test1(); 12 userDao.test2(); 13 userDao.test3(); 14 userDao.test4(); 15 userDao.test5(); 16 17 } 18 } 19 20 interface UserDao { 21 22 public void test1(); 23 public void test2(); 24 public String test3(); 25 public void test4(); 26 public void test5(); 27 } 28 29 class UserDaoImpl implements UserDao { 30 31 @Override 32 public void test1() { 33 System.out.println("UserDaoImpl -> test1()"); 34 } 35 36 @Override 37 public void test2() { 38 System.out.println("UserDaoImpl -> test2()"); 39 } 40 41 @Override 42 public String test3() { 43 System.out.println("UserDaoImpl -> test3()"); 44 return "OK"; 45 } 46 47 @Override 48 public void test4() { 49 System.out.println("UserDaoImpl -> test4()"); 50 } 51 52 @Override 53 public void test5(){ 54 System.out.println("UserDaoImpl -> test5()"); 55 } 56 57 } 58 59 class MyAspect { 60 61 public void before() { 62 System.out.println("MyAspect -> before()"); 63 } 64 65 public void after() { 66 System.out.println("MyAspect -> after()"); 67 } 68 69 public void afterReturning(Object returnValue) { 70 System.out.println("MyAspect -> afterReturning(): returnValue = " + returnValue); 71 } 72 73 public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { 74 System.out.println("MyAspect -> around(): before"); 75 proceedingJoinPoint.proceed(); 76 System.out.println("MyAspect -> around(): after"); 77 } 78 79 public void afterThrowing(Throwable exception) { 80 System.out.println("MyAspect -> afterThrowing(): error = " + exception.getMessage()); 81 } 82 83 }
输出:
MyAspect -> before()
UserDaoImpl -> test1()
UserDaoImpl -> test2()
MyAspect -> after()
UserDaoImpl -> test3()
MyAspect -> afterReturning(): returnValue = OK
MyAspect -> around(): before
UserDaoImpl -> test4()
MyAspect -> around(): after
UserDaoImpl -> test5()
3. 基于注解方式实现 AspectJ 的 AOP 开发
在 Spring 中,虽然我们可以使用 XML 配置文件可以实现 AOP 开发,但如果所有的配置都集中在 XML 配置文件中,就势必会造成 XML 配置文件过于臃肿,从而给维护和升级带来一定困难。
AspectJ 框架为 AOP 开发提供了一套 @AspectJ 注解。它允许我们直接在 Java 类中通过注解的方式对切面(Aspect)、切入点(Pointcut)和增强(Advice)进行定义,Spring 框架可以根据这些注解生成 AOP 代理。
注解的介绍:
名称 | 说明 |
@Aspect | 用于定义一个切面。 |
@Pointcut | 用于定义一个切入点。 |
@Before | 用于定义前置通知,相当于 BeforeAdvice。 |
@After | 用于定义后置通知,不管是否异常,该通知都会执行。 |
@AfterReturning | 用于定义后置返回通知,相当于 AfterReturningAdvice。 |
@Around | 用于定义环绕通知,相当于 MethodInterceptor。 |
@AfterThrowing | 用于定义抛出通知,相当于 ThrowAdvice。 |
@DeclareParents | 用于定义引介通知,相当于 IntroductionInterceptor(不要求掌握)。 |
示例:
1 package com.example; 2 3 import org.aspectj.lang.JoinPoint; 4 import org.aspectj.lang.ProceedingJoinPoint; 5 import org.springframework.context.annotation.AnnotationConfigApplicationContext; 6 7 import org.springframework.stereotype.Component; 8 import org.springframework.context.annotation.ComponentScan; 9 import org.springframework.context.annotation.Configuration; 10 import org.springframework.context.annotation.EnableAspectJAutoProxy; 11 import org.aspectj.lang.annotation.*; 12 13 public class App { 14 public static void main( String[] args ) { 15 16 AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(OrderAspectConfig.class); 17 OrderDao orderDao = context.getBean("orderDao", OrderDao.class); 18 orderDao.test1(); 19 orderDao.test2(); 20 orderDao.test3(); 21 orderDao.test4(); 22 orderDao.test5(); 23 24 } 25 } 26 27 interface OrderDao { 28 29 public void test1(); 30 public void test2(); 31 public String test3(); 32 public void test4(); 33 public void test5(); 34 35 } 36 37 @Component("orderDao") 38 class OrderDaoImpl implements OrderDao { 39 40 @Override 41 public void test1() { 42 System.out.println("OrderDaoImpl -> test1()"); 43 } 44 45 @Override 46 public void test2() { 47 System.out.println("OrderDaoImpl -> test2()"); 48 } 49 50 @Override 51 public String test3() { 52 System.out.println("OrderDaoImpl -> test3()"); 53 return "SUCCESS"; 54 } 55 56 @Override 57 public void test4() { 58 System.out.println("OrderDaoImpl -> test4()"); 59 } 60 61 @Override 62 public void test5() { 63 System.out.println("OrderDaoImpl -> test5()"); 64 int i = 1 / 0; 65 } 66 } 67 68 @Configuration 69 @ComponentScan(basePackages = "com.example") //注解扫描 70 @EnableAspectJAutoProxy //开启 AspectJ 的自动代理 71 class OrderAspectConfig { 72 73 } 74 75 @Component // 定义成 Bean 76 @Aspect // 定义为切面 77 class TestAspect { 78 @Before("execution(* com.example.OrderDao.test1(..))") 79 public void before(JoinPoint joinPoint) { 80 System.out.println("TestAspect -> before(): joinPoint = " + joinPoint); 81 } 82 83 @After("execution(* com.example.OrderDao.test2(..))") 84 public void after(JoinPoint joinPoint) { 85 System.out.println("TestAspect -> after(): joinPoint = " + joinPoint); 86 } 87 88 @AfterReturning(value = "execution(* com.example.OrderDao.test3(..))", returning = "returnValue") 89 public void afterReturning(Object returnValue) { 90 System.out.println("TestAspect -> afterReturning(): returnValue = " + returnValue); 91 } 92 93 @Pointcut(value = "execution(* com.example.OrderDao.test4(..))") 94 public void pointCut1() { 95 System.out.println("TestAspect -> pointCut1()"); 96 } 97 98 @Around("TestAspect.pointCut1()") 99 public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { 100 System.out.println("TestAspect -> around(): before"); 101 proceedingJoinPoint.proceed(); 102 System.out.println("TestAspect -> around(): after"); 103 } 104 105 @Pointcut(value = "execution(* com.example.OrderDao.test5(..))") 106 public void pointCut2() { 107 System.out.println("TestAspect -> pointCut2()"); 108 } 109 110 @AfterThrowing(pointcut = "pointCut2()", throwing = "ex") 111 public void afterThrowing(JoinPoint joinPoint, Exception ex) { 112 String name = joinPoint.getSignature().getName(); 113 System.out.println("TestAspect -> afterThrowing(): name = " + name + ", error = " + ex.getMessage()); 114 } 115 }
输出:
TestAspect -> before(): joinPoint = execution(void com.example.OrderDao.test1())
OrderDaoImpl -> test1()
OrderDaoImpl -> test2()
TestAspect -> after(): joinPoint = execution(void com.example.OrderDao.test2())
OrderDaoImpl -> test3()
TestAspect -> afterReturning(): returnValue = SUCCESS
TestAspect -> around(): before
OrderDaoImpl -> test4()
TestAspect -> around(): after
OrderDaoImpl -> test5()
TestAspect -> afterThrowing(): name = test5, error = / by zero
Exception in thread "main" java.lang.ArithmeticException: / by zero
...