Spring AOP详解

扫码查看

一.AOP(Aspect Oriented Programming)使用背景:

  AOP主要实现的目的是针对业务处理过程中的切面进行提取,它所面对的是处理过程中的某个步骤或阶段,以获得逻辑过程中各部分之间低耦合性的隔离效果。比如我们最常见的就是日志记录了,举个例子,我们现在提供一个查询学生信息的服务,但是我们希望记录有谁进行了这个查询。如果按照传统的OOP(Object Oriented Programming)的实现的话,我们定义一个查询学生信息的服务接口(StudentInfoService)并且定义一个实现类(StudentInfoServiceImpl.java)实现该接口,手动的在该实现类中添加相关的日志记录就可以了.

  但是,假如我们要实现的服务有多个呢?那就要在每个实现的类都添加这些记录过程。这样做的话就会有点繁琐,而且每个实现类都与记录服务日志的行为紧耦合,违反了面向对象的规则。那么怎样才能把记录服务的行为与业务处理过程中分离出来呢?看起来好像就是查询学生的服务自己在进行,但却是背后日志记录对这些行为进行记录,并且查询学生的服务不知道存在这些记录过程,这就是我们要讨论AOP的目的所在。AOP的编程,好像就是把我们在某个方面的功能提出来与一批对象进行隔离,这样与一批对象之间降低了耦合性,可以就某个功能进行编程。(简单的来说AOP就是在程序运行过程中某段代码动态的切入到指定方法指定位置进行运行)

二.AOP使用场景

  使用AOP主要是将辅助功能(日志记录、异常处理、性能统计等)和核心功能(业务逻辑)进行有效的分离,能够更好的进行解耦合

三.实现AOP的几种常见方式

   (3.1)XML方式:

通知:

 1 package com.xiaomaomao.advice;
 2
 3 import org.aspectj.lang.JoinPoint;
 4 import org.aspectj.lang.ProceedingJoinPoint;
 5 import org.aspectj.lang.annotation.*;
 6 import org.springframework.stereotype.Component;
 7
 8 import java.util.Arrays;
 9
10 /**
11  * @Content 定义通知类
12  * @Author xiaomaomao
13  * @CreateTime 2019-10-25 9:57
14  */
15 /*
16 使用@Aspect注解将该类标记为切面类
17 使用@Component将该类交由Spring容器进行管理
18 */
19
20 public class Advice {
21     //随意定义一个返回值类型为void的方法,在其上加上@Pointcut(value = "execution(* com.xiaomaomao..*.*(..))")注解,引入的时候
22     //只需要引入该方法名即可
23     @Pointcut(value = "execution(* com.xiaomaomao..*.*(..))")
24     public void pointCutExpression() {
25     };
26
27
28     public void before(JoinPoint jointPoint) {
29         String funName = jointPoint.getSignature().getName();
30         Object[] funArgs = jointPoint.getArgs();
31         System.out.println("前置通知");
32     }
33
34
35     public void after(JoinPoint joinPoint) {
36         System.out.println("后置通知");
37     }
38
39
40
41     public void afterReturning(JoinPoint joinPoint, Object result) {
42         System.out.println("返回通知,返回值是:" + result);
43     }
44
45
46
47     public void afterThrowing(JoinPoint joinPoint, Exception exception) {
48         System.out.println("异常通知");
49     }
50
51
52     public Object around(ProceedingJoinPoint pjp) {
53         String funName = pjp.getSignature().getName();
54         Object[] args = pjp.getArgs();
55         Object proceed = null;
56
57         try {
58             System.out.println("环绕通知前置:");
59             //类似于invoke方法,获取参数,拿到目标方法的返回值
60             proceed = pjp.proceed(args);
61             System.out.println("环绕通知正常返回,返回值是:" + proceed);
62         } catch (Throwable throwable) {
63             System.out.println("环绕通知异常:");
64             throwable.printStackTrace();
65         } finally {
66             System.out.println("环绕通知后置:");
67         }
68         return proceed;
69     }
70
71 }

 配置:

 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:c="http://www.springframework.org/schema/c"
 6        xmlns:cache="http://www.springframework.org/schema/cache"
 7        xmlns:context="http://www.springframework.org/schema/context"
 8        xmlns:jdbc="http://www.springframework.org/schema/jdbc"
 9        xmlns:jee="http://www.springframework.org/schema/jee"
10        xmlns:lang="http://www.springframework.org/schema/lang"
11        xmlns:mvc="http://www.springframework.org/schema/mvc"
12        xmlns:p="http://www.springframework.org/schema/p"
13        xmlns:task="http://www.springframework.org/schema/task"
14        xmlns:tx="http://www.springframework.org/schema/tx"
15        xmlns:util="http://www.springframework.org/schema/util"
16        xsi:schemaLocation="http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-4.3.xsd
17         http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.3.xsd
18         http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
19         http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd
20         http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.3.xsd
21         http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-4.3.xsd
22         http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache-4.3.xsd
23         http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-4.3.xsd
24         http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
25         http://www.springframework.org/schema/lang http://www.springframework.org/schema/lang/spring-lang-4.3.xsd
26         http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd ">
27
28     <!--此配置代表会扫描com.xiaomaomao包及其自爆下面的所有注解-->
29     <context:component-scan base-package="com.xiaomaomao"></context:component-scan>
30
31     <!--通知类-->
32     <bean name="advice" class="com.xiaomaomao.advice.Advice"></bean>
33     <!--目标类-->
34     <bean name="userServiceImpl" class="com.xiaomaomao.service.impl.UserServiceImpl"></bean>
35
36     <aop:config proxy-target-class="true">
37         <aop:pointcut expression="execution(* com.xiaomaomao..*.*(..))" id="myPointcut"></aop:pointcut>
38         <aop:aspect ref="advice">
39             <aop:before method="before" pointcut-ref="myPointcut"></aop:before>
40             <aop:after method="after" pointcut-ref="myPointcut"></aop:after>
41             <aop:after-returning method="afterReturning" pointcut-ref="myPointcut" returning="result"></aop:after-returning>
42             <aop:after-throwing method="afterThrowing" pointcut-ref="myPointcut" throwing="exception"></aop:after-throwing>
43             <aop:around method="around" pointcut-ref="myPointcut"></aop:around>
44         </aop:aspect>
45     </aop:config>
46 </beans>

测试结果:

前置通知
环绕通知前置:
执行查询操作...
环绕通知正常返回,返回值是:Test Spring Aop
环绕通知后置:
返回通知,返回值是:Test Spring Aop
后置通知

  (3.2)注解方式

    (3.2.1)定义一个通知类(里面封装了各种通知,也就是具有增强功能的代码),一个业务逻辑的接口以及该接口的实现类.

    (3.2.2)将通知类,业务逻辑接口实现类加入到Spring容器当中,交由Spring容器进行管理

    (3.2.3)编写配置文件

通知类:

通知类型:

  @Before:在目标方法之前运行  (前置通知)
  @After:在目标方法结束之后  (后置通知)
  @AfterReturning:在目标方法正常返回之后  (返回通知)
  @AfterThrowing:在目标方法抛出异常之后运行  (异常通知)
  @Around:作用类似于上面4个通知的全部功能  (环绕通知)

 1 package com.xiaomaomao.advice;
 2
 3 import org.aspectj.lang.JoinPoint;
 4 import org.aspectj.lang.annotation.*;
 5 import org.springframework.stereotype.Component;
 6 import java.util.Arrays;
 7 /**
 8  * @Content 定义通知类
 9  * @Author xiaomaomao
10  * @CreateTime 2019-10-25 9:57
11  */
12 /*
13 使用@Aspect注解将该类标记为切面类
14 使用@Component将该类交由Spring容器进行管理
15 */
16 @Component
17 @Aspect
18 public class Advice {
19     //使用@Before注解,将该方法标记为前置通知
20     //JoinPoint jointPoint 可以获取到方法的参数信息,方法签名等,签名里有定义方法名,声明类型等信息
21     @Before(value = "execution(* com.xiaomaomao..*.*(..))")
22     public void before(JoinPoint jointPoint) {
23         String funName = jointPoint.getSignature().getName();
24         Object[] funArgs = jointPoint.getArgs();
25         System.out.println( funName + "方法开始执行,所使用的参数是:" + Arrays.asList(funArgs));
26     }
27
28     //使用@After注解,将该方法标记为后置通知
29     @After(value = "execution(* com.xiaomaomao..*.*(..))")
30     public void after(JoinPoint joinPoint) {
31         System.out.println( joinPoint.getSignature().getName() + "方法执行结束,所使用的参数是:" + Arrays.asList(joinPoint.getArgs()));
32     }
33
34     //使用@AfterReturning将该方法标记为正常返回通知
35     //方法上有必须要定义一个参数Object result(这里之所以定义为Object类型,主要是为了可以接收任意类型的返回值,因为你不能确定返回值到底是什么类型的)
36     //在注解后面还需要定义returning = "result"是为了告诉容器这个方法的返回值是result
37     @AfterReturning(value = "execution(* com.xiaomaomao..*.*(..))",returning = "result")
38     public void afterReturning(JoinPoint joinPoint,Object result) {
39         System.out.println( joinPoint.getSignature().getName() + "方法正常执行并返回,返回值是:" + result);
40     }
41
42     //使用@AfterThrowing将该方法标记为正常返回通知
43     //方法上有必须要定义一个参数Exception exception(这里之所以定义为Exception类型,主要是为了可以接收任意类型的异常)
44     //在注解后面还需要定义returning = "result"是为了告诉容器这个方法的返回值是result
45     @AfterThrowing(value = "execution(* com.xiaomaomao..*.*(..))",throwing="exception")
46     public void afterThrowing(JoinPoint joinPoint,Exception exception) {
47         System.out.println( joinPoint.getSignature().getName() + "方法出现了异常,异常信息是:" + exception.getCause());
48     }
49 }

业务逻辑接口及其实现类:

 1 package com.xiaomaomao.service;
 2
 3 /**
 4  * @Content 业务逻辑接口
 5  * @Author xiaomaomao
 6  * @CreateTime 2019-10-25 11:10
 7  */
 8 public interface UserService {
 9     public abstract void insert(int a);
10     public abstract void delete(int a,int b);
11     public abstract void update(int a,int b,int c);
12     public abstract String query(int a,int b,int c,int d);
13 }
14
15
16 package com.xiaomaomao.service.impl;
17
18 import com.xiaomaomao.service.UserService;
19 import org.springframework.stereotype.Service;
20
21 /**
22  * @Content 业务逻辑实现类
23  * @Author xiaomaomao
24  * @CreateTime 2019-10-25 11:13
25  */
26 //使用@Service将该类交由Spring容器进行管理,bean的默认id是userServiceImpl
27 @Service
28 public class UserServiceImpl implements UserService {
29     @Override
30     public void insert(int a) {
31         System.out.println("执行插入操作...");
32     }
33
34     @Override
35     public void delete(int a,int b) {
36         System.out.println("执行删除操作...");
37     }
38
39     @Override
40     public void update(int a,int b,int c) {
41         System.out.println("执行修改操作...");
42     }
43
44     @Override
45     public String query(int a,int b,int c,int d) {
46         System.out.println("执行查询操作...");
47         return "Test Spring Aop";
48     }
49 }

配置文件:

 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:c="http://www.springframework.org/schema/c"
 6        xmlns:cache="http://www.springframework.org/schema/cache"
 7        xmlns:context="http://www.springframework.org/schema/context"
 8        xmlns:jdbc="http://www.springframework.org/schema/jdbc"
 9        xmlns:jee="http://www.springframework.org/schema/jee"
10        xmlns:lang="http://www.springframework.org/schema/lang"
11        xmlns:mvc="http://www.springframework.org/schema/mvc"
12        xmlns:p="http://www.springframework.org/schema/p"
13        xmlns:task="http://www.springframework.org/schema/task"
14        xmlns:tx="http://www.springframework.org/schema/tx"
15        xmlns:util="http://www.springframework.org/schema/util"
16        xsi:schemaLocation="http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-4.3.xsd
17       http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.3.xsd
18       http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
19       http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd
20       http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.3.xsd
21       http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-4.3.xsd
22       http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache-4.3.xsd
23       http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-4.3.xsd
24       http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
25       http://www.springframework.org/schema/lang http://www.springframework.org/schema/lang/spring-lang-4.3.xsd
26       http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd ">
27
28     <!--此配置代表会扫描com.xiaomaomao包及其自爆下面的所有注解-->
29     <context:component-scan base-package="com.xiaomaomao"></context:component-scan>
30     <!--开启aop自动代理 千万注意,使用注解AOP的时候必须要配置这个-->
31     <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
32
33 </beans>

测试类:

 1 package com.xiaomaomao;
 2
 3 import com.xiaomaomao.service.UserService;
 4 import org.junit.Test;
 5 import org.springframework.context.ApplicationContext;
 6 import org.springframework.context.support.ClassPathXmlApplicationContext;
 7
 8 /**
 9  * @Content 测试类
10  * @Author xiaomaomao
11  * @CreateTime 2019-10-17 19:08
12  */
13 public class testSpring {
14     @Test
15     public void test01(){
16         ApplicationContext ioc = new ClassPathXmlApplicationContext("ioc.xml");
17
18         /*这里我们之所以能使用userServiceImpl获取UserServiceImpl对象,是因为我们在UserServiceImpl类中使用
19         @Service将该类交由Spring容器进行管理,bean的默认id是userServiceImpl*/
20         UserService userService = (UserService) ioc.getBean("userServiceImpl");
21         userService.query(1,2,3,4);
22     }
23 }

测试结果:

  query方法开始执行,所使用的参数是:[1, 2, 3, 4]
  执行查询操作...
  query方法执行结束,所使用的参数是:[1, 2, 3, 4]
  query方法正常执行并返回,返回值是:Test Spring Aop

四.AOP几个注意事项:

(4.1):切点表达式

  (4.1.1)切点表达式:用来告诉通知应该切入到哪个包,哪个类哪个方法中

    在使用spring框架配置AOP的时候,不管是通过XML配置文件还是注解的方式都需要定义pointcut"切入点"

    例如定义切入点表达式 @Before("execution (* com.xiaomaomao.service.impl..*.*(..))") 

    execution()是最常用的切点函数,其语法如下所示:其中 * 和 .. 代表通配符

     整个表达式可以分为五个部分:

       1、execution(): 表达式主体。

       2、第一个*号:表示返回值类型,*号表示所有的返回值类型。

       3、包名:表示需要拦截的包名,后面的两个句点表示当前包和当前包的所有子包,例如com.xiaomaomao.service.impl..代表com.xiaomaomao.service.impl包、

       及其子孙包

       4、第二个*号:表示类名,*号表示所有的类。

       5、*(..):最后这个星号表示方法名,*号表示所有的方法,后面括弧里面的 .. 表示方法的参数,两个句点表示任何参数。

   (4.1.2)切点表达式的可重用

    通过上面的例子可以看到,我们每使用一个通知都需要在通知的注解上写上长长的切点表达式,这样很不方便,我们可以将切点表达式进行重用

 1 @Component
 2 @Aspect
 3 public class Advice {
 4     //随意定义一个返回值类型为void的方法,在其上加上@Pointcut(value = "execution(* com.xiaomaomao..*.*(..))")注解,引入的时候
 5     //只需要引入该方法名即可
 6     @Pointcut(value = "execution(* com.xiaomaomao..*.*(..))")
 7     public void pointCutExpression(){};
 8
 9     @Before(value = "pointCutExpression()")
10     public void before(JoinPoint jointPoint) {
11         String funName = jointPoint.getSignature().getName();
12         Object[] funArgs = jointPoint.getArgs();
13         System.out.println( funName + "方法开始执行,所使用的参数是:" + Arrays.asList(funArgs));
14     }
15
16     @After(value = "pointCutExpression()")
17     public void after(JoinPoint joinPoint) {
18         System.out.println( joinPoint.getSignature().getName() + "方法执行结束,所使用的参数是:" + Arrays.asList(joinPoint.getArgs()));
19     }
20
21
22     @AfterReturning(value = "pointCutExpression()",returning = "result")
23     public void afterReturning(JoinPoint joinPoint,Object result) {
24         System.out.println( joinPoint.getSignature().getName() + "方法正常执行并返回,返回值是:" + result);
25     }
26
27
28     @AfterThrowing(value = "pointCutExpression()",throwing="exception")
29     public void afterThrowing(JoinPoint joinPoint,Exception exception) {
30         System.out.println( joinPoint.getSignature().getName() + "方法出现了异常,异常信息是:" + exception.getCause());
31     }
32
33 }

(4.2):我们如何在通知方法运行的时候拿到目标方法的具体信息呢?

  (4.2.1)只需要为通知方法的参数列表上写一个参数:JoinPoint joinPoint,里面封装了方法的签名,以及参数等信息

  (4.2.2)如果是返回通知,要想知道返回值是什么.在注解上需要加上returning="xxx",并且给通知方法一个参数Object xxx,这里参数的类型之所以定为Object,是为了可以接收

  任何类型的返回值

1 @AfterReturning(value = "pointCutExpression()",returning = "result")
2     public void afterReturning(JoinPoint joinPoint,Object result) {
3         System.out.println( joinPoint.getSignature().getName() + "方法正常执行并返回,返回值是:" + result);
4     }

  (4.2.3)如果是异常通知,要想捕获异常相关的信息,在注解上需要加上throwing="xxx",并且给通知方法一个参数Exception xxx,这里异常的类型之所以定为Exception,是为了可以

  接收任何类型的异常信息

1 @AfterThrowing(value = "pointCutExpression()",throwing="exception")
2     public void afterThrowing(JoinPoint joinPoint,Exception exception) {
3         System.out.println( joinPoint.getSignature().getName() + "方法出现了异常,异常信息是:" + exception.getMessage());
4     }

(4.3)环绕通知(Spring中最强大的通知,作用基本类似前面4个通知之和)

 1 @Around(value = "pointCutExpression()")
 2     public Object around(ProceedingJoinPoint pjp) {
 3         String funName = pjp.getSignature().getName();
 4         Object[] args = pjp.getArgs();
 5         Object proceed = null;
 6
 7         try {
 8             System.out.println("环绕通知前置:");
 9             //类似于invoke方法,获取参数,拿到目标方法的返回值
10             proceed = pjp.proceed(args);
11             System.out.println("环绕通知正常返回,返回值是:" + proceed);
12         } catch (Throwable throwable) {
13             System.out.println("环绕通知异常:");
14             throwable.printStackTrace();
15         } finally {
16             System.out.println("环绕通知后置:");
17         }
18         return proceed;
19     }

测试结果:

环绕通知前置:
前置通知
执行查询操作...
环绕通知正常返回,返回值是:Test Spring Aop
环绕通知后置:
后置通知
返回通知,返回值是:Test Spring Aop
12-30 06:55
查看更多