1.背景

2.AOP的概念

AOP:用鸟语解释就是 面向切面编程,详细的解释大家可以看百度百科,
百度百科:https://baike.baidu.com/item/AOP/1332219?fr=aladdin
不过估计看了后还是一头雾水…
通俗的理解是:假设有方法M1、M2、M3….,现在需要在这些方法中增加新的业务逻辑(如:打印方法名称、方法参数、执行结果),但是不把这些新的业务逻辑写到这些方法(M1、M2、M3….)中,这就是AOP的思想;
简单一句话说就是:在方法中增加新的业务逻辑,但是不修改方法中的代码。

3.AOP的底层原理

实现方式一:有接口的情况下,使用JDK动态代理
实现方式二:没有接口情况,使用 CGLIB 动态代理

3.1.JDK动态代理实现AOP思想

需求:假设有方法M1、M2、M3….,现在需要在这些方法中增加新的业务逻辑(如:打印方法名称、方法参数、执行结果),但是不把这些新的业务逻辑写到这些方法(M1、M2、M3….)中,这就是AOP的思想;

步骤一:准备一个dao接口、dao实现、测试

dao接口

  1. package com.ldp.dao;
  2. /**
  3. * @Copyright (C) XXXXXXXXXXX科技股份技有限公司
  4. * @Author: lidongping
  5. * @Date: 2021-01-12 10:44
  6. * @Description:
  7. */
  8. public interface IProductDao {
  9. /**
  10. * 保存产品
  11. *
  12. * @param name
  13. * @param price
  14. * @return
  15. */
  16. int saveProduct(String name, Integer price);
  17. /**
  18. * 根据id删除产品
  19. *
  20. * @param id
  21. * @return
  22. */
  23. int deleteProduct(Integer id);
  24. /**
  25. * 根据id修改产品
  26. *
  27. * @param id
  28. * @param name
  29. * @param price
  30. * @return
  31. */
  32. int updateProduct(Integer id, String name, Integer price);
  33. /**
  34. * 根据id查询产品
  35. *
  36. * @return
  37. */
  38. String queryProduct(Integer id);
  39. }

dao实现

  1. package com.ldp.dao.impl;
  2. import com.ldp.dao.IProductDao;
  3. /**
  4. * @Copyright (C) XXXXXXXXXXX科技股份技有限公司
  5. * @Author: lidongping
  6. * @Date: 2021-01-12 10:51
  7. * @Description:
  8. */
  9. public class ProductDaoImpl implements IProductDao {
  10. @Override
  11. public int saveProduct(String name, Integer price) {
  12. System.out.println("模拟 saveProduct---------------");
  13. return 1;
  14. }
  15. @Override
  16. public int deleteProduct(Integer id) {
  17. System.out.println("模拟 deleteProduct---------------");
  18. return 1;
  19. }
  20. @Override
  21. public int updateProduct(Integer id, String name, Integer price) {
  22. System.out.println("模拟 updateProduct---------------");
  23. return 1;
  24. }
  25. @Override
  26. public String queryProduct(Integer id) {
  27. System.out.println("模拟 queryProduct---------------");
  28. return "模拟返回产品数据";
  29. }
  30. }

dao测试

  1. /**
  2. * 没有代理的测试
  3. */
  4. @Test
  5. public void test01() {
  6. // 创建到dao实例对象
  7. IProductDao productDao = new ProductDaoImpl();
  8. // 测试增加
  9. int saveProduct = productDao.saveProduct("苹果", 3);
  10. System.out.println(saveProduct);
  11. // 测试删除
  12. int deleteProduct = productDao.deleteProduct(12);
  13. System.out.println(deleteProduct);
  14. // 测试修改
  15. int updateProduct = productDao.updateProduct(11, "修改苹果", 4);
  16. System.out.println(updateProduct);
  17. // 测试查询
  18. String queryProduct = productDao.queryProduct(11);
  19. System.out.println(queryProduct);
  20. }

步骤二:编写一个InvocationHandler的实例对象,编写需要增强的具体功能

  1. package com.ldp.proxy;
  2. import java.lang.reflect.InvocationHandler;
  3. import java.lang.reflect.Method;
  4. import java.util.Arrays;
  5. /**
  6. * @Copyright (C) XXXXXXXXXXX科技股份技有限公司
  7. * @Author: lidongping
  8. * @Date: 2021-01-12 11:01
  9. * @Description:
  10. */
  11. public class MyDaoProxy implements InvocationHandler {
  12. private Object object;
  13. /**
  14. * 定义一个有参数的构造方法
  15. */
  16. public MyDaoProxy(Object object) {
  17. this.object = object;
  18. }
  19. /**
  20. * 这里使用了反射的机制
  21. *
  22. * @param proxy
  23. * @param method
  24. * @param args
  25. * @return
  26. * @throws Throwable
  27. */
  28. @Override
  29. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  30. // 方法执行前
  31. System.out.println("方法执行前执行.....请求方法名:" + method.getName() + ",请求参数:" + Arrays.toString(args));
  32. // 执行固有的方法(被增强的方法)
  33. Object result = method.invoke(object, args);
  34. // 方法执行后
  35. System.out.println("方法执行后.....方法执行结果:" + result);
  36. return result;
  37. }
  38. }

步骤三:使用代理对象生成dao实现

  1. // 使用动态代理 创建到dao实例对象 TestProxy.class.getClassLoader()中的TestProxy为当前测试类对象
  2. // IProductDao productDao = new ProductDaoImpl();
  3. Class[] interfaces = {IProductDao.class};
  4. IProductDao productDao = (IProductDao) Proxy.newProxyInstance(TestProxy.class.getClassLoader(), interfaces, new MyDaoProxy(new ProductDaoImpl()));

步骤四:测试代理对象生成的dao

  1. /**
  2. * 代理对象的测试
  3. * newProxyInstance(ClassLoader loader, 类<?>[] interfaces, InvocationHandler h)
  4. * 返回指定接口的代理类的实例,该接口将方法调用分派给指定的调用处理程序。
  5. */
  6. @Test
  7. public void test02() {
  8. // 使用动态代理 创建到dao实例对象 TestProxy.class.getClassLoader()中的TestProxy为当前测试类对象
  9. // IProductDao productDao = new ProductDaoImpl();
  10. Class[] interfaces = {IProductDao.class};
  11. IProductDao productDao = (IProductDao) Proxy.newProxyInstance(TestProxy.class.getClassLoader(), interfaces, new MyDaoProxy(new ProductDaoImpl()));
  12. // 测增加
  13. int saveProduct = productDao.saveProduct("苹果", 3);
  14. System.out.println(saveProduct);
  15. // 测试删除
  16. int deleteProduct = productDao.deleteProduct(12);
  17. System.out.println(deleteProduct);
  18. // 测试修改
  19. int updateProduct = productDao.updateProduct(11, "修改苹果", 4);
  20. System.out.println(updateProduct);
  21. // 测试查询
  22. String queryProduct = productDao.queryProduct(11);
  23. System.out.println(queryProduct);
  24. }

参考资料
jdk8Api:https://www.matools.com/api/java8
Spring之AOP详解 - 图1

3.2.cglib动态代理实现aop思想

步骤一:引入jar包

Spring之AOP详解 - 图2

步骤二:编写一个普通的需要被增强的方法

  1. package com.ldp.controller;
  2. /**
  3. * @Copyright (C) XXXXXXXXXXX科技股份技有限公司
  4. * @Author: lidongping
  5. * @Date: 2021-01-12 12:07
  6. * @Description:
  7. */
  8. public class ProductController {
  9. /**
  10. * 产品查询
  11. *
  12. * @param name
  13. * @param price
  14. * @return
  15. */
  16. public String queryProduct(String name, Integer price) {
  17. System.out.println("模拟查询中............");
  18. return "香蕉";
  19. }
  20. }

步骤三:编写一个cglib动态代理类

  1. package com.ldp.proxy;
  2. import net.sf.cglib.proxy.Enhancer;
  3. import net.sf.cglib.proxy.MethodInterceptor;
  4. import net.sf.cglib.proxy.MethodProxy;
  5. import java.lang.reflect.Method;
  6. import java.util.Arrays;
  7. /**
  8. * @Copyright (C) XXXXXXXXXXX科技股份技有限公司
  9. * @Author: lidongping
  10. * @Date: 2021-01-12 11:50
  11. * @Description:
  12. */
  13. public class MyDaoCglibProxy implements MethodInterceptor {
  14. private Object target;
  15. public Object getInstance(Object target) {
  16. this.target = target;
  17. Enhancer enhancer = new Enhancer();
  18. enhancer.setSuperclass(this.target.getClass());
  19. // 设置回调方法
  20. enhancer.setCallback(this);
  21. // 创建代理对象
  22. return enhancer.create();
  23. }
  24. /**
  25. * 实现MethodInterceptor接口中重写的方法
  26. * <p>
  27. * 回调方法
  28. */
  29. @Override
  30. public Object intercept(Object object, Method method, Object[] args, MethodProxy proxy) throws Throwable {
  31. // 方法执行前
  32. System.out.println("方法执行前执行.....请求方法名:" + method.getName() + ",请求参数:" + Arrays.toString(args));
  33. // 执行固有的方法(被增强的方法)
  34. Object result = proxy.invokeSuper(object, args);
  35. // 方法执行后
  36. System.out.println("方法执行后.....方法执行结果:" + result);
  37. return result;
  38. }
  39. }

步骤四:测试

  1. /**
  2. * CGLIB 代理对象的测试
  3. */
  4. @Test
  5. public void test03() {
  6. // 创建动态代理
  7. MyDaoCglibProxy cglibProxy = new MyDaoCglibProxy();
  8. // 获取代理的实例对象
  9. ProductController productController = (ProductController) cglibProxy.getInstance(new ProductController());
  10. System.out.println(productController.queryProduct("香蕉", 100));
  11. }

测试结果如下:

  1. 方法执行前执行.....请求方法名:queryProduct,请求参数:[香蕉, 100]
  2. 模拟查询中............
  3. 方法执行后.....方法执行结果:香蕉
  4. 香蕉

4.AOP的几个专业术语

AOP的基本概念
1、连接点:可以被增强的方法
2、切入点:实际被增强的方法
3、通知(增强):
3.1.实际增强的逻辑部分叫做通知
3.2.通知类型包括

  1. 前置通知(执行方法前执行,通常用作参数日志输出、权限校验等)
  2. 后置通知(逻辑代码执行完,准备执行return的代码时通知,通常用作执行结果日志输出、结果加密等)
  3. 环绕通知(是前置通知和后置通知的综合,方法执行前和方法执行后都要执行,通常用作方法性能统计、接口耗时、统一加密、解密等)
  4. 异常通知(相当于try{}catch ()中catch执行的部分,程序抛出异常时执行,通常用作告警处理、事务回滚等)
  5. 最终通知(相当于try{}catch (Exception e){}finally { }中的finally执行的部分,通常用在关闭资源、清理缓存等业务逻辑中)

4、切面:把通知(增强)应用到切入点的过程

5.AOP实现技术

1、Spring 框架一般都是基于 AspectJ 实现 AOP 操作
(1)AspectJ 不是 Spring 组成部分,独立 AOP 框架,一般把 AspectJ 和 Spirng 框架一起使 用,进行 AOP 操作
2、基于 AspectJ 实现 AOP 操作
(1)基于 xml 配置文件实现
(2)基于注解方式实现(使用)
3、在项目工程里面引入 AOP 相关依赖
Spring之AOP详解 - 图3
4、切入点表达式
(1)切入点表达式作用:知道对哪个类里面的哪个方法进行增强
(2)语法结构: execution([权限修饰符] [返回类型] [类全路径] 方法名称 )
说明: 号表示通配符,.. 符号表示一个或多个参数
举例 1:对 com.ldp.controller.ProductController 类里面的 queryProduct 进行增强 execution(
com.ldp.controller.ProductController.queryProduct (..))
举例 2:对 com.ldp.controller.ProductController 类里面的所有的方法进行增强 execution( com.ldp.controller.ProductController . (..))
举例 3:对 com.ldp.controller 包里面所有以controller结尾的类里面所有方法进行增强 execution( com.ldp.controller.Controller .* (..))

6.AOP案例

6.1.AOP 操作(AspectJ 注解)

这里使用spring中的aop实现对controller里日志输出来加深对aop的理解
实现步骤:
准备工作:

步骤一:创建一个controller类并定义方法

  1. public class UserController {
  2. /**
  3. * 查询用户
  4. */
  5. public Object queryUser(String name, Integer age) {
  6. System.out.println("数据查询中模拟...........");
  7. return "张无忌,18岁";
  8. }
  9. }

步骤二:创建一个增强类,编写增强逻辑,也就是说建立一个切面

  1. public class LogAop {
  2. public void method01() {
  3. System.out.println("method01-前置通知-增强逻辑-------------");
  4. }
  5. public void method02() {
  6. System.out.println("method02-后置通知-增强逻辑-------------");
  7. }
  8. public void method03() {
  9. System.out.println("method03-环绕通知-增强逻辑-------------");
  10. }
  11. public void method04() {
  12. System.out.println("method04-异常通知-增强逻辑-------------");
  13. }
  14. public void method05() {
  15. System.out.println("method05-最终通知-增强逻辑-------------");
  16. }
  17. }

AOP配置
步骤一:在 spring 配置文件中,开启注解扫描

  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:context="http://www.springframework.org/schema/context"
  5. xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
  6. http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
  7. <!-- 开启注解扫描 -->
  8. <context:component-scan base-package="com.ldp.aop"></context:component-scan>
  9. </beans>

步骤二:增强类上加上注解,以及在方法加不同类型的通知
类上加注解
Spring之AOP详解 - 图4
方法上加不同类型的通知

  1. package com.ldp.aop;
  2. import org.aspectj.lang.annotation.*;
  3. import org.springframework.stereotype.Component;
  4. /**
  5. * @Copyright (C) XXXXXXXXXXX科技股份技有限公司
  6. * @Author: lidongping
  7. * @Date: 2021-01-14 9:52
  8. * @Description: <p>
  9. * 前置通知(执行方法前执行,通常用作参数日志输出、权限校验等)
  10. * 后置通知(逻辑代码执行完,准备执行return的代码时通知,通常用作执行结果日志输出、结果加密等)
  11. * 环绕通知(是前置通知和后置通知的综合,方法执行前和方法执行后都要执行,通常用作方法性能统计、接口耗时、统一加密、解密等)
  12. * 异常通知(相当于try{}catch ()中catch执行的部分,程序抛出异常时执行,通常用作告警处理、事务回滚等)
  13. * 最终通知(相当于try{}catch (Exception e){}finally { }中的finally执行的部分,通常用在关闭资源、清理缓存等业务逻辑中)
  14. * </p>
  15. * 语法结构: execution([权限修饰符] [返回类型] [类全路径] [方法名称]([参数列表]) )
  16. */
  17. @Component
  18. @Aspect
  19. public class LogAop {
  20. /**
  21. * 前置通知
  22. */
  23. @Before(value = "execution(* com.ldp.controller.*Controller.*(..))")
  24. public void method01() {
  25. System.out.println("method01-前置通知-增强逻辑-------------");
  26. }
  27. /**
  28. * 后置通知
  29. */
  30. @AfterReturning(value = "execution(* com.ldp.controller.*Controller.*(..))")
  31. public void method02() {
  32. System.out.println("method02-后置通知-增强逻辑-------------");
  33. }
  34. /**
  35. * 环绕通知
  36. */
  37. @Around(value = "execution(* com.ldp.controller.*Controller.*(..))")
  38. public void method03() {
  39. System.out.println("method03-环绕通知-增强逻辑-------------");
  40. }
  41. /**
  42. * 异常通知
  43. */
  44. @AfterThrowing(value = "execution(* com.ldp.controller.*Controller.*(..))")
  45. public void method04() {
  46. System.out.println("method04-异常通知-增强逻辑-------------");
  47. }
  48. /**
  49. * 最终通知
  50. */
  51. @After(value = "execution(* com.ldp.controller.*Controller.*(..))")
  52. public void method05() {
  53. System.out.println("method05-最终通知-增强逻辑-------------");
  54. }
  55. }

步骤三:controller类上加上注解

Spring之AOP详解 - 图5

步骤四:测试

  1. package com.ldp.test;
  2. import com.ldp.controller.UserController;
  3. import org.junit.Test;
  4. import org.junit.runner.RunWith;
  5. import org.springframework.beans.factory.annotation.Autowired;
  6. import org.springframework.test.context.ContextConfiguration;
  7. import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
  8. /**
  9. * @Copyright (C) XXXXXXXXXXX科技股份技有限公司
  10. * @Author: lidongping
  11. * @Date: 2021-01-14 10:19
  12. * @Description:
  13. */
  14. @RunWith(SpringJUnit4ClassRunner.class)
  15. @ContextConfiguration("classpath:bean01.xml")
  16. public class TestAop {
  17. @Autowired
  18. private UserController userController;
  19. /**
  20. * 测试
  21. */
  22. @Test
  23. public void test01() {
  24. Object queryUser = userController.queryUser("张无忌", 18);
  25. System.out.println("queryUser=" + queryUser);
  26. }
  27. }

注意:当前测试需要关闭环绕通知
当没有异常的时候,执行结果

  1. method01- @Before-前置通知-增强逻辑-------------
  2. 数据查询中模拟...........
  3. method02- @AfterReturning-后置通知-增强逻辑-------------
  4. method05- @After-最终通知-增强逻辑-------------
  5. queryUser=张无忌,18

当有异常的时候

  1. method01- @Before-前置通知-增强逻辑-------------
  2. 数据查询中模拟...........
  3. method04- @AfterThrowing-异常通知-增强逻辑-------------
  4. method05- @After-最终通知-增强逻辑-------------

6.2.XML实现AOP配置(现在很少使用,了解)

步骤一:创建一个普通的增强类和增强方法

  1. public class LogAop03 {
  2. public void method01() {
  3. System.out.println("-----xml的方式-----------");
  4. }
  5. }

步骤二:xml中配置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:context="http://www.springframework.org/schema/context"
  5. xmlns:aop="http://www.springframework.org/schema/aop"
  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 https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
  8. <!-- 开启注解扫描 -->
  9. <context:component-scan base-package="com.ldp.*"></context:component-scan>
  10. <!-- 开启 Aspect 生成代理对象-->
  11. <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
  12. <bean id="logAop03" class="com.ldp.aop.LogAop03"></bean>
  13. <!--配置 aop 增强-->
  14. <aop:config>
  15. <!--切入点-->
  16. <aop:pointcut id="p" expression="execution(* com.ldp.controller.*Controller.*(..))"/>
  17. <!--配置切面-->
  18. <aop:aspect ref="logAop03">
  19. <!--增强作用在具体的方法上-->
  20. <aop:before method="method01" pointcut-ref="p"/>
  21. </aop:aspect>
  22. </aop:config>
  23. </beans>

步骤三:测试
测试与之前的一样,略。

7.AOP优化与扩展

1.抽取相同的切入点

Spring之AOP详解 - 图6

2.同一个通知有多个切入点

Spring之AOP详解 - 图7

3.同一个方法有多个通知

使用@Order(数字) 排序
Spring之AOP详解 - 图8

4.使用全注解开发

删除之前的配置文件,添加一个Aop的配置对象

  1. @Configuration
  2. @ComponentScan(basePackages = {"com.ldp"})
  3. @EnableAspectJAutoProxy(proxyTargetClass = true)
  4. public class AopConfig {
  5. }

测试
Spring之AOP详解 - 图9

8.ProceedingJoinPoint 与 JoinPoint 的使用

二者的关系
Spring之AOP详解 - 图10
作用:
1、用来获取被增强方法的参数、方法名、权限命名、注解等,实际上能获取到这些在结合反射可以实现很强大的功能;
2、在环绕通知时让代理执行方法, proceedingJoinPoint.proceed();
上面的日志实际实现案例
案例一:不使用环绕通知的情况

  1. package com.ldp.aop;
  2. import org.aspectj.lang.JoinPoint;
  3. import org.aspectj.lang.Signature;
  4. import org.aspectj.lang.annotation.*;
  5. import org.springframework.stereotype.Component;
  6. import java.util.Arrays;
  7. import java.util.List;
  8. /**
  9. * @Copyright (C) XXXXXXXXXXX科技股份技有限公司
  10. * @Author: lidongping
  11. * @Date: 2021-01-14 9:52
  12. * @Description: <p>
  13. */
  14. @Component
  15. @Aspect
  16. public class LogAop04 {
  17. /**
  18. * 抽取相同的切入点
  19. */
  20. @Pointcut(value = "execution(* com.ldp.controller.*Controller.*(..))")
  21. public void methodPointcut01() {
  22. }
  23. /**
  24. * 前置通知
  25. */
  26. @Before(value = "methodPointcut01()")
  27. public void method01(JoinPoint joinPoint) {
  28. // 获取请求参数
  29. Object[] args = joinPoint.getArgs();
  30. Signature pointSignature = joinPoint.getSignature();
  31. // 获取包+类名
  32. String declaringTypeName = pointSignature.getDeclaringTypeName();
  33. // 获取方法名
  34. String name = pointSignature.getName();
  35. System.out.println("参数:" + Arrays.toString(args));
  36. System.out.println("类权限命名:" + declaringTypeName);
  37. System.out.println("方法名:" + name);
  38. }
  39. /**
  40. * 后置通知
  41. */
  42. @AfterReturning(returning = "result", value = "methodPointcut01()")
  43. public void method02(Object result) {
  44. System.out.println("方法执行结果:" + result);
  45. }
  46. /**
  47. * 异常通知
  48. */
  49. @AfterThrowing(throwing = "ex", value = "methodPointcut01()")
  50. public void method04(JoinPoint joinPoint, Exception ex) {
  51. String methodName = joinPoint.getSignature().getName();
  52. List<Object> args = Arrays.asList(joinPoint.getArgs());
  53. System.out.println("连接点方法为:" + methodName + ",参数为:" + args + ",异常为:" + ex.getMessage());
  54. }
  55. /**
  56. * 最终通知
  57. */
  58. @After(value = "methodPointcut01()")
  59. public void method05() {
  60. System.out.println("method05- @After-最终通知-增强逻辑-------------");
  61. }
  62. }

案例二:使用环绕通知的情况

  1. package com.ldp.aop;
  2. import org.aspectj.lang.ProceedingJoinPoint;
  3. import org.aspectj.lang.annotation.Around;
  4. import org.aspectj.lang.annotation.Aspect;
  5. import org.springframework.stereotype.Component;
  6. import java.util.Arrays;
  7. /**
  8. * @Copyright (C) XXXXXXXXXXX科技股份技有限公司
  9. * @Author: lidongping
  10. * @Date: 2021-01-14 9:52
  11. * @Description: <p>
  12. */
  13. @Component
  14. @Aspect
  15. public class LogAop04Around {
  16. /**
  17. * 环绕通知
  18. */
  19. @Around(value = "execution(* com.ldp.controller.*Controller.*(..))")
  20. public Object method03(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
  21. // 获取开始时间
  22. long start = System.currentTimeMillis();
  23. System.out.println("方法参数:" + Arrays.toString(proceedingJoinPoint.getArgs()));
  24. //让代理方法执行
  25. Object result = proceedingJoinPoint.proceed();
  26. // 获取结束执行时间
  27. long end = System.currentTimeMillis();
  28. System.out.println("方法返回:" + result);
  29. System.out.println("方法执行时间:" + (end - start) + " millisecond");
  30. return result;
  31. }
  32. }

测试与上面一样略!

完美!