我们在了解SpringMVC的时候,我们使用@ControllerAdvice来完成统一异常处理或响应的统一数据格式封装,这其实就是我们的AOP思想,AOP是面向切面编程的一种编程语言,但是和语言无关,那么今天就让你对AOP思想不在陌生

背景介绍

AOP(Aspect-Oriented Programming):面向切面编程。
AOP可以说是OOP(Object Oriented Programming,面向对象编程)的补充和完善。OOP引入封装、
继承、多态等概念来建立一种对象层次结构,用于模拟公共行为的一个集合。如传统的后端三层架构分
层设计模式为:Controller、Service、DAO三层,每一层对应不同的作用

  • Conytroller 抽象了业务中,接受HTTP请求,解析/校验请求数据等操作,是前端控制器
  • Service 抽象了业务中,比较复杂的业务逻辑,是业务处理类
  • DAO 抽象了业务中,对数据的处理,是数据访问类
    其调用次序为:Controller方法调用Service方法,Service方法调用DAO方法

假如有以下三个功能:付款、转账及贷款功能,要实现统一的业务,如下图
(单个业务纵向执行:Controller -> Service -> DAO)

要在以上所有功能中,都加入记录HTTP请求执行时间的功能,需要在以上三个Controller方法
中,以方法执行完毕的时间点,减去方法执行前的时间点,得到方法执行时间。这样的代码往往横向地
散布在所有对象层次中,而与它对应的对象的核心功能毫无关系。这样的编码设计会产生大量的重复代
码,且对原有代码的侵入性非常大,也就是我们说的代码耦合性很高。

这种散布在各处的无关的代码被称为横切(Cross Cutting),在OOP设计中,它导致了大量代码的重 复,而不利于各个模块的重用。

什么是AOP

AOP(Aspect Oriented Programming)称为面向切面编程,在程序开发中主要用来解决一些系统层面
上的问题(如日志,事务,权限等),利用一种称为"横切"的技术,将那些影响了多个类的公共行为封装到一个可重用模块,并将其命名为"Aspect",即切面,使用"横切"技术,AOP把软件系统分为两个部分:核心关注点和横切关注点。业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点

AOP的使用场景及作用

对于统一业务来说,常见切面业务如:

  1. 响应统一的数据格式
  2. 统一异常处理
  3. 统一日志记录,如跟踪用户访问信息
  4. 用户统一会话管理、权限管理
  5. 方法执行时间计算
  6. 事务管理
    ……

AOP技术解决了对横切业务的统一管理

  • 横切代码的高度复用,和业务代码互相独立,满足代码设计上的高内聚低耦合
  • 系统更好的扩展性,可维护性

静态代理

例如现在有以下代码

public class AliPayService implements PayService {@Overridepublic void pay() {//1.安全检查System.out.println("安全检查");//2.记录日志System.out.println("记录日志");//3.时间统计开始System.out.println("记录开始时间");   //支付业务逻辑System.out.println("支付宝支付...");//4.时间记录结束}}

存在问题1
比如现在有支付宝和微信两种支付手段,然后每种支付方式都有很多一样的逻辑,此时就会出现

  • 每个实现类都有大量的重复代码
  • 对原有代码的侵入性,必须修改原有代码

解决办法
那肯定很多人都会把公共部分提取出模板或者公共的父类方法。然后让调用的类来使用公共模板或者继承公共的父类

存在问题二
虽然问题一解决了代码的重复问题,但是是必须要求实现类继承统一的父类,会对实现类的代码造成侵入性。那么在不改动原有代码的基础如何实现呢?

解决办法
使用代理设计模式,代理模式又分为静态代理和动态代理两种模式,关键在于:

  • 被代理类:原始类不进行任何修改,但创建和使用时,不再使用原始的被代理类,而是设计一个原始类的代理类
  • 代理类:基于被代理类,构造一个代理类,在使用时,也是使用代理类

静态代理

从代码设计上解决,可以使用静态代理设计模式。该设计模式可以采取两种手段:继承的方式,或聚合+接口的方式。

继承的方式

import org.example.demo.service.AliPayService;//通过继承的方式实现静态代理类
class AliPayServiceStaticProxyByExtends extends AliPayService {@Overridepublic void pay() {//1.安全检查System.out.println("安全检查");//2.记录日志System.out.println("记录日志");//3.时间统计开始System.out.println("记录开始时间");//支付业务逻辑:实际还是调用父类的业务super.pay();//4.时间统计结束System.out.println("记录结束时间");}
}public static void main(String[] args) throws InterruptedException {//使用时,不再直接使用原有对象,而是使用代理对象,所以是new代理类AliPayService aliPayService = new AliPayServiceStaticProxyByExtends();//使用:发起支付业务aliPayService.pay();}
}

接口的方式

import org.example.demo.service.AliPayService;
import org.example.demo.service.PayService;public class StaticProxyByImplements implements PayService {private PayService payService;//构造方法中传入被代理的对象public StaticProxyByImplements(PayService payService) {this.payService = payService;}@Overridepublic void pay() {//1.安全检查System.out.println("安全检查");//2.记录日志System.out.println("记录日志");//3.时间统计开始System.out.println("记录开始时间");//支付业务逻辑:其实还是调用传入的被代理类的方法payService.pay();//4.时间统计结束System.out.println("记录结束时间");}public static void main(String[] args) {//使用时,同样是使用代理类:通过被代理类创建代理类PayService aliPayService = new StaticProxyByImplements(new
AliPayService());//使用代理类完成支付业务aliPayService.pay();}
}

静态织入技术

从 AOP 的技术实现上看,可以通过 java 代码编译期及类加载期动态的织入字节码来解决。
java程序的开发、执行流程为:

使用一些静态织入的字节码技术,如 AspectJ,可以完成在真正运行前就织入字节码的内容。

  • 编译期织入:在Java编译期,采用特殊的编译器,将切面的字节码织入到生成的 class 字节码文件中。可以在编译 java 文件为 class 文件时织入,也可以在已生成的 class 文件再次织入。
  • 加载期织入:通过特殊的类加载器,在类被加载进虚拟机之前织入。

动态代理

上面的织入技术这种设计模式成为动态代理模式,都是在class代码运行期,动态的织入字节码

动态代理的实现方式:JDKCGLIB 的方式,这两种方式的代理目标都是被代理类中的方法,在运行期,动态的织入字节码生成代理类

  • CGLIB是Java中的动态代理框架,主要作用就是根据目标类和方法,动态生成代理类。
  • Java中的动态代理框架,几乎都是依赖字节码框架(如 ASM,Javassist 等)实现的。
  • 字节码框架是直接操作class字节码的框架。可以加载已有的class字节码文件信息,修改部分信息,或动态生成一个class

JDK实现

JDK实现时,先通过实现 InvocationHandler 接口创建方法调用处理器,再通过 Proxy 来创建代理
类。

//动态代理:使用JDK提供的api(InvocationHandler、Proxy实现),此种方式实现,要求被代理类必
须实现接口
public class PayServiceJDKInvocationHandler implements InvocationHandler {//目标对象即就是被代理对象private Object target;public PayServiceJDKInvocationHandler( Object target) {this.target = target;}//proxy代理对象@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws
Throwable {//1.安全检查System.out.println("安全检查");//2.记录日志System.out.println("记录日志");//3.时间统计开始System.out.println("记录开始时间");//通过反射调用被代理类的方法Object retVal = method.invoke(target, args);//4.时间统计结束System.out.println("记录结束时间");return retVal;}public static void main(String[] args) {PayService target=  new AliPayService();//方法调用处理器InvocationHandler handler =new PayServiceJDKInvocationHandler(target);//创建一个代理类:通过被代理类、被代理实现的接口、方法调用处理器来创建PayService proxy = (PayService) Proxy.newProxyInstance(target.getClass().getClassLoader(),new Class[]{PayService.class},handler);proxy.pay();}
}

CGLIB实现

public class PayServiceCGLIBInterceptor implements MethodInterceptor {//被代理对象private Object target;public PayServiceCGLIBInterceptor(Object target){this.target = target;}@Overridepublic Object intercept(Object o, Method method, Object[] args, MethodProxy
methodProxy) throws Throwable {//1.安全检查System.out.println("安全检查");//2.记录日志System.out.println("记录日志");//3.时间统计开始System.out.println("记录开始时间");//通过cglib的代理方法调用Object retVal = methodProxy.invoke(target, args);//4.时间统计结束System.out.println("记录结束时间");return retVal;}public static void main(String[] args) {PayService target=  new AliPayService();PayService proxy= (PayService) Enhancer.create(target.getClass(),new
PayServiceCGLIBInterceptor(target));proxy.pay();}
}

JDK和GCLIB实现的区别

  1. JDK 实现,要求被代理类必须实现接口,之后是通过 InvocationHandler 及 Proxy ,在运行时
    动态的在内存中生成了代理类对象,该代理对象是通过实现同样的接口实现(类似静态代理接口实
    现的方式),只是该代理类是在运行期时,动态的织入统一的业务逻辑字节码来完成。
  2. CGLIB 实现,被代理类可以不实现接口,是通过继承被代理类,在运行时动态的生成代理类对象。(因此不能被final修饰,因为被final修饰就不能够实现继承)

SpringAOP的实现

Spring AOP由 spring-aop 、 spring-aspects 和 spring-instrument 3 个模块组成。
SpringAOP构建在动态代理基础上,因此Spring对AOP的支持局限于方法拦截。
SpringAOP支持 JDK 和 CGLIB 方式实现动态代理。默认情况下,实现了接口的类,使用 AOP 会基于JDK 生成代理类,没有实现接口的类,会基于 CGLIB 生成代理类

注意:虽然Spring AOP由 spring-aop 、 spring-aspects 和 spring-instrument 3 个模块组成,但是SpringAOP值提供了AspectJ的注解语法支持,并没有真的实现AspectJ的编辑器,也就是说,加入spring-aspects依赖包,只是可以使用 AspectJ 的语法,运行时还是基于spring-aop 依赖包的动态代理实现

SpringAOP示例

AOP已经形成了自己的术语。描述切面的常用术语有通知(advice),切点(pointcut)和连接点(joinpoint) ![在这里插入图片描述](https://img-blog.csdnimg.cn/9aca0dc630de45ffbd7cb126ccef0cbc.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3draDE4ODkxODQzMTY1,size_16,color_FFFFFF,t_70)

切面(Aspect)

切面(Aspect)由 切点(Pointcut)和通知(Advice)组成,它既包含了横切逻辑的定义,也包括了连接点的定义。Spring AOP就是负责实施切面的框架,它将切面所定义的横切逻辑织入到切面所指定的连接点中。 切面由容器中的Bean使用@Aspect注解实现,如:

@Aspect
@Component
public class PojoAspect {}

连接点(Join Point)

  • 应用执行过程中能够插入切面的一个点,这个点可以是方法调用时,抛出异常时,甚至修改字段时。切
    面代码可以利用这些点插入到应用的正常流程之中,并添加新的行为。

  • 在 Spring AOP 中,Join Point 总是方法的执行点, 即只有方法连接点。所以使用SpringAOP,无需使用
    连接点的代码

切点(Pointcut)

在 Spring 中, 所有的方法都可以认为是 Join Point,但是我们并不希望在所有的方法上都添加 Advice, 而 Pointcut 的作用就是提供一组规则(使用 AspectJ pointcut expression language 来描述)来匹配Join Point,给满足规则的 Join Point 添加 Advice。

如:
在切面类中的定义一个 public void 的普通方法,并使用切点注解@Pointcut

@Aspect
@Component
public class PojoAspect {/*** 切点方法:查找org.example.demo.controller包下,以Test开头,Controller结尾的类,* 在找到的类中,再查找以pojo开头的方法。*/@Pointcut("execution(* org.example.demo.controller.Test*Controller.pojo*
(..))")public void pojoPointcut() {}
}
  • :匹配任意字符,只匹配一个元素(包,类,或方法,方法参数)
  • … :匹配任意字符,可以匹配多个元素 ,在表示类时,必须和 * 联合使用。
  • :表示按照类型匹配指定类的所有类,必须跟在类名后面,如 com.cad.Car+ ,表示继承该类的所有
    子类包括本身

通知(Advice)

切面也是有目标的 ——它必须完成的工作。在AOP术语中,切面的工作被称之为通知。 通知:定义了切面是什么,何时使用,其描述了切面要完成的工作,还解决何时执行这个工作的问题。Spring切面类中,可以在方法上使用以下注解,会设置方法为通知方法,在满足条件后会通知本方法进行调用:

  • 前置通知
    使用@Before:通知方法会在目标方法调用之前执行。
    后置通知
  • 使用@After:通知方法会在目标方法返回或者抛出异常后调用。
  • 返回之后通知
    使用@AfterReturning:通知方法会在目标方法返回后调用。
  • 抛异常后通知
    使用@AfterThrowing:通知方法会在目标方法抛出异常后调用。
  • 环绕通知
    使用@Around:通知包裹了被通知的方法,在被通知的方法通知之前和调用之后执行自定义的行
    为。

织入

织入是把切面应用到目标对象并创建新的代理对象的过程,切面在指定的连接点被织入到目标对象中。在目标对象的生命周期里有多个点可以进行织入:

  • 编译期
    切面在目标类编译时被织入。这种方式需要特殊的编译器。AspectJ的织入编译器就是以这种方式织入切面的。
  • 类加载器
    切面在目标类加载到JVM时被织入。这种方式需要特殊的类加载器(ClassLoader),它可以在目标类被引入应用之前增强该目标类的字节码。AspectJ5的加载时织入(load-time weaving. LTW)就支持以这种方式织入切面。
  • 运行期
    切面在应用运行的某一时刻被织入。一般情况下,在织入切面时,AOP容器会为目标对象动态创建一个代理对象。SpringAOP就是以这种方式织入切面的。

常见面试题:

  1. AOP的概念、使用场景、优点
  2. springAOP的实现方式(JDK和CGLIB),以及两种方式的区别
  3. springAOP的注解(如:@Aspect(切面)、@Pointcut(切点)、以及通知的几种注解)

掌握SpringAOP相关推荐

  1. 【spring】springAop开发

    xml开发方式 springboot的使用 1.声明一个parent 代码实现: <parent><groupId>org.springframework.boot</g ...

  2. Spring事务管理 与 SpringAOP

    1,Spring事务的核心接口 Spring事务管理的实现有许多细节,如果对整个接口框架有个大体了解会非常有利于我们理解事务,下面通过讲解Spring的事务接口来了解Spring实现事务的具体策略.  ...

  3. 关于Quartz的Job 不能被注入以及SpringAop对Job失效

    关于Quartz的Job 不能被注入以及SpringAop对Job失效 Problem(问题) ​ 最近在工作遇到需要对Quartz的Job进行异常后将异常记录到数据库的操作,第一反应就想到了使用Sp ...

  4. Spring AOP源码分析(八)SpringAOP要注意的地方

    2019独角兽企业重金招聘Python工程师标准>>> SpringAOP要注意的地方有很多,下面就举一个,之后想到了再列出来: (1)SpringAOP对于最外层的函数只拦截pub ...

  5. Spring-AOP的五种通知和切面的优先级、通知变量声明

    SpringAOP的通知分为以下五种: 1前置通知(@before) 在连接点执行之前执行的代码 2后置通知(@after) 在连接点执行之后执行的代码,不管连接点执行后是否出现异常,后置通知都会执行 ...

  6. Spring系列之Spring框架和SpringAOP集成过程分析(十)

    转载请注明出处:https://blog.csdn.net/zknxx/article/details/80724180 在开始这个系列之前大家先想一下我们是怎么在项目中使用SpringAOP的(这里 ...

  7. 一文带你学会基于SpringAop实现操作日志的记录

    前言 大家好,这里是经典鸡翅,今天给大家带来一篇基于SpringAop实现的操作日志记录的解决的方案.大家可能会说,切,操作日志记录这么简单的东西,老生常谈了.不! 网上的操作日志一般就是记录操作人, ...

  8. Spring-AOP实践 - 统计访问时间

    公司的项目有的页面超级慢,20s以上,不知道用户会不会疯掉,于是老大说这个页面要性能优化.于是,首先就要搞清楚究竟是哪一步耗时太多. 我采用spring aop来统计各个阶段的用时,其中计时器工具为S ...

  9. 使用Spring-AOP

    在开始使用Spring的AOP之前我们需要在bean.xml中引入aop约束,并在pom.xml导入依赖 <?xml version="1.0" encoding=" ...

  10. 注解的方式实现动态代理基于SpringAOP

    1.配置spring容器 导入jar包 com.springsource.net.sf.cglib-2.2.0.jar com.springsource.org.aopalliance-1.0.0.j ...

最新文章

  1. 启动Genymotion时报错Failed to initialize backend EGL display
  2. 如何在ubuntu上安装nvidia-docker同时与宿主共享GPU cuda加速
  3. 使用OUYA第一次启动OUYA
  4. python读excel字体颜色_无法使用python xlsxwri更改excel中的字体颜色
  5. DbHelperSQL 判断数据库表结构公用方法
  6. “约见”面试官系列之常见面试题之第七十四篇之v-if和v-for优先级(建议收藏)
  7. 如何在.NET Core中创建API
  8. 因 Cannot resolve com.lowagie:itext:2.1.7.js6,选择手动安装 Jar 包进 Maven 仓库
  9. 我学习图像处理的小结
  10. 这款Linux 恶意软件正在劫持全球的超级计算机
  11. 虚拟电脑键盘app_App发布倒计时
  12. 性能服务器可以同时标注吗,服务器上有内存标注
  13. sql语句之case when的用法
  14. 神策分析 1.17 重磅来袭,四大特性让数据治理更轻松,数据分析更深入
  15. Findbugs异常总汇
  16. dpbs和pbs的区别_PBS与TBS区别
  17. Windows 7 Service Pack 1已发布:但是您应该安装它吗?
  18. 微型计算机字,小型微型计算机系统
  19. P1478 陶陶摘苹果(升级版)(C++_贪心)
  20. PostgreSQL测试套-pg_regress使用

热门文章

  1. C++vector容器
  2. Oracle join多表查询
  3. VMware-vRealize-Log-Insight-8.2.0.0安装部署指南
  4. 如何解决常见的 Active Directory 复制错误
  5. word vba 点击任意域代码,刷新整个文档的域代码值
  6. Dubbo 学习总结(12)—— 十年再出发,Dubbo 3.0 Preview 即将在 3 月发布
  7. Spring Boot学习总结(27)—— Spring Boot中两个数据库迁移工具Liquibase和Flyway的比较
  8. Java基础学习总结(63)——Java集合总结
  9. “更高效 更自由 更开放”—— 艺赛旗RPA9.0重磅来袭
  10. Kubernetes资源分配(limit/request)