笔记来源:尚硅谷Spring框架视频教程(spring5源码级讲解)

AOP

1、AOP 概述

  • 定义:AOP(Aspect Oriented Programming,面向切面编程),通过预编译和运行时动态代理扩展程序功能
  • 作用:利用 AOP 可以对业务逻辑的各个部分进行隔离,降低耦合性,提高程序可重用性和开发效率
  • 场景:日志记录,性能统计,安全控制,事务处理,异常处理
  • 通俗描述:不修改源代码,在主干功能中添加新功能

使用登录功能案例说明 AOP

02-AOP - 图1

2、AOP 底层原理

  • 底层原理:动态代理

    • 有接口情况:JDK动态代理
    • 无接口情况:CGLib动态代理

如果学习过设计模式,应该对上述两种代理方式非常了解了。没有学习过也没关系,我们接着往下看

2.1、JDK 动态代理

  1. public interface UserDao {
  2. void login();
  3. }
  4. public class UserDaoImpl implements UserDao {
  5. @Override
  6. public void login(){
  7. //登录实现过程
  8. }
  9. }

有接口情况:创建 UserDao 接口实现类代理对象

2.2、CGlib 动态代理

  1. public class User {
  2. public void add(){
  3. //...
  4. }
  5. }
  6. // 原始方法:通过子类继承,重写User类方法
  7. public class Person extends User {
  8. @Override
  9. public void add(){
  10. super.add();
  11. //增强代码逻辑
  12. }
  13. }

无接口情况:创建 User 类子类代理对象


由于 Spring5 中对上述代理已经做了很好的封装,我们只需要通过最简单的方式进行配置即可

但仍然需要我们对原理有一定的认识,只有做到“知其然,知其所以然”,才能真正“以不变应万变”

3、JDK 动态代理实现

实现方式:使用Proxy中的方法创建代理对象

02-AOP - 图2

具体方法newProxyInstance()

方法参数

  • ClassLoader loader:类加载器
  • Class<?>[] interfaces:增强方法所在类实现的接口数组
  • InvocationHandler h:实现InvocationHandler接口,创建代理对象,编写增强方法

02-AOP - 图3

常言道:“Talking is cheap, show me the code”。话不多说,下面上代码~

  • 1)创建 UserDao 接口和对应实现类
  1. public interface UserDao {
  2. int add(int a, int b);
  3. String update(String id);
  4. }
  5. public class UserDaoImpl implements UserDao {
  6. @Override
  7. public int add(int a, int b) {
  8. return a + b;
  9. }
  10. @Override
  11. public String update(String id) {
  12. return id;
  13. }
  14. }
  • 2)创建 UserDao 代理对象
  1. public class UserDaoProxy {
  2. private UserDao target;
  3. public UserDaoProxy(UserDao target) {
  4. this.target = target;
  5. }
  6. public UserDao newProxyInstance() {
  7. Class<?> targetClass = target.getClass();
  8. ClassLoader classLoader = targetClass.getClassLoader();
  9. Class<?>[] interfaces = targetClass.getInterfaces();
  10. return (UserDao) Proxy.newProxyInstance(classLoader, interfaces, new UserDaoInvocationHandler());
  11. }
  12. class UserDaoInvocationHandler implements InvocationHandler {
  13. @Override
  14. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  15. // 被代理对象方法前置逻辑
  16. System.out.print("method=" + method.getName() + ", args=" + Arrays.toString(args));
  17. // 被代理对象方法
  18. Object result = method.invoke(target, args);
  19. // 被代理对象方法后置逻辑
  20. System.out.println(", result=" + result);
  21. return result;
  22. }
  23. }
  24. }
  • 3)测试
  1. UserDao target = new UserDaoImpl();
  2. UserDaoProxy userDaoProxy = new UserDaoProxy(target);
  3. UserDao userDao = userDaoProxy.newProxyInstance();
  4. userDao.add(1, 2);
  5. userDao.update("UUID1");
  6. // method=add, args=[1, 2], result=3
  7. // method=update, args=[UUID1], result=UUID1

4、AOP 术语

  • 连接点:类中可以被增强的方法,称为连接点

  • 切入点:类中实际被增强的方法,称为切入点

  • 通知(增强):实际增强的逻辑部分,称为通知
    通知分为五种类型:

    • 前置通知:方法执行之前的处理
    • 后置通知:方法执行之后的处理
    • 环绕通知:方法执行前后的处理
    • 异常通知:方法抛出异常的处理
    • 最终通知:方法执行最终的处理(相当于try-catch-finally中的finally
  • 切面:是一个动作,即把通知应用到切入点的过程

5、AOP 准备工作

5.1、AspectJ 介绍

Spring 一般都是基于AspectJ实现 AOP 操作的

  • AspectJ不是 Spring 的一部分,而是一个独立的 AOP 框架
  • 一般会把AspectJ和 Spring 搭配使用,进行 AOP 操作,因为这样更加方便

基于 AspectJ 进行 AOP 操作的两种方式:

  • 基于 XML 配置文件方式实现
  • 基于注解方式实现(推荐使用)

5.2、引入 AOP 相关依赖

02-AOP - 图4

  1. <dependency>
  2. <groupId>org.aspectj</groupId>
  3. <artifactId>aspectjrt</artifactId>
  4. <version>1.9.8</version>
  5. </dependency>
  6. <dependency>
  7. <groupId>org.aspectj</groupId>
  8. <artifactId>aspectjweaver</artifactId>
  9. <version>1.9.8</version>
  10. </dependency>

5.3、切入点表达式

切入点表达式的作用:知道对哪个类的哪个方法进行增强

语法结构:execution([权限修饰符][返回类型][类全路径][方法名]([参数列表]))

举例

02-AOP - 图5 举例1:对com.vectorx.dao.BookDao中的add()方法进行增强

  1. execution(* com.vectorx.dao.BookDao.add(..))

02-AOP - 图6 举例2:对com.vectorx.dao.BookDao中的所有方法进行增强

  1. execution(* com.vectorx.dao.BookDao.*(..))

02-AOP - 图7 举例3:对com.vectorx.dao包中所有类的所有方法进行增强

  1. execution(* com.vectorx.dao.*.*(..))

6、AspectJ 注解实现

6.1、Spring 配置文件

  • 1)引入contextaop名称空间
  • 2)配置组件扫描基础包
  • 3)开启AspectJ生成代理对象
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:context="http://www.springframework.org/schema/context"
  4. xmlns:aop="http://www.springframework.org/schema/aop"
  5. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  6. xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
  7. http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
  8. http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
  9. <!--组件扫描配置-->
  10. <context:component-scan base-package="com.vectorx.spring5.s13_aspectj_annatation"/>
  11. <!--开启AspectJ生成代理对象-->
  12. <aop:aspectj-autoproxy/>
  13. </beans>

6.2、创建被增强对象和增强对象

  • 1)创建 User 对象,并添加@Component注解
  • 2)创建 UserProxy 对象,并添加@Component注解
  1. @Component
  2. public class User {
  3. public void add() {
  4. System.out.println("add...");
  5. }
  6. }
  7. @Component
  8. public class UserProxy {
  9. /**
  10. * 前置通知
  11. */
  12. public void before() {
  13. System.out.println("before...");
  14. }
  15. /**
  16. * 后置通知
  17. */
  18. public void afterReturning() {
  19. System.out.println("afterReturning...");
  20. }
  21. /**
  22. * 最终通知
  23. */
  24. public void after() {
  25. System.out.println("after...");
  26. }
  27. /**
  28. * 异常通知
  29. */
  30. public void afterThrowing() {
  31. System.out.println("afterThrowing...");
  32. }
  33. /**
  34. * 环绕通知
  35. */
  36. public void around() {
  37. System.out.println("around...");
  38. }
  39. }

6.3、添加增强类注解和切入点表达式

  1. @Component
  2. @Aspect
  3. public class UserProxy {
  4. /**
  5. * 前置通知
  6. */
  7. @Before(value = "execution(* com.vectorx.spring5.s13_aspectj_annatation.User.add(..))")
  8. public void before() {
  9. System.out.println("before...");
  10. }
  11. /**
  12. * 后置通知
  13. */
  14. @AfterReturning(value = "execution(* com.vectorx.spring5.s13_aspectj_annatation.User.add(..))")
  15. public void afterReturning() {
  16. System.out.println("afterReturning...");
  17. }
  18. /**
  19. * 最终通知
  20. */
  21. @After(value = "execution(* com.vectorx.spring5.s13_aspectj_annatation.User.add(..))")
  22. public void after() {
  23. System.out.println("after...");
  24. }
  25. /**
  26. * 异常通知
  27. */
  28. @AfterThrowing(value = "execution(* com.vectorx.spring5.s13_aspectj_annatation.User.add(..))")
  29. public void afterThrowing() {
  30. System.out.println("afterThrowing...");
  31. }
  32. /**
  33. * 环绕通知
  34. */
  35. @Around(value = "execution(* com.vectorx.spring5.s13_aspectj_annatation.User.add(..))")
  36. public void around(ProceedingJoinPoint joinPoint) throws Throwable {
  37. System.out.println("around before...");
  38. // 执行被增强的方法
  39. joinPoint.proceed();
  40. System.out.println("around after...");
  41. }
  42. }

6.4、代码测试

  1. ApplicationContext context = new ClassPathXmlApplicationContext("bean11.xml");
  2. User user = context.getBean("user", User.class);
  3. user.add();

结果

  1. around before...
  2. before...
  3. add...
  4. afterReturning...
  5. after...
  6. around after...

为了演示异常通知,需要修改下被增强对象中的方法,模拟一个异常

  1. @Component
  2. public class User {
  3. public void add() {
  4. System.out.println("add...");
  5. // 模拟一个异常
  6. int i = 2 / 0;
  7. }
  8. }

运行结果

  1. around before...
  2. before...
  3. add...
  4. afterThrowing...
  5. after...

对比正常情况下,发现少了afterReturning即后置异常和around after即环绕增强的后置处理

6.5、抽取相同切入点表达式

通过上述的例子,应该对AspectJ注解实现有了一定的了解

同时我们发现切入点表达式都是完全一样的,可以对这些相同的切入点表达式进行抽取,以达到重用切入点表达式定义的目的

  • 1)首先想到的应该是定义成员变量
  1. private final String execution = "execution(* com.vectorx.spring5.s13_aspectj_annatation.User.add(..))";
  2. @Before(value = execution)
  3. public void before() {
  4. System.out.println("before...");
  5. }
  • 2)AspectJ中提供了Pointcut注解(推荐)
  1. @Pointcut(value = "execution(* com.vectorx.spring5.s13_aspectj_annatation.User.add(..))")
  2. private void pointcut(){}
  3. @Before(value = "pointcut()")
  4. public void before() {
  5. System.out.println("before...");
  6. }

6.6、设置增强类优先级

如果有多个增强类对类中同一个方法进行增强,可以设置增强类的优先级,来决定哪个增强类先执行,哪个增强类后执行

使用@Order注解设置增强类的优先级,其中指定优先级数字,注解格式:@Order(数字类型值)

  • 数字类型值越小,优先级越高
  • 数字类型值越大,优先级越低

02-AOP - 图8最佳实践

  1. @Component
  2. @Aspect
  3. @Order(1)
  4. public class PersonProxy {
  5. //...
  6. }
  7. @Component
  8. @Aspect
  9. @Order(3)
  10. public class UserProxy {
  11. //...
  12. }

测试结果

  1. person around before...
  2. person before...
  3. user around before...
  4. user before...
  5. add...
  6. user afterReturning...
  7. user after...
  8. user around after...
  9. person afterReturning...
  10. person after...
  11. person around after...

我们发现:

  • PersonProxy 中的前置通知先于 UserProxy 中的前置通知执行
  • PersonProxy 中的后置通知晚于 UserProxy 中的后置通知执行

6.7、完全注解开发

如果要用完全注解的方式进行开发,可以使用注解类代替 Spring 配置文件

  1. @Configuration
  2. @ComponentScan(value = "com.vectorx.spring5.s13_aspectj_annatation")
  3. @EnableAspectJAutoProxy(proxyTargetClass = true)
  4. public class AopConfig {
  5. }

其中:

  • 注解@ComponentScan(value = "com.vectorx.spring5.s13_aspectj_annatation")代替了<context:component-scan base-package="com.vectorx.spring5.s13_aspectj_annatation"/>进行组件扫描的配置
  • 注解@EnableAspectJAutoProxy(proxyTargetClass = true)代替了<aop:aspectj-autoproxy/>开启AspectJ生成代理对象

对应关系

注解方式 配置文件方式
@ComponentScan <context:component-scan>
@EnableAspectJAutoProxy <aop:aspectj-autoproxy>

7、AspectJ 配置文件实现

7.1、创建被增强对象和增强对象

  1. public class Book {
  2. public void buy() {
  3. System.out.println("buy...");
  4. }
  5. }
  6. public class BookProxy {
  7. public void before() {
  8. System.out.println("before...");
  9. }
  10. public void afterReturning() {
  11. System.out.println("afterReturning...");
  12. }
  13. public void after() {
  14. System.out.println("after...");
  15. }
  16. public void afterThrowing() {
  17. System.out.println("afterThrowing...");
  18. }
  19. public void around(ProceedingJoinPoint joinPoint) throws Throwable {
  20. System.out.println("around before...");
  21. joinPoint.proceed();
  22. System.out.println("around after...");
  23. }
  24. }

7.2、Spring 配置文件

  • 1)引入aop名称空间
  • 2)配置被增强对象和增强对象创建
  • 3)配置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" xmlns:aop="http://www.springframework.org/schema/aop"
  4. xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
  5. <!--创建对象-->
  6. <bean id="book" class="com.vectorx.spring5.s14_aspectj_xml.Book"></bean>
  7. <bean id="bookProxy" class="com.vectorx.spring5.s14_aspectj_xml.BookProxy"></bean>
  8. <!--配置aop增强-->
  9. <aop:config>
  10. <!--配置切入点-->
  11. <aop:pointcut id="p" expression="execution(* com.vectorx.spring5.s14_aspectj_xml.Book.buy(..))"/>
  12. <!--配置切面-->
  13. <aop:aspect ref="bookProxy">
  14. <!--前置通知-->
  15. <aop:before method="before" pointcut-ref="p"/>
  16. <!--后置通知-->
  17. <aop:after-returning method="afterReturning" pointcut-ref="p"/>
  18. <!--最终通知-->
  19. <aop:after method="after" pointcut-ref="p"/>
  20. <!--异常通知-->
  21. <aop:after-throwing method="afterThrowing" pointcut-ref="p"/>
  22. <!--环绕通知-->
  23. <aop:around method="around" pointcut-ref="p"/>
  24. </aop:aspect>
  25. </aop:config>
  26. </beans>

其中,配置文件的标签与注解的对应关系如下表

配置文件方式 注解方式
<aop:pointcut> @Pointcut
<aop:aspect> @Aspect
<aop:before> @Before
<aop:after-returning> @AfterReturning
<aop:after> @After
<aop:after-throwing> @AfterThrowing
<aop:around> @Around

7.3、代码测试

  1. ApplicationContext context = new ClassPathXmlApplicationContext("bean12.xml");
  2. Book book = context.getBean("book", Book.class);
  3. book.buy();

测试结果

  1. before...
  2. around before...
  3. buy...
  4. around after...
  5. after...
  6. afterReturning...

小结

本节重点

  1. AOP 概述
  2. AOP 底层原理
  3. AOP 术语
  4. 切入点表达式
  5. AspectJ 实现
  6. 完全注解开发

以下总结仅供参考

02-AOP - 图9