当前位置: 首页 > news >正文

09.AOP-尚硅谷Spring零基础入门到进阶,一套搞定spring6全套视频教程(源码级讲解)

现有代码缺陷
针对带日志功能的实现类,我们发现有如下缺陷:

  • 对核心业务功能有干扰,导致程序员在开发核心业务功能时分散了精力
  • 附加功能分散在各个业务功能方法中,不利于统一维护
    解决思路
    解决核心:解耦。把附加功能从业务功能代码中抽取出来。
    困难
    解决问题的困难:要抽取的代码在方法内部,靠以前把子类中的重复代码抽取到父类的方式没法解决。所以需要引
    入新的技术。

代理模式

概念

介绍
二十三种设计模式中的一种,属于结构型模式。它的作用就是通过提供一个代理类,让我们在调用目标方法的时
候,不再是直接对目标方法进行调用,而是通过代理类间接调用。让不属于目标方法核心逻辑的代码从目标方法中
剥离出来一一解耦。调用目标方法时先调用代理对象的方法,减少对目标方法的调用和打扰,同时让附加功能能够
集中在一起也有利于统一维护。
相关术语
,代理:将非核心逻辑剥离出来以后,封装这些非核心逻辑的类、对象、方法。
·目标:被代理“套用”了非核心逻辑代码的类、对象、方法。

场景模拟

  1. 声明计算器接口Calculator,包含加减乘除的抽象方法

    package org.example;  public interface Calculator {  public int add(int i, int j);  public int sub(int i, int j);  public int mul(int i, int j);  public int div(int i, int j);  
    }
    
  2. 写一个实现Calculator业务的实现类

    package org.example;
    public class CalculatorImpl implements Calculator {@Overridepublic int add(int i, int j) {int result = i + j;System.out.println("result=" + result);return result;}@Overridepublic int sub(int i, int j) {int result = i - j;System.out.println("result=" + result);return result;}@Overridepublic int mul(int i, int j) {int result = i * j;System.out.println("result=" + result);return result;}@Overridepublic int div(int i, int j) {int result = i / j;System.out.println("result=" + result);return result;}
    }
    
  3. 写一个实现Calculator业务的带有日志功能的实现类

    package org.example;  
    public class CalculatorLogImpl implements Calculator {@Overridepublic int add(int i, int j) {System.out.println("计算开始,i=" + i + "j=" + j);int result = i + j;System.out.println("计算结束,i=" + i + "j=" + j + "result=" + result);System.out.println("result=" + result);return result;}@Overridepublic int sub(int i, int j) {System.out.println("计算开始,i=" + i + "j=" + j);int result = i - j;System.out.println("计算结束,i=" + i + "j=" + j + "result=" + result);System.out.println("result=" + result);return result;}@Overridepublic int mul(int i, int j) {System.out.println("计算开始,i=" + i + "j=" + j);int result = i * j;System.out.println("计算结束,i=" + i + "j=" + j + "result=" + result);System.out.println("result=" + result);return result;}@Overridepublic int div(int i, int j) {System.out.println("计算开始,i=" + i + "j=" + j);int result = i / j;System.out.println("计算结束,i=" + i + "j=" + j + "result=" + result);System.out.println("result=" + result);return result;}
    }
    

静态代理

静态代理确实实现了解耦,但是由于代码都写死了,完全不具备任何的灵活性。就拿日志功能来说,将来其
他地方也需要附加日志,那还得再声明更多个静态代理类,那就产生了大量重复的代码,日志功能还是分散
的,没有统一管理。
提出进一步的需求:将日志功能集中到一个代理类中,将来有任何日志需求,都通过这一个代理类来实现。
这就需要使用动态代理技术了。

动态代理

使用java.lang.reflect.Proxy类实现动态代理
官方示例代码

InvocationHandler handler = new MyInvocationHandler(...);  
Foo f = (Foo) Proxy.newProxyInstance(Foo.class.getClassLoader(),  new class<?>[]{Foo.class},  handler);

创建一个代理工厂类

package org.example;  import lombok.val;  import javax.print.attribute.standard.JobKOctets;  
import java.lang.reflect.InvocationHandler;  
import java.lang.reflect.Method;  
import java.lang.reflect.Proxy;  public class ProxyFactory {  Object target;  public ProxyFactory(Object target) {  this.target = target;  }  public Object getProxy() {  
/*      有三个参数  第一个参数:CLassLoader:加载动态生成代理类的来加载器  第二个参数:CLass[]interfaces:目录对象实现的所有接口cLass类型数组  第三个参数:InvocationHandler:设置代理对象实现目标对象方法的过程*/  ClassLoader cLassLoader = target.getClass().getClassLoader();  Class[] classes = target.getClass().getInterfaces();  InvocationHandler invocationHandler = new InvocationHandler() {  @Override  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {  //调用方法前日志  System.out.println("[动态代理][调用前日志]" + method.getName() + "参数:" + args);  //调用目标方法  Object result = method.invoke(target, args);  //调用方法后日志  System.out.println("[动态代理][调用后日志]" + method.getName() + "参数:" + args);  return result;  }  };  return Proxy.newProxyInstance(cLassLoader, classes, invocationHandler);  }  
}

编写测试类

@Test  
public void calculatorTest(){  ProxyFactory proxyFactory=new ProxyFactory(new CalculatorImpl());  Calculator proxy=(Calculator) proxyFactory.getProxy();  proxy.add(1,1);  
}

输出结果

[动态代理][调用前日志]add参数:[Ljava.lang.Object;@7d0587f1
result=2
[动态代理][调用后日志]add参数:[Ljava.lang.Object;@7d0587f1

基于注解的AOP

动态代理分类:JDK动态代理和cglib动态代理
JDK动态代理生成接口实现类代理对象
cglib动态代理继承被代理的目标类,生成子类代理对象,不需要目标类实现接口

  • 有接口可以使用JDK动态代理和cblib动态代理
  • 没有接口只能使用cblib动态代理
    Aspect:是AOP思想的一种实现。本质上是静态代理,将代理逻辑“织入"被代理的目标类编译得到的字节码
    文件,所以最终效果是动态的。weaver就是织入器。Spring只是借用了Aspect)中的注解。

使用AOP步骤

  1. 引入aop相关依赖

    <!--spring aop依赖-->  
    <dependency>  <groupId>org.springframework</groupId>  <artifactId>spring-aop</artifactId>  <version>6.0.2</version>  
    </dependency>  
    <!--spring aspects依赖-->  
    <dependency>  <groupId>org.springframework</groupId>  <artifactId>spring-aspects</artifactId>  <version>6.0.2</version>  
    </dependency>
    
  2. 创建目标资源

    1. 接口

      package com.example.annoAOP;  public interface Calculator {  public int add(int i, int j);  public int sub(int i, int j);  public int mul(int i, int j);  public int div(int i, int j);  
      }
      
    2. 实现类

      package com.example.annoAOP;  import org.springframework.stereotype.Component;  @Component  
      public class CalculatorImpl implements Calculator {  @Override  public int add(int i, int j) {  int result = i + j;  System.out.println("result=" + result);  return result;  }  @Override  public int sub(int i, int j) {  int result = i - j;  System.out.println("result=" + result);  return result;  }  @Override  public int mul(int i, int j) {  int result = i * j;  System.out.println("result=" + result);  return result;  }  @Override  public int div(int i, int j) {  int result = i / j;  System.out.println("result=" + result);  return result;  }  
      }
      

第三步创建切面类

  1. 创建bean.xml,使用AOP约束,开启AOP功能和扫描功能

    <?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:context="http://www.springframework.org/schema/context"  xmlns:aop="http://www.springframework.org/schema/aop"  xsi:schemaLocation="http://www.springframework.org/schema/beans  http://www.springframework.org/schema/beans/spring-beans.xsd    http://www.springframework.org/schema/context    http://www.springframework.org/schema/context/spring-context.xsd    http://www.springframework.org/schema/aop  http://www.springframework.org/schema/aop/spring-aop.xsd">  <!-- 开启组件扫描           -->  <context:component-scan base-package="com.example"/>  <!--开启aspectj自动代理,为目标对象生成代理-->  <aop:aspectj-autoproxy></aop:aspectj-autoproxy>  
    </beans>
    
  2. 创建LogAscept类,增加一个方法的前置切入点

    package com.example.annoAOP;  import org.aspectj.lang.annotation.Aspect;  
    import org.aspectj.lang.annotation.Before;  
    import org.springframework.stereotype.Component;  @Aspect//表明这是一个AOP文件  
    @Component//让IoC进行管理  
    public class LogAspect {  //设置切入点和通知类型  //通知类型:  // 前置   @Before(value="切入点表达式")  // 返回   @AfterReturning    // 异常   @AfterThrowing    // 后置   @After()    // 环绕   @Around()    //切入点表达式写法:execution(权限修饰 方法返回值 方法所在全类名.方法名 (参数列表))  //execution:固定语法  //权限修饰:这里写*表示权限修饰符和返回值任意  //方法所在全类名:写*表示任意包名;写*...表示包名任意同时包层次深度任意  //类名用*号代替表示类名任意,部分用*代替,如*Service,表示匹配以Service结尾的列或接口  //方法名:用*号代替表示方法名任意;部分用*代替,如get*,表示匹配以get开头的方法  //参数列表可以使用(...)形式表示参数列表任意  @Before(value = "execution(public int com.example.annoAOP.CalculatorImpl.add (int,int))")  public void beforeAdd() {  System.out.println("[前置通知][add()]计算开始");  }  
    }
    

    方法表达式写法:

    在这里插入图片描述

  3. 创建测试方法

    @Test
    public void testAOPAdd(){ApplicationContext applicationContext=new ClassPathXmlApplicationContext("bean.xml");Calculator calculator=applicationContext.getBean(Calculator.class);calculator.add(1,1);
    }
    
  4. 输出结果

    [前置通知][add()]计算开始
    result=2
    

通知类型

  • 前置通知:在被代理的目标方法前执行
  • 返回通知:在被代理的目标方法成功结束后执行(寿终正寝)
  • 异常通知:在被代理的目标方法异常结束后执行(死于非命)
  • 后置通知:在被代理的目标方法最终结束后执行(盖棺定论)
  • 环绕通知:使用try.catch.finally结构围绕整个被代理的目标方法,包括上面四种通知对应的所有位置
    修改LogAspect类,添加五种通知方法
package com.example.annoAOP;  import org.aspectj.lang.JoinPoint;  
import org.aspectj.lang.ProceedingJoinPoint;  
import org.aspectj.lang.annotation.*;  
import org.springframework.stereotype.Component;  @Aspect//表明这是一个AOP文件  
@Component//让IoC进行管理  
public class LogAspect {  //前置通知  @Before(value = "execution(* com.example.annoAOP.CalculatorImpl.* (..))")  public void beforeMethod(JoinPoint joinPoint) {  String MethodName = joinPoint.getSignature().getName();  Object[] args = joinPoint.getArgs();  System.out.println("[前置通知][CalculatorImpl.MethodName=" + MethodName + "()");  System.out.println("Args[]=" + args);  }  //后置通知  @After(value = "execution(* com.example.annoAOP.CalculatorImpl.* (..))")  public void afterMethod(JoinPoint joinPoint) {  String MethodName = joinPoint.getSignature().getName();  Object[] args = joinPoint.getArgs();  System.out.println("[后置通知][CalculatorImpl.MethodName=" + MethodName + "()");  System.out.println("Args[]=" + args);  }  //返回通知  @AfterReturning(value = "execution(* com.example.annoAOP.CalculatorImpl.* (..))", returning = "result")  public void afterReturnMethod(JoinPoint joinPoint, Object result) {  String MethodName = joinPoint.getSignature().getName();  System.out.println("[返回通知][CalculatorImpl.MethodName=" + MethodName + "()");  System.out.println("[返回通知]result=" + result);  }  //异常通知  @AfterThrowing(value = "execution(* com.example.annoAOP.CalculatorImpl.* (..))", throwing = "exp")  public void afterThrowing(JoinPoint joinPoint, Throwable exp) {  String MethodName = joinPoint.getSignature().getName();  System.out.println("[异常通知][CalculatorImpl.MethodName=" + MethodName + "()");  System.out.println(exp);  }  //环绕通知  @Around("execution(* com.example.annoAOP.CalculatorImpl.* (..))")  //ProceedingJoinPoint继承JoinPoint,比JoinPoint功能更强大,可以更好的调用目标方法  public Object around(ProceedingJoinPoint joinPoint) {  Object result = null;  try {  System.out.println("环绕通知-目标方法执行前");  result = joinPoint.proceed();  System.out.println("环绕通知-目标方法执行后");  } catch (Throwable throwable) {  System.out.println("环绕通知-目标方法执行异常");  } finally {  System.out.println("环绕通知-目标方法执行完成");  }  return result;  }  
}

输出结果

环绕通知-目标方法执行前
[前置通知][CalculatorImpl.MethodName=add()
Args[]=[Ljava.lang.Object;@62727399
result=2
[返回通知][CalculatorImpl.MethodName=add()
[返回通知]result=2
[后置通知][CalculatorImpl.MethodName=add()
Args[]=[Ljava.lang.Object;@62727399
环绕通知-目标方法执行后
环绕通知-目标方法执行完成

编写测试方法,使测试方法引发异常

@Test  
public void testAOPexp(){  ApplicationContext applicationContext=new ClassPathXmlApplicationContext("bean.xml");  Calculator calculator=applicationContext.getBean(Calculator.class);  calculator.div(1,0);  
}

运行结果

环绕通知-目标方法执行前
[前置通知][CalculatorImpl.MethodName=div()
Args[]=[Ljava.lang.Object;@4d9ac0b4
[异常通知][CalculatorImpl.MethodName=div()
java.lang.ArithmeticException: / by zero
[后置通知][CalculatorImpl.MethodName=div()
Args[]=[Ljava.lang.Object;@4d9ac0b4
环绕通知-目标方法执行异常
环绕通知-目标方法执行完成[之后是异常报错信息]

重用切入点

  1. 定义一个切入点

    	package com.example.annoAOP;
    @Pointcut(value = "execution(* com.example.annoAOP.CalculatorImpl.* (..))")  
    public void pointCut() {}
    
  2. 使用切入点

    1. 内部使用切入点

      @After(value = "pointCut")  
      public void afterMethod(JoinPoint joinPoint) {  String MethodName = joinPoint.getSignature().getName();  Object[] args = joinPoint.getArgs();  System.out.println("[后置通知][CalculatorImpl.MethodName=" + MethodName + "()");  System.out.println("Args[]=" + args);  
      }  
      
    2. 外部使用切入点

      @After(value = "com.example.annoAOP.pointCut")  
      public void afterMethod(JoinPoint joinPoint) {  String MethodName = joinPoint.getSignature().getName();  Object[] args = joinPoint.getArgs();  System.out.println("[后置通知][CalculatorImpl.MethodName=" + MethodName + "()");  System.out.println("Args[]=" + args);  
      }  
      

切面的优先级

相同目标方法上同时存在多个切面时,切面的优先级控制切面的内外嵌套顺序。

  • 优先级高的切面:外面
  • 优先级低的切面:里面
    使用@Order注解可以控制切面的优先级:
  • @Order(较小的数):优先级高
  • @Order(较大的数):优先级低

XML形式配置AOP

  1. 创建新包xmlaop,复制上文接口、实现类、AOP配置类

  2. 删除LogAspect类的@Aspect注解和AOP注解

  3. 新建XmlAop.xml配置文件

    <?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:context="http://www.springframework.org/schema/context"  xmlns:aop="http://www.springframework.org/schema/aop"  xsi:schemaLocation="http://www.springframework.org/schema/beans  http://www.springframework.org/schema/beans/spring-beans.xsd    http://www.springframework.org/schema/context    http://www.springframework.org/schema/context/spring-context.xsd    http://www.springframework.org/schema/aop    http://www.springframework.org/schema/aop/spring-aop.xsd">  <!-- 开启组件扫描           -->  <context:component-scan base-package="com.example.xmlAOP"/>  <!--配置AOP-->  <aop:config>  <!-- 配置切面类       -->  <aop:aspect ref="logAspect">  <!-- 配置切入点       -->  <aop:pointcut id="cutpoint" expression="execution(* com.example.xmlAOP.CalculatorImpl.* (..))"/>  <!-- 配置方法执行前通知       -->  <aop:before method="beforeMethod" pointcut-ref="cutpoint"/>  <!-- 配置方法执行后通知       -->  <aop:after method="afterMethod" pointcut-ref="cutpoint"/>  <!-- 配置方法返回后通知       -->  <aop:after-returning method="afterReturnMethod" pointcut-ref="cutpoint" returning="result"/>  <!-- 配置环绕通知       -->  <aop:around method="around" pointcut-ref="cutpoint"/>  <!-- 配置异常通知       -->  <aop:after-throwing method="afterThrowing" pointcut-ref="cutpoint" throwing="exp"/>  </aop:aspect>  </aop:config>  </beans>
    
  4. 编写测试方法

    @Test  
    public void testXML_AOP(){ApplicationContext applicationContext=new ClassPathXmlApplicationContext("XmlAop.xml");//本项目存在两个Calculator,需要注意使用的是哪个Calculator类com.example.xmlAOP.Calculator calculator=applicationContext.getBean(com.example.xmlAOP.Calculator.class);calculator.add(1,1);
    }
    
  5. 输出结果

[前置通知][CalculatorImpl.MethodName=add()
Args[]=[Ljava.lang.Object;@eda25e5
环绕通知-目标方法执行前
result=2
环绕通知-目标方法执行后
环绕通知-目标方法执行完成
[返回通知][CalculatorImpl.MethodName=add()
[返回通知]result=2
[后置通知][CalculatorImpl.MethodName=add()
Args[]=[Ljava.lang.Object;@eda25e5

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • aws slb
  • 【数据结构】链表带环问题分析及顺序表链表对比分析
  • 【Linux】进程间通信之System V共享内存
  • 华宇携TAS应用中间件亮相2024年山东江信智能信创产品推介会
  • Unity之Text组件换行\n没有实现+动态中英互换
  • AIGC时代创意设计师从“创作”向“智作”升级
  • 深入解析 HTTP Headers 中的 baggage 参数
  • 【前端】包管理器:npm、Yarn 和 pnpm 的全面比较
  • pytorch实现水果2分类(蓝莓,苹果)
  • FastApi+WebSocket 解析
  • 【数据结构】双向链表
  • DDR3 (四)
  • WPF引入多个控件库使用
  • 机器学习——LR、‌GBDT、‌SVM、‌CNN、‌DNN、‌RNN、‌Word2Vec等模型的原理和应用
  • Kodcloud可道云安装与一键发布上线实现远程访问详细教程
  • [LeetCode] Wiggle Sort
  • Android路由框架AnnoRouter:使用Java接口来定义路由跳转
  • Angular2开发踩坑系列-生产环境编译
  • CentOS从零开始部署Nodejs项目
  • JS学习笔记——闭包
  • Kibana配置logstash,报表一体化
  • PHP 使用 Swoole - TaskWorker 实现异步操作 Mysql
  • Twitter赢在开放,三年创造奇迹
  • Web标准制定过程
  • 短视频宝贝=慢?阿里巴巴工程师这样秒开短视频
  • 机器学习 vs. 深度学习
  • 基于遗传算法的优化问题求解
  • 解析带emoji和链接的聊天系统消息
  • 开源中国专访:Chameleon原理首发,其它跨多端统一框架都是假的?
  • 理解在java “”i=i++;”所发生的事情
  • 每个JavaScript开发人员应阅读的书【1】 - JavaScript: The Good Parts
  • 强力优化Rancher k8s中国区的使用体验
  • 世界编程语言排行榜2008年06月(ActionScript 挺进20强)
  • 腾讯视频格式如何转换成mp4 将下载的qlv文件转换成mp4的方法
  • 移动端唤起键盘时取消position:fixed定位
  • 06-01 点餐小程序前台界面搭建
  • 你对linux中grep命令知道多少?
  • (11)MSP430F5529 定时器B
  • (delphi11最新学习资料) Object Pascal 学习笔记---第8章第5节(封闭类和Final方法)
  • (Java实习生)每日10道面试题打卡——JavaWeb篇
  • (JSP)EL——优化登录界面,获取对象,获取数据
  • (二)测试工具
  • (每日一问)基础知识:堆与栈的区别
  • (生成器)yield与(迭代器)generator
  • (十八)三元表达式和列表解析
  • (微服务实战)预付卡平台支付交易系统卡充值业务流程设计
  • (正则)提取页面里的img标签
  • (转载)跟我一起学习VIM - The Life Changing Editor
  • . Flume面试题
  • .Net 6.0--通用帮助类--FileHelper
  • .NET Core IdentityServer4实战-开篇介绍与规划
  • .NET Core 成都线下面基会拉开序幕
  • .net Signalr 使用笔记
  • .NET/C# 使窗口永不激活(No Activate 永不获得焦点)
  • .NET/C# 使用反射调用含 ref 或 out 参数的方法