zoukankan      html  css  js  c++  java
  • Spring面向切面编程(AOP)

    一、AOP相关概念

    • Aspect(切面): Aspect 声明类似于 Java 中的类声明,在 Aspect 中会包含着一些 Pointcut 以及相应的 Advice。
    • Joint point(连接点):表示在程序中明确定义的点,典型的包括方法调用,对类成员的访问以及异常处理程序块的执行等等,它自身还可以嵌套其它 joint point。
    • Pointcut(切点):表示一组 joint point,这些 joint point 或是通过逻辑关系组合起来,或是通过通配、正则表达式等方式集中起来,它定义了相应的 Advice 将要发生的地方。
    • Advice(增强):Advice 定义了在 Pointcut 里面定义的程序点具体要做的操作,它通过 before、after 和 around 来区别是在每个 joint point 之前、之后还是代替执行的代码。
    • Target(目标对象):织入 Advice 的目标对象.。
    • Weaving(织入):将 Aspect 和其他对象连接起来, 并创建 Adviced object 的过程。

    更通俗地讲:

    • 重复代码就叫做关注点。
    • 关注点形成的类,就叫做切面(类)。面向切面编程,就是指对很多功能都有的重复代码抽取,再在运行的时候往业务方法上动态植入"切面类代码"。
    • 执行目标对象方法,动态植入切面代码就是切入点。可以通过切入点表达式,指定拦截那些类的那些方法,给指定的类在运行的时候植入切面类代码。

    【以一个简单的例子来比喻一下 AOP 中 Aspect, Joint point, Pointcut 与 Advice之间的关系:

    假设一下, 从前有一个叫爪哇的小县城, 在一个月黑风高的晚上, 这个县城中发生了命案. 作案的凶手十分狡猾, 现场没有留下什么有价值的线索. 不过万幸的是, 刚从隔壁回来的老王恰好在这时候无意中发现了凶手行凶的过程, 但是由于天色已晚, 加上凶手蒙着面, 老王并没有看清凶手的面目, 只知道凶手是个男性, 身高约七尺五寸. 爪哇县的县令根据老王的描述, 对守门的士兵下命令说: 凡是发现有身高七尺五寸的男性, 都要抓过来审问. 士兵当然不敢违背县令的命令, 只好把进出城的所有符合条件的人都抓了起来。

    上面的一个小故事和 AOP 到底有什么对应关系?

    首先我们知道, 在 Spring AOP 中 Joint point 指代的是所有方法的执行点, 而 point cut 是一个描述信息, 它修饰的是 Joint point, 通过 point cut, 我们就可以确定哪些 Joint point 可以被织入 Advice. 对应到我们在上面举的例子, 我们可以做一个简单的类比, Joint point 就相当于 爪哇的小县城里的百姓,pointcut 就相当于 老王所做的指控, 即凶手是个男性, 身高约七尺五寸, 而 Advice 则是施加在符合老王所描述的嫌疑人的动作: 抓过来审问.
    为什么可以这样类比呢?

    Joint point : 爪哇的小县城里的百姓: 因为根据定义, Joint point 是所有可能被织入 Advice 的候选的点, 在 Spring AOP中, 则可以认为所有方法执行点都是 Joint point. 而在我们上面的例子中, 命案发生在小县城中, 按理说在此县城中的所有人都有可能是嫌疑人.
    Pointcut :男性, 身高约七尺五寸: 我们知道, 所有的方法(joint point) 都可以织入 Advice, 但是我们并不希望在所有方法上都织入 Advice, 而 Pointcut 的作用就是提供一组规则来匹配joinpoint, 给满足规则的 joinpoint 添加 Advice. 同理, 对于县令来说, 他再昏庸, 也知道不能把县城中的所有百姓都抓起来审问, 而是根据凶手是个男性, 身高约七尺五寸, 把符合条件的人抓起来. 在这里 凶手是个男性, 身高约七尺五寸 就是一个修饰谓语, 它限定了凶手的范围, 满足此修饰规则的百姓都是嫌疑人, 都需要抓起来审问.
    Advice :抓过来审问, Advice 是一个动作, 即一段 Java 代码, 这段 Java 代码是作用于 point cut 所限定的那些 Joint point 上的. 同理, 对比到我们的例子中, 抓过来审问 这个动作就是对作用于那些满足 男性, 身高约七尺五寸 的爪哇的小县城里的百姓.
    Aspect::Aspect 是 point cut 与 Advice 的组合, 因此在这里我们就可以类比: “根据老王的线索, 凡是发现有身高七尺五寸的男性, 都要抓过来审问” 这一整个动作可以被认为是一个 Aspect。】

    AOP中的Joinpoint可以有多种类型:构造方法调用,字段的设置和获取,方法的调用,方法的执行,异常的处理执行,类的初始化。也就是说在AOP的概念中我们可以在上面的这些Joinpoint上织入我们自定义的Advice,但是在Spring中却没有实现上面所有的joinpoint,确切的说,Spring只支持方法执行类型的Joinpoint。

    通知Advice的类型:

    • before advice, 在 join point 前被执行的 advice. 虽然 before advice 是在 join point 前被执行, 但是它并不能够阻止 join point 的执行, 除非发生了异常(即我们在 before advice 代码中, 不能人为地决定是否继续执行 join point 中的代码)
    • after return advice, 在一个 join point 正常返回后执行的 advice
    • after throwing advice, 当一个 join point 抛出异常后执行的 advice
    • after(final) advice, 无论一个 join point 是正常退出还是发生了异常, 都会被执行的 advice.
    • around advice, 在 join point 前和 joint point 退出后都执行的 advice。这个是最常用的 advice。

    在Spring AOP中目前只有执行方法这一个连接点(因为Spring基于动态代理,所以Spring只支持方法连接点。这与一些其他的AOP框架是不同的,例如AspectJ和JBoss,除了方法切点,它们还提供了字段和构造器接入点。Spring缺少对字段连接点的支持,无法让我们创建细粒度的通知,例如拦截对象字段的修改。而且它不支持构造器连接点,我们就无法在bean创建时应用通知。但是方法拦截可以满足绝大部分的需求。如果需要方法拦截之外的连接点拦截功能,那么我 们可以利用Aspect来补充Spring AOP的功能)。

    二、实现AOP

    1. 引入依赖

    <!-- aop 依赖-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aop</artifactId>
        <version>4.3.23.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjrt</artifactId>
        <version>1.9.2</version>
    </dependency>
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>1.9.2</version>
    </dependency>
    <dependency>
        <groupId>cglib</groupId>
        <artifactId>cglib</artifactId>
        <version>3.3.0</version>
        <!-- 否则报错  Unable to process Jar entry [module-info.class] from Jar
        [jar:file:/E:/englishPath/respority/org/ow2/asm/asm/7.1/asm-7.1.jar!/] for annotations -->
        <exclusions>
            <exclusion>
                <groupId>org.ow2.asm</groupId>
                <artifactId>asm</artifactId>
            </exclusion>
        </exclusions>
    </dependency>

    2. 业务类

    import org.springframework.stereotype.Service;
    
    @Service
    public class BusinessService {
    
        public void testAop(){
            System.out.println("aop business test");
        }
    }

    3. 切面

    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.Around;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Pointcut;
    import org.springframework.stereotype.Component;
    
    /**
     * 1. 创建一个切片类(用@Aspect注解)
     * 2. 定义好切入点(用@Pointcut注解)
     * 3. 定义好通知类型
     */
    @Component //注入到ioc容器
    @Aspect //声明式一个切片类
    public class AopDemo {
    
        @Pointcut("execution(* *com.linhw.spring.service.BusinessService.testAop(..))")
        public void performance(){}
    
    
        @Around("performance()")
        public void watchPerformance(ProceedingJoinPoint jp) throws Throwable {
            //在方法之前执行
            System.out.println(" before the method is invoked");
            jp.proceed();//控制权交给目标方法
            //在方法之后执行
            System.out.println(" after the method is invoked");
        }
    }

    在配置类上添加 @EnableAspectJAutoProxy//启动AspectJ自动代理 。

    可以发现在调用业务类的testAop前后会输出切面类的两个输出文本。

    三、@Pointcut表达式语法

    *: 匹配任意数量的字符
    +:匹配制定数量的类及其子类
    ..:一般用于匹配任意数量的子包或参数

    &&:与操作符
    ||:或操作符
    !:非操作符

    1. 方法描述匹配

    execution(   方法修饰符  方法返回值  方法所属类 匹配方法名 (  方法中的形参表 )  方法申明抛出的异常  )

    其中红色字体的部分时不能省略的,各部分都支持通配符 “*” 来匹配全部。

    使用“execution(方法表达式)”匹配方法执行。

    例:* cn.javass..IPointcutService.*()    cn.javass包及所有子包下IPointcutService接口中的任何无参方法

    2. 方法参数匹配

    args():用于匹配当前执行的方法传入的参数为指定类型的执行方法;

    @args:用于匹配当前执行的方法传入的参数持有指定注解的执行;

    使用“args(参数类型列表)”匹配当前执行的方法传入的参数为指定类型的执行方法;注意是匹配传入的参数类型,不是匹配方法签名的参数类型;参数类型列表中的参数必须是类型全限定名,通配符不支持;args属于动态切入点,这种切入点开销非常大,非特殊情况最好不要使用;

    例:args (java.io.Serializable,..)   任何一个以接受“传入参数类型为 java.io.Serializable” 开头,且其后可跟任意个任意类型的参数

    使用“@args(注解列表)”匹配当前执行的方法传入的参数持有指定注解的执行;注解类型也必须是全限定类型名。

    例:@args (cn.javass.spring.chapter6.Secure)  任何一个只接受一个参数的方法,且方法运行时传入的参数持有注解 cn.javass.spring.chapter6.Secure;动态切入点,类似于arg指示符。

    3. 当前AOP代理对象类型匹配

    this():用于匹配当前AOP代理对象类型的执行方法;注意是AOP代理对象的类型匹配,这样就可能包括引入接口也类型匹配;

    使用“this(类型全限定名)”匹配当前AOP代理对象类型的执行方法;注意是AOP代理对象的类型匹配,这样就可能包括引入接口方法也可以匹配;注意this中使用的表达式必须是类型全限定名,不支持通配符。

    例:this(cn.javass.spring.chapter6.service.IIntroductionService)  当前AOP对象实现了 IIntroductionService接口的任何方法也可能是引入接口

    4. 目标类匹配

    target():用于匹配当前目标对象类型的执行方法;注意是目标对象的类型匹配,这样就不包括引入接口也类型匹配;注意target中使用的表达式必须是类型全限定名,不支持通配符。

    @target:用于匹配当前目标对象类型的执行方法,其中目标对象持有指定的注解;

    within():用于匹配指定类型内的方法执行;

    @within:用于匹配所以持有指定注解类型内的方法;

    使用“target(类型全限定名)”匹配当前目标对象类型的执行方法;注意是目标对象的类型匹配,这样就不包括引入接口也类型匹配;注意target中使用的表达式必须是类型全限定名,不支持通配符。

    例:target(cn.javass.spring.chapter6.service.IPointcutService)   当前目标对象(非AOP对象)实现了 IPointcutService接口的任何方法

    使用“@target(注解类型)”匹配当前目标对象类型的执行方法,其中目标对象持有指定的注解;注解类型也必须是全限定类型名

    例:@target (cn.javass.spring.chapter6.Secure)  任何目标对象持有Secure注解的类方法,必须是在目标对象上声明这个注解,在接口上声明的对它不起作用。

    使用“within(类型表达式)”匹配指定类型内的方法执行。

    例:within(cn.javass..*)  cn.javass包及子包下的任何方法执行

    使用“@within(注解类型)”匹配所以持有指定注解类型内的方法;注解类型也必须是全限定类型名。

    例:@within (cn.javass.spring.chapter6.Secure)    任何目标对象对应的类型持有Secure注解的类方法,必须是在目标对象上声明这个注解,在接口上声明的对它不起作用。

    5. 标有此注解的方法匹配

    @annotation:用于匹配当前执行方法持有指定注解的方法;

    使用“@annotation(注解类型)”匹配当前执行方法持有指定注解的方法;注解类型也必须是全限定类型名;

    例:@annotation(cn.javass.spring.chapter6.Secure )  当前执行方法上持有注解 cn.javass.spring.chapter6.Secure将被匹配

    6. 匹配特定名称的bean对象

    bean():Spring AOP扩展的,AspectJ没有对于指示符,用于匹配特定名称的Bean对象的执行方法;

    使用“bean(Bean id或名字通配符)”匹配特定名称的Bean对象的执行方法;Spring AOP扩展的,在AspectJ中无相应概念。

    例:bean(*Service)  匹配所有以Service命名(id或name)结尾的Bean。

    AspectJ切入点支持的切入点指示符还有: call、get、set、preinitialization、staticinitialization、initialization、handler、adviceexecution、withincode、cflow、cflowbelow、if、@this、@withincode;但Spring AOP目前不支持这些指示符,使用这些指示符将抛出IllegalArgumentException异常。这些指示符Spring AOP可能会在以后进行扩展。

    四、通知参数

    如果想获取被被通知方法参数并传递给通知方法,该如何实现呢?接下来我们将介绍两种获取通知参数的方式。

    1. 使用JoinPoint获取

    Spring AOP提供使用org.aspectj.lang.JoinPoint类型获取连接点数据,任何通知方法的第一个参数都可以是JoinPoint(环绕通知是ProceedingJoinPoint,JoinPoint子类),当然第一个参数位置也可以是JoinPoint.StaticPart类型,这个只返回连接点的静态部分。

    (1) JoinPoint:提供访问当前被通知方法的目标对象、代理对象、方法参数等数据。

    package org.aspectj.lang; 
    import org.aspectj.lang.reflect.SourceLocation; 
    public interface JoinPoint { 
        String toString();         //连接点所在位置的相关信息 
        String toShortString();     //连接点所在位置的简短相关信息 
        String toLongString();     //连接点所在位置的全部相关信息 
        Object getThis();         //返回AOP代理对象 
        Object getTarget();       //返回目标对象 
        Object[] getArgs();       //返回被通知方法参数列表 
        Signature getSignature();  //返回当前连接点签名 
        SourceLocation getSourceLocation();//返回连接点方法所在类文件中的位置 
        String getKind();        //连接点类型 
        StaticPart getStaticPart(); //返回连接点静态部分 
    }

    (2) ProceedingJoinPoint:用于环绕通知,使用proceed()方法来执行目标方法。

    public interface ProceedingJoinPoint extends JoinPoint { 
        public Object proceed() throws Throwable; 
        public Object proceed(Object[] args) throws Throwable; 
    } 

    (3) JoinPoint.StaticPart:提供访问连接点的静态部分,如被通知方法签名、连接点类型等。

    public interface StaticPart { 
    Signature getSignature();    //返回当前连接点签名 
    String getKind();          //连接点类型 
        int getId();               //唯一标识 
    String toString();         //连接点所在位置的相关信息 
        String toShortString();     //连接点所在位置的简短相关信息 
        String toLongString();     //连接点所在位置的全部相关信息 
    }

    示例:使用如下方式在通知方法上声明,必须是在第一个参数,然后使用jp.getArgs()就能获取到被通知方法参数:

    @Before(value="execution(* sayBefore(*))") 
    public void before(JoinPoint jp) {} 
     
    @Before(value="execution(* sayBefore(*))") 
    public void before(JoinPoint.StaticPart jp) {} 

    2. 自动获取:通过切入点表达式可以将相应的参数自动传递给通知方法,例如前边章节讲过的返回值和异常是如何传递给通知方法的。

    在Spring AOP中,除了execution和bean指示符不能传递参数给通知方法,其他指示符都可以将匹配的相应参数或对象自动传递给通知方法。

    示例:

    @Before(value="execution(* test(*)) && args(param)", argNames="param") 
    public void before1(String param) { 
        System.out.println("===param:" + param); 
    } 

    参考博客:

    https://blog.csdn.net/q982151756/article/details/80513340

    https://www.cnblogs.com/duanxz/p/5217689.html

  • 相关阅读:
    vue 插件的使用 todolist案例
    vue 传值 混入mixin
    vue 生命周期函数
    vue 指令总结
    vue 其它的指令
    vue 监听数据变化的原理 表单数据的收集
    vue for循环中的key
    vue 学习
    vue 学习
    HDU 1029
  • 原文地址:https://www.cnblogs.com/myitnews/p/12904148.html
Copyright © 2011-2022 走看看