likes
comments
collection
share

极简Spring之AOP

作者站长头像
站长
· 阅读数 36

简简单单实现个AOP

书接上回,这里接着介绍AOP

🍍什么是AOP?

假设有一个方法foo,现在有个需求想要记录下foo方法执行的耗时,于是就可以在foo执行之前记录一次当前时间T1,在执行之后再记录一次当前时间T2,两个时间相减就是foo方法执行耗时

以上场景就体现了AOP思想:在目标方法(foo)执行前后增加一些额外的逻辑,从而达到增强目标方法的目的

AOP最经典的实现方式就是动态代理,同时也有其他实现方法比如AJC代理和Agent代理,不过用的不多

Spring中AOP的核心就是:站在使用者角度来看,他们调用的方法看上去还是原来的foo方法,但是这个foo方法除了会执行原本的任务外,还额外增加了记录耗时的逻辑,这是怎么做到的呢?

举个栗子,Spring中有一个Bean,长这样

//目标类
public class Target {
    //目标方法
    public void foo(){
        System.out.println("执行目标方法...");
    }
}

Spring在实例化这个Bean时,发现foo方法需要被增强,于是动态生成一个代理类对象$Proxy0,大概长下面这样

//动态生成的代理类
public class $Proxy0 extend Target {
    
    Target target = new Target();
    
    //动态代理增强的方法
    public void foo(){
        long t1 = System.currentTimeMillis();
        
        //调用目标方法
        target.foo();
        
        long t2 = System.currentTimeMillis();
        System.out.println("foo方法耗时:" + (t2 - t1));
    }
}

然后用这个代理类对象顶替掉原始Bean并返回,于是Spring的IOC容器中持有的这个Target类型的Bean,本质就变成了$Proxy0这个代理类型

但是使用者并不知道,使用者看到的还是Target类型,调用这个Target类型Bean的foo方法,惊讶地发现在原本方法的基础上,这个方法还多了个计算耗时的增强逻辑

🍈要实现上面描述的功能,需要解决下面几个问题

  1. 在实例化Bean时,Spring怎么知道这个Bean应该被增强?
  2. 增强的逻辑放在哪的?怎么知道这些增强的代码分别属于哪些目标方法?
  3. 该怎么把增强逻辑与目标方法结合起来并以此产生一个代理类?
  4. 实例化Bean的哪个步骤进行上面的操作?
  5. 平时都是提前把类和方法写好,然后再编译运行,这里怎么在运行时动态生成类?

对于最后一个问题,Java有一个第三方库ASM,就是专门用来在程序运行时动态生成字节码,动态代理的两种实现方式,JDK自带的代理和CGLIB代理底层都是使用了ASM技术产生字节码,并加载进虚拟机

对前面几个问题,Spring的AOP定义了几个术语

  • 连接点JoinPoint

    也就是目标方法,需要被增强的方法

  • 切点Pointcut

    一个表达式,用来描述目标方法的特征,Spring可以通过切点来判断Bean中有没有符合切点条件的方法

  • 通知Advice

    也就是增强的逻辑,Spring可以通过某些注解得到所有通知

  • 切面Advisor

    切点和通知结合到一起,形成一个实体,也就是切面

  • 织入

    表示何时、何地以及如何将增强的逻辑应用到目标方法上

可以将这些术语理解为一个个抽象的组件,Spring一旦拥有了这些组件,在Bean后处理器中合理运用这些组件,不就可以达到用代理对象顶替掉原始Bean的目的了吗?

这就是AnnotationAwareAspectJAutoProxyCreator,如果没有循环依赖就在后初始化完成,如果有循环依赖就在依赖注入前完成并暂存在二级缓存

再好的理念,必然要有落地的代码实现,本篇AOP文档就是介绍如何使用代码实现上面的几个组件

Spring中切面分为下面两大类

  • Aspect高级切面:也就是使用@Aspect注解修饰的类,包含了多个通知 + 切点
  • Advisor低级切面:更细粒度的切面,只有一个通知 + 切点

其实Aspect只是为了方便程序员开发才出现的,而Advisor则是为了方便程序处理,将来解析时会先将Aspect解析成若干个Advisor

1 Pointcut

Pointcut——切点,也叫切点表达式,用来描述目标方法长啥样

比如* org.springframework.bean.Person.*(..))这个切点,描述了Person这个类中的所有方法

切点有了,如何判断某个方法是否匹配该切点呢?这里使用第三方库aspectJ来判断

<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.7</version>
</dependency>

封装一下这个库,得到一个用来工具类,该工具类用于判断某个方法是否匹配指定切点

首先定义两个组件,表示切点是否匹配某个类、是否匹配某个方法,抽取成如下接口

public interface ClassFilter {
    //判断指定类是否匹配切点,也就是判断类中有没有存在匹配切点的方法
    boolean matches(Class<?> clazz);
}
public interface MethodMatcher {
    //判断指定类中的指定方法是否匹配切点表达式
    boolean matches(Method method, Class<?> targetClass);
}

再添加一个接口用于管理上面的两个组件

public interface Pointcut {
    ClassFilter getClassFilter();
    MethodMatcher getMethodMatcher();
}

添加一个类AspectJExpressionPointcut,实现上面三个接口

public class AspectJExpressionPointcut implements Pointcut, ClassFilter, MethodMatcher {

    private static final Set<PointcutPrimitive> SUPPORTED_PRIMITIVES;
    static {
        SUPPORTED_PRIMITIVES = new HashSet<PointcutPrimitive>();
        //添加一个用于解析execution(* com.example.service.*.*(..))这种切点的解析器
        SUPPORTED_PRIMITIVES.add(PointcutPrimitive.EXECUTION);
    }

    //切点表达式对象
    private final PointcutExpression pointcutExpression;
    //通过构造器传入具体的切点表达式,然后根据这个表达式生成PointcutExpression
    public AspectJExpressionPointcut(String expression) {
        //aspectJ的一个名称巨长的方法
        PointcutParser pointcutParser = PointcutParser
.getPointcutParserSupportingSpecifiedPrimitivesAndUsingSpecifiedClassLoaderForResolution(SUPPORTED_PRIMITIVES, this.getClass().getClassLoader());
        pointcutExpression = pointcutParser.parsePointcutExpression(expression);
    }

    /**
     * 上面代码使用了aspectJ的api,用于根据指定切点表达式生成一个切点对象
     * 下面是实现自接口的方法,就用到了这个切点对象
     */
    @Override
    public boolean matches(Class<?> clazz) {
        //判断指定类是否匹配切点
        return pointcutExpression.couldMatchJoinPointsInType(clazz);
    }
    @Override
    public boolean matches(Method method, Class<?> targetClass) {
        return pointcutExpression.matchesMethodExecution(method).alwaysMatches();
    }
    @Override
    public ClassFilter getClassFilter() {return this;}
    @Override
    public MethodMatcher getMethodMatcher() {return this;}
}

该类封装了aspectJ提供的api,用来判断某个方法或类是否匹配指定的切点

测试一下

//准备一个切点
AspectJExpressionPointcut pointcut = 
    new AspectJExpressionPointcut("execution(* org.springframework.entity.Person.*(..))");
Class<Person> clazz = Person.class;
Method method = clazz.getDeclaredMethod("sayHello");
//判断切点是否匹配Person类
assertThat(pointcut.matches(clazz)).isTrue();
//判断切点是否匹配Person类的sayHello方法
assertThat(pointcut.matches(method, clazz)).isTrue();

2 动态代理

在运行时动态生成代理对象,有两种方式,JDK动态代理和CGLIB动态代理

下面介绍下这两种动态代理的使用方式

2.1 JDK动态代理

基于JDK的动态代理,生成的代理类和目标类会实现同样的接口,代理类和目标类属于兄弟关系

准备一个接口Foo

public interface Foo {
    void foo();
    long bar(int n);
}

准备一个实现了该接口的目标类Target

public class Target implements Foo{
    private String field;
    public void setField(String field) {this.field = field;}
    @Override
    public void foo() {
        System.out.println("正在调用目标类的foo方法...");
    }

    @Override
    public long bar(int n) {
        int r = n * n;
        System.out.println("正在调用目标类的bar方法,field是[" + field + "],并返回:" + r);
        return r;
    }
}

使用JDK代理为Target生成一个代理类,代码如下

//要生成一个代理类,需要准备四个组件:目标类、类加载器、实现的接口、回调函数
//1.首先准备一个目标类对象
Target target = new Target();
//2.将要动态生成的代理类要使用类加载器加载进JVM,所以需要提供一个类加载器
ClassLoader classLoader = this.getClass().getClassLoader();
//3.代理类要实现的接口
Class[] interfaces = {Foo.class};
/**
 * 4.代理类要使用的回调函数
 * 由于知道了要实现哪些接口,也就知道了将要生成的代理类里有哪些方法,以及方法长什么样子
 * 唯一不知道的是代理类中实现方法的代码体长啥样,对于这种不明确的地方需要我们来指定
 * 将代码体封装进函数式对象中,代理类中的实现方法内部就会调用到这里提供的函数式对象,函数对象需要几个参数:
 *    1.proxy:代理类对象本身 2.method:正在调用的代理类方法 3.args:正在调用的方法参数
 * 由于代理类和目标类实现了同样的接口,所以代理类中有的方法目标类中也有,因此可以调用到目标类中对应的方法
 */
InvocationHandler handler = (proxy, method, args) -> {
    //在调用目标方法前执行增强逻辑
    System.out.println("前置增强...");
    //调用目标类对应的方法
    Object result = method.invoke(target, args);
    //在调用目标方法后执行增强逻辑
    System.out.println("前置增强...");

    //如果目标方法有返回值,代理方法肯定也会返回值
    return result;
};

//使用下面代码生成代理类对象,由于代理类是实现自Foo接口,所以可以直接强转成Foo类型
Foo foo = (Foo)Proxy.newProxyInstance(classLoader, interfaces, handler);
//调用foo方法,发现target的foo方法就被增强了
foo.foo();

调用代理类的方法,本质都会调用到提供的回调函数,然后回调函数内部根据传入的不同方法对象再调用目标

JDK代理底层使用了ASM生成字节码并加载进JVM,生成字节码翻译成Java类大概长下面这样,了解即可

/**
 * 这里模拟了JDK生成的代理类,不过这里的代理类是写死的
 * 而JDK生成代理类是在运行时使用字节码技术ASM动态生成的
 * 有兴趣可以去了解下ASM如何生成代理类
 */
public class $Proxy0 implements Foo {
    InvocationHandler invocationHandler;
    //使用JDK代理时传入的回调函数,就是赋值给了invocationHandler
    //将来调用代理类的方法时,就是调用InvocationHandler接口的invoke方法
    public $Proxy0(InvocationHandler invocationHandler) {
        this.invocationHandler = invocationHandler;
    }

    /**
     * 这里定义了方法对象(方法来自目标类),这里为了模拟简单,方法对象是写死的
     * 运行时会分析接口方法的签名,返回值,然后生成几个方法对象的静态变量,以及实现类的代码
     */
    static Method foo;
    static Method bar;
    static {
        try {
            foo = Foo.class.getMethod("foo");
            bar = Foo.class.getMethod("bar");
        } catch (NoSuchMethodException e) {
            throw new NoSuchMethodError(e.getMessage());
        }
    }

    @Override
    public void foo() {
        try {
            //回调调用方定义的方法
            invocationHandler.invoke(this, foo, new Object[0]);
        } catch (RuntimeException | Error e) {
            throw e;
        } catch (Throwable e) {
            //将编译时异常变成运行时异常
            throw new UndeclaredThrowableException(e);
        }
    }

    @Override
    public long bar() {
        try {
            Object result = invocationHandler.invoke(this, bar, new Object[0]);
            return (long)result;
        } catch (RuntimeException | Error e) {
            throw e;
        } catch (Throwable e) {
            throw new UndeclaredThrowableException(e);
        }
    }
}

2.2 CGLIB动态代理

基于CGLIB的动态代理,其生成的代理类既可以是目标类的子类,属于父子关系,又可以跟目标类实现同样接口

CGLIB代理属于第三方库

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib-nodep</artifactId>
    <version>3.3.0</version>
</dependency>

CGLIB代理相对于JDK代理,有三种方式调用目标方法,下面演示生成目标类子类的代理对象

//高版本的JDK要想成功运行下面程序,要加JVM参数:--add-opens java.base/java.lang=ALL-UNNAMED
Target t = new Target();
//这里的MethodInterceptor是CGLIB提供的函数式接口,意思跟前面我们定义的MethodInterceptor差不多
MethodInterceptor interceptor = (MethodInterceptor) (p, method, arg, methodProxy) -> {
    System.out.println("before...");

    //通过反射调用,跟JDK代理一样
    method.invoke(t, arg);
    //methodProxy方法调用,不走反射
    methodProxy.invoke(t, arg);
    //methodProxy方法调用,不需要额外定义一个目标类对象,使用代理类对象本身即可,同样不走反射
    methodProxy.invokeSuper(p, arg);

    System.out.println("after...");
    return null;
};
Target target = (Target)Enhancer.create(Target.class, interceptor);
target.foo();

CGLIB生成的代理类结构比jdk代理类复杂,主要复杂在:CGLIB可以不通过反射调用到目标类方法

其原理就是:CGLIB内部会额外生成两个代理类(FastClass的子类),这两个代理类一个结合目标类使用,一个结合代理类使用,了解即可

3 MethodInterceptor

MethodInterceptor表示方法拦截器,拦截目标方法并在其调用前后执行增强逻辑

而Spring中具体的通知Advice就是实现了MethodInterceptor

将Adivce抽象成接口

//Advice表示增强的逻辑,也就是通知
public interface Advice {}

增强逻辑,本质在调用目标方法的前后执行代理行为

//拦截器,拦截目标方法对其进行增强
public interface Interceptor extends Advice {}

在目标方法执行前后执行代理行为,这一系列过程又可以抽取成一个接口

public interface MethodInterceptor extends Interceptor {
    //参数method表示被拦截的目标方法
    Object invoke(MethodInvocation method) throws Throwable;
}

实际上,实现MethodInterceptor接口的

4 Joinpoint与调用链Invocation

Joinpoint代表被增强的目标方法

public interface Joinpoint {
    //调用目标方法
    Object proceed() throws Throwable;
	//得到目标类
    Object getThis();
	//得到连接点,也就是目标方法,AccessibleObject是方法对象Method的父类
    AccessibleObject getStaticPart();
}

将增强的逻辑应用到Joinpoint的过程称为织入,织入后面介绍

织入成功并生成代理类之后,代理类中的代理方法大概长这样:

| -> before1 --------------------------------
|  -> before2 ------------------            |
|    -> target -------> 目标    |advice2    advice1
|  -> after2  ------------------            |
| -> after1  --------------------------------

可以看出,这本质就是一条链,可以将这条调用链封装成一个组件Invocation

调用Invocation组件,就会沿着这条调用链从上到下执行,站在使用者的角度,看起来就像在执行一个增强方法,下面实现一条Invocation调用链

首先将Invocation抽象成接口

//调用链里面肯定会调用到目标方法,所以继承Joinpoint
public interface Invocation extends Joinpoint {
    //得到调用方法传入的参数
    Object[] getArguments();
}

在定义一个接口MethodInvocation,继承自Invocation,表示目标方法的调用链

public interface MethodInvocation extends Invocation {
    //目标方法
    Method getMethod();
}

其实现类ReflectiveMethodInvocation负责实现调用链,负责执行所有增强以及调用目标方法

想要通过反射调用方法,必备三要素:目标类对象、目标方法对象、方法的参数值

而要想执行增强,就必须持有所有的MethodInterceptor

于是,ReflectiveMethodInvocation长下面这样

public class ReflectiveMethodInvocation implements MethodInvocation {

    protected final Object target;
    protected final Method method;
    protected final Object[] arguments;
    protected final List<MethodInterceptor> interceptors;
    //表示当前调用到了第几个MethodInterceptor
    private int count = -1;

    public ReflectiveMethodInvocation(Object target, Method method, Object[] arguments, List<MethodInterceptor> interceptors) {
        this.target = target;
        this.method = method;
        this.arguments = arguments;
        this.interceptors = interceptors;
    }

    //调用目标方法
    @Override
    public Object proceed() throws Throwable {
        //如果所有MethodInterceptor的前置增强都执行完毕了,则开始调用目标方法
        if (this.count == this.interceptors.size() - 1) {
            return method.invoke(target, arguments);
        }
        //间接递归调用MethodInterceptor
        MethodInterceptor interceptor = interceptors.get(++count);
        return interceptor.invoke(this);
    }

    @Override
    public Method getMethod() {return method;}
    @Override
    public Object[] getArguments() {return arguments;}
    @Override
    public Object getThis() {return target; }
    @Override
    public AccessibleObject getStaticPart() {return method;}
}

Invocation的proceed方法里会调用MethodInterceptor的invoke方法,然后invoke里又调用proceed方法,所以是间接递归,通过这种方法,就可以实现一条调用链

测试一下,先准备两个MethodInterceptor

class MyInterceptor1 implements MethodInterceptor {
    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        System.out.println("前置增强1...");
        //调用目标方法
        Object proceed = invocation.proceed();
        System.out.println("后置增强1...");
        return proceed;
    }
}
class MyInterceptor2 implements MethodInterceptor {
    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        System.out.println("前置增强2...");
        //调用目标方法
        Object proceed = invocation.proceed();
        System.out.println("后置增强2...");
        return proceed;
    }
}

然后执行这条调用链

//准备目标类
Target target = new Target();
//准备目标方法
Method bar = target.getClass().getDeclaredMethod("bar", int.class);
//准备方法参数值
Object[] args = {233};
//准备MethodInterceptor集合
List<MethodInterceptor> interceptors = List.of(new MyInterceptor1(), new MyInterceptor2());
//将目标方法和interceptors封装成调用链对象
ReflectiveMethodInvocation invocation =
        new ReflectiveMethodInvocation(target, bar, args, interceptors);

/**
 * 执行的打印结果如下:
 *  前置增强1...
 *  前置增强2...
 *  正在调用目标类的bar方法,field是[null],并返回:54289
 *  后置增强2...
 *  后置增强1...
 */
invocation.proceed();

MethodInterceptor里面一定要调用到目标方法,否则这条链就会断掉

5 AdvisedSupport

AdvisedSupport承载了 AOP 织入所需的关键配置信息和管理功能,为实际的织入操作提供了基础和支持

要执行织入操作,首先需要知道目标类,将目标类封装一下

这里将目标类封装成TargetSource

public class TargetSource {
	//持有目标类对象
    private final Object target;
	//通过构造参数传入目标类对象
    public TargetSource(Object target) {
       this.target = target;
    }
	//得到目标类实现的接口列表
    public Class<?>[] getTargetClass() {
       return this.target.getClass().getInterfaces();
    }
	//得到目标类对象本身
    public Object getTarget() {
       return this.target;
    }
}

此外,还需要知道所有增强的逻辑,以及增强逻辑需要应用到哪个方法上

于是可以推断出AdvisedSupport长下面这样

public class AdvisedSupport {
	//表示目标类
    private TargetSource targetSource;
	//表示增强的逻辑
    private List<MethodInterceptor> interceptors;
	//匹配方法的组件
    private MethodMatcher methodMatcher;
    
    //省略了上面三个组件的get、set方法
    //.....
}

6 AopProxy

AopProxy是一个接口,抽象了获取代理对象的这个动作

public interface AopProxy {
	//得到代理类
    Object getProxy();
}

其有两个实现类,分别表示获取JDK代理对象和获取CGLIB的代理对象

JdkDynamicAopProxy表示使用获取JDK代理对象,织入的操作就封装在里面

//实现了InvocationHandler接口,以此给代理对象传回调函数时传this就行了
public class JdkDynamicAopProxy implements AopProxy, InvocationHandler {

    //AdvisedSupport用于给织入操作提供支持
    private final AdvisedSupport advised;
    public JdkDynamicAopProxy(AdvisedSupport advised) {
        this.advised = advised;
    }

    /**
     * 生成的代理类中的方法会调用到这的invoke方法
     * invoke方法中,将增强的逻辑应用到目标方法上,这就是织入
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Class<?> targetClass = advised.getTargetSource().getTarget().getClass();
        //判断切点是否匹配目标类的目标方法
        if (advised.getMethodMatcher().matches(method, targetClass)) {
            //如果匹配,说明目标方法需要被增强,methodInterceptor表示增强的逻辑
            List<MethodInterceptor> interceptors = advised.getInterceptors();
            //invocation表示目标方法的调用
            ReflectiveMethodInvocation invocation = new ReflectiveMethodInvocation(advised.getTargetSource().getTarget(), method, args, interceptors);
            //执行调用链
            return invocation.proceed();
        }

        //不匹配就直接执行目标方法,无需增强
        return method.invoke(advised.getTargetSource().getTarget(), args);
    }

    //创建代理类并返回
    @Override
    public Object getProxy() {
        ClassLoader classLoader = getClass().getClassLoader();
        Class<?>[] interfaces = advised.getTargetSource().getTargetClass();
        return Proxy.newProxyInstance(classLoader, interfaces, this);
    }
}

测试一下JdkDynamicAopProxy

TargetSource targetSource = new TargetSource(new Target()); //封装目标类
List<org.springframework.aop.MethodInterceptor> interceptors = List.of(new MyInterceptor1(), new MyInterceptor2());    //封装通知
MethodMatcher methodMatcher = new AspectJExpressionPointcut("execution(* org.springframework.test.bean.Foo.*(..))").getMethodMatcher(); //封装切点

//封装AdvisedSupport
AdvisedSupport advisedSupport = new AdvisedSupport();
advisedSupport.setTargetSource(targetSource);
advisedSupport.setInterceptors(interceptors);
advisedSupport.setMethodMatcher(methodMatcher);

//创建代理类
Foo foo = (Foo)new JdkDynamicAopProxy(advisedSupport).getProxy();
foo.foo();

AopProxy的另一个实现类CglibAopProxy用于获取CGLIB代理对象,同样封装了织入

该类中持有两个内部类

  • DynamicAdvisedInterceptorAdapter:将CGLIB提供的MethodInterceptor适配成我们定义的AdvisedSupport
  • CglibMethodInvocation:CGLIB提供其他方式调用目标,所有封装一个适用于CGLIB的Invocation类

代码如下

public class CglibAopProxy implements AopProxy {

    //织入支持
    private final AdvisedSupport support;

    public CglibAopProxy(AdvisedSupport support) {
        this.support = support;
    }

    @Override
    public Object getProxy() {
        //创建代理
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(support.getTargetSource().getTarget().getClass());
        enhancer.setInterfaces(support.getTargetSource().getTargetClass());
        //该方法需要的MethodInterceptor类型参数是CGLIB中提供的MethodInterceptor
        enhancer.setCallback(new DynamicAdvisedInterceptorAdapter(support));
        return enhancer.create();
    }

    /**
     * 这是一个典型的适配器模式,将我们上面定义的MethodInterceptor转为CGLIB提供的MethodInterceptor
     */
    private record DynamicAdvisedInterceptorAdapter(AdvisedSupport advised) 
        implements MethodInterceptor {
        @Override
        public Object intercept(Object o, Method method, Object[] args, 
                                MethodProxy methodProxy) throws Throwable {
            CglibMethodInvocation invocation = 
                new CglibMethodInvocation(advised.getTargetSource().getTarget(), 
                method, args, methodProxy, advised.getInterceptors());
            if (advised.getMethodMatcher().matches(method, advised.getTargetSource()
                                                   .getTarget().getClass())) {
                //代理方法
                return invocation.proceed();
            }
            return method.invoke(o, args);
        }
    }

    /**
     * CGLIB可以通过其他方式调用目标,所有增加一个适用于CGLIB的Invocation
     */
    private static class CglibMethodInvocation extends ReflectiveMethodInvocation {
        //使用该对象可以无需反射调用到目标方法
        private final MethodProxy methodProxy;

        public CglibMethodInvocation(Object target, Method method, 
                         	Object[] arguments, MethodProxy methodProxy,
                            //这的MethodInterceptor是我们上面定义的MethodInterceptor
                         	List<org.springframework.aop.MethodInterceptor> interceptors) {
            super(target, method, arguments, interceptors);
            this.methodProxy = methodProxy;
        }

        @Override
        public Object proceed() throws Throwable {
            if (this.count == this.interceptors.size() - 1) {
                //通过CGLIB提供的方法调用目标
                return methodProxy.invoke(target, args);
            }
            org.springframework.aop.MethodInterceptor interceptor = interceptors.get(++count);
            return interceptor.invoke(this);
        }
    }
}

测试一下

TargetSource targetSource = new TargetSource(new Target()); //封装目标类
List<org.springframework.aop.MethodInterceptor> interceptors = List.of(new MyInterceptor1(), new MyInterceptor2());//封装通知
MethodMatcher methodMatcher = new AspectJExpressionPointcut(
    "execution(* org.springframework.test.bean.Foo.*(..))").getMethodMatcher(); //封装切点

//封装AdvisedSupport
AdvisedSupport advisedSupport = new AdvisedSupport();
advisedSupport.setTargetSource(targetSource);
advisedSupport.setInterceptors(interceptors);
advisedSupport.setMethodMatcher(methodMatcher);

//创建代理类
Foo foo = (Foo)new CglibAopProxy(advisedSupport).getProxy();
foo.foo();

AopProxy的两个实现类,分别表示基于JDK代理和基于CGLIB代理,关系如下

«interface»
AopProxy
+Object getProxy()
«interface»
InvocationHandler
+Object invoke(Object proxy, Method method, Object[] args)
JdkDynamicAopProxy
+ final AdvisedSupport advised
+Object getProxy()
+Object invoke(Object proxy, Method method, Object[] args)
CglibAopProxy
+ final AdvisedSupport advised
+Object getProxy()

7 代理工厂

织入时希望可以选择代理类是基于JDK代理还是CGLIB代理,于是给AdvisedSupport增加一个属性proxyTargetClass

public class AdvisedSupport {
    //是否使用cglib代理,默认false,也就是默认使用JDK代理
    private boolean proxyTargetClass = false;
    private TargetSource targetSource;
    private List<MethodInterceptor> interceptors;
    private MethodMatcher methodMatcher;
    //省略了getsetis方法
}

再封装一层ProxyFactory,依赖了AopProxy的两个实现类,并根据proxyTargetClass自动选择一个实现类创建代理对象

//ProxyFactory表示代理工厂
public class ProxyFactory {
	//织入
    private AdvisedSupport advisedSupport;

    public ProxyFactory(AdvisedSupport advisedSupport) {
       this.advisedSupport = advisedSupport;
    }

    //获取代理对象
    public Object getProxy() {
       return createAopProxy().getProxy();
    }

    //根据proxyTargetClass选择一个AopProxy实现类
    private AopProxy createAopProxy() {
       if (advisedSupport.isProxyTargetClass()) {
          return new CglibAopProxy(advisedSupport);
       }
       return new JdkDynamicAopProxy(advisedSupport);
    }
}

测试一下

//设置切点
MethodMatcher methodMatcher = new AspectJExpressionPointcut("execution(* org.springframework.test.bean.Foo.*(..))").getMethodMatcher();
//封装织入
AdvisedSupport support = new AdvisedSupport();
support.setTargetSource(new TargetSource(new Target()));
support.setMethodInterceptor(new MyInterceptor());
support.setMethodMatcher(methodMatcher);

/**
 * 测试使用CGLIB代理
 */
support.setProxyTargetClass(true);
//生成的代理类是目标类Target的子类
Target target = (Target)new ProxyFactory(support).getProxy();
target.foo();

/**
 * 测试使用JDK代理
 */
support.setProxyTargetClass(false);
//生成的代理类与目标类实现了同样的接口Foo
Foo foo = (Foo)new ProxyFactory(support).getProxy();
foo.bar();

8 几种Advice

Adivce表示通知,也就是增强的逻辑

Spring中定义了五种通知

  • 前置通知:目标方法执行前执行的增强逻辑
  • 后置通知:目标方法执行后执行的增强逻辑
  • 环绕通知:目标方法执行前后执行的增强逻辑
  • 异常通知:执行目标方法发生异常时的增强逻辑
  • 最终通知:目标方法执行后无论是否发生异常都会执行的增强逻辑,相当于finally

前面讲过,通知会和目标一起被封装成一个调用链对象,为了方便形成调用链,这些通知应该实现同一个接口,这个接口就是MethodInterceptor,环绕、异常、最终通知都实现了该接口

但由于历史原因,前置、后置通知并没有实现这个接口,Spring会使用适配器来进行转换

定义前置通知

//前置通知接口
public interface BeforeAdvice extends Advice {}
//实现该接口即可认为是一个前置通知
public interface MethodBeforeAdvice extends BeforeAdvice {
	void before(Method method, Object[] args, Object target) throws Throwable;
}

定义后置通知

//后置通知接口
public interface AfterAdvice extends Advice {}
//实现该接口即可认为是一个后置通知
public interface AfterReturningAdvice extends AfterAdvice {
	void afterReturning(Object returnValue, Method method, Object[] args, 
                        Object target) throws Throwable;
}

将前置、后置通知转化为环绕通知的适配器类

class MethodBeforeAdviceInterceptor implements MethodInterceptor {

	private MethodBeforeAdvice advice;
	public MethodBeforeAdviceInterceptor(MethodBeforeAdvice advice) {
		this.advice = advice;
	}

	@Override
	public Object invoke(MethodInvocation mi) throws Throwable {
		//在执行目标方法之前,先执行before advice操作
		this.advice.before(mi.getMethod(), mi.getArguments(), mi.getThis());
		return mi.proceed();
	}
}

class AfterReturningAdviceInterceptor implements MethodInterceptor {

	private AfterReturningAdvice advice;
	public AfterReturningAdviceInterceptor(AfterReturningAdvice advice) {
		this.advice = advice;
	}

	@Override
	public Object invoke(MethodInvocation mi) throws Throwable {
        Object retVal = mi.proceed();
        //在执行目标方法之后,在执行after advice操作
		this.advice.afterReturning(retVal, mi.getMethod(), mi.getArguments(), mi.getThis());
		return retVal;
	}
}

测试,在业务代码中定义前置、后置通知的实现类

class MyBeforeAdvice implements MethodBeforeAdvice {
    @Override
    public void before(Method method, Object[] args, Object target) throws Throwable {
        System.out.println("这是一个前置通知...");
    }
}
class MyAfterReturningAdvice implements AfterReturningAdvice {
    @Override
    public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
        System.out.println("这是一个后置通知...");
    }
}

测试代码

//设置切点
MethodMatcher methodMatcher = new AspectJExpressionPointcut(
    "execution(* org.springframework.test.bean.Foo.*(..))").getMethodMatcher();
//设置增强
List<MethodInterceptor> interceptors = List.of(
    new MethodBeforeAdviceInterceptor(new MyBeforeAdvice()), 
    new AfterReturningAdviceInterceptor(new MyAfterReturningAdvice())
);
AdvisedSupport support = new AdvisedSupport();
support.setTargetSource(new TargetSource(new Target()));
support.setInterceptors(interceptors);
support.setMethodMatcher(methodMatcher);
support.setProxyTargetClass(true);

//生成的代理类是目标类Target的子类
Target target = (Target)new ProxyFactory(support).getProxy();
target.foo();

9 Advisor

Advisor表示切面,包含了一个Pointcut和一个Advice

  • Pointcut用于匹配和捕获JoinPoint
  • Advice决定对JoinPoint进行何种增强

首先定义一个Advisor接口

//该接口依赖了Advice
public interface Advisor {
    Advice getAdvice();
}

还差一个JoinPoint,再定义一个子接口

public interface PointcutAdvisor extends Advisor {
    Pointcut getPointcut();
}

其实现类长这样

public class AspectJExpressionPointcutAdvisor implements PointcutAdvisor {

    private AspectJExpressionPointcut pointcut;
    private Advice advice;
	//切点表达式
    private String expression;

    //根据表达式生成AspectJExpressionPointcut
    public void setExpression(String expression) {
       this.expression = expression;
       pointcut = new AspectJExpressionPointcut(expression);
    }

    //pointcut和advice的get、set方法
    //.....
}

简单测试一下

//目标类
Target target = new Target();
//切点表达式
String express = "execution(* org.springframework.test.bean.Foo.*(..))";

//创建两个切面
AspectJExpressionPointcutAdvisor advisor1 = new AspectJExpressionPointcutAdvisor();
advisor1.setExpression(express);
advisor1.setAdvice(new AfterReturningAdviceInterceptor(new MyAfterReturningAdvice()));

AspectJExpressionPointcutAdvisor advisor2 = new AspectJExpressionPointcutAdvisor();
advisor2.setExpression(express);
advisor2.setAdvice(new MethodBeforeAdviceInterceptor(new MyBeforeAdvice()));
//假设这是容器内所有的切面
List<Advisor> advisors = List.of(advisor1, advisor2);



//收集符合目标类的所有通知
List<Advisor> suitableAdvisors = advisors.stream()
        .filter(advisor -> ((PointcutAdvisor) advisor).getPointcut()
                .getClassFilter().matches(target.getClass())
               ).collect(Collectors.toList());

//如果存在一个切面能够匹配目标类,就进行增强
if (suitableAdvisors.size() > 0) {
    List<MethodInterceptor> interceptors = suitableAdvisors.stream().map(
        advisor -> (MethodInterceptor)advisor.getAdvice()
    ).collect(Collectors.toList());

    AdvisedSupport support = new AdvisedSupport();
    support.setTargetSource(new TargetSource(target));
    support.setInterceptors(interceptors);
    support.setMethodMatcher(
        ((PointcutAdvisor)suitableAdvisors.get(0)).getPointcut().getMethodMatcher()
    );

    //使用JDK代理
    Foo foo = (Foo)new ProxyFactory(support).getProxy();
    foo.bar(1);
}

10 与Spring结合

前面都是动态代理的独角戏,而与Spring结合才叫AOP,怎么结合呢?自然是融入到Bean的生命周期,所以需要用到Bean后处理器

BeanPostProcessor只能扩展前后初始化阶段,扩展点太少

引入一个新的Bean后处理器,负责为Bean构造前、Bean构造后依赖注入前、依赖注入阶段提供扩展

这就是InstantiationAwareBeanPostProcessor,继承BeanPostProcessor

public interface InstantiationAwareBeanPostProcessor extends BeanPostProcessor {
    //Bean构造前回调
	Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException;
    
    //Bean构造后依赖注入前回调
	boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException;

    //依赖注入时回调
    PropertyValues postProcessPropertyValues(PropertyValues pvs, Object bean, 
                                             String beanName) throws BeansException;
    
}

依赖注入和属性填充有区别

  • 依赖注入是通过@Value、@Autowired或者构造器注入为属性赋值
  • 属性填充是根据BeanDefinition的PropertyValues为属性赋值

这里了解下,后面会介绍依赖注入

DefaultAdvisorAutoProxyCreator是其实现类,在后初始化时判断是否需要生成代理并进行织入

public class DefaultAdvisorAutoProxyCreator implements InstantiationAwareBeanPostProcessor, BeanFactoryAware {

    private DefaultListableBeanFactory beanFactory;

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        this.beanFactory = (DefaultListableBeanFactory) beanFactory;
    }

    //构造前,如果返回对象就以该对象作为成品Bean而不走后面实例化流程,返回null则跟以前一样走流程
    @Override
    public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
        return null;
    }

    //构造后,返回false则介绍实例化流程立即以当前Bean作为成品Bean返回
    @Override
    public boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {
        return true;
    }

    //依赖注入
    @Override
    public PropertyValues postProcessPropertyValues(PropertyValues pvs, Object bean, String beanName) throws BeansException {
        return pvs;
    }

    //前初始化,以返回的对象顶替调原始Bean
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }

    //后初始化,以返回的对象顶替调原始Bean,在这里创建并返回代理类
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        //避免死循环
        if (isInfrastructureClass(bean.getClass())) {
            return bean;
        }
        
        BeanDefinition bd = beanFactory.getBeanDefinition(beanName);
		//如果需要被代理,则返回代理类,否则返回当前bean
        return wrapIfNecessary(bean, beanName);
    }

    //根据目标类找到有资格的所有Advisor
    private List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName) {
        //从容器得到所有Advisor类型的Bean
        Map<String, Advisor> advisorMap = beanFactory.getBeansOfType(Advisor.class);
        //找到匹配当前Bean的Advisor
        List<Advisor> advisors = advisorMap.values().stream().filter(
            advisor -> ((PointcutAdvisor) advisor).getPointcut()
            .getClassFilter().matches(beanClass)
        ).toList();

        return advisors;
    }

    //如果至少有一个Advisor匹配当前Bean,就创建一个代理类,返回null就说明无需代理类
    private Object wrapIfNecessary(Object bean, String beanName) {
        try {
            List<Advisor> advisors = findEligibleAdvisors(bean.getClass(), beanName);
            if (!advisors.isEmpty()) {
                List<MethodInterceptor> interceptors = advisors.stream().map(advisor ->
                        (MethodInterceptor)advisor.getAdvice()).toList();

                AdvisedSupport advisedSupport = new AdvisedSupport();
                TargetSource targetSource = new TargetSource(bean);
                advisedSupport.setTargetSource(targetSource);
                advisedSupport.setInterceptors(interceptors);
                advisedSupport.setMethodMatcher(
                    ((PointcutAdvisor)advisors.get(0)).getPointcut().getMethodMatcher()
                );

                //使用JDK代理
                return new ProxyFactory(advisedSupport).getProxy();
            }
        } catch (Exception e) {
            throw new BeansException("创建代理对象失败: " + beanName, e);
        }
        return bean;
    }

    private boolean isInfrastructureClass(Class<?> beanClass) {
        return Advice.class.isAssignableFrom(beanClass)
                || Pointcut.class.isAssignableFrom(beanClass)
                || Advisor.class.isAssignableFrom(beanClass);
    }
}

织入的逻辑放在后初始化中,前面该咋样咋样,属性填充,初始化什么的都没影响,在实例化的最后一部后初始化中返回代理对象,AbstractAutowireCapableBeanFactory增加如下代码

public abstract class AbstractAutowireCapableBeanFactory 
    extends AbstractBeanFactory implements AutowireCapableBeanFactory {
    
    //修改createBean的方法
     @Override
    protected Object createBean(String beanName, 
                                BeanDefinition beanDefinition) throws BeansException {
        //调用bean后处理器执行构造前的逻辑,如果的bean不为空就不走后面的实例化流程了
        Object bean = resolveBeforeInstantiation(beanName, beanDefinition);
        if (bean != null) {
            return bean;
        }
        //构造前的后处理器返回null,说明还是要走实例化流程的
        return doCreateBean(beanName, beanDefinition);
    }
    
    //Bean构造前的逻辑
    protected Object resolveBeforeInstantiation(String beanName, 
                                                BeanDefinition beanDefinition) {
        Object bean = applyBeanPostProcessorsBeforeInstantiation(
            beanDefinition.getBeanClass(), beanName);
        if (bean != null) {
            bean = applyBeanPostProcessorsAfterInitialization(bean, beanName);
        }
        return bean;
    }
    
    //Bean实例化前的解析
    protected Object applyBeanPostProcessorsBeforeInstantiation(Class beanClass, 
                                                                String beanName) {
        for (BeanPostProcessor beanPostProcessor : getBeanPostProcessors()) {
            if (beanPostProcessor instanceof InstantiationAwareBeanPostProcessor bpp) {
                Object result = bpp.postProcessBeforeInstantiation(beanClass, beanName);
                if (result != null) {
                    return result;
                }
            }
        }
        return null;
    }
    
    //修改doCreateBean
    protected Object doCreateBean(String beanName, BeanDefinition beanDefinition) {
        Object bean = null;
        try {
            //构造Bean
            bean = createBeanInstance(beanDefinition);
            //调用bean后处理器执行构造后的逻辑,如果返回false,就不走后面实例化流程
            boolean continueWithPropertyPopulation 
                = applyBeanPostProcessorsAfterInstantiation(beanName, bean);
            if (!continueWithPropertyPopulation) {
                return bean;
            }
            //依赖注入
            applyBeanPostprocessorsBeforeApplyingPropertyValues(
                beanName, bean, beanDefinition
            );
            //属性填充
            applyPropertyValues(beanName, bean, beanDefinition);
            //前初始化、初始化、后初始化
            bean = initializeBean(beanName, bean, beanDefinition);
        } catch (Exception e) {
            throw new BeansException("实例化bean失败", e);
        }
        //将成品Bean注册到单例Bean注册表之前,先注册拥有销毁方法的Bean
        registerDisposableBeanIfNecessary(beanName, bean, beanDefinition);
        //实例化完成后,将Bean注册进单例Bean注册表
        //只有Scope为singleton的Bean才保存起来
        if (beanDefinition.isSingleton()) {
            addSingleton(beanName, bean);
        }
        return bean;
    }
    
    //构造后的逻辑
    private boolean applyBeanPostProcessorsAfterInstantiation(String beanName, Object bean) {
        boolean continueWithPropertyPopulation = true;
        for (BeanPostProcessor beanPostProcessor : getBeanPostProcessors()) {
            if (beanPostProcessor instanceof InstantiationAwareBeanPostProcessor) {
                if (!((InstantiationAwareBeanPostProcessor) beanPostProcessor)
                    .postProcessAfterInstantiation(bean, beanName)) {
                    continueWithPropertyPopulation = false;
                    break;
                }
            }
        }
        return continueWithPropertyPopulation;
    }
    
    //TODO 依赖注入,此时还没有实现功能
    protected void applyBeanPostprocessorsBeforeApplyingPropertyValues(
        				String beanName, Object bean, BeanDefinition beanDefinition) {
        for (BeanPostProcessor beanPostProcessor : getBeanPostProcessors()) {
            //如果是InstantiationAwareBeanPostProcessor类型,就回调postProcessPropertyValues
            if (beanPostProcessor instanceof InstantiationAwareBeanPostProcessor) {
                PropertyValues pvs = ((InstantiationAwareBeanPostProcessor) beanPostProcessor)
                        .postProcessPropertyValues(beanDefinition.getPropertyValues(),
                                bean, beanName);
                //设置新的PropertyValue
                if (pvs != null) {
                    for (PropertyValue propertyValue : pvs.getPropertyValues()) {
                        beanDefinition.getPropertyValues().addPropertyValue(propertyValue);
                    }
                }
            }
        }
    }
    
    //为了符合Spring,属性填充时调用set方法为属性赋值,而不是直接给属性赋值
    protected void applyPropertyValues(String beanName, Object bean, 
                                       BeanDefinition beanDefinition) {
        PropertyValues pvs = beanDefinition.getPropertyValues();
        try {
            for (PropertyValue pv : pvs.getPropertyValues()) {
                String name = pv.getName();
                Object value = pv.getValue();

                if (value instanceof BeanReference beanReference) {
                    value = getBean(beanReference.getBeanName());
                }
                //通过反射调用属性对应的set方法
                String setMethod = "set" + 
                    name.substring(0, 1).toUpperCase() + name.substring(1);
                ReflectUtil.invoke(bean, setMethod, value);
            }
        } catch (Exception e) {
            throw new BeansException("属性填充发生异常,在Bean:" + beanName, e);
        }
    }
}

注意,依赖注入没有实现,因为此时没有一个Bean后处理器能够进行依赖注入,在后面会引入

测试阶段,先添加一个XML文件

<!--aop.xml-->
<?xml version="1.0" encoding="UTF-8"?>
<beans>
    <!--目标类-->
    <bean id="target" class="org.springframework.test.bean.Target">
    	<property name="field" ref="属性填充" />
    </bean>

    <!--Bean后处理器-->
    <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/>

    <!--将前置、后置增强适配为MethodInterceptor类型-->
    <bean id="beforeAdviceMethodInterceptor" class="org.springframework.aop.framework.advice.MethodBeforeAdviceInterceptor">
        <property name="advice" ref="beforeAdvice" />
    </bean>
    <bean id="afterAdviceMethodInterceptor" class="org.springframework.aop.framework.advice.AfterReturningAdviceInterceptor">
        <property name="advice" ref="afterAdvice" />
    </bean>

    <!--单纯的前置和后置增强-->
    <bean id="beforeAdvice" class="org.springframework.test.service.MyBeforeAdvice"/>
    <bean id="afterAdvice" class="org.springframework.test.service.MyAfterReturningAdvice"/>
</beans>

实例化策略是通过无参构造创建对象,但是MethodBeforeAdviceInterceptor和AfterReturningAdviceInterceptor并没有无参构造,为了测试通过,需要给这两个类添加一个无参构造哦

测试代码如下

ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath:aop.xml");
//得到的foo就是一个代理类
Foo foo = context.getBean("target", Foo.class);
/**
 * 调用下面方法,发现是个增强方法,并且属性填充功能也是正常的,打印如下
 * 这是一个前置通知...
 * 正在调用目标类的bar方法,field是[属性填充],并返回:1
 * 这是一个后置通知...
 */
foo.bar(1);

11 使用注解开发

使用AOP时,我们更多的是使用注解@Aspect作为切面

@Component
@Aspect
class MyAspect {
    @Before("execution(* foo())")
    public void before() {
        System.out.println("前置通知");
    }
    
    @AfterReturning("execution(* foo())")
    public void afterReturning() {
        System.out.println("后置通知");
    }

    @Around("execution(* foo())")
    public void around(ProceedingJoinPoint point) throws Throwable {
        System.out.println("环绕前置");
        point.proceed();
        System.out.println("环绕后置");
    }

    @After("execution(* foo())")
    public void after() {
        System.out.println("最终通知");
    }
}

这就跟IOC篇的注解监听器一样,最终会被解析成Advisor并被注册进容器,解析方法就像下面一样

//aspect就是加了@Aspect注解的Bean
//下面演示的类和方法是Spring中提供的
public List<Advisor> convert(Object aspect) {
    //切面工厂,使用这个工厂可以拿到aspect这个切面对象
    AspectInstanceFactory factory = new SingletonAspectInstanceFactory(aspect);
    //收集低级切面的集合
    List<Advisor> list = new ArrayList<>();

    //判断aspect有没有加@Aspect注解
    if (aspect.getClass().isAnnotationPresent(Aspect.class)) {
        //循环结束,就将aspect转成了若干个Advisor
        for (Method method : aspect.getClass().getDeclaredMethods()) {
            //下面演示了四种通知注解的解析
            if (method.isAnnotationPresent(Before.class)) {
                //解析切点
                String value = method.getAnnotation(Before.class).value();
                AspectJExpressionPointcut pt = new AspectJExpressionPointcut();
                pt.setExpression(value);
  
                AspectJMethodBeforeAdvice advice = 
                    new AspectJMethodBeforeAdvice(method, pt, factory);
                //将方法转为Advisor
                Advisor advisor = new DefaultPointcutAdvisor(pt, advice);
                list.add(advisor);
            } 
            else if (method.isAnnotationPresent(AfterReturning.class)) {
                String value = method.getAnnotation(AfterReturning.class).value();
                AspectJExpressionPointcut pt = new AspectJExpressionPointcut();
                pt.setExpression(value);
                AspectJAfterReturningAdvice advice = 
                    new AspectJAfterReturningAdvice(method, pt, factory);
                Advisor advisor = new DefaultPointcutAdvisor(pt, advice);
                list.add(advisor);
            } 
            else if (method.isAnnotationPresent(Around.class)) {
                String value = method.getAnnotation(Around.class).value();
                AspectJExpressionPointcut pt = new AspectJExpressionPointcut();
                pt.setExpression(value);
                AspectJAroundAdvice advice = new AspectJAroundAdvice(method, pt, factory);
                Advisor advisor = new DefaultPointcutAdvisor(pt, advice);
                list.add(advisor);
            } 
            else if (method.isAnnotationPresent(After.class)) {
                String value = method.getAnnotation(After.class).value();
                AspectJExpressionPointcut pt = new AspectJExpressionPointcut();
                pt.setExpression(value);
                AspectJAfterAdvice advice = new AspectJAfterAdvice(method, pt, factory);
                Advisor advisor = new DefaultPointcutAdvisor(pt, advice);
                list.add(advisor);
            }
        }
    }

    return list;
}

将解析出来的Advisor注册到Spring中很简单,就跟IOC篇的注解监听器一样,这里就不演示了

转载自:https://juejin.cn/post/7368355894708584488
评论
请登录