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

Java八股 深入理解Spring的AOP 面向切面编程 底层 保姆级教程 手写例子

目录

概念

AOP 术语

1. 连接点(Jointpoint):

2. 切入点(Pointcut):

3. 通知(Advice):

4. 方面/切面(Aspect):

5. 引入(inter-type declaration):

6. 目标对象(Target Object):

7. 织入(Weaving):

8. AOP代理(AOP Proxy):

通知类型:

1. 前置通知(Before advice):

2. 后置通知(After returning advice):

3. 异常通知(After throwing advice):

4. 最终通知(After (finally) advice):

5. 环绕通知(Around Advice):

AOP 术语 图解 重要

AOP 核心概念 小总结

Spring AOP和 AspectJ 是什么关系 区别

AOP 的配置方式

XML 形式

AspectJ 注解

最后想手写一个 AOP 例子结束

参考文章


概念

AOP 的目的是为了解耦 其次是简化开发

AOP 是 Spring 的核心 面向切面编程

他是一套规范

通过预编译方式和运行期间动态代理实现程序的统一维护

核心概念 就是 将分散在各个业务逻辑代码中的相同的代码通过横向切割的方式抽取到一个独立的模块中

AOP 术语

首先让我们从一些重要的AOP概念和术语开始。

这些术语不是Spring特有的

1. 连接点(Jointpoint):

表示需要在程序中插入横切关注点的扩展点,连接点可能是类初始化、方法执行、方法调用、字段调用或处理异常等等,Spring只支持方法执行连接点,在AOP中表示为在哪里干

2. 切入点(Pointcut):

选择一组相关连接点的模式,即可以认为连接点的集合,Spring支持perl5正则表达式和AspectJ切入点模式,Spring默认使用AspectJ语法,在AOP中表示为在哪里干的集合

3. 通知(Advice):

在连接点上执行的行为,通知提供了在AOP中需要在切入点所选择的连接点处进行扩展现有行为的手段;包括前置通知(before advice)、后置通知(after advice)、环绕通知(around advice),在Spring中通过代理模式实现AOP,并通过拦截器模式以环绕连接点的拦截器链织入通知;在AOP中表示为干什么

4. 方面/切面(Aspect):

横切关注点的模块化,比如上边提到的日志组件。可以认为是通知、引入和切入点的组合;在Spring中可以使用Schema和@AspectJ方式进行组织实现;在AOP中表示为在哪干和干什么集合

5. 引入(inter-type declaration):

也称为内部类型声明,为已有的类添加额外新的字段或方法,Spring允许引入新的接口(必须对应一个实现)到所有被代理对象(目标对象), 在AOP中表示为干什么(引入什么)

6. 目标对象(Target Object):

需要被织入横切关注点的对象,即该对象是切入点选择的对象,需要被通知的对象,从而也可称为被通知对象;由于Spring AOP 通过代理模式实现,从而这个对象永远是被代理对象,在AOP中表示为对谁干

7. 织入(Weaving):

把切面连接到其它的应用程序类型或者对象上,并创建一个被通知的对象。这些可以在编译时(例如使用AspectJ编译器),类加载时和运行时完成。Spring和其他纯Java AOP框架一样,在运行时完成织入。在AOP中表示为怎么实现的

8. AOP代理(AOP Proxy):

AOP框架使用代理模式创建的对象,从而实现在连接点处插入通知(即应用切面),就是通过代理来对目标对象应用切面。在Spring中,AOP代理可以用JDK动态代理或CGLIB代理实现,而通过拦截器模型应用切面。在AOP中表示为怎么实现的一种典型方式

通知类型:

1. 前置通知(Before advice):

在某连接点之前执行的通知,但这个通知不能阻止连接点之前的执行流程(除非它抛出一个异常)。

2. 后置通知(After returning advice):

在某连接点正常完成后执行的通知:例如,一个方法没有抛出任何异常,正常返回。

3. 异常通知(After throwing advice):

在方法抛出异常退出时执行的通知。

4. 最终通知(After (finally) advice):

当某连接点退出的时候执行的通知(不论是正常返回还是异常退出)。

5. 环绕通知(Around Advice):

包围一个连接点的通知,如方法调用。这是最强大的一种通知类型。环绕通知可以在方法调用前后完成自定义的行为。它也会选择是否继续执行连接点或直接返回它自己的返回值或抛出异常来结束执行。

环绕通知是最常用的通知类型。和AspectJ一样,Spring提供所有类型的通知,我们推荐你使用尽可能简单的通知类型来实现需要的功能。例如,如果你只是需要一个方法的返回值来更新缓存,最好使用后置通知而不是环绕通知,尽管环绕通知也能完成同样的事情。用最合适的通知类型可以使得编程模型变得简单,并且能够避免很多潜在的错误。比如,你不需要在JoinPoint上调用用于环绕通知的proceed()方法,就不会有调用的问题。

AOP 术语 图解 重要

AOP 核心概念 小总结

AOP 的核心是连接点

连接点是我们需要关注的程序拓展点

可能是类初始化 方法执行 方法调用 字段调用 异常处理等

Spring 支持的连接点是方法执行点

切入点是一系列连接点的集合,Spring默认使用AspectJ语法,在AOP中抽象表示为可以进行操作的集合

之后就是通知

通知就是我们在连接点上执行的行为

连接点 切入点 通知组合在一起 就是一个切面

把切面映入到其他应用程序或者对象上,创建一个被通知的对象,这些就是织入,Spring 在运行时完成织入 ,在 AOP 中表示为怎么实现的,实现方式

Spring AOP和 AspectJ 是什么关系 区别

AspectJ 是一个更加强大的 AOP 框架 是一个 AOP 标准

如果只是简单的业务 可以使用 AOP

AOP 一个重要的原则就是无侵入性

AspectJ 重要的是 一般在编译期进行 即静态织入

在这个期间使用AspectJ的acj编译器(类似javac)把aspect类编译成class字节码后,在java目标类编译时织入,即先编译aspect类再编译目标类。

动态织入的方式是在运行时动态将要增强的代码织入到目标类中,这样往往是通过动态代理技术完成的,如Java JDK的动态代理(Proxy,底层通过反射实现)或者CGLIB的动态代理(底层通过继承实现),Spring AOP采用的就是基于运行时增强的代理技术。

Spring AOP更易用,AspectJ更强大

AOP 的配置方式

XML 形式

首先是业务逻辑

package aopByXml;// 目标类 核心业务
public class AopDemoServiceImpl {public void doMethod1() {System.out.println("aopByXml.AopDemoServiceImpl.doMethod1()");}public String doMethod2() {System.out.println("aopByXml.AopDemoServiceImpl.doMethod2()");return "hello world";}public String doMethod3() throws Exception {System.out.println("aopByXml.AopDemoServiceImpl.doMethod3()");throw new Exception("some exception");}
}

其次是切面类 我这边定义的方法都是通知

package aopByXml;import org.aspectj.lang.ProceedingJoinPoint;
// 切面类
public class LogAspect {/*** 环绕通知.** @param pjp pjp* @return obj* @throws Throwable exception*/public Object doAround(ProceedingJoinPoint pjp) throws Throwable {System.out.println("-----------------------");System.out.println("环绕通知: 进入方法");Object o = pjp.proceed();System.out.println("环绕通知: 退出方法");return o;}/*** 前置通知.*/public void doBefore() {System.out.println("前置通知");}/*** 后置通知.** @param result return val*/public void doAfterReturning(String result) {System.out.println("后置通知, 返回值: " + result);}/*** 异常通知.** @param e exception*/public void doAfterThrowing(Exception e) {System.out.println("异常通知, 异常: " + e.getMessage());}/*** 最终通知.*/public void doAfter() {System.out.println("最终通知");}
}

然后是 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:aop="http://www.springframework.org/schema/aop"xmlns:context="http://www.springframework.org/schema/context"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsd
"><context:component-scan base-package="tech.pdai.springframework" /><aop:aspectj-autoproxy/><!-- 目标类 --><bean id="demoService" class="tech.pdai.springframework.service.AopDemoServiceImpl"><!-- configure properties of bean here as normal --></bean><!-- 切面 --><bean id="logAspect" class="tech.pdai.springframework.aspect.LogAspect"><!-- configure properties of aspect here as normal --></bean><aop:config><!-- 配置切面 --><aop:aspect ref="logAspect"><!-- 配置切入点 --><aop:pointcut id="pointCutMethod" expression="execution(* tech.pdai.springframework.service.*.*(..))"/><!-- 环绕通知 --><aop:around method="doAround" pointcut-ref="pointCutMethod"/><!-- 前置通知 --><aop:before method="doBefore" pointcut-ref="pointCutMethod"/><!-- 后置通知;returning属性:用于设置后置通知的第二个参数的名称,类型是Object --><aop:after-returning method="doAfterReturning" pointcut-ref="pointCutMethod" returning="result"/><!-- 异常通知:如果没有异常,将不会执行增强;throwing属性:用于设置通知第二个参数的的名称、类型--><aop:after-throwing method="doAfterThrowing" pointcut-ref="pointCutMethod" throwing="e"/><!-- 最终通知 --><aop:after method="doAfter" pointcut-ref="pointCutMethod"/></aop:aspect></aop:config><!-- more bean definitions for data access objects go here -->
</beans>

AspectJ 注解

XML 声明式存在不足 在配置文件里面写了太多繁琐的东西

下面是 AspectJ 提供的注解

Spring AOP的实现方式是动态织入,动态织入的方式是在运行时动态将要增强的代码织入到目标类中,这样往往是通过动态代理技术完成的;

如Java JDK的动态代理(Proxy,底层通过反射实现)

如 CGLIB的动态代理(底层通过继承实现)

Spring AOP采用的就是基于运行时增强的代理技术。

以 JDK 动态代理举例

接口 规则

package aopByAspectJJdk;// 定义接口
public interface IJdkProxyService {void doMethod1();String doMethod2();String doMethod3() throws Exception;
}

接口实现类 具体实现

package aopByAspectJJdk;// 接口实现类
public class JdkProxyDemoServiceImpl implements IJdkProxyService{@Overridepublic void doMethod1() {}@Overridepublic String doMethod2() {return "";}@Overridepublic String doMethod3() throws Exception {return "";}
}

动态代理类 织入

package aopByAspectJJdk;import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.stereotype.Component;/*** @author pdai*/
@EnableAspectJAutoProxy
@Component
@Aspect
public class LogAspect {/*** define point cut.*/@Pointcut("execution(* tech.pdai.springframework.service.*.*(..))")private void pointCutMethod() {}/*** 环绕通知.** @param pjp pjp* @return obj* @throws Throwable exception*/@Around("pointCutMethod()")public Object doAround(ProceedingJoinPoint pjp) throws Throwable {System.out.println("-----------------------");System.out.println("环绕通知: 进入方法");Object o = pjp.proceed();System.out.println("环绕通知: 退出方法");return o;}/*** 前置通知.*/@Before("pointCutMethod()")public void doBefore() {System.out.println("前置通知");}/*** 后置通知.** @param result return val*/@AfterReturning(pointcut = "pointCutMethod()", returning = "result")public void doAfterReturning(String result) {System.out.println("后置通知, 返回值: " + result);}/*** 异常通知.** @param e exception*/@AfterThrowing(pointcut = "pointCutMethod()", throwing = "e")public void doAfterThrowing(Exception e) {System.out.println("异常通知, 异常: " + e.getMessage());}/*** 最终通知.*/@After("pointCutMethod()")public void doAfter() {System.out.println("最终通知");}}

最后想手写一个 AOP 例子结束

加减乘除的接口

public interface Calculator {// 加法int add(int i, int j);// 减法int sub(int i, int j);// 乘法int mul(int i, int j);// 除法int div(int i, int j);
}

具体实现逻辑

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;}
}

切面


import org.aspectj.lang.*;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;import java.util.Arrays;@Component
@Aspect
public class LogAspect {@Pointcut("execution(public int com.dc.esb.CalculatorImpl.*(..))")public void pointCut() {}@Before("pointCut()")public void beforeMethod(JoinPoint joinPoint) {String methodName = joinPoint.getSignature().getName();String args = Arrays.toString(joinPoint.getArgs());System.out.println("Logger-->前置通知,方法名:" + methodName + ",参数:" + args);}@After("execution(public int com.dc.esb.CalculatorImpl.*(..))")public void afterMethod(JoinPoint joinPoint) {String methodName = joinPoint.getSignature().getName();System.out.println("Logger-->后置通知,方法名:" + methodName);}@AfterReturning(value = "execution(public int com.dc.esb.CalculatorImpl.*(..))", returning = "result")public void afterReturningMethod(JoinPoint joinPoint, Object result) {String methodName = joinPoint.getSignature().getName();System.out.println("Logger-->返回通知,方法名:" + methodName + ",结果:" + result);}@AfterThrowing(value = "execution(public int com.dc.esb.CalculatorImpl.*(..))", throwing = "ex")public void afterThrowingMethod(JoinPoint joinPoint, Throwable ex) {String methodName = joinPoint.getSignature().getName();System.out.println("Logger-->异常通知,方法名:" + methodName + ",异常:" + ex);}@Around("execution(public int com.dc.esb.CalculatorImpl.*(..))")public Object aroundMethod(ProceedingJoinPoint joinPoint) {String methodName = joinPoint.getSignature().getName();String args = Arrays.toString(joinPoint.getArgs());Object result = null;try {System.out.println("环绕通知-->目标对象方法执行之前");//目标对象(连接点)方法的执行result = joinPoint.proceed();System.out.println("环绕通知-->目标对象方法返回值之后");} catch (Throwable throwable) {throwable.printStackTrace();System.out.println("环绕通知-->目标对象方法出现异常时");} finally {System.out.println("环绕通知-->目标对象方法执行完毕");}return result;}
}

参考文章

Spring基础 - Spring核心之面向切面编程(AOP) | Java 全栈知识体系

http://www.xdnf.cn/news/1142.html

相关文章:

  • vue3+canvas裁剪框样式【前端】
  • 车载软件架构 --- 驾驶员不感知的控制器软件运行
  • Sentinel源码—8.限流算法和设计模式总结一
  • Java中的方法重写(Override)与方法重载(Overload)详解
  • CSI D-PHY 散谈
  • 【Linux网络】各版本TCP服务器构建 - 从理解到实现
  • 云原生周刊:KubeSphere 平滑升级
  • UWB与GPS技术融合的室内外无缝定位方案
  • QT6 源(43):class QGroupBox : public QWidget ,最常用的容器类 QGroupBox 的源码
  • 网络编程基础
  • mybatis-plus开发orm
  • Word处理控件Spire.Doc系列教程:C# 为 Word 文档设置背景颜色或背景图片
  • 静压模型SWASH学习(9)——平底水槽高频驻波算例(Standing short wave in closed basin)
  • Django 入门实战:从环境搭建到构建你的第一个 Web 应用
  • PyTorch卷积层填充(Padding)与步幅(Stride)详解及代码示例
  • 一款丰富的工作流自动化平台 | N8N 83.6K ⭐
  • 基于外部中中断机制,实现以下功能: 1.按键1,按下和释放后,点亮LED 2.按键2,按下和释放后,熄灭LED 3.按键3,按下和释放后,使得LED闪烁
  • Android 中实现图片翻转动画(卡片翻转效果)
  • react使用01
  • 基于微信小程序的走失儿童帮助系统-项目分享
  • PerfettoSQL
  • 火山引擎实时语音合成WebSocket V3协议Python实现demo
  • redis数据类型-基数统计HyperLogLog
  • 搜索引擎的高级语法
  • 前端性能优化全攻略:JavaScript 优化、DOM 操作、内存管理、资源压缩与合并、构建工具及性能监控
  • 复刻低成本机械臂 SO-ARM100 3D 打印篇
  • RHCE 作业二(密钥登录实验)
  • XPath 语法入门
  • day35图像处理OpenCV
  • docker镜像新增加用户+sudo权限,无dockerfile