Spring-AOP的理解与实现
本文最后更新于 1126 天前,其中的信息可能已经有所发展或是发生改变。

img

Proxy的理解

img

静态代理

角色分析

  • 抽象角色 : 一般使用接口或者抽象类来实现
  • 真实角色 : 被代理的角色
  • 代理角色 : 代理真实角色
  • 代理真实角色后 , 一般会做一些附属的操作
  • 客户 : 使用代理角色来进行一些操作

代码理解1

Rent . java 即抽象角色

//抽象角色:租房
public interface Rent {
public void rent();
}

Host . java 即真实角色

//真实角色: 房东,房东要出租房子
public class Host implements Rent{
    public void rent() {
        System.out.println("房屋出租");
    }
}

Proxy . java 即代理角色

//代理角色:中介
public class Proxy implements Rent {
    private Host host;
    public Proxy() { }
    public Proxy(Host host) {
        this.host = host;
    }
    //租房
    public void rent(){
        seeHouse();
        host.rent();
        fare();
    }
    //看房
    public void seeHouse(){
        System.out.println("带房客看房");
    }
    //收中介费
    public void fare(){
        System.out.println("收中介费");
    }
}

Client . java 即客户

//客户类,一般客户都会去找代理!
public class Client {
    public static void main(String[] args) {
        //房东要租房
        Host host = new Host();
        //中介帮助房东
        Proxy proxy = new Proxy(host);
        //你去找中介!
        proxy.rent();
    }
}

代码理解2

抽象角色:

//抽象角色:增删改查业务
public interface UserService {
    void add();
    void delete();
    void update();
    void query();
}

真实角色:

//真实对象,完成增删改查操作的人
public class UserServiceImpl implements UserService {
    public void add() {
        System.out.println("增加了一个用户");
    }
    public void delete() {
        System.out.println("删除了一个用户");
    }
    public void update() {
        System.out.println("更新了一个用户");
    }
    public void query() {
        System.out.println("查询了一个用户");
    }
}

代理角色:增加日志功能

//代理角色,在这里面增加日志的实现
public class UserServiceProxy implements UserService {
    private UserServiceImpl userService;
    public void setUserService(UserServiceImpl userService) {
        this.userService = userService;
    }
    public void add() {
        log("add");
        userService.add();
    }
    public void delete() {
        log("delete");
        userService.delete();
    }
    public void update() {
        log("update");
        userService.update();
    }
    public void query() {
        log("query");
        userService.query();
    }
    public void log(String msg){
        System.out.println("执行了"+msg+"方法");
    }
}

客户:

//客户类,一般客户都会去找代理!
public class Client {
    public static void main(String[] args) {
        //真实业务
        UserServiceImpl userService = new UserServiceImpl();
        //代理类
        UserServiceProxy proxy = new UserServiceProxy();
        //使用代理类实现日志功能!
        proxy.setUserService(userService);
        proxy.add();
    }
}

利弊分析

优点:

  • 可以使得我们的真实角色更加纯粹 . 不再去关注一些公共的事情 .
  • 公共的业务由代理来完成 . 实现了业务的分工 ,
  • 公共业务发生扩展时变得更加集中和方便 .

缺点 :

  • 类多了 , 多了代理类 , 工作量变大了 . 开发效率降低 .

动态代理

上面的静态代理可以看出,如果需要代理的类如果多,首先代码量会增加,其次不够方便。

因此需要动态代理。

实现方式

  • 基于接口动态代理 ------JDK动态代理

    基本思想是:1:显示Invokhandler接口,2:使用Proxy类生成代理类对象,3:利用反射执行代理类的指定方法。

  • 基于类的动态代理-------cglib

    cglib是一个开源的项目,使用cglib也可以实现动态代理。

    实现思路是,1:导入cglib依赖包,2:代理类实现MethodInterceptor,3:使用Enhance类得到动态代理的实例,并执行指定方法。

  • 基于字节码----------------ASM、javasist

    ASM在创建class字节码的过程中,操纵的级别是底层JVM的汇编指令级别,这要求ASM使用者要对class组织结构和JVM汇编指令有一定的了解。 ClassWriter接口。

    javassit直接使用java编码的形式,而不需要了解虚拟机指令,就能动态改变类的结构,或者动态生成类。 ClassPoll,CtClass,CtMethod,CtField。

快速理解AOP

动态代理

JDK动态代理

核心 : InvocationHandler 和 Proxy

【InvocationHandler:调用处理程序】

image-20210817010251496

【Proxy : 代理】

image-20210817010307044

image-20210817010350239

修改上述代码1

代理角色:

//代理角色:public class ProxyInvocationHandler implements InvocationHandler {    private Rent rent;    public void setRent(Rent rent) {        this.rent = rent;    }    //生成代理类,重点是第二个参数,获取要代理的抽象角色!之前都是一个角色,现在可以代理一类角色    public Object getProxy(){        return Proxy.newProxyInstance(this.getClass().getClassLoader(), rent.getClass().getInterfaces(),this);    }    // proxy : 代理类 method : 代理类的调用处理程序的方法对象.    // 处理代理实例上的方法调用并返回结果    @Override    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {        seeHouse();        //核心:本质利用反射实现!        Object result = method.invoke(rent, args);        fare();        return result;    }    //看房    public void seeHouse(){        System.out.println("带房客看房");    }    //收中介费    public void fare(){        System.out.println("收中介费");    }}

客户

package com.notoop.staticproxy;/** * @author X to Y * @date 2021/8/17-1:11 *///租客public class Client1{    public static void main(String[] args) {        //真实角色        Host host = new Host();        //代理实例的调用处理程序        ProxyInvocationHandler pih = new ProxyInvocationHandler();        pih.setRent(host); //将真实角色放置进去!        Rent proxy = (Rent)pih.getProxy(); //动态生成对应的代理类!        proxy.rent();    }}

更通用的代码:

public class ProxyInvocationHandler implements InvocationHandler {    private Object target;    public void setTarget(Object target) {        this.target = target;    }    //生成代理类    public Object getProxy(){        return Proxy.newProxyInstance(this.getClass().getClassLoader(),                target.getClass().getInterfaces(),this);    }    // proxy : 代理类    // method : 代理类的调用处理程序的方法对象.    public Object invoke(Object proxy, Method method, Object[] args) throws            Throwable {        log(method.getName());        Object result = method.invoke(target, args);        return result;    }    public void log(String methodName){        System.out.println("执行了"+methodName+"方法");    }}public class Test { public static void main(String[] args) {        //真实对象        UserServiceImpl userService = new UserServiceImpl();        //代理对象的调用处理程序        ProxyInvocationHandler pih = new ProxyInvocationHandler();        pih.setTarget(userService); //设置要代理的对象        UserService proxy = (UserService)pih.getProxy(); //动态生成代理类!        proxy.delete();    }}

优点

  • 可以使得我们的真实角色更加纯粹 . 不再去关注一些公共的事情 .
  • 公共的业务由代理来完成 . 实现了业务的分工。
  • 公共业务发生扩展时变得更加集中和方便 。
  • 一个动态代理 , 一般代理某一类业务 。
  • 一个动态代理可以代理多个类,代理的是接口!

AOP是什么

AOP(Aspect-Oriented Programming,即 面向切面编程)与 OOP( Object-Oriented Programming,面向对象编程) 相辅相成,提供了与 OOP 不同的抽象软件结构的视角。

在 OOP 中,我们以类(class)作为我们的基本单元,而 AOP 中的基本单元是 Aspect(切面)

AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的 一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使 得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

img

image-20210817012850015

相关术语

Spring AOP Introduction and Concepts of AOP - DEV Community

Aspect-切面

切面是通知和切点的结合。通知和切点共同定义了切面的全部内容——它是什么,在何时和在何处完成其功能。

横切关注点 被模块化 的特殊对象。即,它是一个类。

aspectpointcountadvice 组成, 它既包含了横切逻辑的定义, 也包括了连接点的定义. Spring AOP 就是负责实施切面的框架, 它将切面所定义的横切逻辑织入到切面所指定的连接点中.
AOP 的工作重心在于如何将增强织入目标对象的连接点上, 这里包含两个工作:

  1. 如何通过 pointcut 和 advice 定位到特定的 joinpoint 上
  2. 如何在 advice 中编写切面代码.

可以简单地认为, 使用 @Aspect 注解的类就是切面.

Advice-通知/增强

切面的工作被称为通知。通知定义了切面是什么以及何时使用。除了描述切面要完成的工作,通知还解决了何时执行这个工作的问题。

切面必须要完成的工作。即,它是类中的一个方法。

由 aspect 添加到特定的 join point(即满足 point cut 规则的 join point) 的一段代码.
许多 AOP 框架, 包括 Spring AOP, 会将 advice 模拟为一个拦截器(interceptor), 并且在 join point 上维护多个 advice, 进行层层拦截.
例如 HTTP 鉴权的实 现, 我们可以为每个使用 RequestMapping 标注的方法织入 advice, 当 HTTP 请求到来时, 首先进入到 advice 代码中, 在这里我们可以分析这个 HTTP 请求是否有相应的权限, 如果有, 则执行 Controller, 如果没有, 则抛出异常. 这里的 advice 就扮演着鉴权拦截器的角色了.

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.

  • 前置通知(Before):在目标方法被调用之前调用通知功能
    后置通知(After):在目标方法完成之后调用通知,此时不会关心方法的输出是什么
    返回通知(After-returning):在目标方法成功执行之后调用通知
    异常通知(After-throwing):在目标方法抛出异常后调用通知
    环绕通知(Around):通知包裹了被通知的方法,在被通知的方法调用之前和之后执行自定义的行为

    后置通知和返回通知的区别是,后置通知是不管方法是否有异常,都会执行该通知;而返回通知是方法正常结束时才会执行。

image-20210817013700147

Point cut-切点

一个切面并不需要通知应用的所有连接点,切点有助于缩小切面所通知的连接点范围。如果说通知定义了切面的“什么”和“何时”的话,那么切点就定义了“何处”。因此,切点其实就是定义了需要执行在哪些连接点上执行通知。

切面通知 执行的 “地点”的定义。

匹配 join point 的谓词(a predicate that matches join points).
Advice 是和特定的 point cut 关联的, 并且在 point cut 相匹配的 join point 中执行.
在 Spring 中, 所有的方法都可以认为是 joinpoint, 但是我们并不希望在所有的方法上都添加 Advice, 而 pointcut 的作用就是提供一组规则(使用 AspectJ pointcut expression language 来描述) 来匹配joinpoint, 给满足规则的 joinpoint 添加 Advice.

Join point-连接点

连接点是在应用执行过程中能够插入切面的一个点。

与切入点匹配的执行点。

a point during the execution of a program, such as the execution of a method or the handling of an exception. In Spring AOP, a join point always represents a method execution.

程序运行中的一些时间点, 例如一个方法的执行, 或者是一个异常的处理.
在 Spring AOP 中, join point 总是方法的执行点, 即只有方法连接点.

关于 join point 和 point cut 的区别:

在 Spring AOP 中, 所有的方法执行都是 join point. 而 point cut 是一个描述信息, 它修饰的是 join point, 通过 point cut, 我们就可以确定哪些 join point 可以被织入 Advice. 因此 join point 和 point cut 本质上就是两个不同纬度上的东西.
advice 是在 join point 上执行的, 而 point cut 规定了哪些 join point 可以执行哪些 advice

Introduction-引入

引入允许我们向现有的类添加新方法或属性。

为一个类型添加额外的方法或字段. Spring AOP 允许我们为 目标对象 引入新的接口(和对应的实现). 例如我们可以使用 introduction 来为一个 bean 实现 IsModified 接口, 并以此来简化 caching 的实现.

Weaving-织入

织入是把切面应用到目标对象并创建新的代理对象的过程。切面在指定的连接点被织入到目标对象中。在目标对象的生命周期中有很多个点可以进行织入:

将 aspect 和其他对象连接起来, 并创建 adviced object 的过程.

根据不同的实现技术, AOP 织入有三种方式:

  • 编译器织入, 这要求有特殊的 Java 编译器.

  • 类装载期织入, 这需要有特殊的类装载器.

  • 动态代理织入, 在运行期为目标类添加增强(Advice)生成子类的方式.
    Spring 采用动态代理织入, 而 AspectJ 采用编译器织入和类装载期织入.

Target-目标对象

被通知对象。

织入 advice 的目标对象. 目标对象也被称为 advised object.
因为 Spring AOP 使用运行时代理的方式来实现 aspect, 因此 adviced object 总是一个代理对象(proxied object)
注意, adviced object 指的不是原来的类, 而是织入 advice 后所产生的代理类.

AOP proxy

向目标对象应用通知之后创建的对象。

一个类被 AOP 织入 advice, 就会产生一个结果类, 它是融合了原类和增强逻辑的代理类.
在 Spring AOP 中, 一个 AOP 代理是一个 JDK 动态代理对象或 CGLIB 代理对象.

关于 AOP Proxy

Spring AOP 默认使用标准的 JDK 动态代理(dynamic proxy)技术来实现 AOP 代理, 通过它, 我们可以为任意的接口实现代理.
如果需要为一个类实现代理, 那么可以使用 CGLIB 代理. 当一个业务逻辑对象没有实现接口时, 那么 Spring AOP 就默认使用 CGLIB 来作为 AOP 代理了. 即如果我们需要为一个方法织入 advice, 但是这个方法不是一个接口所提供的方法, 则此时 Spring AOP 会使用 CGLIB 来实现动态代理. 鉴于此, Spring AOP 建议基于接口编程, 对接口进行 AOP 而不是类.

image-20210817013740426

Spring中实现AOP

环境准备

导入依赖

 <!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->        <dependency>            <groupId>org.aspectj</groupId>            <artifactId>aspectjweaver</artifactId>            <version>1.9.6</version>        </dependency>

注:导入后一定要刷新maven,都是泪。。

通过SpringAPI

在代码理解2中增加如下类:

前置通知

public class BeforeLog implements MethodBeforeAdvice {    //method : 要执行的目标对象的方法    //objects : 被调用的方法的参数    //Object : 目标对象    @Override    public void before(Method method, Object[] objects, Object o) throws Throwable {        System.out.println( o.getClass().getName() + "的" + method.getName()                + "方法被执行了");    }}

后置通知

public class AfterLog implements AfterReturningAdvice {    //returnValue 返回值    //method被调用的方法    //args 被调用的方法的对象的参数    //target 被调用的目标对象    @Override    public void afterReturning(Object returnValue, Method method, Object[]            args, Object target) throws Throwable {        System.out.println("执行了" + target.getClass().getName()                +"的"+method.getName()+"方法,"                +"返回值:"+returnValue);    }}

编写ContextApplication.xml文件,注册bean,实现aop,导入约束

<?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:aop="http://www.springframework.org/schema/aop"       xsi:schemaLocation="http://www.springframework.org/schema/beans        http://www.springframework.org/schema/beans/spring-beans.xsd        http://www.springframework.org/schema/aop        http://www.springframework.org/schema/aop/spring-aop.xsd">    <!--注册bean-->    <bean id="userService" class="com.notoop.service.UserServiceImpl"/>    <bean id="beforeLog" class="com.notoop.service.BeforeLog"/>    <bean id="afterLog" class="com.notoop.service.AfterLog"/>    <!--aop的配置-->    <aop:config>        <!--切入点 expression:表达式匹配要执行的方法-->        <aop:pointcut id="pointcut" expression="execution(* com.notoop.service.UserServiceImpl.*(..))"/>        <!--执行环绕; advice-ref执行方法 . pointcut-ref切入点-->        <aop:advisor advice-ref="beforeLog" pointcut-ref="pointcut"/>        <aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>    </aop:config></beans>

通过自定义类

自定义类

public class DiyPointcut {    public void before(){        System.out.println("---------方法执行前---------");    }    public void after(){        System.out.println("---------方法执行后---------");    }}

配置文件

<?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:aop="http://www.springframework.org/schema/aop"       xsi:schemaLocation="http://www.springframework.org/schema/beans        http://www.springframework.org/schema/beans/spring-beans.xsd        http://www.springframework.org/schema/aop        http://www.springframework.org/schema/aop/spring-aop.xsd">    <!--注册bean-->    <bean id="userService" class="com.notoop.service.UserServiceImpl"/>    <!--第二种方式自定义实现-->    <!--注册bean-->    <bean id="diy" class="com.notoop.service.DiyPointcut"/>    <!--aop的配置-->    <aop:config>        <!--第二种方式:使用AOP的标签实现-->        <aop:aspect ref="diy">            <aop:pointcut id="diyPonitcut" expression="execution(* com.notoop.service.UserServiceImpl.*(..))"/>            <aop:before pointcut-ref="diyPonitcut" method="before"/>            <aop:after pointcut-ref="diyPonitcut" method="after"/>        </aop:aspect>    </aop:config></beans>

通过注解

注解配置类

@Aspectpublic class AnnotationPointcut {    @Before("execution(* com.notoop.service.UserServiceImpl.*(..))")    public void before(){        System.out.println("---------方法执行前---------");    }    @After("execution(* com.notoop.service.UserServiceImpl.*(..))")    public void after(){        System.out.println("---------方法执行后---------");    }    @Around("execution(* com.notoop.service.UserServiceImpl.*(..))")    public void around(ProceedingJoinPoint jp) throws Throwable {        System.out.println("环绕前");        System.out.println("签名:"+jp.getSignature());        //执行目标方法proceed        Object proceed = jp.proceed();        System.out.println("环绕后");        System.out.println(proceed);    }}

配置文件

<?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:aop="http://www.springframework.org/schema/aop"       xsi:schemaLocation="http://www.springframework.org/schema/beans        http://www.springframework.org/schema/beans/spring-beans.xsd        http://www.springframework.org/schema/aop        http://www.springframework.org/schema/aop/spring-aop.xsd">    <!--注册bean-->    <bean id="userService" class="com.notoop.service.UserServiceImpl"/>    <!--第三种方式:注解实现-->    <bean id="annotationPointcut" class="com.notoop.service.AnnotationPointcut"/>    <aop:aspectj-autoproxy/></beans>

注:

通过aop命名空间的声明自动为spring容器中那些配置@aspectJ切面 的bean创建代理,织入切面。当然,spring 在内部依旧采用 AnnotationAwareAspectJAutoProxyCreator进行自动代理的创建工作,但具体实现的细节已经被 隐藏起来了。

有一个proxy-target-class属性,默认为false,表示使用jdk动态 代理织入增强,当配为时,表示使用 CGLib动态代理技术织入增强。不过即使proxy-target-class设置为false,如果目标类没有声明接 口,则spring将自动使用CGLib动态代理。

关于@AspectJ

@AspectJ 是一种使用 Java 注解来实现 AOP 的编码风格。

@AspectJ 风格的 AOP 是 AspectJ Project 在 AspectJ 5 中引入的, 并且 Spring 也支持 @AspectJ 的 AOP 风格.

使能 @AspectJ 支持

@AspectJ 可以以 XML 的方式或以注解的方式来使能, 并且不论以哪种方式使能@ASpectJ, 我们都必须保证 aspectjweaver.jar 在 classpath 中.

使用@AspectJ

使用 Java Configuration 方式使用@AspectJ

@Configuration@EnableAspectJAutoProxypublic class AppConfig {}

使用 XML 方式使用@AspectJ

<aop:aspectj-autoproxy/>

定义 Aspect

当使用注解 @Aspect 标注一个 Bean 后, 那么 Spring 框架会自动收集这些 Bean, 并添加到 Spring AOP 中, 例如:

@Component@Aspectpublic class MyTest {}

注意, 仅仅使用@Aspect 注解, 并不能将一个 Java 对象转换为 Bean, 因此我们还需要使用类似 @Component 之类的注解.
注意, 如果一个 类被@Aspect 标注, 则这个类就不能是其他 aspect 的 **advised object** 了, 因为使用 @Aspect 后, 这个类就会被排除在 auto-proxying 机制之外.

声明 Pointcut

一个 pointcut 的声明由两部分组成:

  • 一个方法签名, 包括方法名和相关参数
  • 一个 pointcut 表达式, 用来指定哪些方法执行是我们感兴趣的(即因此可以织入 advice).

在@AspectJ 风格的 AOP 中, 我们使用一个方法来描述 pointcut, 即:

@Pointcut("execution(* com.notoop.service.UserService.*(..))") // 切点表达式
private void dataAccessOperation() {} // 切点前面

这个方法必须无返回值.
这个方法本身就是 pointcut signature, pointcut 表达式使用@Pointcut 注解指定.
上面我们简单地定义了一个 pointcut, 这个 pointcut 所描述的是: 匹配所有在包 com.notoop.service.UserService 下的所有方法的执行.

切点标志符(designator)

AspectJ5 的切点表达式由标志符(designator)和操作参数组成. 如 "execution(* greetTo(..))" 的切点表达式, **execution 就是 标志符, 而圆括号里的 ***greetTo(..) 就是操作参数

execution

匹配 join point 的执行, 例如 "execution(* hello(..))" 表示匹配所有目标类中的 hello() 方法. 这个是最基本的 pointcut 标志符.

within

匹配特定包下的所有 join point, 例如 within(com.notoop.*) 表示 com.notoop 包中的所有连接点, 即包中的所有类的所有方法. 而within(com.notoop.service.*Service) 表示在 com.notoop.service 包中所有以 Service 结尾的类的所有的连接点.

this 与 target

this 的作用是匹配一个 bean, 这个 bean(Spring AOP proxy) 是一个给定类型的实例(instance of). 而 target 匹配的是一个目标对象(target object, 即需要织入 advice 的原始的类), 此对象是一个给定类型的实例(instance of).

bean

匹配 bean 名字为指定值的 bean 下的所有方法, 例如:

bean(*Service) // 匹配名字后缀为 Service 的 bean 下的所有方法
bean(myService) // 匹配名字为 myService 的 bean 下的所有方法
args

匹配参数满足要求的的方法.
例如:

@Pointcut("within(com.notoop.demo2.*)")
public void pointcut2() {
}

@Before(value = "pointcut2()  &&  args(name)")
public void doSomething(String name) {
    logger.info("---page: {}---", name);
}
@Service
public class NormalService {
    private Logger logger = LoggerFactory.getLogger(getClass());

    public void someMethod() {
        logger.info("---NormalService: someMethod invoked---");
    }

    public String test(String name) {
        logger.info("---NormalService: test invoked---");
        return "服务一切正常";
    }
}

当 NormalService.test 执行时, 则 advice doSomething 就会执行, test 方法的参数 name 就会传递到 doSomething 中.

常用例子:

// 匹配只有一个参数 name 的方法
@Before(value = "aspectMethod()  &&  args(name)")
public void doSomething(String name) {
}

// 匹配第一个参数为 name 的方法
@Before(value = "aspectMethod()  &&  args(name, ..)")
public void doSomething(String name) {
}

// 匹配第二个参数为 name 的方法
Before(value = "aspectMethod()  &&  args(*, name, ..)")
public void doSomething(String name) {
}
@annotation

匹配由指定注解所标注的方法, 例如:

@Pointcut("@annotation(com.notoop.demo1.AuthChecker)")
public void pointcut() {
}

则匹配由注解 AuthChecker 所标注的方法.

常见的切点表达式

匹配方法签名
// 匹配指定包中的所有的方法
execution(* com.notoop.service.*(..))

// 匹配当前包中的指定类的所有方法
execution(* UserService.*(..))

// 匹配指定包中的所有 public 方法
execution(public * com.notoop.service.*(..))

// 匹配指定包中的所有 public 方法, 并且返回值是 int 类型的方法
execution(public int com.notoop.service.*(..))

// 匹配指定包中的所有 public 方法, 并且第一个参数是 String, 返回值是 int 类型的方法
execution(public int com.notoop.service.*(String name, ..))
匹配类型签名
// 匹配指定包中的所有的方法, 但不包括子包
within(com.notoop.service.*)

// 匹配指定包中的所有的方法, 包括子包
within(com.notoop.service..*)

// 匹配当前包中的指定类中的方法
within(UserService)

// 匹配一个接口的所有实现类中的实现的方法
within(UserDao+)
匹配 Bean 名字
// 匹配以指定名字结尾的 Bean 中的所有方法
bean(*Service)
切点表达式组合
// 匹配以 Service 或 ServiceImpl 结尾的 bean
bean(*Service || *ServiceImpl)

// 匹配名字以 Service 结尾, 并且在包 com.notoop.service 中的 bean
bean(*Service) && within(com.notoop.service.*)

声明 Advice

advice 是和一个 pointcut 表达式关联在一起的, 并且会在匹配的 join point 的方法执行的前/后/周围 运行. pointcut 表达式可以是简单的一个 pointcut 名字的引用, 或者是完整的 pointcut 表达式.
下面我们以几个简单的 advice 为例子, 来看一下一个 advice 是如何声明的.

Before advice

/**
 * @author xiongyongshun
 * @version 1.0
 * @created 16/9/9 13:13
 */
@Component
@Aspect
public class BeforeAspectTest {
    // 定义一个 Pointcut, 使用 切点表达式函数 来描述对哪些 Join point 使用 advise.
    @Pointcut("execution(* com.notoop.service.UserService.*(..))")
    public void dataAccessOperation() {
    }
}
@Component
@Aspect
public class AdviseDefine {
    // 定义 advise
    @Before("com.notoop.aspect.PointcutDefine.dataAccessOperation()")
    public void doBeforeAccessCheck(JoinPoint joinPoint) {
        System.out.println("*****Before advise, method: " + joinPoint.getSignature().toShortString() + " *****");
    }
}

这里, @Before 引用了一个 pointcut, 即 "com.notoop.aspect.PointcutDefine.dataAccessOperation()" 是一个 pointcut 的名字.
如果我们在 advice 在内置 pointcut, 则可以:

@Component@Aspectpublic class AdviseDefine {    // 将 pointcut 和 advice 同时定义    @Before("within(com.notoop.service..*)")    public void doAccessCheck(JoinPoint joinPoint) {        System.out.println("*****doAccessCheck, Before advise, method: " + joinPoint.getSignature().toShortString() + " *****");    }}

around advice

around advice 比较特别, 它可以在一个方法的之前之前和之后添加不同的操作, 并且甚至可以决定何时, 如何, 是否调用匹配到的方法.

@Component
@Aspect
public class AdviseDefine {
    // 定义 advise
    @Around("com.notoop.aspect.PointcutDefine.dataAccessOperation()")
    public Object doAroundAccessCheck(ProceedingJoinPoint pjp) throws Throwable {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        // 开始
        Object retVal = pjp.proceed();
        stopWatch.stop();
        // 结束
        System.out.println("invoke method: " + pjp.getSignature().getName() + ", elapsed time: " + stopWatch.getTotalTimeMillis());
        return retVal;
    }
}

around advice 和前面的 before advice 差不多, 只是我们把注解 @Before 改为了 @Around 了.

参考资料

  1. JDK、CGLIB、Javassist和ASM的动态代理使用对比
  2. AOP-JDK 和CGLIB、Javassist、ASM之间的差别 (详细)(一)
  3. Spring AOP(通知、连接点、切点、切面)
  4. kuangstudy-spring5
  5. Spring 5.3.9-core
文章作者:XY
文章链接:https://notoop.com/archives/spring-aop-understanding-and-implementation/
版权声明:本文版权归作者所有,欢迎转载,但转载请注明来自本博客X to Y‘Blog
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
error: Content is protected !!