【重写SpringFramework】第二章aop模块:AOP代理上(chapter 2-4)

零 Java教程评论79字数 11502阅读38分20秒阅读模式

【重写SpringFramework】第二章aop模块:AOP代理上(chapter 2-4)

1. 前言

前边介绍了 AOP 机制的两个基础功能,分别是增强和切面,但目标对象仍然游离于整个体系之外。先前在测试代码需要通过反射的方式寻找需要增强的方法,但这种做法只是权宜之计,我们需要一种解决方案将目标对象也纳入整个体系中来。Spring 框架提供了 AOP 代理完成这一工作,具体的做法使用代理包装目标对象,当调用目标对象的方法时,由代理对象完成相关的增强逻辑,目标方法只需要关心本身的业务即可。

2. 整体分析

2.1 继承结构

AOP 代理的继承结构比较复杂,按照功能划分为以下四组:文章源自灵鲨社区-https://www.0s52.com/bcjc/javajc/16760.html

  • 蓝色区域表示代理工厂,创建代理对象是一项复杂的工作,使用工厂类屏蔽创建的细节
  • 紫色区域表示代理对象,这里需要先通过工厂拿到 AOP 代理,进一步才能获取通常意义上的代理对象
  • 黄色区域表示目标对象,也就是被增强的对象
  • 绿色区域表示增强和切面功能,Advisor 组件已经在上一节详解论述过了

4.1 AOP代理类图结构.png文章源自灵鲨社区-https://www.0s52.com/bcjc/javajc/16760.html

2.2 代理模式

代理模式是指为某个对象提供一个代理对象,并由代理对象接管对该对象的访问。代理模式的结构由三个角色组成:文章源自灵鲨社区-https://www.0s52.com/bcjc/javajc/16760.html

  • 抽象主题角色:声明了真实主题和代理的共同接口,这样可以在任何使用真实主题的地方都可以使用代理角色
  • 真实主题角色:定义了代理角色所代表的真实对象
  • 代理角色:内部包含一个对真实主题的引用,在将客户端的调用传递给真实主题时,代理角色可以完成额外的逻辑

4.2 代理模式类图.png文章源自灵鲨社区-https://www.0s52.com/bcjc/javajc/16760.html

我们发现,代理模式有两个显著的特点。其一,对目标对象的访问都是经由代理对象处理的。其二,代理对象在是否调用目标对象的方法时有很大的决策权,比如在权限访问系统的设计中,代理对象甚至可以不调用目标对象的方法。这两个特点都适用于 AOP,前者在调用方法时检查是否需要增强,后者可以在调用方法时执行增强逻辑。因此,Spring 使用代理模式来实现 AOP 功能。文章源自灵鲨社区-https://www.0s52.com/bcjc/javajc/16760.html

2.3 代理分类

代理可以分为两大类,静态代理和动态代理。静态代理是指代理类在程序运行之前已经存在,所谓的静态是针对编译期而言的。一般来说,静态代理应用的场景较少,仅了解即可。动态代理是指在运行时动态地生成代理类,动态代理有两种主要的实现方式,一是 JDK 提供的动态代理功能,二是通过 CGLIB 框架创建代理。两者的区别在于,JDK 动态代理必须指定一个接口,CGLIB 代理是通过生成目标类的子类,并重写父类方法实现的。文章源自灵鲨社区-https://www.0s52.com/bcjc/javajc/16760.html

3. TargetSource

TargetSource 接口的作用是封装 AOP 调用的目标对象,getTarget 方法的作用是获取目标对象,这说明目标对象的来源可能有多种途径。文章源自灵鲨社区-https://www.0s52.com/bcjc/javajc/16760.html

java

复制代码
public interface TargetSource {
    Class<?> getTargetClass();
    Object getTarget() throws Exception;
}

SingletonTargetSource 实现了 TargetSource 接口,从类名中可以看出,SingletonTargetSource 是对 Spring 容器管理的单例进行包装。文章源自灵鲨社区-https://www.0s52.com/bcjc/javajc/16760.html

java

复制代码
public class SingletonTargetSource implements TargetSource {
    private final Object target;

    public SingletonTargetSource(Object target) {
        this.target = target;
    }

    @Override
    public Class<?> getTargetClass() {
        return this.target.getClass();
    }

    @Override
    public Object getTarget() {
        return this.target;
    }
}

4. 代理工厂

4.1 ProxyConfig

ProxyConfig 定义了创建 AOP 代理的配置信息,从功能上来说与 BeanDefinition 类似。该类比较重要的属性有两个,简单介绍如下:文章源自灵鲨社区-https://www.0s52.com/bcjc/javajc/16760.html

  • proxyTargetClass:表示创建代理的方式。如果为 true,表示代理整个类,即 Cglib 代理。如果为 false 表示代理接口,也就是 JDK 动态代理。
  • exposeProxy:表示是否将代理对象暴露给外界,允许通过 AopContext 来访问

java

复制代码
public class ProxyConfig {
    private boolean proxyTargetClass;
    boolean exposeProxy;
}

4.2 Advised

Advised 接口的作用是对代理工厂进行操作,在创建代理时需要设置一些必要的信息。比如设置目标对象,获取目标对象实现的接口类型,添加增强组件等。文章源自灵鲨社区-https://www.0s52.com/bcjc/javajc/16760.html

  • setTargetSource 方法:设置目标对象
  • getProxiedInterfaces 方法:获取获取需要代理的类或接口的集合
  • addAdvisor 方法:添加 Advisor 实例
  • addAdvice 方法:添加任意类型的通知,并包装成 DefaultPointcutAdvisor 实例

java

复制代码
public interface Advised {
    void setTargetSource(TargetSource targetSource);
    TargetSource getTargetSource();
    Class<?> getTargetClass();
    Class<?>[] getProxiedInterfaces();

    void addAdvisor(Advisor advisor);
    void addAdvice(Advice advice);
}

4.3 AdvisedSupport

AdvisedSupport 实现了 Advised 接口的相关方法,起到了辅助的作用。AdvisedSupport 以组合的方式成为 AOP 代理的一部分,基本属性如下:

  • targetSource:目标对象
  • interfaces:代理对象需要实现的接口
  • advisors:缓存了目标对象所适用的 Advisor 集合
  • methodCache:缓存了适用于每个方法的拦截器集合
  • registry:对各种类型的增强提供通用的适配

java

复制代码
public class AdvisedSupport extends ProxyConfig implements Advised {
    TargetSource targetSource;
    private List<Class<?>> interfaces = new ArrayList<>();
    private List<Advisor> advisors = new LinkedList<>();
    private transient Map<MethodCacheKey, List<Object>> methodCache = new ConcurrentHashMap<>(32);
    private static AdvisorAdapterRegistry registry = new DefaultAdvisorAdapterRegistry();
}

4.4 ProxyFactory

ProxyFactory 扩展了 AdvisedSupport,提供了创建代理对象的功能。getProxy 方法分为两步,先通过工厂类 AopProxyFactory 创建 AopProxy 实例,然后调用该实例的 getProxy 方法创建代理对象。这里用到了两个工厂方法,先创建 AopProxy 实例,然后通过 AopProxy 实例创建真正的代理对象,也就是最终的返回值。

java

复制代码
public class ProxyFactory extends AdvisedSupport {
	private AopProxyFactory aopProxyFactory = new DefaultAopProxyFactory();

    public Object getProxy(){
        return createAopProxy().getProxy();
    }

    protected final synchronized AopProxy createAopProxy() {
        return this.aopProxyFactory.createAopProxy(this);
    }
}

DefaultAopProxyFactory 是 AopProxyFactory 接口的唯一实现类,createAopProxy 方法支持创建两种动态代理。如果 proxyTargetClass 属性为 true,且目标类型不是一个接口,则创建 ObjenesisCglibAopProxy实例,也就是 Cglib 代理。其余情况则创建 JdkDynamicAopProxy 实例,即 JDK 动态代理。

java

复制代码
public class DefaultAopProxyFactory implements AopProxyFactory {

    @Override
    public AopProxy createAopProxy(AdvisedSupport config) {
        //如果proxyTargetClass属性为true,需要进一步检查是否能使用Cglib类代理
        if (config.isProxyTargetClass()) {
            Class<?> targetClass = config.getTargetClass();
            if (targetClass == null) {
                throw new RuntimeException("TargetClass不存在,创建代理需要一个接口或目标类");
            }

            //如果目标类型是一个接口或Proxy的子类,只能使用JDK动态代理
            if(targetClass.isInterface() || Proxy.isProxyClass(targetClass)){
                return new JdkDynamicAopProxy(config);
            }
            //排除所有不符合的条件,使用Cglib代理
            else{
                return new ObjenesisCglibAopProxy(config);
            }
        }
        //默认是JDK动态代理
        return new JdkDynamicAopProxy(config);
    }
}

5. AOP 代理

5.1 概述

AOP 代理的继承结构可以分为两个部分,一是 AopProxy 接口的实现,包括 JDK 动态代理和 Cglib 代理。二是 MethodInvocation 接口的实现,负责增强逻辑与目标方法的具体调用。本节我们先实现 JdkDynamicAopProxy 的相关内容,下一节介绍 CglibAopProxy 的实现。

4.3 AopProxy类图.png

5.2 创建代理对象

AopProxy 接口的实现类必须完成两个工作,一是创建代理对象,二是代理对象实现的增强逻辑。先来看第一步,JdkDynamicAopProxy 实现了 AopProxy 接口的 getProxy 方法,完成了创建代理对象的工作。具体来说,首先获取代理对象需要实现哪些接口,然后创建 JDK 动态代理对象。

java

复制代码
final class JdkDynamicAopProxy implements AopProxy, InvocationHandler {
    private final AdvisedSupport advised;

    public JdkDynamicAopProxy(AdvisedSupport advised) {
        this.advised = advised;
    }

    @Override
    public Object getProxy() {
        //获取需要代理的接口类型
        Class<?>[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised);
        //通过反射的方式创建代理对象
        return Proxy.newProxyInstance(ClassUtils.getDefaultClassLoader(), proxiedInterfaces, this);
    }
}

5.3 调用过程

JdkDynamicAopProxy 实现了 InvocationHandler 接口,当代理对象的方法被调用的时候,会自动执行 invoke 方法。invoke 方法主要做了三件事,一是对外暴露代理对象,二是获取拦截器链,三是执行拦截操作,最终调用目标方法。

第一步,准备工作。如果 exposeProxy 属性为 true,则将代理对象绑定到当前线程上。有时候我们需要在代码中获取代理对象的引用,AopContext 的 currentProxy 方法就会非常有用。需要注意的是,只有在目标方法的内部使用时,才能拿到代理对象的引用。当方法执行完毕,finally 块中解除了代理对象与当前线程的绑定。

java

复制代码
//所属类[cn.stimd.spring.aop.framework.JdkDynamicAopProxy]
//调用目标对象的方法前,先执行代理对象的逻辑
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    MethodInvocation invocation;
    Object oldProxy = null;
    boolean setProxyContext = false;

    TargetSource targetSource = this.advised.targetSource;
    Class<?> targetClass = null;
    Object target;

    try {
        //1. 如果exposeProxy属性为true,将代理对象绑定到ThreadLocal上
        Object retVal;
        if (this.advised.exposeProxy) {
            oldProxy = AopContext.setCurrentProxy(proxy);
            setProxyContext = true;
        }

        //获取目标对象及其类型
        target = targetSource.getTarget();
        if (target != null) {
            targetClass = target.getClass();
        }

        //2. 获取拦截器链
        List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);

        //3. 执行拦截操作,最终调用目标方法
        //如果拦截器链为空,当作普通方法调用
        if (chain.isEmpty()) {
            ReflectionUtils.makeAccessible(method);
            retVal = method.invoke(target, args);
        }else{
            //拦截器不为空,执行增强逻辑并调用目标方法
            invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
            retVal = invocation.proceed();
        }
        return retVal;
    } finally {
        //解除代理对象与当前线程的绑定
        if (setProxyContext) {
            AopContext.setCurrentProxy(oldProxy);
        }
    }
}

第二步,获取目标方法对应的拦截器链。AdvisedSupport 的 advisors 属性保存了一个类所有的拦截器对象,而不是只针对某个方法,因此需要寻找适用于指定方法的拦截器链。Advisor 分为切点和引入两大类,这里我们只关心切点。对于切点来说,需要检查 ClassFilter 和 MethodMatcher。一般情况下,ClassFilter 是默认匹配的,因此我们只关心 MethodMatcher 即可。

java

复制代码
//所属类[cn.stimd.spring.aop.framework.AdvisedSupport]
//获取与方法相对应的拦截器集合
public List<Object> getInterceptorsAndDynamicInterceptionAdvice(Method method, Class<?> targetClass) {
    MethodCacheKey cacheKey = new MethodCacheKey(method);
    List<Object> cached = this.methodCache.get(cacheKey);

    if(cached == null){
        List<Object> interceptorList = new ArrayList<>(this.advisors.size());
        for (Advisor advisor : this.advisors) {
            //处理PointcutAdvisor
            if (advisor instanceof PointcutAdvisor) {
                PointcutAdvisor pa = (PointcutAdvisor) advisor;
                //检查ClassFilter和MethodMatcher是否符合条件
                if (pa.getPointcut().getClassFilter().matches(targetClass)) {
                    MethodInterceptor[] interceptors = registry.getInterceptors(advisor);

                    if (pa.getPointcut().getMethodMatcher().matches(method, targetClass)) {
                        interceptorList.addAll(Arrays.asList(interceptors));
                    }
                }
            }
            //处理IntroductionAdvisor,略
        }
        cached = interceptorList;
        this.methodCache.put(cacheKey, cached);     //加入缓存
    }
    return cached;
}

第三步,执行拦截操作。如果拦截器链为空,把目标方法当成普通方法正常调用即可。这里有一个问题,普通方法为什么也会被拦截?首先我们要明确,一个类被代理只能说明它获得了拦截的资格,至于执行哪些拦截逻辑要视具体的方法而定。当一个类至少有一个增强方法,Spring 就认为这个类应该被代理。换句话说,目标类中除了增强方法,还可以有普通方法。

我们来看增强方法是如何处理的。ReflectiveMethodInvocation 是 MethodInvocation 接口的实现类,通过反射的方式来调用方法。该类持有一系列字段,包括代理对象、目标对象、目标方法,传入的参数、拦截器链。在 proceed 方法的执行过程中,先依次执行拦截器链中的每个拦截器,最后通过 invokeJoinpoint 方法完成对目标方法的调用。

java

复制代码
public class ReflectiveMethodInvocation implements ProxyMethodInvocation {
	protected final Object proxy;       //代理对象
    protected final Object target;      //目标对象
    private final Class<?> targetClass; //目标对象的类型
    protected final Method method;      //调用的方法
    protected Object[] arguments;       //方法参数
    protected final List<?> interceptorsAndDynamicMethodMatchers;   //拦截器链
    private int currentInterceptorIndex = -1;       //记录当前拦截器的下标

    @Override
    public Object proceed() throws Throwable {
        //2. 当拦截器链的所有拦截器都执行完毕后调用,即执行目标方法
        if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
            return invokeJoinpoint();
        }
        //1. 执行拦截器链中的下一个拦截器
        Object advice = this.interceptorsAndDynamicMethodMatchers.get(++currentInterceptorIndex);
        return ((MethodInterceptor) advice).invoke(this);
    }

    //默认使用反射调用,允许子类重写
    protected Object invokeJoinpoint() throws Throwable {
        ReflectionUtils.makeAccessible(this.method);
        try {
            return this.method.invoke(this.target, this.arguments);
        }catch (InvocationTargetException ex){
            throw ex.getTargetException();      //抛出原始异常
        }
    }
}

6. 测试

6.1 JDK 动态代理

JDK 动态代理需要先定义接口,ProxyTarget 接口声明了 execute 方法,ProxyTargetImpl 类实现了 ProxyTarget 接口。

java

复制代码
//测试类
public interface ProxyTarget {
    void execute();
}

public class ProxyTargetImpl implements ProxyTarget {

    @Override
    public void execute() {
        System.out.println("执行execute方法");
    }
}

JDK 动态代理的功能是由 Proxy 和 InvocationHandler 这两个 API 提供的,其中 Proxy 类负责创建代理对象,InvocationHandler 接口表示一个调用处理器。当代理对象执行方法时,首先会执行调用处理器的 invoke 方法,可以在目标方法的前后进行额外的操作,此处仅打印一条日志。

java

复制代码
//测试类,简单的处理器实现
public class SimpleInvocationHandler implements InvocationHandler {
    private Object target;

    public SimpleInvocationHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("JDK动态代理测试...");
        method.invoke(this.target, args);
        return null;
    }
}

在测试方法中,创建了目标对象 ProxyTargetImpl 和调用处理器 SimpleInvocationHandler 的实例,然后调用 Proxy 类的静态方法 newProxyInstance 创建代理对象。这里需要注意,proxy 变量的强转类型必须是接口类型,如果声明成实现类 ProxyTargetImpl,会抛出 ClassCastException 异常。

java

复制代码
//测试方法
@Test
public void testJdkProxy() {
    ProxyTargetImpl target = new ProxyTargetImpl();
    SimpleInvocationHandler handler = new SimpleInvocationHandler(target);
    ProxyTarget proxy = (ProxyTarget) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), handler);
    proxy.execute();
}

从测试结果可以看到,先打印了代理拦截的日志,然后才是目标方法,说明 JDK 动态代理对象创建成功。

java

复制代码
JDK动态代理测试...
执行execute方法

6.2 JdkAopProxy

在测试方法中,首先创建 ProxyFactory 实例,然后设置需要代理的接口类型,这是比较重要的一步。接下来设置目标对象,并添加前置通知和返回通知,然后调用代理工厂的 getProxy 方法,完成代理创建的流程。

java

复制代码
//测试方法
@Test
public void testJdkAopProxy(){
    ProxyFactory factory = new ProxyFactory();
    //使用JDK代理需要指定代理接口类型
    factory.addInterface(ProxyTarget.class);
    factory.setTarget(new ProxyTargetImpl());
    factory.addAdvice(new SimpleAfterReturningAdvice());
    factory.addAdvice(new SimpleMethodBeforeAdvice());

    ProxyTarget proxy = (ProxyTarget) factory.getProxy();
    proxy.execute();
}

从测试结果中可以看出,在执行目标方法前后,分别执行了前置通知和返回通知的逻辑。

lua

复制代码
前置通知,方法名:execute
执行execute方法
返回通知,方法名:execute

7. 总结

我们实现了增强和切面两大功能,其中增强作用于方法执行的前后,切面则是对符合条件的方法进行筛选。鉴于此,我们可以说 AOP 功能最终的落脚点是一个对象(的方法),Spring 通过代理模式实现了对目标对象的整合。一般来说,代理可以分为静态代理和动态代理,其中动态代理有两种实现,一是 JDK 动态代理,二是 CGLIB 代理。

AOP 代理的创建流程比较复杂,Spring 通过工厂模式屏蔽了创建的细节,代理工厂的实现可以分为四个部分:

  • ProxyFactory 负责设置创建代理的相关属性,以及创建代理对象,相当于兼具 BeanDefinition 与 BeanFactory 的功能。
  • AopProxy 表示一个 AOP 代理对象,根据创建方式分为两种,即 JDK 动态代理和 Cglib 代理。
  • TargetSource 表示目标对象,通常来源于 Spring 容器托管的单例。
  • Advisor 组件整合了增强和切面的功能,与 AopProxy 一起实现了 Spring AOP 的基本功能。

4.4 AOP代理脑图.png

8. 项目信息

新增修改一览,新增(18),修改(0)

scss

复制代码
aop
└─ src
   ├─ main
   │  └─ java
   │     └─ cn.stimd.springwheel.aop
   │        ├─ framework
   │        │  ├─ Advised.java (+)
   │        │  ├─ AdvisedSupport.java (+)
   │        │  ├─ AopContext.java (+)
   │        │  ├─ AopProxy.java (+)
   │        │  ├─ AopProxyFactory.java (+)
   │        │  ├─ AopProxyUtils.java (+)
   │        │  ├─ DefaultAopProxyFactory.java (+)
   │        │  ├─ JdkDynamicAopProxy.java (+)
   │        │  ├─ ProxyConfig.java (+)
   │        │  ├─ ProxyFactory.java (+)
   │        │  └─ ReflectiveMethodInvocation.java (+)
   │        ├─ target
   │        │  └─ SingletonTargetSource.java (+)
   │        ├─ ProxyMethodInvocation.java (+)
   │        └─ TargetSource.java (+)
   └─ test
      └─ java
         └─ cn.stimd.springwheel.aop.test
            └─ proxy
               ├─ ProxyTarget.java (+)
               ├─ ProxyTargetImpl.java (+)
               ├─ ProxyTest.java (+)
               └─ SimpleInvocationHandler.java (+)

注:+号表示新增、*表示修改

零
  • 转载请务必保留本文链接:https://www.0s52.com/bcjc/javajc/16760.html
    本社区资源仅供用于学习和交流,请勿用于商业用途
    未经允许不得进行转载/复制/分享

发表评论