关于aop:一文带你搞定AOP切面

6次阅读

共计 11355 个字符,预计需要花费 29 分钟才能阅读完成。

摘要: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 种类型的告诉注解:

  1. @Before:前置告诉,在办法执行之前执行
  2. @After:后置告诉,在办法执行之后执行
  3. @AfterRunning:返回告诉,在办法返回后果之后执行
  4. @AfterThrowing:异样告诉,在办法抛出异样之后执行
  5. @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 就想到了一个方法,重用切入点表达式。

也就是说将这些会重复使用的切入点表达式用一个办法来示意,那么咱们的告诉注解只须要调用这个应用了该切入点表达式的办法即可实现和之前一样的成果,这样的话,咱们即便想要更改切入点表达式的接入办法,也不必一个一个的去告诉注解上批改了。

获取可重用的切入点表达式的办法是:

  1. 轻易定义一个 void 的无实现的办法
  2. 为办法增加注解 @Pointcut()
  3. 在注解中退出抽取进去的可重用的切入点表达式
  4. 应用 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 切面编程的过程:

通过配置文件实现切面

  1. 将指标类和切面类退出到容器中 相当于注解 @component
  2. 申明哪个类是切面类,相当于注解 @Aspect
  3. 在配置文件中配置五个告诉办法,通知切面类中的办法都何时运行
  4. 开启基于注解的 AOP 性能

这里有一点还须要留神:

当有两个切面类和一个盘绕办法时,办法的执行是依照配置文件中配置的先后顺序执行的,配置在前的就会先执行,配置在后的就会后执行,但同时盘绕办法进入之后就会先执行盘绕办法。

最初总结

至此通过 AspectJ 注解和 XML 配置两种形式来实现 AOP 切面编程的过程就和大家分享完了,

总体来说基于注解的申明要优先于基于 XML 的申明。通过 AspectJ 注解,切面能够与 AspectJ 兼容,而基于 XML 的配置则是 Spring 专有的。因为 AspectJ 失去越来越多的 AOP 框架反对,所以以注解格调编写的切面将会有更多重用的机会。

点击关注,第一工夫理解华为云陈腐技术~

正文完
 0