• Spring学习笔记12 面向切面编程AOP


    Spring学习笔记11 GoF代理模式_biubiubiu0706的博客-CSDN博客

    AOP(Aspect Oriented Programming):面向切面编程,面向方面编程.

    AOP是对OOP的补充延申.底层使用动态代理实现.

    Spring的AOP使用的动态代理是:JDK动态代理_CGLIB动态代理技术.Spring在这两种动态代理中灵活切换.如果是代理接口,会默认使用JDK动态代理,如果要代理某个类,这个类没有实现接口,就会切换使用CGLIB.当然,也可以强制通过一些配置让Spring只使用CGLIB

    日志,事务管理,安全这些交叉业务(非业务代码)都属于AOP

    AOP是一种思想,JDK代理,CGLIB代理都是AOP思想的实现,Spring AOP底层用的就是JDK代理和CGLIB代理

    面向切面编程的七大术语:

    连接点  Joinpoint:在程序的整个执行流程中,可以织入切面的位置.方法的执行前,异常抛出之后等位置.----->我的理解连接点就是用户需要被扩展的方法,其实我们将自定义注解放到目标方法上做标识,那么该注解其实就是一个连接点 连接点描述的是位置

    切入点 Pointcut:程序执行流程中,真正织入切面的方法.(一个切入点对应多个连接点).---->我的理解:用户实际扩展的方法,确定了连接点,那么该方法就是一个切入点

    通知 Advice: 扩展方法的具体实现

            1.前置通知:在目标方法执行之前执行  @Before

            2.后置通知:在目标方法执行之后,返回时执行  @AfterReturning

            3.环绕通知:在目标方法执行前后,都要执行的通知 也可以控制方法是否执行  @Around

            4.异常通知:在目标方法执行之后,抛出异常时执行  @AfterThrowing

            5.最终通知:无论程序是否执行成功,都要最后执行的通知   @After

    切面 Aspect:切入点+通知就是切面

    织入 Weaving:把通知应用到目标对象的过程

    代理对象 Proxy:一个目标对象被织入通知后产生的新对象

    目标对象 Target:被织入通知的对象

    切入点表达式:

    概念:当程序满足切入点表达式,才能进入切面,执行通知方法

    1.bean("Bean的id") 根据Bean名称 进行拦截 只能匹配一个

    2.within("com.example") 包名+类名 可以使用通配符*? 能匹配多个

    3.execution(返回值类型  包名.类名.方法名(参数列表))

    4.annotation(包名.注解名)

    使用SpringAOP

    Spring对AOP的实现包括已下3种方式:

    第一种方式:Spring框架结合AspectJ框架实现的AOP,基于注解方式

    第二种方式:Spring框架结合AspectJ框架实现的AOP,基于XML方式

    第三种方式:Spring框架自己实现的AOP,基于XML配置方式

    实际开发中,都是Spring+AspectJ来实现AOP,重点第一种和第二种方式.

    准备环境

    新建模块spring-aspectj-xml

    导入依赖

    
        
        
            org.springframework
            spring-context
            6.0.10
        
        
        
            org.springframework
            spring-aspects
            6.0.10
        
        
        
            junit
            junit
            4.13.2
            test
        
    

    Spring配置文件中添加context命名空间和aop命名空间

    先搭个架子

    配置文件    注意下  这个注释可能没写对

    默认情况下,会使用Spring的JDK动态代理来代理目标对象。这意味着如果你的目标对象实现了至少一个接口,Spring将会使用JDK动态代理。这种代理方式仅代理实现了接口的方法。

    如果目标对象没有实现任何接口,Spring将会使用CGLIB(Code Generation Library)来创建一个子类代理。CGLIB允许代理类继承目标对象,因此可以代理非接口类型的目标对象。


    是强制使用CGLIB代理意思

    测试,当调用业务层login方法时候

    业务层新增一个方法

    如果我在切入点表达式中修改下

    可见切入点表达式很重要

    新加一个业务类

    修改切入点表达式

    测试

    下面引入所有通知

    将UserService注释掉  避免看着混乱

    1. package com.example.aspect;
    2. import org.aspectj.lang.ProceedingJoinPoint;
    3. import org.aspectj.lang.annotation.*;
    4. import org.springframework.stereotype.Component;
    5. /**
    6. * @author hrui
    7. * @date 2023/9/26 0:33
    8. */
    9. @Component
    10. @Aspect//标注是一个切面
    11. public class LogAspect {//切面
    12. //切面=切入点+通知
    13. //通知就是增强,就是具体要编写的增强代码
    14. //前置通知 方法执行之前
    15. @Before("execution(* com.example.service..*(..))")//里面要写切入点表达式 UserService里的所有方法
    16. public void 增强(){
    17. System.out.println("我是一个通知,我是一段增强代码");
    18. }
    19. //后置通知 方法执行之后返回时执行
    20. @AfterReturning("execution(* com.example.service..*(..))")
    21. public void afterAdvice(){
    22. System.out.println("方法执行之后通知");
    23. }
    24. //环绕通知 目标方法执行之前和执行之后 并且在前置通知之前 在后置通知之后
    25. @Around("execution(* com.example.service..*(..))")
    26. public Object aroundAdvice(ProceedingJoinPoint joinPoint){//注意多参情况ProceedingJoinPoint要放第一位
    27. Object result = null;
    28. try {
    29. System.out.println("执行方法前");
    30. //调用目标方法
    31. result=joinPoint.proceed();
    32. System.out.println("执行方法后");
    33. } catch (Throwable throwable) {
    34. throwable.printStackTrace();
    35. }finally {
    36. }
    37. return result;
    38. }
    39. //异常通知 发生异常之后
    40. @AfterThrowing(value = "execution(* com.example.service..*(..))",throwing = "e")//当目标方法执行时,抛出异常时,可以用AfterThrowing记录
    41. public void afterThrowingAdvice(){
    42. System.out.println("报错了我就执行了");
    43. }
    44. //最终通知asd
    45. @After("execution(* com.example.service..*(..))")
    46. public void after(){
    47. System.out.println("最后我肯定会执行");
    48. }
    49. }

    当系统有多个切面时候

    加个切面

    可以用@Order排序   数字小在前面

    测试

    通用切入点

    切面上的代码,每个切入点都要写切入点表达式,代码冗余

    1. package com.example.aspect;
    2. import org.aspectj.lang.ProceedingJoinPoint;
    3. import org.aspectj.lang.annotation.*;
    4. import org.springframework.core.annotation.Order;
    5. import org.springframework.stereotype.Component;
    6. /**
    7. * @author hrui
    8. * @date 2023/9/26 0:33
    9. */
    10. @Component
    11. @Aspect//标注是一个切面
    12. @Order(2)
    13. public class LogAspect {//切面
    14. //切面=切入点+通知
    15. //通知就是增强,就是具体要编写的增强代码
    16. @Pointcut("execution(* com.example.service..*(..))")
    17. public void 通用切点(){
    18. //这个方法只是一个标记,方法名随意,方法体也不需要写任何代码
    19. }
    20. //前置通知 方法执行之前
    21. @Before("通用切点()")//里面要写切入点表达式 UserService里的所有方法
    22. public void 增强(){
    23. System.out.println("我是一个通知,我是一段增强代码");
    24. }
    25. //后置通知 方法执行之后返回时执行
    26. @AfterReturning("通用切点()")
    27. public void afterAdvice(){
    28. System.out.println("方法执行之后通知");
    29. }
    30. //环绕通知 目标方法执行之前和执行之后 并且在前置通知之前 在后置通知之后
    31. @Around("通用切点()")
    32. public Object aroundAdvice(ProceedingJoinPoint joinPoint){//注意多参情况ProceedingJoinPoint要放第一位
    33. Object result = null;
    34. try {
    35. System.out.println("执行方法前");
    36. //调用目标方法
    37. result=joinPoint.proceed();
    38. System.out.println("执行方法后");
    39. } catch (Throwable throwable) {
    40. throwable.printStackTrace();
    41. }finally {
    42. }
    43. return result;
    44. }
    45. //异常通知 发生异常之后
    46. @AfterThrowing(value = "通用切点()",throwing = "e")//当目标方法执行时,抛出异常时,可以用AfterThrowing记录
    47. public void afterThrowingAdvice(){
    48. System.out.println("报错了我就执行了");
    49. }
    50. //最终通知asd
    51. @After("通用切点()")
    52. public void after(){
    53. System.out.println("最后我肯定会执行");
    54. }
    55. }

    测试

    范式

    1. package com.jt.aop;
    2. import org.aspectj.lang.JoinPoint;
    3. import org.aspectj.lang.ProceedingJoinPoint;
    4. import org.aspectj.lang.annotation.*;
    5. import org.springframework.stereotype.Component;
    6. import java.util.Arrays;
    7. /**
    8. * AOP(面向切面编程) 主要利用**动态代理**的模式 **降低程序的耦合度,扩展业务功能方法.**
    9. * 1.AOP需要被Spring容器管理 @Component
    10. * 2.标识该类是AOP切面 @Aspect
    11. * 关于AOP名词介绍
    12. * 1).连接点: 用户可以被扩展的方法 其实我们将自定义注解放到目标方法上做标识,那么该注解其实就是个连接点
    13. * 2).切入点: 用户实际扩展的方法 确定了连接点,那么该方法也就是个切入点
    14. * 3).通知: 扩展方法的具体实现 5个通知
    15. * 4).切面: 将通知应用到切入点的过程
    16. *
    17. * 通知类型(必会)
    18. * 1. before: 在目标方法执行之前执行
    19. * 2. afterReturning: 在目标方法执行之后返回时执行
    20. * 3. afterThrowing: 在目标方法执行之后,抛出异常时执行
    21. * 4. after: 无论程序是否执行成功,都要最后执行的通知
    22. * 5. around: 在目标方法执行前后 都要执行的通知(完美体现了动态代理模式)
    23. * 功能最为强大 只有环绕通知可以控制目标方法的执行
    24. *
    25. * 关于通知方法总结:
    26. * 1.环绕通知是处理业务的首选. 可以修改程序的执行轨迹
    27. * 2.另外的四大通知一般用来做程序的监控.(监控系统) 只做记录
    28. * @author TB
    29. * @date 2020/2/12 0:24
    30. */
    31. @Component
    32. //虽然标识了该类为AOP切面 但是Spring容器默认不能识别切面注解,需要手动配置
    33. //需要在配置类SpringConfig里加上注解@EnableAspectJAutoProxy
    34. @Aspect
    35. public class SpringAOP {
    36. /**
    37. * 切入点表达式
    38. * 概念:当程序满足切入点表达式,才能进入切面,执行通知方法.
    39. *
    40. * 1.bean("bean的ID") 根据beanId进行拦截 只能匹配一个
    41. * 2.within("包名.类名") 可以使用通配符*? 能匹配多个.
    42. * 粒度: 上述的切入点表达式 粒度是类级别的. 粗粒度.
    43. * 3.execution(返回值类型 包名.类名.方法名(参数列表...))
    44. * 粒度: 控制的是方法参数级别. 所以粒度较细. 最常用的.
    45. * 4.@annotation(包名.注解名) 只拦截注解.
    46. * 粒度: 注解是一种标记 根据规则标识某个方法/属性/类 细粒度
    47. */
    48. /**
    49. * 切入点表达式练习
    50. * within:
    51. * 1.within(com.jt.*.DeptServiceImpl) 一级包下的类
    52. * 2.within(com.jt..*.DeptServiceImpl) ..代表多级包下的类
    53. * 3.within(com.jt..*) 包下的所有的类
    54. *
    55. * execution(返回值类型 包名.类名.方法名(参数列表))
    56. * 1.execution(* com.jt..*.DeptServiceImpl.add*())
    57. * 注释: 返回值类型任意的, com.jt下的所有包中的DeptServiceImpl的类
    58. * 的add开头的方法 ,并且没有参数.
    59. *
    60. * 2.execution(* com.jt..*.*(..))
    61. * 注释: 返回值类型任意,com.jt包下的所有包的所有类的所有方法 任意参数.
    62. *
    63. * 3.execution(int com.jt..*.*(int))
    64. * 4.execution(Integer com.jt..*.*(Integer))
    65. * 强调: 在Spring表达式中没有自动拆装箱功能! 注意参数类型
    66. *
    67. * @annotation(包名.注解名)
    68. * @Before("@annotation(com.jt.anno.Cache)")
    69. * 只拦截特定注解的内容.
    70. */
    71. //1.定义before通知
    72. //@Before("bean(deptServiceImpl)")//扫描的是一个类 因此该类里所有方法都被扩展到了
    73. //@Before("within(com.jt.service.DeptServiceImpl)")//和上面效果一样
    74. //@Before("execution(* com.jt.service.DeptServiceImpl.add*())")//*表示返回值类型任意 add*表示以add开头的方法名 最后()表示参数是空的
    75. //@Before("@annotation(com.jt.anno.Cache)")//意思有该注解 就作为切入点 因此用注解标识最常用(自定义个注解)
    76. /**
    77. * spring为了AOP动态获取目标对象及方法中的数据,则通过Joinpoint
    78. * JoinPoint是所有通知的公共参数,无论哪种通知里都可以使用
    79. * 在Before里可以获取
    80. * 对象做数据传递获取如:
    81. * 1.获取目标对象的类型
    82. * 2.获取目标方法的名称
    83. * 3.获取目标方法的参数
    84. * @param joinPoint
    85. */
    86. @Before("pointcut()")
    87. public void before(JoinPoint joinPoint){//前置方法一般作用获取参数,方法名,等等
    88. System.out.println("目标对象的Class类对象: "+joinPoint.getTarget().getClass());
    89. System.out.println("获取目标方法的方法签名: "+joinPoint.getSignature());
    90. System.out.println("获取目标对象的类名: "+ joinPoint.getSignature().getDeclaringTypeName());
    91. System.out.println("获取目标对象方法名: "+ joinPoint.getSignature().getName());
    92. System.out.println("获取目标方法参数: "+ Arrays.toString(joinPoint.getArgs()));
    93. System.out.println("我是before通知");
    94. }
    95. //1.定义一个切入点
    96. @Pointcut("@annotation(com.jt.anno.Cache)")
    97. public void pointcut(){
    98. }
    99. //如果每个通知前都加个切入点表达式 那么也太冗余了 因此我们可以定义个切入点 其他通知都围绕切入点
    100. //@BafterReturning("@annotation(com.jt.anno.Cache)")
    101. /**
    102. * JoinPoint参数是所有通知方法公有的
    103. *AfterReturning是目标方法返回执行之后返回时执行
    104. * 可以记录方法的返回值
    105. * AfterReturning注解里 value和pointcut是相同的效果:也就是说
    106. * @AfterReturning(value="pointcut()",returning="result")和@AfterReturning(pointcut="pointcut()",returning="result")
    107. * 效果一样
    108. * returning:将方法的返回值,通过形参result(这个随便取名)来进行传递(Spring会将返回值赋值给你定义的这个变量)
    109. */
    110. @AfterReturning(value="pointcut()",returning="result")
    111. public void afterReturning(JoinPoint joinPoint,Object result){//这里注意 如果有需要用到JointPoint参数 那么必须放在第一个位置 不用可以去掉
    112. System.out.println("目标返回值结果是: "+result);
    113. System.out.println("我是AfterReturning的通知");
    114. }
    115. @AfterThrowing(pointcut = "pointcut()",throwing="e")//当目标方法执行时,抛出异常时,可以用AfterThrowing记录
    116. public void afterThrowing(Exception e){
    117. System.out.println("获取目标异常信息: "+e.getMessage());
    118. System.out.println("获取目标异常类型: "+e.getClass());
    119. System.out.println("我是AfterThrowing的通知,出现异常了");
    120. }
    121. @After("pointcut()")
    122. public void after(){
    123. System.out.println("我是After的通知");
    124. }
    125. /**
    126. * 关于环绕通知的说明
    127. * 作用: 可以控制目标方法是否执行.
    128. * 参数: ProceedingJoinPoint 通过proceed方法控制目标方法执行.
    129. * 注意事项:
    130. * ProceedingJoinPoint 只能适用环绕通知
    131. * @return
    132. */
    133. @Around("pointcut()")
    134. public Object around(ProceedingJoinPoint joinPoint){//注意多参情况ProceedingJoinPoint要放第一位
    135. Object result = null;
    136. try {
    137. System.out.println("环绕通知开始");
    138. //1.执行下一个通知 2.执行目标方法 3.接收返回值
    139. Long start = System.currentTimeMillis();
    140. result = joinPoint.proceed();
    141. Long end = System.currentTimeMillis();
    142. System.out.println("耗时:"+(end-start));
    143. } catch (Throwable throwable) {
    144. throwable.printStackTrace();
    145. }
    146. System.out.println("环绕通知结束");
    147. return result;
    148. }
    149. }

    关于全注解式开发,不用Spring配置文件

    测试也需要改下

    Spring AOP基于XML方式的实现

    新建模块spring-aspectj-xml2

    依赖

    
        
        
            org.springframework
            spring-context
            6.0.10
        
        
        
            org.springframework
            spring-aspects
            6.0.10
        
        
        
            junit
            junit
            4.13.2
            test
        
    

     目标类

    切面类

  • 相关阅读:
    @ConditionalOnProperty配置属性作为条件
    验证工程师如何才能脱颖而出?
    iOS17.2正式版什么时候发布? 13大新功能细节抢先看
    线程中的LockSapport于线程中断(一)
    linux常见下载安装工具
    数据分析面试手册《指标篇》
    谷歌、AMD、英特尔加入挑战,英伟达AI解决方案还能继续“遥遥领先”吗?
    算法面经广联达、中兴、电信篇
    文盘 Rust -- tokio 绑定 cpu 实践
    GAN实战笔记——第七章半监督生成对抗网络(SGAN)
  • 原文地址:https://blog.csdn.net/tiantiantbtb/article/details/133282000