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 操作。

本文在 “” 的基础上,导入 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,添加依赖包:

       
            ...

           
                ...

                
                
                    org.springframework
                    spring-aop
                    4.3.9.RELEASE
                

                
                
                    org.springframework
                    spring-aspects
                    4.3.9.RELEASE
                

                
                
                    org.aspectj
                    aspectjweaver
                    1.9.6
                



                ...
           


            ...
       


    在IDE中项目列表 -> 点击鼠标右键 -> Maven -> Reload Project

2. 基于XML配置实现 AspectJ 的 AOP 开发


    Spring 提供了基于 XML 的 AOP 支持,并提供了一个名为 “aop” 的命名空间,该命名空间提供了一个 元素。

    标签和含义:

        (1) 表示一个 aop 配置;
        (2) 表示切入点;
        (3) 表示通知(Advice)和切入点(Pointcut) 的组合;
        (4) 表示切面,它的内部也是通知(Advice)和切入点(Pointcut) 的组合;

     区别:

        (1) 在面向切面编程时(比如用于日志、缓存等),我们一般会用 定义切面(包括通知和切入点);
        (2) 在进行事务管理时,我们一般会用 定义通知器 (通知器跟切面一样,也包括通知和切入点);
        (3) 如果用配置切面的话也可以配置,但切面类跟 有所不同,需要实现接口;

    配置规则:
        (1) 在 Spring 配置中,所有的切面信息(切面、切点、通知)都必须定义在 元素中;
        (2) 在 Spring 配置中,可以使用多个 ;
        (3) 每一个 元素内可以包含 3 个子元素: ,这些子元素必须按照这个顺序进行声明;


    1) 引入 aop 命名空间

        需要在 Spring 配置文件中导入 Spring aop 命名空间,如下所示。

 1             <?xml version="1.0" encoding="UTF-8"?>
 2              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             


    2) 定义切面

        在 Spring 配置文件中,使用 元素定义切面。该元素可以将定义好的 Bean 转换为切面 Bean,所以使用 之前需要先定义一个普通的 Spring Bean。

        格式如下:

            
                
                    ...
                

            


        其中,id 用来定义该切面的唯一标识名称,ref 用于引用Spring Bean。


    3) 定义切入点

        用来定义一个切入点,用来表示对哪个类中的那个方法进行增强。它既可以在 元素中使用,也可以在 元素下使用。

            a) 作为 元素的子元素定义时,表示该切入点是全局切入点,它可被多个切面所共享;
            b) 作为 元素的子元素时,表示该切入点只对当前切面有效;

        (1) 使用格式如下:

            
                
            


            其中,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 (通知/增强),格式如下:

            
                
                
                
                

                
                                                    returning="returnValue">
                
                                                    throwing="exception">
                
                
            


    5) Spring 配置文件(spring-beans.xml)

        配置格式如下:

 1             <?xml version="1.0" encoding="UTF-8"?>
 2              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                 
14                 class="com.example.UserDaoImpl">
15                 
16                 class="com.example.MyAspect">
17 
18                 
19                     
20                     
21                     
22                     
23                     
24                     
25                         
26                         
27                         
28                         
29                         
30                         31                                             returning="returnValue">
32                         
33                         
34                         
35                         36                                             throwing="exception">
37                     
38                 
39 
40             


    示例:

 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
            ...