【Spring基础系列】Spring AOP基础_守夜人爱吃兔子的博客-程序员宅基地

技术标签: spring  java  aop  编程语言  大数据  

关于AOP

AOP概述

如果说 IoC 是 Spring 的核心,那么面向切面编程就是 Spring 最为重要的功能之一了。

  • 面向切面编程(AOP)和面向对象编程(OOP)类似,也是一种编程模式。Spring AOP 是基于 AOP 编程模式的一个框架,它的使用有效减少了系统间的重复代码,达到了模块间的松耦合目的。
  • AOP 的全称是“Aspect Oriented Programming”,即面向切面编程,它将业务逻辑的各个部分进行隔离,使开发人员在编写业务逻辑时可以专心于核心业务,从而提高了开发效率。
  • AOP 采取横向抽取机制,取代了传统纵向继承体系的重复性代码,其应用主要体现在事务处理、日志管理、权限控制、异常处理等方面。
  • 目前最流行的 AOP 框架有两个,分别为 Spring AOP 和 AspectJ:
    • Spring AOP 使用纯 Java 实现,不需要专门的编译过程和类加载器,在运行期间通过代理方式向目标类植入增强的代码。
    • AspectJ 是一个基于 Java 语言的 AOP 框架,从 Spring 2.0 开始,Spring AOP 引入了对 AspectJ 的支持。AspectJ 扩展了 Java 语言,提供了一个专门的编译器,在编译时提供横向代码的植入。

面向切面编程

首先,在面向切面编程的思想里面,把功能分为核心业务功能,和周边功能:

  • 所谓的核心业务,比如登陆,增加数据,删除数据都叫核心业务
  • 所谓的周边功能,比如性能统计,日志,事务管理等等

周边功能在 Spring 的面向切面编程AOP思想里,即被定义为切面。在面向切面编程AOP的思想里面,核心业务功能和切面功能分别独立进行开发,然后把切面功能和核心业务功能 "编织" 在一起,这就叫AOP。

AOP能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性。

 

AOP基础知识

基础概念

为了更好地理解 AOP,就需要对 AOP 的相关术语有一些了解,这些专业术语主要包含 Joinpoint、Pointcut、Advice、Target、Weaving、Proxy 和 Aspect,它们的含义如下表所示。

上面的讲解有点抽象,我们再进一步进行讲解:

  • 切面(Aspect):Aspect 声明类似于 Java 中的类声明,事务管理是AOP一个最典型的应用。在AOP中,切面一般使用 @Aspect 注解来使用,在XML中,可以使用 <aop:aspect> 来定义一个切面。
  • 连接点(Join Point):一个在程序执行期间的某一个操作,就像是执行一个方法或者处理一个异常。在Spring AOP中,一个连接点就代表了一个方法的执行。
  • 通知(Advice):在切面中(类)的某个连接点(方法)采取的动作,会有四种不同的通知方式: around(环绕通知),before(前置通知),after(后置通知), exception(异常通知),return(返回通知)。许多AOP框架(包括Spring)将建议把通知作为为拦截器,并在连接点周围维护一系列拦截器。
  • 切入点(Pointcut):表示一组连接点,通知与切入点表达式有关,并在切入点匹配的任何连接点处运行(例如执行具有特定名称的方法)。由切入点表达式匹配的连接点的概念是AOP的核心,Spring默认使用AspectJ切入点表达式语言。
  • 介绍(Introduction):introduction可以为原有的对象增加新的属性和方法。例如,你可以使用introduction使bean实现IsModified接口,以简化缓存。
  • 目标对象(Target Object):由一个或者多个切面代理的对象。也被称为"切面对象"。由于Spring AOP是使用运行时代理实现的,因此该对象始终是代理对象。
  • AOP代理(AOP proxy):由AOP框架创建的对象,在Spring框架中,AOP代理对象有两种:JDK动态代理和CGLIB代理
  • 织入(Weaving):是指把增强应用到目标对象来创建新的代理对象的过程,它(例如 AspectJ 编译器)可以在编译时期,加载时期或者运行时期完成。与其他纯Java AOP框架一样,Spring AOP在运行时进行织入。

什么?还是有点蒙圈?可以看看下面这位大神的总结,言简意赅,如果还不懂,那楼哥也没办法了。

  • 切入点(Pointcut):在哪些类,哪些方法上切入(where)
  • 通知(Advice):在方法执行的什么实际(when:方法前/方法后/方法前后)做什么(what:增强的功能)
  • 切面(Aspect):切面 = 切入点 + 通知,通俗点就是:在什么时机,什么地方,做什么增强!
  • 织入(Weaving):把切面加入到对象,并创建出代理对象的过程。(由 Spring 来完成)

通知的分类

  • 前置通知(Before Advice): 在目标方法被调用前调用通知功能;相关的类org.springframework.aop.MethodBeforeAdvice
  • 后置通知(After Advice): 在目标方法被调用之后调用通知功能;相关的类org.springframework.aop.AfterReturningAdvice
  • 返回通知(After-returning): 在目标方法成功执行之后调用通知功能;
  • 异常通知(After-throwing): 在目标方法抛出异常之后调用通知功能;相关的类org.springframework.aop.ThrowsAdvice
  • 环绕通知(Around): 把整个目标方法包裹起来,在被调用前和调用之后分别调用通知功能相关的类org.aopalliance.intercept.MethodInterceptor

AOP织入的三种时期

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

 

AOP简单示例

学习语言最好的方式就是实战,其它的介绍我们就先不进行,直接看一个具体的使用示例,我们先定义一个实现类:

@Component("customerDao")
public class CustomerDaoImpl {
    public void add() {
        System.out.println("添加客户...");
    }
    public void update() {
        System.out.println("修改客户...");
    }
    public void delete() {
        System.out.println("删除客户...");
    }
    public void find() {
        System.out.println("修改客户...");
    }
}

然后再定义一个切面类:

@Component
public class MyAspect {
    // 前置通知
    public void myBefore(JoinPoint joinPoint) {
        System.out.println("前置通知,方法名称:" + joinPoint.getSignature().getName());
    }
    // 后置通知
    public void myAfterReturning(JoinPoint joinPoint) {
        System.out.println("后置通知,方法名称:" + joinPoint.getSignature().getName());
    }
    // 环绕通知
    public Object myAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        System.out.println("环绕开始"); // 开始
        Object obj = proceedingJoinPoint.proceed(); // 执行当前目标方法
        System.out.println("环绕结束"); // 结束
        return obj;
    }
    // 异常通知
    public void myAfterThrowing(JoinPoint joinPoint, Throwable e) {
        System.out.println("异常通知,出错了");
    }
    // 最终通知
    public void myAfter() {
        System.out.println("最终通知");
    }
}

下面这个非常重要,就是在applicationContext.xml文件中增加相应配置:

<!-- 用于注解扫描 -->
<context:component-scan base-package="com.java.spring.aop.xml2" />
<context:component-scan base-package="com.java.spring.aop.customer" />
<!-- 使切面开启自动代理 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
<!--AOP 编程 -->
<aop:config>
    <aop:aspect ref="myAspect">
        <!-- 配置切入点,通知最后增强哪些方法 -->
        <aop:pointcut expression="execution (* com.java.spring.aop.customer.*.* (..))" id="myPointCut" />
        <!--前置通知,关联通知 Advice和切入点PointCut -->
        <aop:before method="myBefore" pointcut-ref="myPointCut" />
        <!--后置通知,在方法返回之后执行,就可以获得返回值returning 属性 -->
        <aop:after-returning method="myAfterReturning" pointcut-ref="myPointCut" returning="returnVal" />
        <!--环绕通知 -->
        <aop:around method="myAround" pointcut-ref="myPointCut" />
        <!--抛出通知:用于处理程序发生异常,可以接收当前方法产生的异常 -->
        <!-- *注意:如果程序没有异常,则不会执行增强 -->
        <!-- * throwing属性:用于设置通知第二个参数的名称,类型Throwable -->
        <aop:after-throwing method="myAfterThrowing" pointcut-ref="myPointCut" throwing="e" />
        <!--最终通知:无论程序发生任何事情,都将执行 -->
        <aop:after method="myAfter" pointcut-ref="myPointCut" />
    </aop:aspect>
</aop:config>

这里必须要开启“使切面开启自动代理”,否则AOP不生效,因为我都是通过注解扫描自动注入,所以不要忘记“用于注解扫描”中的内容,因为我的CustomerDaoImpl和MyAspect分别放到xml2和customer这两个包下面,所以需要扫描这两个包。

对于下面这个,我估计很多同学看到这个,第一时间处于蒙圈状态:

execution (* com.java.spring.aop.customer.*.* (..))

这个其实是Spring AOP表达式的写法:

任意公共方法的执行:
execution(public * *(..))
##public可以省略, 第一个* 代表方法的任意返回值 第二个参数代表任意包+类+方法 (..)表示任意参数
 
任何一个以“get”开始的方法的执行:
execution(* get*(..))
 
UserService接口的任意方法:
execution(* com.einblatt.service.UserService.*(..))
 
定义在com.einblatt.service包里的任意方法的执行:
execution(* com.einblatt.service.*.*(..))
#第一个 .* 代表任意类, 第二个 .* 代表任意方法
 
定义在service包和所有子包里的任意类的任意方法的执行:
execution(* com.einblatt.service..*.*(..))
# ..* 代表任意包或者子包
 
定义在com.einblatt包和所有子包里的UserService类的任意方法的执行:
execution(* com.einblatt..UserService.*(..))

我们再回顾一下上面的这个表达式,第一个“星号”表示任意返回值,com.java.spring.aop.custome是需要加强对象的包路径,第二个“星号”表示这个包路径下所有的文件,第三个“星号”表示文件中类的所有的方法,最后的两个“..”,表示可以是任意参数。

编辑器不能直接打“*”符号,会被当做排版符号,我就用“星号”代替。

对于applicationContext.xml文件中的其它内容,我们配置了切面myAspect,定义切面的切入点myPointCut,然后针对该切入点,配置了前置、后置、环绕、抛出异常和最终通知等功能,所以我们执行切入点中的方法时,都会通过切面中的方法进行增强,下面的示例:

public class XMLTest {
    @Test
    public void test() {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
        CustomerDaoImpl customerDao = (CustomerDaoImpl) applicationContext.getBean("customerDao");
        // 执行方法
        customerDao.add();
    }
}
// 输出:
// 前置通知,方法名称:add
// 环绕开始
// 添加客户...
// 最终通知
// 环绕结束
// 后置通知,方法名称:add

我们的核心方法输出的是“添加客户...”,这里AOP给我们增加了前置通知、环绕通知、最终通知和后置通知,大家可以看一下调用顺序,下面测试一下抛出通知,我们修改CustomerDaoImpl内容如下:

@Component("customerDao")
public class CustomerDaoImpl {
    public void add() throws Exception {
        System.out.println("添加客户...");
        throw new Exception("抛出异常测试");
    }
    // ...
}

还是通过上面的示例测试,输出结果如下:

前置通知,方法名称:add
环绕开始
添加客户...
最终通知
异常通知,出错了

可以看到抛出异常加强输出“异常通知,出错了”,但是后面的“环绕结束”和“后置通知”就没有了。

通过这个简单的示例,大家应该多AOP有一个初步的了解,这个示例其实是基于AspectJ的XML实现方式。其实JAVA中实现AOP的方式有很多种,下面我们对现有的实现方式,进行一个整体的介绍,让大家有一个宏观的认识。

 

AOP的两种实现方式

AOP 采用了两种实现方式:静态织入(AspectJ 实现)和动态代理(Spring AOP实现)

  • 静态织入(AspectJ 实现):又可以分为基于XML的声明式,基于Annotation的声明式。(上面的示例,是AspectJ基于XML的声明式)
  • 动态代理(Spring AOP实现):又可以分为JDK动态代理和CGLib 动态代理。

所以准确来说,实现AOP其实有4种实现姿势。

AspectJ

  • AspectJ 是一个采用Java 实现的AOP框架,它能够对代码进行编译(一般在编译期进行),让代码具有AspectJ 的 AOP 功能。
  • AspectJ 是目前实现 AOP 框架中最成熟,功能最丰富的语言。
  • ApectJ 主要采用的是编译期静态织入的方式。在这个期间使用 AspectJ 的 acj 编译器(类似 javac)把 aspect 类编译成 class 字节码后,在 java 目标类编译时织入,即先编译 aspect 类再编译目标类。

Spring AOP 实现

Spring AOP 是通过动态代理技术实现的,而动态代理是基于反射设计的。Spring AOP 采用了两种混合的实现方式:JDK 动态代理和 CGLib 动态代理,分别来理解一下:

  • JDK动态代理:Spring AOP的首选方法。 每当目标对象实现一个接口时,就会使用JDK动态代理。目标对象必须实现接口
  • CGLIB代理:如果目标对象没有实现接口,则可以使用CGLIB代理。

 

 

总结

通过这篇文章,你可以掌握AOP的基础知识,以及对AOP的使用有一个初步的认识,目前项目中用的最多的其实是AspectJ基于Annotation的声明

 

最后


给大家分享一篇一线开发大牛整理的java高并发核心编程神仙文档,里面主要包含的知识点有:多线程、线程池、内置锁、JMM、CAS、JUC、高并发设计模式、Java异步回调、CompletableFuture类等。

领取文档地址:GitHub标星12.5K+Java高并发核心编程知识笔记助我提升,感觉之前学的都是渣渣

感谢阅读,文章对你有帮助的话,不妨 一键三连 支持一下吧。你们的支持是我最大的动力,祝大家万事胜意!
 

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/m0_57711043/article/details/117560339

智能推荐

面试问烂的 Spring AOP 原理、SpringMVC 过程_朱小厮的博客-程序员宅基地

点击上方“朱小厮的博客”,选择“设为星标”后台回复”加群“加入公众号专属技术群 正文   Spring AOP ,SpringMVC ,这两个应该是国内面试必问题...

WIFI 认证表_专业开发者的博客-程序员宅基地

Test plan Test Program AP Mode Support STA Mode Support Test Requirement Note Wi-Fi CERTIFIED 802.11 a/b/g/n (1 role: STA or AP) Full Test 11a/b/g/n 11a/b/g/n DUT must support Sigma tool or other throughput test tool - ..

开源Xen是如何衰落的?_weixin_34272308的博客-程序员宅基地

原文地址:http://www.searchvirtual.com.cn/showcontent_60278.htm 开源Xen到底肿么了?   五年前,开源Xen通过其最新的半虚拟化技术震撼了数据中心用户。Xen的技术支持hypervisor和虚拟机互相通讯,而且提供在所有Linux版本上的免费产品。然而,现在开源Xen已经远远落后于其他hypervisor,如VMwa...

Warnsdorff‘s algorithm 完成骑兵游行(Knight tour)问题_doubiiii的博客-程序员宅基地

问题描述:在一个8x8(或者nxn)的棋盘上,一个骑兵(马)走日(对角)能否遍历整个棋盘。http://en.wikipedia.org/wiki/Knight%27s_tourWarnsdorff's algorithm: Heruistic剪枝,排除不需要的回溯路规则如下两条:1)我们可以从棋盘上任意一处开始移动2)我们每次移动到最近,最狭隘(周围没遍历过点最少)的点(be more greedy!)算法的基本结构:1.任意选取点P为棋盘上起点2.标记P...

js利用document.visibilityState 实现类似原生的onShow onHide事件的监听 以及 监听 切换标签页_大米绿豆的博客-程序员宅基地_js实现onshow

移动端嵌入的webview一直有一个困扰就是网页端没有onShow事件 这样有很多需求都不太好做; 其实早就有了一个事件来做这个事儿; 经过我测试 目前最新版本的ios14和安卓10 11 兼容性都没问题; 可以正常使用. 所以把这个方法记录一下 分享给还不知道的小伙伴; 具体的文档我放个地址 可以自行去查看 这里我简单放一个录像演示一下https://developer.mozilla.org/zh-CN/docs/Web/API/Document/visibilityState视频展示...

不同位宽赋值方法_ChlorineBlue的博客-程序员宅基地_verilog不同位宽赋值

问题: x, y, z 位宽为 8bit, c位宽为 4bit, a=1'b1; b=8'h12; 如果 c=~a; x=b+{~a}; y=b+~a; z=b+c; 则x, y, z用二进制数表示分别是多少?c = ~a = ~4'b0001 = 4'b1110b = 8'h12 = 8'd18 = 8'b 00000001x = b + {~a} = 8'b00010010 + 1'b0 = 8'b00010010 //由于{~a},所以先对a进行取反,再补充到8位相加y = ...

随便推点

springcloud负载均衡中RestTemplate与@LoadBalanced_择业的博客-程序员宅基地

负载均衡对于一个系统架构来说非常重要,是必须要有的一个基础设施,它能够有效的缓解网络压力和流量扩容。我们知道的负载均衡可能分为两种,一种是硬件的负载均衡,比如F5服务器或者SLB负载设施;另外一种是软件负载均衡设施,比如我们耳熟能详的Nginx反向代理负载均衡、Lvs负载均衡、Haproxy负载均衡等等。但是只要是负载均衡服务都是能以类似的架构方式来构建。1.硬件方面负载均衡:F5服务器或者SLB负载设施2.软件级别负载均衡:Nginx反向代理负载均衡、Lvs负载均衡、Haproxy负载均衡等等。但

android URL 和 URI编程_pku_android的博客-程序员宅基地

作者:鲍先强涉及到网络编程时,很容易混淆的两个概念就是URL和URI,本文将会通过一个简单的程序,比较一下两者的区别,主要内容包括:1,URL。2,相对URI和绝对URI 。3,不透明的URI。4.一个演示两者区别的程序。 1.  URL: 通用资源定位器(Universal Resource Locator),按照RFC2396标准,URL对象用来定位网络里的

2022-01-24 五.Swagger API文档_不爱吃奶昔的博客-程序员宅基地_swagger的api文档

Swagger-API文档Swagger配置导包编写配置类配置类资源类yaml注解@[email protected]@[email protected]@[email protected]@ApiIgnoreswagger-ui3以下版本3及以上版本参考文章Swagger配置导包3.0.0以下版本maven导入依赖:&lt;!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagg

hdu 1248完全背包二维数组做法_昔拉天使的博客-程序员宅基地_完全背包二维数组

有几个注意点:k数量循环必须降序,jj金钱遍历必须从1或0开始code:#include &lt;iostream&gt;using namespace std;int c[4]={0,150,200,350};int w[4]={0,150,200,350};int main(int argc, char *argv[]){ int n; scanf("%d",&...

gulp 编译es6 探究_weixin_30340775的博客-程序员宅基地

1.gulp配置:var gulp = require('gulp')var fs = require("fs")var babelify = require('babelify')var browserify = require('browserify')var rename=require('gulp-rename')var uglifyjs = require('...

python绘制logistic曲线_Python使用matplotlib绘制Logistic曲线操作示例_罗让的博客-程序员宅基地

本文实例讲述了Python使用matplotlib绘制Logistic曲线操作。分享给大家供大家参考,具体如下:标准Logistic函数为:f(x) = 1 / ( 1 + exp(-x) )其导函数为:f'(x) = f(x) * ( 1 - f(x) )下面使用matplotlib绘制逻辑斯蒂函数及其导函数的曲线。Python代码:# -*- coding:utf-8 -*-#!python3...

推荐文章

热门文章

相关标签