SpringBoot 系列教程 - 源码地址:https://github.com/laolunsi/spring-boot-examples


大家知道我现在还是一个 CRUD 崽,平时用 AOP 也是 CV 大法。最近痛定思痛,决定研究一下 Spring AOP 的原理。

这里写一篇文章总结一下。主要介绍 Java 中 AOP 的实现原理,最后以两个简单的示例来收尾。


一、AOP 的基本概念

1.1 什么是 AOP

Aspect Oriented Programming,面向切面编程。

就跟我们说 OOP 是面向对象一样,AOP 是面向切面的。切面是分散在应用中的一个标准代码或功能。切面通常与实际的业务逻辑不同(例如,事务管理)。每个切面专注于一个特定的环切功能。

这里的切面呢,可以理解为横切。比如在所有的 DAO 层方法上加上一个同样的切面,功能是记录日志;又或者在某个接口上应用一个切面,作用是检查权限。

AOP 是基于代理来实现的。而代理又分为静态代理和动态代理。两者的区别在于代理类于何时生成。

下面我们讲讲代理是怎么回事?


1.2 代理与 Spring AOP

代理分为静态代理和动态代理:

  • 静态代理:代理类在编译阶段生成,程序运行前就存在。包括:AspectJ 静态代理、JDK 静态代理

  • 动态代理:代理类在程序运行时创建。包括:JDK 动态代理、CGLib 动态代理

Spring AOP 原理:

  • JDK Proxy: interface based

  • CGLib Proxy: class based

Spring AOP 中默认使用 JDK 动态代理,通过反射获取被代理的类,这个类必须实现一个接口。如果目标类没有实现接口,就会默认使用 CGLIB Proxy 来动态生成代理目标类,后者是被代理类的子类。

可以通过获取代理对象并打印的方式来查看其类型(JDK Proxy 下是 com.sun.prxy, CGlib 下是子类.

AspectJ: 用特定的编译器和语法,在编译时增强,实现了静态代理技术。

1.3 Spring AOP 与 AspectJ 的区别

AspectJ 是一套完整的 AOP 解决方案,而 Spring AOP 并不是 —— 它只是在 Spring 框架下满足其使用要求的一个解决方法,比如 Spring AOP 仅支持对方法使用切面。


二、静态代理

2.1 AspectJ 静态代理

基于特殊的编译器和语法。这里不多介绍了。

IDEA 下编译 AspectJ 可以参考这篇:https://blog.csdn.net/gavin_john/article/details/80156963


2.2 JDK 静态代理

实际上是利用实现一个具体的代理类来调用业务类。代理类持有了一个业务类的引用。

更概况地说,JDK 静态代理体现的是一种设计模式。

缺点很明显,代码冗余,难以维护。

这里以 借书还书 这两个行为来作为一个示例:

编写一个 BookService 接口:

public interface BookService {boolean borrow(String id, String userName);boolean reBack(String id, String userName);
}

然后实现这个接口:

public class BookServiceImpl implements BookService {@Overridepublic boolean borrow(String id, String userName) {System.out.println(userName + " 借书:" + id);return true;}@Overridepublic boolean reBack(String id, String userName) {System.out.println(userName + " 还书:" + id);return true;}
}

下面我们来编写 BookService 的代理类:

public class BookProxy implements BookService {private BookServiceImpl bookService;public BookProxy(BookServiceImpl bookService) {this.bookService = bookService;}@Overridepublic boolean borrow(String id, String userName) {boolean res = false;if (check()) {res = bookService.borrow(id, userName);}addLog();return res;}@Overridepublic boolean reBack(String id, String userName) {boolean res = false;if (check()) {res = bookService.reBack(id, userName);}addLog();return res;}//private boolean check() {System.out.println("检查权限");return true;}private void addLog() {System.out.println("操作完成");}
}

编写一个测试类:

public class MainTest {public static void main(String[] args) {BookProxy proxy = new BookProxy(new BookServiceImpl());proxy.borrow("123", "eknown");proxy.reBack("234", "java");}
}

这里我们可以看到,JDK 静态代理就是说在原来的实现类上套一层 代理。它好像是体现了代理模式,但实际上并没有带来太多的好处。代码相当冗余,也不利于维护。

真正体现代理模式好处的还是动态代理,下面我们来看看动态代理的原理。


三、动态代理

动态代理是程序运行时,由 JVM 根据反射等机制动态生成代理类的。

也就是说,程序运行前,我们仅仅定义了代理的规则,而不知道代理类具体长什么样,这不像上面的静态代理里,我们完整地定义了代理对象。


3.1 JDK 动态代理

JDK 动态代理是基于接口的。

我们可以通过实现 InvocationHandler 接口来手动创建一个 JDK 代理类。

首先需要定义一个接口,让业务类和代理类都实现这个接口。

然后编写一个 InvocationHandler 接口的实现类:

public class BookProxy implements InvocationHandler {// 被该代理类处理的业务类private BookService bookService;public BookProxy(BookService bookService) {this.bookService = bookService;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {Object res = null;if (check()) {// 调用实际的 method,参数是 接口 + 参数res = method.invoke(bookService, args);}addLog();return res;}private boolean check() {System.out.println("检查权限");return true;}private void addLog() {System.out.println("操作完成");}
}

测试:

public class MainTest {public static void main(String[] args) {// 创建被代理的实际业务类BookServiceImpl bookServiceImpl = new BookServiceImpl();ClassLoader classLoader = bookServiceImpl.getClass().getClassLoader();// 获取所有的接口方法Class[] interfaces = bookServiceImpl.getClass().getInterfaces();// 构造 HandlerInvocationHandler invocationHandler = new BookProxy(bookServiceImpl);// 创建代理Object obj = Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);BookService bookService = (BookService) obj;bookService.borrow("abc", "eknown");bookService.reBack("c23", "py");}
}

3.2 CGLIB 动态代理

CGLIB 代理的原理是:让代理类继承业务类(也就自动拥有了业务类的所有非 final 的 public 方法)

我们这里手动编写一个 CGLIB 的代理试试看。

首先我们有一个 BookServiceImpl 业务类,这个业务类可以实现接口,也可以就是单纯的一个业务类。

然后我们定义一个 BookCglibProxy 类:

public class BookCglibProxy implements MethodInterceptor {@Overridepublic Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {check();// 调用实际的 methodObject obj = methodProxy.invokeSuper(o, objects);addLog();return obj;}private boolean check() {System.out.println("检查权限");return true;}private void addLog() {System.out.println("操作完成");}
}

测试类:

public class CglibTest {public static void main(String[] args) {BookServiceImpl bookServiceImpl = new BookServiceImpl();BookCglibProxy proxy = new BookCglibProxy();// cjlib 中的增强器,用于创建动态代理(被代理类的子类)Enhancer enhancer = new Enhancer();// 设置要被代理的类enhancer.setSuperclass(bookServiceImpl.getClass());// 设置回调enhancer.setCallback(proxy);// 强转成父类BookServiceImpl proxyResult = (BookServiceImpl) enhancer.create();proxyResult.borrow("12333", "ye");proxyResult.reBack("123", "fe");}
}

在第一节我们提到过 Spring AOP 是基于 JDK 动态代理和 CGLIB 动态代理的。下面我们来 Spring AOP 的一些基本案例。


四、Spring AOP 实例

AOP 中一些概念词汇,通过这些词汇,我们可以对 AOP 有更高一层的抽象。

  • Aspect - 切面,分散在应用中的一个标准代码或功能。切面通常与实际的业务逻辑不同(例如,事务管理)。每个切面专注于一个特定的环切功能。
  • Joinpoint - 连接点,是程序执行过程中的特定点,比如方法执行、构造器调用、字段赋值
  • Advice - 通知,切面在某个连接点采取的操作。Advice 有 5 种类型。
  • Pointcut - 切入点,一个匹配连接点的正则表达式。每当连接点匹配了一个切入点时,一个特定的通知就会被执行。
  • Weaving - 织入,指的是将切面和目标对象连接起来以创建代理对象的过程。

Spring AOP 有两种实现方式:基于 XML 或基于注解。更流行、更方便的是后者。(阿 sir,不会还有人用 XML 来做 Bean 的配置文件吧?)


4.1 基于 XML 的实例

首先定义一下接口和实现类(没有注解的!)。再编写一个代理类:

这里的代理类方法以 JoinPoint 为参数即可:

public class BookAspect {public void checkUser(JoinPoint point) {System.out.println("-----before-----");Object[] args = point.getArgs();for (Object arg : args) {System.out.println(arg);}System.out.println("检查用户权限...");}public void saveLog(JoinPoint point) {System.out.println("-----after-----");Object[] args = point.getArgs();for (Object arg : args) {System.out.println(arg);}System.out.println("请求完毕,记录日志...");}
}

然后编写 Spring 的配置文件:

<?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/beanshttp://www.springframework.org/schema/beans/spring-beans-4.2.xsdhttp://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop-4.2.xsd"><!-- 定义 bean --><bean id="bookService" class="com.example.springaopdemo.basicxml.BookServiceImpl" /><bean id="bookAspect" class="com.example.springaopdemo.basicxml.BookAspect" /><aop:config><!-- 这是定义一个切面,切面是切点和通知的集合--><aop:aspect id="do" ref="bookAspect"><!-- 定义切点 ,后面是 expression 语言,表示包括该接口中定义的所有方法都会被执行 --><aop:pointcut id="point" expression="execution(* com.example.springaopdemo.basicxml.BookService.*(..))" /><!-- 定义通知 --><aop:before method="checkUser" pointcut-ref="point" /><aop:after method="saveLog" pointcut-ref="point" /></aop:aspect></aop:config>
</beans>

运行测试:

public class AopXMLTest {public static void main(String[] args) {ApplicationContext context = new ClassPathXmlApplicationContext("SpringAop.xml");BookService bookService = context.getBean("bookService", BookService.class);bookService.borrow("123", "eknown");bookService.reback("123", "eknown");}
}

基于 XML 配置的 Spring 现在已经很少使用了。下面我们来看看如何基于注解使用 Spring AOP


4.2 基于注解的实例

这里以一个使用 SpringBoot 框架的 Web 项目作为简单的实例。

首先创建一个 SpringBoot 项目,写好 Controller、Service、DAO 层的基本类。(示例源码中没有使用 Mybatis 等持久层框架,而是用 Map 来模拟数据的存取)

下面我们针对 UserService 接口类,添加切面。

@Aspect
@Component
public class UserAspect {@Before(value = "execution(* com.example.springaopdemo.boot.UserService.*(..))")public void checkUser(JoinPoint point) {System.out.println("-----before-----");Object[] args = point.getArgs();for (Object arg : args) {System.out.println(arg);}System.out.println("检查..." + point);}@After(value = "execution(* com.example.springaopdemo.boot.UserService.*(..))")public void saveLog(JoinPoint point) {System.out.println("-----after-----");Object[] args = point.getArgs();for (Object arg : args) {System.out.println(arg);}// 这里可以使用 point.getTarget() 获取到切面对应的 bean//Object target = point.getTarget();//UserService userService = (UserService) target;//List<User> userList = userService.findAll();System.out.println("请求完毕,记录日志..." + point);}@Around(value = "execution(* com.example.springaopdemo.boot.UserService.save(..))")public Object saveAround(ProceedingJoinPoint point) {System.out.println("around-before");Object obj = null;try {obj = point.proceed();} catch (Throwable throwable) {throwable.printStackTrace();}System.out.println("around-after");return obj;}}

示例中使用了 @Before/@After/@Aroud 三个注解,value 中使用切点表达式,分别匹配了 UserService 接口的所有方法和单个 save 方法。

我们还可以通过切点表达式匹配自定义的注解,比如实现一个 UserMonitor 注解,然后定义其切点方法:

public @interface UserMonitor {String value() default "";int roleLimit() default 0;}

切点:

@Around("@annotation(com.example.springaopdemo.boot.UserMonitor)")public Object userRolePointCut(ProceedingJoinPoint point) {System.out.println("检查用户权限...");// 获取参数Object[] args = point.getArgs();Class<?>[] argTypes = new Class[point.getArgs().length];for (int i = 0; i < args.length; i++) {argTypes[i] = args[i].getClass();}// 获取方法Method method = null;try {method = point.getTarget().getClass().getMethod(point.getSignature().getName(), argTypes);} catch (NoSuchMethodException | SecurityException e) {e.printStackTrace();}// 获取方法上的该注解,之后可以根据注解中的值进行一些操作,比如判定是否具有权限UserMonitor monitor = method.getAnnotation(UserMonitor.class);System.out.println(monitor);Object obj = null;try {obj = point.proceed();} catch (Throwable throwable) {throwable.printStackTrace();}return obj;}

参考资料:

  • 什么是面向切面编程AOP?https://www.zhihu.com/question/24863332

  • 一起来谈谈 Spring AOP!: https://juejin.im/post/5aa7818af265da23844040c6

    一个简单的 AOP 示例,从 XML 到注解。

  • Spring AOP的实现原理: https://juejin.im/post/5b67b9a86fb9a04fc71afeb4

    也是一篇简单的使用示例文章。

  • 从代理机制到Spring AOP: https://juejin.im/post/5b90e648f265da0aea695672

    这篇讲得非常好,深刻讲述了各种代理的原理和示例。强烈推荐看看这一篇!

  • 比较 Spring AOP 和AspectJ:https://www.baeldung.com/spring-aop-vs-aspectj

    一篇有关 Spring AOP 和 AspectJ 的英文资料

  • 三歪 - Spring【AOP模块】就是这么简单: [https://juejin.im/post/5aa8edf06fb9a028d0432584](

Spring AOP 与代理详解相关推荐

  1. 跟着小马哥学系列之 Spring AOP(Advisor 详解)

    学好路更宽,钱多少加班. --小马哥 简介 大家好,我是小马哥成千上万粉丝中的一员!2019年8月有幸在叩丁狼教育举办的猿圈活动中知道有这么一位大咖,从此结下了不解之缘!此系列在多次学习极客时间< ...

  2. Spring AOP实现原理详解之Cglib代理实现

    引入 我们在前文中已经介绍了SpringAOP的切面实现和创建动态代理的过程,那么动态代理是如何工作的呢?本文主要介绍Cglib动态代理的案例和SpringAOP实现的原理. 要了解动态代理是如何工作 ...

  3. Spring AOP切点表达式详解

    1. 简介 面向对象编程,也称为OOP(即Object Oriented Programming)最大的优点在于能够将业务模块进行封装,从而达到功能复用的目的.通过面向对象编程,不同的模板可以相互组装 ...

  4. Spring AOP 功能使用详解

    前言 AOP 既熟悉又陌生,了解过 Spring 人的都知道 AOP 的概念,即面向切面编程,可以用来管理一些和主业务无关的周边业务,如日志记录,事务管理等:陌生是因为在工作中基本没有使用过,AOP ...

  5. 跟着小马哥学系列之 Spring AOP(AbstractAutoProxyCreator 详解)

    学成路更宽,吊打面试官. --小马哥 版本修订 2021.5.19:去除目录 简介 大家好,我是小马哥成千上万粉丝中的一员!2019年8月有幸在叩丁狼教育举办的猿圈活动中知道有这么一位大咖,从此结下了 ...

  6. Spring之AOP动态代理详解

    动态代理 动态代理和静态代理角色一样 动态代理类是动态生成的,不是我们直接写好的. 动态代理分为两大类:基于接口的动态代理:JDK动态代理[我们在这里使用]基于类的动态代理:cglibjava字节码: ...

  7. Spring AOP 的proxy详解

    spring 提供了多种不同的方案实现对 bean 的 aop proxy, 包括 ProxyFactoryBean, 便利的 TransactionProxyFactoryBean 以及 AutoP ...

  8. Spring AOP之PointCut详解

    一.PointCut接口 /** Copyright 2002-2012 the original author or authors.** Licensed under the Apache Lic ...

  9. Spring之Joinpoint类详解

    说明 Joinpoint是AOP的连接点.一个连接点代表一个被代理的方法.我们从源码角度看连接点有哪些属性和功能. 源码 /** Copyright 2002-2016 the original au ...

最新文章

  1. Javascript_初学第1天
  2. a[0]和a 的区别?
  3. .NET Framework 4.0源代码
  4. java飞鸽传书_feige 飞鸽传书源代码java 实现不错的联系网络编程的资料飞鸽传书的GUI(java实现) - 下载 - 搜珍网...
  5. 并发下,使用redis防止数据重复插入(数据库未对表字段设置唯一情况下)
  6. Tomcat学习总结(19)—— 为什么首选Tomcat作为JavaWeb应用服务器?
  7. 转义字符html识别吗,HTML_谈谈html转义字符如何通过代码识别,偶尔会在数据中看到诸如#3 - phpStudy...
  8. coco 语义分割_YOLACT++:目前最热门的实时实例分割开源库
  9. 【iCore1S 双核心板_ARM】例程五:IWDG看门狗实验——复位ARM
  10. Linux进程地址空间探究
  11. 设计原则(6):开闭原则
  12. 一家麻辣烫店如何实现月净利五万
  13. Crosses and Crosses
  14. 牛客网刷题day21
  15. c语言怎么判断一个目录下面的文件是否为隐藏文件,怎么能显示出隐藏文件?...
  16. WPS Office 2019 v11.8.2专业增强版+教程
  17. python微信语音转发_python实现微信小程序自动回复
  18. PHP读取Json文件
  19. 【路径规划】基于遗传算法实现外卖订单动态变换模型求解附matlab代码
  20. 盛迈坤电商:新店铺的运营技巧分析

热门文章

  1. 喷涂机器人主要组成部分
  2. pyaudio usb playback_苹果还会为 iPhone 换上 USB-C 吗?_详细解读_最新资讯_热点事件...
  3. python not用法_python中not、and和or的优先级与详细用法介绍
  4. 【Prompting】ChatGPT Prompt Engineering开发指南(1)
  5. 《前端从傻逼到牛逼》序章
  6. 上位机和MYSQL连接_上位机下位机串口通信设计详解
  7. openlayers6【十九】vue HeatmapLayer热力图层实现热力图效果详解
  8. 音频编解码·实战篇(1)PCM转至AAC(AAC编码)
  9. 教你怎么玩indexedDB浏览器数据库,执行流程以及常见错误等等
  10. MATLAB如何得到回文数(用fliplr函数)