AOP学习笔记

142 阅读1分钟

就我浅薄的理解,AOP适合需要关注点分离的场景。

    • Aspect 相当于一个关注点,比如要在所有service前后加日志。
    • PointCut 往哪加日志 具体的校验通过需要加advice的地方,加上jointpoint(joint point 还挺形象的哈)
    • Advice 具体加日志操作

开启AOP

引入依赖

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

配置类开启

@Configuration
@EnableAspectJAutoProxy()

@EnableAspectJAutoProxy()有俩参数:

  1. proxyTargetClass 默认false,设置为true则强制使用CGLib代理。默认的话,如果目标类实现了至少一个接口,走JDK动态代理,否则cglib
  2. exposeProxy 默认false,设置为true就会把代理作为一个ThreadLocal放进AopContext里。

AOP使用的代理

后面的是Spring官方文档上写的,但是事实上如果使用Spring-Boot,默认用的是CGLIb哈。

Spring AOP是基于代理实现的。默认使用JDK动态代理。如果模板对象实现了至少一个接口,走JDK动态代理,没有实现任何接口的,才走CGLIB。可以强制使用CGLIB,但是注意CGLIB的限制:

    • final方法,因为无法被运行时生成的子类覆盖,所以无法被织入其他逻辑
      • 会抛Caused by: java.lang.IllegalArgumentException: Cannot subclass final class org.springframework.boot.autoconfigure.AutoConfigurationPackages$BasePackages
    • Spring 4.0之后,被代理对象的构造函数不会再被两次调用,因为CGLIB代理对象是通过Objenesis创建的。如果你的JVM允许构造函数链(编译后加的super()),有可能会在AOP打的日志上看到两倍的调用日志。

使用AOP

定义PointCut

使用@PointCut

@Pointcut("execution(* transfer(..))") // the pointcut expression
	private void anyOldTransfer() {
}

行内表达式定义

 @Before("execution( * com..*.service..*.dataAccessOperation(..))&& args(..,count,name)")
    public void doAccessBefore( int count, String name) {
    log.info("<-----------------doAccessBefore 2 params----------------------->{}----{}", count, name);
}

Pointcut Designators (PCD/切点函数)

1. execution

  @Pointcut("execution(* transfer(..))") // the pointcut expression
    private void anyOldTransfer() {
    } // the pointcut signature

    @Pointcut("execution(public * *(..))")
    public void publicMethod() {
    }

2. within

   /*    
    *  * within: Limits matching to join points within certain types (the execution of a method declared within a matching type when using Spring AOP).
     */
    @Pointcut("within(com.jeanne.lowcode.searchservice..*)")
    public void inTrading() {
    }

3. this

匹配bean引用 (Spring AOP proxy)

4. target

匹配 目标对象 (application object being proxied)

this 和 target定义的都是State-based pointcuts,也就是会根据运行时的动态类型来确定joint point。根据AspectJ官方文档,这个主要是为了方便开发在代码里区分实际的运行对象。

Many concerns cut across the dynamic times when an object of a particular type is executing, being operated on, or being passed around. AspectJ provides primitive pointcuts that capture join points at these times. These pointcuts use the dynamic types of their objects to pick out join points. They may also be used to expose the objects used for discrimination.

this与target的区别:

The this pointcut picks out each join point where the currently executing object (the object bound to this) is an instance of a particular type. The target pointcut picks out each join point where the target object (the object on which a method is called or a field is accessed) is an instance of a particular type. Note that target should be understood to be the object the current join point is transfering control to. This means that the target object is the same as the current object at a method execution join point, for example, but may be different at a method call join point.

5. args

根据参数匹配

6. @target

执行对象的类有对应的注解

7. @args

运行期的实参有对应的注解

8. @within

执行的对象继承链上有对应注解

9. @annotation

执行的方法有对应的注解

逻辑运算符

1. &&

@Pointcut("publicMethod() && inTrading()")
    public void tradingOperation() {
}

2. ||

3. !

定义Advice

package com.jeanne.lowcode.searchservice.aspect;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

/**
 * @author Jeanne 2023/5/29
 **/
@Aspect
@Slf4j
@Component
public class NotVeryUsefulAspect {

    @Pointcut("execution(* transfer(..))") // the pointcut expression
    private void anyOldTransfer() {
    } // the pointcut signature

    @Around("anyOldTransfer()")
    @AdviceName("en-tr-sssss")
    public Object enhanceAround(ProceedingJoinPoint joinPoint) throws Throwable {
        log.info("<-----------------enhanceAround1 ignore params----------------------->");
        Object r = joinPoint.proceed();
        log.info("<-----------------enhanceAround2 ignore params----------------------->");

        return r;
    }

    @Before("anyOldTransfer()")
    public void enhanceBefore() {
        log.info("<-----------------enhanceBefore ignore params----------------------->");
    }


    @After("anyOldTransfer()")
    public void enhanceAfter() {
        log.info("<-----------------enhanceAfter ignore params----------------------->");
    }

    @AfterReturning("anyOldTransfer()")
    public void enhancAfterReturning() {
        log.info("<-----------------enhancAfterReturning ignore params----------------------->");
    }

    @AfterThrowing("anyOldTransfer()")
    public void enhancAfterThrowing() {
        log.info("<-----------------enhancAfterThrowing ignore params----------------------->");
    }

    @Before("execution( * com..*.service..*.dataAccessOperation(..))")
    public void doAccessBefore( ) {
        log.info("<-----------------doAccessBefore ignore params----------------------->");
    }

}

Advice执行时机

image.png

使用Introduction

代码手工织入

使用AspectJ编译

引入依赖

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <scope>provided</scope>
        </dependency>

使用AspectJ将领域依赖注入到Spring

Spring是不管理手工new出来的对象的,但是如果有new出来的领域对象需要Spring也管理,就可以考虑这个方式。

试了下不成功。

docs.spring.io/spring-fram…

暂时不支持的注解

@DeclarePrecedence

如果加了这个注解,启动时会抛错。这是一种fail-fast实践,核心本质就是问题越早抛出来越好。

Caused by: java.lang.IllegalArgumentException: DeclarePrecedence not presently supported in Spring AOP

@DeclareAnnotation

在aspectj-tools包里搜到的,但应该指的不是这个注解。

一些边界场景

Injection of resource dependencies failed

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'com.jeanne.lowcode.searchservice.service.NotVeryUsefulServiceTest': Injection of resource dependencies failed; nested exception is org.springframework.beans.factory.BeanNotOfRequiredTypeException: Bean named 'notVeryUsefulService' is expected to be of type 'com.jeanne.lowcode.searchservice.service.NotVeryUsefulService' but was actually of type 'jdk.proxy2.$Proxy63'

依赖

即使不加其他依赖,spring也引入了spring-aop

ProxyCreator

Infrastructure是强制使用CGLIB的

参考资料

docs.spring.io/spring-fram…

www.eclipse.org/aspectj/doc…

blog.csdn.net/qq_39368007…