专注于 JetBrains IDEA 全家桶,永久激活,教程
持续更新 PyCharm,IDEA,WebStorm,PhpStorm,DataGrip,RubyMine,CLion,AppCode 永久激活教程

想成为架构师,那这份Spring AOP知识你得好好看看了,写的非常清楚

前言

在开发过程中Spring AOP可以说是经常使用的一个技术了,他能够让我们不污染业务的情况下进行日志、处理结果及一些入参预处理等。像Spring提供的事务操作,也是基于Spring AOP实现的。

AOP

AOP即面向切面编程,该思想主要体现于“切面”这一 理念。通俗来讲理念主要是将业务处理过程流中的某一过程、步骤或阶段,当成像乐高积木一样,可按需对某一模块进行切入所需的其他模块。当然,在这一理念中目前来讲粒度只能达到方法级的切入。

在这篇文章中,仅讲解Spring AOP的使用。

Spring AOP的使用

准备

需要引入spring aop以及aspectj,以下为aspectj所需依赖

<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.8.13</version>
</dependency>
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjrt</artifactId>
    <version>1.8.13</version>
</dependency>

切点 Pointcut

使用AOP切入时,最重要的就是告知切入的位置,其次是要将那些操作切入其中。而切入的位置,也就是切点,需要通过 @Pointcut 来进行定义,其中存在两个参数:

value:切点表达式,支持 ||(or) 和 &&(and) argNames:参数表达式,必须和注释的方法中入参一致 在切点表达式中,存在以下指示符:

execution

匹配执行方法的访问限制符、返回类型等方法信息,将符合的方法作为切点。其中表达式规则如下

execution( 访问修饰符 返回类型全类名.方法(入参类型) ) — 支持通配符

eg:

76_1.png

可用到时再进行查阅

以上表达式基本囊括了绝大部分常见的场景,而部分功能也可以通过其他方式实现

within

匹配指定类型中的方法执行,只有实现该方法的才会被匹配成功。其中类型表达式规则

within( 全类名 ) — 支持通配符

eg:

表达式 匹配目标

76_2.png

this

匹配当前AOP代理对象类型的执行方法,说白了就是匹配类型中重写/实现的方法,父类中未重写或实现的方法则不会被匹配(可以称为作用域为当前对象,非super的方法)。其中表达式规则如下

this( 全类名 )

eg:

76_3.png

target

76_4.png

this( 全类名 )

eg:

76_5.png

args

根据参数类型匹配方法入参(注意这里匹配不是匹配方法签名的类型,而是匹配传入的类型,例如String也可以通过Object匹配)。表达式规则如下

args(全类名) — 支持通 .. 匹配多参数

eg:

76_6.png

注意,由于args匹配是动态切入点,开销比较大,非必要的情况下建议尽量少用(待确认)

@within、@target、@args、@annotation

@within、@target、@args同within、target、args中匹配注解的简略写法,但该种只能匹配基于注解,无法匹配类型

@annotation匹配持有指定类型注解的方法

eg:

76_7.png

bean

该类型由Spring ASP拓展,AspectJ并无该类型。用于匹配Spring容器中指定名称的Bean方法

bean(bean名称) — 支持通 * 通配符

eg:

76_8.png

引用切点

通常使用时,会搭配该方式实现对切点的定义,如:

@Before("execution(* (@org.springframework.stereotype.Controller *).*(..))")
public void classAnnotationAop() {
    System.out.println("class annotation");
}

也可以写成

@Pointcut("execution(* (@org.springframework.stereotype.Controller *).*(..))")
public void classMethod() {}
@Before("classMethod()")
public void classAnnotationAop() {
    System.out.println("class annotation");
}

其中@Before直接引用了方法classMethod()上定义的切点表达式

通知 Advise

通知类型

存在一下几种通知类型:

Before 前置通知,于目标方法执行前执行 After 后置通知,在目标方法执行后执行,但在@AfterReturn、@AfterThrowing之前执行 AfterReturn 返回后通知,在方法成功执行并返回结果时执行 AfterThrowing 异常处理通知,在方法执行过程中抛出异常时执行 Around 环绕通知,最特殊的通知方式,可以实现上面几种通知相同的功能,但该通知不同于@Before,需要自己手动通过ProceedingJoinPoint.proceed进行调用切入方法 执行顺序如图

76_9.png

通知入参

在任何的通知中,都支持JoinPoint类型的入参,可以通过该 入参获取当前被通知方法的目标对象、代理对象、方法参数等数据。该类型提供了以下方法

public interface JoinPoint {
    String toString(); // 连接点的相关信息,如 execution(int xyz.me4cxy.aop.advise.AdviseTarget.test(int))
    String toShortString(); // 连接点的简短相关信息,如 execution(AdviseTarget.test(..))
    String toLongString(); // 连接点的详细相关信息,如 execution(public int xyz.me4cxy.aop.advise.AdviseTarget.test(int))
    Object getThis(); // 获取AOP代理对象
    Object getTarget(); // 获取目标对象
    Object[] getArgs(); // 获取当前执行方法入参
    Signature getSignature(); // 获取当前执行方法签名
    SourceLocation getSourceLocation(); // 获取连接点方法在类文件中的位置
    String getKind(); // 获取连接点类型,如 method-execution
    JoinPoint.StaticPart getStaticPart(); // 获取连接点静态部分属性
}

同时,在@Around类型的通知中,也可以用ProceedingJoinPoint类型,该类型提供了proceed方法用于执行被代理的目标方法。

public interface ProceedingJoinPoint {
    Object proceed() throws Throwable; // 执行目标方法,并传入调用时的参数
    Object proceed(Object[] var1) throws Throwable; // 执行目标方法,并将用户自定义的参数传入
}

注意,如果需要接收JoinPoint类型的参数,那么该参数必须至于方法第一个参数,否则会出现异常

Caused by: java.lang.IllegalArgumentException: error at ::0 formal unbound in pointcut

如果是@AfterReturn或@AfterThrowing类型的通知,还可以分别通过注解参数returning和throwing指定返回值和异常传入的参数名称,如:

@AfterThrowing(value = "test()", throwing = "e")
public void afterThrowing(Exception e) {
    System.out.println("afterThrowing");
    System.out.println(e.getMessage());
}
@AfterReturning(value = "test()", returning = "result")
public void afterReturning(int result) {
    System.out.println("afterReturning");
    System.out.println(result);
}

以上的方式都是内置好会自动匹配传入的。如果我们需要根据我们匹配规则,传入我们需要的方法入参、注解时,可以上面讲述过的指示符来传入,除了execution和bean这两个指示符不能传递参数给通知方法外,其他的都可以将匹配的参数或对象作为方法入参传入。

eg:

传入匹配的入参

@Before("execution(* xyz.me4cxy.aop.advise.AdviseTarget.*(*)) && args(param)")
public void before(JoinPoint joinPoint, int param) {
    System.out.println("before");
    System.out.println(param);
}

上面例子中通过execution指示符匹配AdviseTarget中到仅有一个参数的方法,然后会通过before方法中的args参数的类型进行再次匹配执行方法的入参类型,如果两者相同就会进行切入并将参数作为入参传入param中,例如

@Component
public class AdviseTarget {
    public Object test(Object i) {
        if (i.equals(1))
            System.out.println("执行方法");
        else
            throw new RuntimeException("异常");
        return i;
    }
}

当执行test方法时,因为入参为Object类型,那么在匹配时因为before中param参数的类型是int,不符合所以将不会被切入。但是我们把test的入参修改为int/Integer,调用并传入 1 时,执行结果如下

before 1 执行方法

传入匹配的注解

@Component
public class AnnotationTarget {
    @RequestMapping({"/", "/test"})
    public void test(String param) {
        System.out.println("test");
    }
}

@Pointcut("execution(* xyz.me4cxy.aop.anno..*.*(*))")
public void exec() {}

@Pointcut("@annotation(requestMapping)")
public void requestMapping(RequestMapping requestMapping) {}

@Pointcut("args(str)")
public void stringParam(String str) {}

@Before("exec() && requestMapping(anno) && stringParam(str)")
public void before(RequestMapping anno, String str) {
    System.out.println(ArrayUtil.arrayToString(anno.value()));
    System.out.println(str);
}
@Autowired
private AnnotationTarget target;

@Test
public void test() {
    target.test("123");
}

上面例子利用了“类型自匹配”(非专业名称,纯属个人方便表达使用),通过入参类型来限制匹配的方法类型。同时也可以完成入参的控制。而上例执行结果为

/ /test 123 test

注册 Register

在这里,我使用Java配置的方式来实现AOP的注册,实际上Java配置的方式非常简单,只要在类上添加@Aspect和@Component即可(记得使用@EnableAspectJAutoProxy开启代理)。

在使用Spring AOP时,在配合@Autowired注入bean时可能会出现异常

Bean named ‘‘ is expected to be of type ‘‘ but was actually of type ‘com.sun.proxy.$Proxy**’

原因和Spring AOP代理不无关系,在【设计模式】代理模式中说过,SpringAOP是使用JDK代理和Cglib两种方式实现的,而且不难发现异常中com.sun.proxy.$Proxy**类正是JDK代理生成的代理类。

一般来说,SpringAOP在代理时,如果代理目标类实现了接口,那么SpringAOP将使用JDK代理来生成代理对象,也就是这样原因导致使用@Autowired注入会出现bean类型不一致的问题。

解决这个问题实际上很简单,修改@EnableAspectJAutoProxy注解中的参数proxyTargetClass为true即可。

最后

感谢你看到这里,看完有什么的不懂的可以在评论区问我,觉得文章对你有帮助的话记得给我点个赞,每天都会分享java相关技术文章或行业资讯,欢迎大家关注和转发文章!

文章永久链接:https://tech.souyunku.com/31149

未经允许不得转载:搜云库技术团队 » 想成为架构师,那这份Spring AOP知识你得好好看看了,写的非常清楚

JetBrains 全家桶,激活、破解、教程

提供 JetBrains 全家桶激活码、注册码、破解补丁下载及详细激活教程,支持 IntelliJ IDEA、PyCharm、WebStorm 等工具的永久激活。无论是破解教程,还是最新激活码,均可免费获得,帮助开发者解决常见激活问题,确保轻松破解并快速使用 JetBrains 软件。获取免费的破解补丁和激活码,快速解决激活难题,全面覆盖 2024/2025 版本!

联系我们联系我们