详细说明Spring--AOP
这篇博客较长,耐心读完或许会有“柳暗花明又一村”的感觉哦!
为什么?
我先不说AOP是什么,我先说说为什么要用AOP,依照惯例,我还是先举一个例子:
先把项目结构展现出来:
我们先在com.jd.calculator.service里定义一个接口
ICalculatorService
:package com.jd.calculator.service;public interface ICalculatorService {//定义几个简单的加减法//加法int add(int a, int b);//除法int div(int a, int b);//乘法int mul(int a, int b); }
然后去实现这个接口:
package com.jd.calculator.service;import org.springframework.stereotype.Service;@Service public class CalculatorService implements ICalculatorService{@Overridepublic int add(int a, int b) {System.out.println("The add method begins");System.out.println("The parameters of the add method are"+a+","+b);int result = a + b;return result;}@Overridepublic int div(int a, int b) {System.out.println("The div method begins");System.out.println("The parameters of the div method are"+a+","+b);int result = a / b;return result;}@Overridepublic int mul(int a, int b) {System.out.println("The mul method begins");System.out.println("The parameters of the mul method are"+a+","+b);int result = a * b;return result;} }
我们会发现上面的三个方法除了方法名和具体的计算不同,其他的几乎完全一样,这就造成代码的冗余,开发的效率也会很低。
虽然我们可以再定义一个方法,把相同的部分放进去,然后其他方法再调用它。但假如我们的需求变了,不单单是在输出结果之前要输出一些东西,我们还想要输出结果之后再输出一些东西,出错了也要输出一些东西,那这时如果还采取这种方法就也会显得很复杂了。那这时该怎么办呢?
这时就要用到AOP了。
是什么?
AOP(Aspect Oriented Programming 面向切面编程)是一种指在程序运行期间动态的将某段代码切入到指定方法的指定位置进行运行的编程方式,这种编程方式实现了代码复用,是对传统OOP(Object Oriented Programming,面向对象编程 )的补充。目前,Aspectj是Java社区里最完整最流行的AOP框架,在Spring 2.0以上版本中可以通过Aspectj注解或基于XML配置AOP。
AOP用到上面的例子就是只关注代码重复的部分。
怎么做?
第一种方法:通过注解来实现AOP
@Before
:
- 首先需要配置一些jar包:
- 在Spring 配置文件(也就是
application.xml
)里添加:
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
,它可以自动生成代理类(不理解没关系,接着往下看) - 在
com.jd.aspects
包里自定义一个切面类:CalculatorAspects
package com.jd.aspects;import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;@Aspect //要想生成代理类,要加这个注解
@Component
public class CalculatorAspects {@Before("execution(public int com.jd.calculator.service.CalculatorService.*(..))")//匹配这些的才能生成代理类public void before(JoinPoint js){Object [] args = js.getArgs();//获取参数String name = js.getSignature().getName();//获取方法名System.out.println("The "+name+" method begins");System.out.println("The parameters of the "+name+" method are "+args[0]+","+args[1]);}
}
我们一点一点说上面的代码:
根据
@Component
可以在IOC容器中自动实例化(即生成对象,这里就不做过多讨论了);aspects是切面的意思,你可以理解成从某一点(或一系列点)切入,然后再切出。这个
CalculatorAspects
类 加上@Aspect
这个注解,就表示一个切面(即代理类),至于从哪里切入,且看第3点;@Before("execution(public int com.jd.calculator.service.CalculatorService.*(..))")
,我们先看Before,before什么意思?之前的意思,就是说要做一件事之前,先做另一件事情,我们看到Object [] args = js.getArgs();//获取参数String name = js.getSignature().getName();//获取方法名System.out.println("The "+name+" method begins");System.out.println("The parameters of the "+name+" method are "+args[0]+","+args[1]);
这段代码和
CalculatorService
类中的System.out.println("The add method begins");System.out.println("The parameters of the add method are"+a+","+b);
这段代码一样,具有同样的作用,而且都要在计算输出结果之前输出。这时你能够理解
@Before
这个注解的作用了吧:就是在做某件事之前,先做另一件事。那这时你又疑惑了,是哪件事之前呢?换句话说,切入点在哪呢?切入点就在public int com.jd.calculator.service.CalculatorService.*(..))
,(*
代表所有;..
代表参数,可以没有,也可以有多个),意思是说在执行com.jd.calculator.service
包中的CalculatorService
类 里的public int * ()
这种方法之前,要先执行加了@Before
注解的方法。加了这个
@Aspect
注解之后就会生成一个目标类(即@Before
所指向的类)的代理类,代理什么?代理的就是重复代码的那一部分。
(以下这段话不影响其他点的理解,可以不用看)那么还有一个问题,即是谁生成的这个代理类?直接告诉你答案,当在<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
中加proxy-target-class="true"
时,是由CGLIB
生成的代理类,这个代理类是目标类的子类;若不加proxy-target-class=""
,(默认值为false),则是由JDK生成的代理类,这个代理类是接口实现类。JoinPoint
就是连接点,它表示在程序中明确指明的点,就是说切点附近的东西,它里面常有的方法有:方法名 作用 例子 getSignature() getName() 获取目标方法名 add getDeclaringType().getSimpleName() 获取目标类的简单类名 CalculatorService getDeclaringTypeName() 获取目标类的完整类名 com.jd.calculator.service.CalculatorService Modifier.toString(JoinPoint.getSignature().getModifiers()) 获取目标方法声明的类型 public Object[] getArgs() 获取传入目标方法的参数[列表](从0开始) a,b getThis() 代理对象自己 com.jd.calculator.service.CalculatorService@28d18df5 getTarget() 被代理的对象 com.jd.calculator.service.CalculatorService@28d18df5
@After
、@AfterReturning
、@AfterThrowing
、@Around
:
好了,通过注解的方式实现AOP的方法已经说的大概了,到这里你是否会猜想:既然有了@Before
,那会不会有@After
,还真让你给说对了,而且不但有@After
,还有@AfterReturning
、@AfterThrowing
、@Around
。
这时聪明的你又想:既然都这样了,那它们会不会和@Before
的用法差不多啊!没错,果然smart,@After
、@AfterReturning
、@AfterThrowing
的用法确实和@Before
的用法差不多,只是@AfterReturning
和@AfterThrowing
还多了一些东西,且看下面慢慢道来:
@After
的用法和@Before
的用法完全一样,不一样的地方就是@After
修饰的方法是在目标方法执行后才执行的。@AfterReturning
,从名字中我们可以猜到被@AfterReturning
修饰的方法是在目标方法执行后才执行的,而且还会有一个返回通知:通常是计算的结果。实现的代码例如:@AfterReturning(value = "execution(public int com.jd.calculator.service.CalculatorService.*(..))",returning = "obj")//obj就是返回通知的内容public void afterReturning(JoinPoint js,Object obj){String name = js.getSignature().getName();//获取方法名System.out.println("The "+name+" method result:"+obj);}
@AfterThrowing
,同样的,我们可以看出被@AfterThrowing
修饰的方法是在目标方法执行后才执行的,那我就想问Throwing什么意思?异常的意思。意思就是说被@AfterThrowing
修饰的方法是在目标方法执行过程中出错,才执行的;如果目标方法没有出错,就不执行。实现的代码例如:@AfterThrowing(value = "execution(public int com.jd.calculator.service.CalculatorService.*(..))",throwing = "e")public void afterThrowing(JoinPoint js,Throwable e){System.out.println("错误的信息:"+e.getMessage());e.printStackTrace();//将错误打印出来}
你发现前几个注解都是见名知意,那@Around
是干什么用的?我们先来思考一番:如果要在执行一个目标方法时,我们想分别在目标方法执行前、执行后、执行过程出错时都先输出一些东西、且返回返回一些值,那这时我们要写四个方法,还要分别标注@Before
、@After
、@AfterReturning
和@AfterThrowing
四个注解,这样很麻烦,这时就可以用@Around
了,@Around
就是用来整合前四种的,它的结构如下:
try {try {doBefore();// @Before注解所修饰的方法method.invoke();// 执行目标对象内的方法(即目标方法)} finally {doAfter();// @After注解所修饰的方法}doAfterReturning();// @AfterReturning注解所修饰的方法
} catch (Throwable e) {doAfterThrowing();// @AfterThrowing注解所修饰的方法
}
上面的代码看懂了吧?好,看懂了我们来那上面的例子实战一番:
@Around("execution(public int com.jd.calculator.service.CalculatorService.*(..))")public Object around(ProceedingJoinPoint js){try {Object [] args = js.getArgs();//获取参数String name = js.getSignature().getName();//获取方法名Object result = null;try {//BeforeSystem.out.println("The "+name+" method begins");System.out.println("The parameters of the "+name+" method are "+args[0]+","+args[1]);//目标方法result = js.proceed();}finally {//AfterSystem.out.println("The "+name+" method ends");}//AfterReturningSystem.out.println("The "+name+" method result:"+result);//返回结果return result;}catch (Throwable e){//AfterThrowingSystem.out.println("错误的信息:"+e.getMessage());}return -1;}
在这里有几点需要特别注意一下:
- 首先就是参数类型,
@Around
修饰的方法必须声明ProceedingJoinPoint
类型的参数,该变量可以决定是否执行目标方法 ,这与上面四种生命的JoinPoint
类型不同; @Around
修饰的方法必须有返回值,且返回值类型为目标方法的返回值类型或其父类。而@Before
、@After
、@AfterRunning
和@AfterThrowing
修饰的方法是没有返回值的。
@Pointcut
还有一点就是我们在实现AOP时,都是这样写注解的:
@Before("execution(public int com.jd.calculator.service.CalculatorService.*(..))")
@After("execution(public int com.jd.calculator.service.CalculatorService.*(..))")
@AfterReturning(value = "execution(public int com.jd.calculator.service.CalculatorService.*(..))",returning = "obj")
@AfterThrowing(value = "execution(public int com.jd.calculator.service.CalculatorService.*(..))",throwing = "e")
@Around("execution(public int com.jd.calculator.service.CalculatorService.*(..))")
你发现了什么?哈哈,是不是还是代码冗余啊,所以这时可以用到另一个注解:@Pointcut
,pointcut表示一组joinPoint(连接点),以上重复的代码可以通过单独自定义一个@Pointcut
注解修饰的空方法来解决:
@Pointcut("execution(public int com.jd.calculator.service.CalculatorService.*(..))")public void pointcut(){}
然后其他注解调用即可:
@Before("pointcut()")
@After("pointcut()")
@AfterReturning("pointcut()")
@AfterThrowing("pointcut()")
@Around("pointcut()")
是不是简便多了?
@order(num)
如果一个目标方法匹配多个切面中相同类型增强方法(即上面四种注解修饰的方法),那这几个切面谁先执行、谁后执行呢?默认的都是按类名的字典顺序(A~Z)依次执行的;但如果加上 @order(num)
就会按照指定的顺序依次执行所有匹配的切面,其中num
数值越小,优先级越高(即被 @order(1)
修饰的切面 比 被 @order(2)
修饰的切面 先执行)。
好了,终于说完了用注解的方式去实现AOP,如果大家对AOP有了较好的理解,那接下来的用xml
文件来配置AOP就更容易理解了。
第二种方法:通过xml
文件来实现AOP:
这次我们通过xml
文件来实现AOP,也就是说像什么@Aspects
、@Order
、@Before
、@After
、@Order
等的注解都不需要再加了,
只需要自定义普通的类(但它通过配置后还起到切面的作用,所以我们就还叫它切面类)就可以了,例如:
package com.jd.aspects;import org.aspectj.lang.JoinPoint;public class CalculatorAspects {//定义普通的方法,然后在xml文件里进行配置public void before(JoinPoint js){Object [] args = js.getArgs();//获取参数String name = js.getSignature().getName();//获取方法名System.out.println("The "+name+" method begins");System.out.println("The parameters of the "+name+" method are "+args[0]+","+args[1]);}public void after(JoinPoint js){String name = js.getSignature().getName();//获取方法名System.out.println("The "+name+" method ends");}public void afterReturning(JoinPoint js,Object obj){String name = js.getSignature().getName();//获取方法名System.out.println("The "+name+" method result:"+obj);}public void afterThrowing(JoinPoint js,Throwable e){System.out.println("错误的信息:"+e.getMessage());} }
在Spring配置文件(也就是
application.xml
)里进行如下配置:
<!--因为上面的CalculatorAspects类未加'@Component'注解,所以这里需要先通过<bean></bean>来实例化对象--><bean id="calculatorAspects" class="com.jd.aspects.CalculatorAspects"></bean><!--直接在xml里配置代理类--><aop:config proxy-target-class="true"><aop:pointcut id="p" expression="execution(public int com.jd.calculator.service.CalculatorService.*(..))"/><aop:aspect ref="calculatorAspects" order="1"><aop:before pointcut-ref="p" method="before"></aop:before><aop:after pointcut-ref="p" method="after"></aop:after><aop:after-returning pointcut-ref="p" method="afterReturning" returning="obj"></aop:after-returning><aop:after-throwing pointcut-ref="p" method="afterThrowing" throwing="e"></aop:after-throwing></aop:aspect></aop:config>
我们会发现这个配置和使用注解有很多相似的地方,但更加简便:
其中的
proxy-target-class=""
和上面讲的一样,这里就不再赘述了;接着是
pointcut
和上面讲的也基本一样,这里指定一个id便于下面调用,expression=""
指向目标类;再往下:
<aop:aspect ref="calculatorAspects" order="1"></aop:aspect>
这个就是具体的某个切面类了,其中
ref=""
就是指向某个切面类,order=""
就是这个切面类的优先执行顺序;具体的:
<aop:before pointcut-ref="p" method="before"></aop:before><aop:after pointcut-ref="p" method="after"></aop:after><aop:after-returning pointcut-ref="p" method="afterReturning" returning="obj"></aop:after-returning><aop:after-throwing pointcut-ref="p" method="afterThrowing" throwing="e"></aop:after-throwing>
pointcut-ref=""
调用上面的pointcut
,指向目标类,method=""
执行切面类中具体的方法,其中after-returning
和after-throwing
分别多了一个returning=""
和throwing=""
。若直接使用around(环绕增强),则可以在
CalculatorAspects
类(代理类)中只定义一个aroud()
方法即可:package com.jd.aspects;import org.aspectj.lang.JoinPoint;public class CalculatorAspects {//定义普通的方法,然后在xml文件里进行配置public Object around(ProceedingJoinPoint js){try {Object [] args = js.getArgs();//获取参数String name = js.getSignature().getName();//获取方法名Object result = null;try {//beforeSystem.out.println("The "+name+" method begins");System.out.println("The parameters of the "+name+" method are "+args[0]+","+args[1]);//目标对象中的方法result = js.proceed();}finally {//afterSystem.out.println("The "+name+" method ends");}//afterReturningSystem.out.println("The "+name+" method result:"+result);return result;}catch (Throwable e){//AfterThrowingSystem.out.println("+++++++++++++++"+e.getMessage());}return -1;} }
需要注意的点也与上面的一样!
在:
<aop:aspect ref="calculatorAspects" order="1"></aop:aspect>
里只需要写入以下配置即可:
<aop:around pointcut-ref="p" method="around"></aop:around>
好了(长舒一口气…),终于把AOP相关的主要内容给说完了,不知你从我这里收获到一些没有?
详细说明Spring--AOP相关推荐
- Spring AOP详细介绍
AOP称为面向切面编程,在程序开发中主要用来解决一些系统层面上的问题,比如日志,事务,权限等待,Struts2的拦截器设计就是基于AOP的思想,是个比较经典的例子. 一 AOP的基本概念 (1)Asp ...
- Spring Aop 日志管理及配置文件的详细配置
Spring 带给我们的另一个好处就是让我们可以"专心做事",下面我们来看下面一个例子: public void doSameSomesing(int age,String nam ...
- Spring AOP切面使用详细解析
相关文章: SpringBoot AOP切面的使用 一步一步手绘Spring AOP运行时序图(Spring AOP 源码分析) 架构师系列内容:架构师学习笔记(持续更新)) Spring AOP 应 ...
- 简单六步上手spring aop,通过各种类型通知,面向切面编程,实现代码解耦(超详细)
aop(面向切面编程)是一种重要的编程思想,是对面向对象编程的完善和补充.我们都很熟悉"高内聚,低耦合",这是评判代码是否优质的标准之一,而aop思想,就是对这一标准的具体实现. ...
- Spring AOP 超详细源码解析
知识章节 基础知识 什么是 AOP AOP 的全称是 "Aspect Oriented Programming",即面向切面编程 在 AOP 的思想里面,周边功能(比如性能统计,日 ...
- Spring AOP全面详解(超级详细)
如果说IOC 是 Spring 的核心,那么面向切面编程AOP就是 Spring 另外一个最为重要的核心@mikechen AOP的定义 AOP (Aspect Orient Programming) ...
- Spring AOP超详细解析
AOP - 面向切面编程(Aspect Oriented Programming) Spring早期版本的核心功能:管理对象生命周期与对象分配. 即Bean本身的管理创建,以及它整个生命周期里跟其他对 ...
- 利用Spring AOP与JAVA注解为系统增加日志功能
Spring AOP一直是Spring的一个比较有特色的功能,利用它可以在现有的代码的任何地方,嵌入我们所想的逻辑功能,并且不需要改变我们现有的代码结构. 鉴于此,现在的系统已经完成了所有的功能的开发 ...
- Spring AOP 增强框架 Nepxion Matrix 详解
点击上方"方志朋",选择"置顶或者星标" 你的关注意义重大! 概述 在<深入聊一聊 Spring AOP 实现机制>一文中,介绍了 Spring A ...
- 深入聊一聊 Spring AOP 实现机制
点击上方"方志朋",选择"置顶或者星标" 你的关注意义重大! 本文转载于公众号:吉姆餐厅ak 概述 AOP(Aspect-Oriented Programmin ...
最新文章
- Python代码编写中的性能优化点
- FTP服务器搭建下的主动模式和被动模式
- 获取2个地址之间的距离(高德API)
- Java模块化方法–模块,模块,模块
- 鱼眼图像畸变校正--透视变换
- 不使用临时变量的swap再思考 -- 六种解法
- 马斯克称特斯拉Model Y今年产量有限 明年会大规模生产
- Delphi中TMediaPlayer播放音乐出现no MCI device open的错误的解决方法
- redhat rpm 卸载mysql_Linux下卸载MySQL rpm包出现error: specifies multiple packages 解决
- macOS Catalina下RX580/Vega系列显卡启动后DP输出黑屏和ALC1220声卡无法驱动的解决方案
- 【知识整理】Nmap与Masscan
- 卡尔曼滤波原理与应用
- [BZOJ4556]-[Tjoi2016Heoi2016]字符串-后缀自动姬+线段树合并+倍增
- EOJ 2月月赛补题
- 解决微博SDK接入全平台支持
- PHP头条爬虫,今日头条爬虫分析-爬取用户发的所有内容
- CoreImage核心图像图像处理
- SpringBoot:Bean生命周期介绍
- 马斯克要将特斯拉汽车送上火星,还要将《太空怪人》作为背景音乐
- layuiadmin(iframe)+tp5开发一个小型后台内容管理系统入门