摘要:AOP在spring中又叫“面向切面编程”,是对传统咱们面向对象编程的一个补充,次要操作对象就是“切面”,能够简略的了解它是贯通于办法之中,在办法执行前、执行时、执行后、返回值后、异样后要执行的操作。
本文分享自华为云社区《一篇文搞懂《AOP面向切面编程》是一种什么样的体验?》,作者: 灰小猿。
一、什么是Spring的AOP?
AOP在spring中又叫“面向切面编程”,它能够说是对传统咱们面向对象编程的一个补充,从字面上顾名思义就能够晓得,它的次要操作对象就是“切面”,所以咱们就能够简略的了解它是贯通于办法之中,在办法执行前、执行时、执行后、返回值后、异样后要执行的操作。相当于是将咱们本来一条线执行的程序在两头切开退出了一些其余操作一样。
在利用AOP编程时,依然须要定义公共性能,但能够明确的定义这个性能利用在哪里,以什么形式利用,并且不用批改受影响的类。这样一来横切关注点就被模块化到非凡的类里——这样的类咱们通常就称之为“切面”。
例如上面这个图就是一个AOP切面的模型图,是在某一个办法执行前后执行的一些操作,并且这些操作不会影响程序自身的运行。
AOP切面编程中有一个比拟业余的术语,我给大家罗切出来了:
当初大略的理解了AOP切面编程的基本概念,接下来就是实际操作了。
二、AOP框架环境搭建
1、导入jar包
目前比拟风行且罕用的AOP框架是AspectJ,咱们在做SSM开发时用到的也是AspectJ,应用该框架技术就须要导入它所反对的jar包,
- aopalliance.jar
- aspectj.weaver.jar
- spring-aspects.jar
对于SSM开发所应用的所有jar包和相干配置文件我都已将帮大家筹备好了!
点击链接下载就能用。【全网最全】SSM开发必备依赖-Jar包、参考文档、罕用配置
2、引入AOP名称空间
应用AOP切面编程时是须要在容器中引入AOP名称空间的,
3、写配置
其实在做AOP切面编程时,最常应用也必备的一个标签就是,< aop:aspectj-autoproxy></aop:aspectj-autoproxy>,
咱们在容器中须要增加这个元素,当Spring IOC容器侦测到bean配置文件中的< aop:aspectj-autoproxy>元素时,会主动为与AspectJ切面匹配的bean创立代理。
同时在当初的spring中应用AOP切面有两种形式,别离是AspectJ注解或基于XML配置的AOP,
上面我顺次和大家介绍一下这两种形式的应用。
三、基于AspectJ注解的AOP开发
在上一篇文章中我也和大家将了对于spring中注解开发的弱小,所以对于AOP开发咱们同样也能够应用注解的模式来进行编写,上面我来和大家介绍一下如何应用注解形式书写AOP。
1、五种告诉注解
首先要在Spring中申明AspectJ切面,只须要在IOC容器中将切面申明为bean实例。
当在Spring IOC容器中初始化AspectJ切面之后,Spring IOC容器就会为那些与 AspectJ切面相匹配的bean创立代理。
在AspectJ注解中,切面只是一个带有@Aspect注解的Java类,它往往要蕴含很多告诉。告诉是标注有某种注解的简略的Java办法。
AspectJ反对5种类型的告诉注解:
- @Before:前置告诉,在办法执行之前执行
- @After:后置告诉,在办法执行之后执行
- @AfterRunning:返回告诉,在办法返回后果之后执行
- @AfterThrowing:异样告诉,在办法抛出异样之后执行
- @Around:盘绕告诉,围绕着办法执行
2、切入点表达式标准
这五种告诉注解前面还能够跟特定的参数,来指定哪一个切面办法在哪一个办法执行时触发。那么具体操作是怎么样的呢?
这里就须要和大家介绍一个名词:“切入点表达式”,通过在注解中退出该表达式参数,咱们就能够通过表达式的形式定位一个或多个具体的连接点,
切入点表达式的语法格局标准是:
execution([权限修饰符] [返回值类型] [简略类名/全类名] [办法名] ([参数列表]))
其中在表达式中有两个罕用的特殊符号:
星号“ * ”代表所有的意思,星号还能够示意任意的数值类型
“.”号:“…”示意任意类型,或任意门路下的文件,
在这里举出几个例子:
表达式:
execution( com.atguigu.spring.ArithmeticCalculator.(…))
含意:
ArithmeticCalculator接口中申明的所有办法。第一个“”代表任意修饰符及任意返回值。第二个“”代表任意办法。“…”匹配任意数量、任意类型的参数。若指标类、接口与该切面类在同一个包中能够省略包名。
表达式:
execution(public ArithmeticCalculator.(…))
含意:
ArithmeticCalculator接口的所有私有办法
表达式:
execution(public double ArithmeticCalculator.*(…))
含意:
ArithmeticCalculator接口中返回double类型数值的办法
表达式:
execution(public double ArithmeticCalculator.*(double, …))
含意:
第一个参数为double类型的办法。“…” 匹配任意数量、任意类型的参数。
表达式:
execution(public double ArithmeticCalculator.*(double, double))
含意:
参数类型为double,double类型的办法
这里还有一个定位最含糊的表达式:
execution(" (…)")
示意任意包下任意类的任意办法,然而这个表达式千万别写,哈哈,不然你每一个执行的办法都会有告诉办法执行的!
同时,在AspectJ中,切入点表达式能够通过 “&&”、“||”、“!”等操作符联合起来。
如:
execution ( .add(int,…)) || execution( .sub(int,…))
示意任意类中第一个参数为int类型的add办法或sub办法
3、注解实际
当初咱们曾经晓得了注解和切入点表达式的应用,那么接下来就是进行实际了,
对于切入点表达式,咱们能够间接在注解中应用“”写在其中,还能够在@AfterReturning注解和@AfterThrowing注解中将切入点赋值给pointcut属性,然而在其余的注解中没有pointcut这个参数。
将切入点表达式利用到理论的切面类中如下:
@Aspect //切面注解@Component //其余业务层public class LogUtli {// 办法执行开始,示意指标办法是com.spring.inpl包下的任意类的任意以两个int为参数,返回int类型参数的办法 @Before("execution(public int com.spring.inpl.*.*(int, int))") public static void LogStart(JoinPoint joinPoint) { System.out.println("告诉记录开始..."); }// 办法失常执行完之后 /** * 在程序失常执行完之后如果有返回值,咱们能够对这个返回值进行接管 * returning用来接管办法的返回值 * */ @AfterReturning(pointcut="public int com.spring.inpl.*.*(int, int)",returning="result") public static void LogReturn(JoinPoint joinPoint,Object result) { System.out.println("【" + joinPoint.getSignature().getName() + "】程序办法执行结束了...后果是:" + result); }}
以上只是一个最简略的告诉办法,然而在理论的应用过程中咱们可能会将多个告诉办法切入到同一个指标办法下来,比方同一个指标办法上既有前置告诉、又有异样告诉和后置告诉。
然而这样咱们也只是在指标办法执行时切入了一些告诉办法,那么咱们能不能在告诉办法中获取到执行的指标办法的一些信息呢?当然是能够的。
4、JoinPoint获取办法信息
在这里咱们就能够应用JoinPoint接口来获取到指标办法的信息,如办法的返回值、办法名、参数类型等。
如咱们在办法执行开始前,获取到该指标办法的办法名和输出的参数并输入。
// 办法执行开始 @Before("execution(public int com.spring.inpl.*.*(int, int))") public static void LogStart(JoinPoint joinPoint) { Object[] args = joinPoint.getArgs(); //获取到参数信息 Signature signature = joinPoint.getSignature(); //获取到办法签名 String name = signature.getName(); //获取到办法名 System.out.println("【" + name + "】记录开始...执行参数:" + Arrays.asList(args)); }
5、接管办法的返回值和异样信息
对于有些指标办法在执行完之后可能会有返回值,或者办法中途异样抛出,那么对于这些状况,咱们应该如何获取到这些信息呢?
首先咱们来获取当办法执行完之后获取返回值,
在这里咱们能够应用@AfterReturning注解,该注解示意的告诉办法是在指标办法失常执行完之后执行的。
在返回告诉中,只有将returning属性增加到@AfterReturning注解中,就能够拜访连接点的返回值。
该属性的值即为用来传入返回值的参数名称,然而留神必须在告诉办法的签名中增加一个同名参数。
在运行时Spring AOP会通过这个参数传递返回值,因为咱们可能不晓得返回值的类型,所以个别将返回值的类型设置为Object型。
与此同时,原始的切点表达式须要呈现在pointcut属性中,如下所示:
// 办法失常执行完之后 /** * 在程序失常执行完之后如果有返回值,咱们能够对这个返回值进行接管 * returning用来接管办法的返回值 * */ @AfterReturning(pointcut="public int com.spring.inpl.*.*(int, int)",returning="result") public static void LogReturn(JoinPoint joinPoint,Object result) { System.out.println("【" + joinPoint.getSignature().getName() + "】程序办法执行结束了...后果是:" + result); }
对于接管异样信息,办法其实是一样的。
咱们须要将throwing属性增加到@AfterThrowing注解中,也能够拜访连接点抛出的异样。Throwable是所有谬误和异样类的顶级父类,所以在异样告诉办法能够捕捉到任何谬误和异样。
如果只对某种非凡的异样类型感兴趣,能够将参数申明为其余异样的参数类型。而后告诉就只在抛出这个类型及其子类的异样时才被执行。
实例如下:
// 异样抛出时 /** * 在执行办法想要抛出异样的时候,能够应用throwing在注解中进行接管, * 其中value指明执行的全办法名 * throwing指明返回的错误信息 * */ @AfterThrowing(pointcut="public int com.spring.inpl.*.*(int, int)",throwing="e") public static void LogThowing(JoinPoint joinPoint,Object e) { System.out.println("【" + joinPoint.getSignature().getName() +"】发现异常信息...,异样信息是:" + e); }
6、盘绕告诉
咱们在下面介绍告诉注解的时候,大家应该也看到了其实还有一个很重要的告诉——盘绕告诉,
盘绕告诉是所有告诉类型中性能最为弱小的,可能全面地管制连接点,甚至能够管制是否执行连接点。
对于盘绕告诉来说,连接点的参数类型必须是ProceedingJoinPoint。它是 JoinPoint的子接口,容许管制何时执行,是否执行连接点。
在盘绕告诉中须要明确调用ProceedingJoinPoint的proceed()办法来执行被代理的办法。如果遗记这样做就会导致告诉被执行了,但指标办法没有被执行。这就意味着咱们须要在办法中传入参数ProceedingJoinPoint来接管办法的各种信息。
留神:
盘绕告诉的办法须要返回指标办法执行之后的后果,即调用 joinPoint.proceed();的返回值,否则会呈现空指针异样。
具体应用能够看上面这个实例:
/** * 盘绕告诉办法 * 应用注解@Around() * 须要在办法中传入参数proceedingJoinPoint 来接管办法的各种信息 * 应用盘绕告诉时须要应用proceed办法来执行办法 * 同时须要将值进行返回,盘绕办法会将须要执行的办法进行放行 * ********************************************* * @throws Throwable * */ @Around("public int com.spring.inpl.*.*(int, int)") public Object MyAround(ProceedingJoinPoint pjp) throws Throwable { // 获取到指标办法外部的参数 Object[] args = pjp.getArgs(); System.out.println("【办法执行前】");// 获取到指标办法的签名 Signature signature = pjp.getSignature(); String name = signature.getName(); Object proceed = null; try {// 进行办法的执行 proceed = pjp.proceed(); System.out.println("办法返回时"); } catch (Exception e) { System.out.println("办法异样时" + e); }finally{ System.out.println("后置办法"); } //将办法执行的返回值返回 return proceed; }
7、告诉注解的执行程序
那么当初这五种告诉注解的应用办法都曾经介绍完了,咱们来总结一下这几个告诉注解都在同一个指标办法中时的一个执行程序。
在失常状况下执行:
@Before(前置告诉)—>@After(后置告诉)---->@AfterReturning(返回告诉)
在异常情况下执行:
@Before(前置告诉)—>@After(后置告诉)---->@AfterThrowing(异样告诉)
当一般告诉和盘绕告诉同时执行时:
执行程序是:
盘绕前置----一般前置----盘绕返回/异样----盘绕后置----一般后置----一般返回/异样
8、重用切入点定义
对于下面的告诉注解,咱们都是在每一个告诉注解上都定义了一遍切入点表达式,
然而试想一个问题,如果咱们不想给这个办法设置告诉办法了,或者咱们想要将这些告诉办法切入到另一个指标办法,那么咱们岂不是要一个一个的更改注解中的切入点表达式吗?这样也太麻烦了吧?
所以spring就想到了一个方法,重用切入点表达式。
也就是说将这些会重复使用的切入点表达式用一个办法来示意,那么咱们的告诉注解只须要调用这个应用了该切入点表达式的办法即可实现和之前一样的成果,这样的话,咱们即便想要更改切入点表达式的接入办法,也不必一个一个的去告诉注解上批改了。
获取可重用的切入点表达式的办法是:
- 轻易定义一个void的无实现的办法
- 为办法增加注解@Pointcut()
- 在注解中退出抽取进去的可重用的切入点表达式
- 应用value属性将办法退出到对应的切面函数的注解中
残缺实例如下:
@Aspect //切面注解@Component //其余业务层public class LogUtli { /** * 定义切入点表达式的可重用办法 * */ @Pointcut("execution(public int com.spring.inpl.MyMathCalculator.*(int, int))") public void MyCanChongYong() {} // 办法执行开始 @Before("MyCanChongYong()") public static void LogStart(JoinPoint joinPoint) { Object[] args = joinPoint.getArgs(); //获取到参数信息 Signature signature = joinPoint.getSignature(); //获取到办法签名 String name = signature.getName(); //获取到办法名 System.out.println("【" + name + "】记录开始...执行参数:" + Arrays.asList(args)); }// 办法失常执行完之后 /** * 在程序失常执行完之后如果有返回值,咱们能够对这个返回值进行接管 * returning用来接管办法的返回值 * */ @AfterReturning(value="MyCanChongYong()",returning="result") public static void LogReturn(JoinPoint joinPoint,Object result) { System.out.println("【" + joinPoint.getSignature().getName() + "】程序办法执行结束了...后果是:" + result); } // 异样抛出时 /** * 在执行办法想要抛出异样的时候,能够应用throwing在注解中进行接管, * 其中value指明执行的全办法名 * throwing指明返回的错误信息 * */ @AfterThrowing(value="MyCanChongYong()",throwing="e") public static void LogThowing(JoinPoint joinPoint,Object e) { System.out.println("【" + joinPoint.getSignature().getName() +"】发现异常信息...,异样信息是:" + e); } // 完结得出后果 @After(value = "execution(public int com.spring.inpl.MyMathCalculator.add(int, int))") public static void LogEnd(JoinPoint joinPoint) { System.out.println("【" + joinPoint.getSignature().getName() +"】执行完结"); } /** * 盘绕告诉办法 * @throws Throwable * */ @Around("MyCanChongYong()") public Object MyAround(ProceedingJoinPoint pjp) throws Throwable { // 获取到指标办法外部的参数 Object[] args = pjp.getArgs(); System.out.println("【办法执行前】");// 获取到指标办法的签名 Signature signature = pjp.getSignature(); String name = signature.getName(); Object proceed = null; try {// 进行办法的执行 proceed = pjp.proceed(); System.out.println("办法返回时"); } catch (Exception e) { System.out.println("办法异样时" + e); }finally{ System.out.println("后置办法"); } //将办法执行的返回值返回 return proceed; }}
以上就是应用AspectJ注解实现AOP切面的全副过程了,
在这里还有一点特地有意思的规定揭示大家,就是当你有多个切面类时,切面类的执行程序是依照类名的首字符先后来执行的(不辨别大小写)。
接下来我来和大家解说一下实现AOP切面编程的另一种办法——基于XML配置的AOP实现。
四、基于XML配置的AOP实现
基于XML配置的AOP切面顾名思义就是摒弃了注解的应用,转而在IOC容器中配置切面类,这种申明是基于aop名称空间中的XML元素来实现的,
在bean配置文件中,所有的Spring AOP配置都必须定义在< aop:config>元素外部。对于每个切面而言,都要创立一个< aop:aspect>元素来为具体的切面实现援用后端bean实例。
切面bean必须有一个标识符,供< aop:aspect>元素援用。
所以咱们在bean的配置文件中首先应该先将所需切面类退出到IOC容器中去,之后在aop的元素标签中进行配置。咱们在应用注解进行开发的时候,五种告诉注解以及切入点表达式这些在xml文件中同样是能够配置进去的。
1、申明切入点
切入点应用
< aop:pointcut>元素申明。
切入点必须定义在< aop:aspect>元素下,或者间接定义在< aop:config>元素下。
定义在< aop:aspect>元素下:只对以后切面无效
定义在< aop:config>元素下:对所有切面都无效
基于XML的AOP配置不容许在切入点表达式中用名称援用其余切入点。
2、申明告诉
在aop名称空间中,每种告诉类型都对应一个特定的XML元素。
告诉元素须要应用< pointcut-ref>来援用切入点,或用< pointcut>间接嵌入切入点表达式。
method属性指定切面类中告诉办法的名称
具体应用能够看上面这里实例:
<?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/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd 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-4.0.xsd"> <!-- 通过配置文件实现切面 1、将指标类和切面类退出到容器中 @component 2、申明哪个类是切面类,@Aspect 3、在配置文件中配置五个告诉办法,通知切面类中的办法都何时运行 4、开启基于注解的AOP性能 --> <!-- 将所需类退出到容器中 --> <bean id="myCalculator" class="com.spring.inpl.MyMathCalculator"></bean> <bean id="logUtil" class="com.spring.utils.LogUtli"></bean> <bean id="SecondUtli" class="com.spring.utils.SecondUtli"></bean> <!-- 进行基于AOP的配置 --> <!-- 当有两个切面类和一个盘绕办法时,办法的执行是依照配置文件中配置的先后顺序执行的 配置在前的就会先执行,配置在后的就会后执行,但同时盘绕办法进入之后就会先执行盘绕办法 --> <aop:config> <!-- 配置一个通用类 --> <aop:pointcut expression="execution(public int com.spring.inpl.MyMathCalculator.*(int, int)))" id="myPoint"/> <!-- 配置某一个指定的切面类 --> <aop:aspect id="logUtil_Aspect" ref="logUtil"> <!-- 为具体的办法进行指定 method指定具体的办法名 pointcut指定具体要对应的办法 --> <aop:before method="LogStart" pointcut="execution(public int com.spring.inpl.MyMathCalculator.add(int, int))"/> <aop:after-throwing method="LogThowing" pointcut="execution(public int com.spring.inpl.MyMathCalculator.*(int, int)))" throwing="e"/> <aop:after-returning method="LogReturn" pointcut-ref="myPoint" returning="result"/> <aop:after method="LogEnd" pointcut-ref="myPoint"/> <!-- 定义一个盘绕办法 --> <aop:around method="MyAround" pointcut-ref="myPoint"/> </aop:aspect> <!-- 定义第二个切面类 --> <aop:aspect ref="SecondUtli"> <aop:before method="LogStart" pointcut="execution(public int com.spring.inpl.MyMathCalculator.*(..))"/> <aop:after-throwing method="LogThowing" pointcut-ref="myPoint" throwing="e"/> <aop:after method="LogEnd" pointcut-ref="myPoint"/> </aop:aspect> </aop:config></beans>
总结一下通过XML配置实现AOP切面编程的过程:
通过配置文件实现切面
- 将指标类和切面类退出到容器中 相当于注解@component
- 申明哪个类是切面类,相当于注解@Aspect
- 在配置文件中配置五个告诉办法,通知切面类中的办法都何时运行
- 开启基于注解的AOP性能
这里有一点还须要留神:
当有两个切面类和一个盘绕办法时,办法的执行是依照配置文件中配置的先后顺序执行的,配置在前的就会先执行,配置在后的就会后执行,但同时盘绕办法进入之后就会先执行盘绕办法。
最初总结
至此通过AspectJ注解和XML配置两种形式来实现AOP切面编程的过程就和大家分享完了,
总体来说基于注解的申明要优先于基于XML的申明。通过AspectJ注解,切面能够与AspectJ兼容,而基于XML的配置则是Spring专有的。因为AspectJ失去越来越多的 AOP框架反对,所以以注解格调编写的切面将会有更多重用的机会。
点击关注,第一工夫理解华为云陈腐技术~