Spring 5 - 理解代理

Spring支持两种代理,JDK代理和CGLIB代理。
代理的核心目标是拦截方法调用,必要的时候执行应用于特定方法的advice链。
为了这个核心功能,代理还必须支持一些附加属性。比如可以通过AopContext类(这是一个抽象类)暴露自己。所有的代理类也都实现了Advised接口,这样,在代理增加以后还可以修改advice链。代理也必须确保返回this的任何方法返回代理而不是目标。

使用JDK动态代理

JDK只能为接口增加代理。想代理的任何类必须最少实现i一个接口,返回的代理是实现了该接口的一个对象。
JDK schema

使用JDK代理的时候,所有的方法调用被JVM拦截,路由到代理的invoke()方法。这个方法决定方法是否是advised(使用定义了pointcut),如果是,就调用advice链,然后是方法自己(反射机制)。
对于代理里的unadvised方法,仍然要调用invoke()方法,还要执行全部检查,方法也仍然通过反射调用。
使用setInterfaces()方法,指定代理的接口列表,ProxyFactory就会使用JDK代理。

使用CGLIB代理

CGLIB为每个代理在运行期动态生成字节码,尽可能地重新使用已经生成的类。这样,代理类型会是目标对象类型的子类。
CGLIB schema

当第一次增加一个CGLIB代理的时候,CGLIB询问Spring,想怎么处理每个方法。这意味着在JDK代理里每次执行invoke()作出的决定,CGLIB代理可能只需要执行一次。因为CGLIB生成实际的字节码,带来很多灵活性。比如,CGLIB生成恰当的字节码调用unadvised的方法,减少了开销。CGLIB还能确定一个方法是否返回this,如果不,允许方法被直接调用。
CGLIB处理frozen-advice链(代理生成以后不再修改)的方法也和JDK代理不同。CGLIB用特殊方法处理frozen-advice链,减少运行期开销。

比较代理性能

我们增加DefaultSimpleBean类,用来增加代理的目标对象。

interface SimpleBean {
    void advised();
    void unadvised();
}

class DefaultSimpleBean implements SimpleBean {
    private long dummy = 0;

    @Override
    public void advised() {
        dummy = System.currentTimeMillis();
    }
    @Override
    public void unadvised() {
        dummy = System.currentTimeMillis();
    }
}

TestPointcut用于静态检查

import org.springframework.aop.support.StaticMethodMatcherPointcut;

import javax.annotation.Nonnull;
import java.lang.reflect.Method;

public class TestPointcut extends StaticMethodMatcherPointcut {
    @Override
    public boolean matches(@Nonnull Method method, @Nonnull Class cls) {
        return ("advise".equals(method.getName()));
    }
}

NoOpBeforeAdvice是一个before advice

import org.springframework.aop.MethodBeforeAdvice;

import javax.annotation.Nonnull;
import java.lang.reflect.Method;

public class NoOpBeforeAdvice implements MethodBeforeAdvice {
    @Override
    public void before(@Nonnull Method method, @Nonnull Object[] args, Object target) throws Throwable {
        // no-op
    }
}

下来,是使用不同代理的测试代码:

import org.springframework.aop.Advisor;
import org.springframework.aop.framework.Advised;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.support.DefaultPointcutAdvisor;

public class ProxyPerfTest {
    public static void main(String... args) {
        SimpleBean target = new DefaultSimpleBean();
        Advisor advisor = new DefaultPointcutAdvisor(new TestPointcut(), new NoOpBeforeAdvice());
        runCglibTests(advisor, target);
        runCglibFrozenTests(advisor, target);
        runJdkTests(advisor, target);
    }

    private static void runCglibTests(Advisor advisor, SimpleBean target) {
        ProxyFactory pf = new ProxyFactory();
        pf.setProxyTargetClass(true);
        pf.setTarget(target);
        pf.addAdvisor(advisor);
        SimpleBean proxy = (SimpleBean) pf.getProxy();
        System.out.println("Running CGLIB (Standard) Tests");
        test(proxy);
    }

    private static void runCglibFrozenTests(Advisor advisor, SimpleBean target) {
        ProxyFactory pf = new ProxyFactory();
        pf.setProxyTargetClass(true);
        pf.setTarget(target);
        pf.addAdvisor(advisor);
        pf.setFrozen(true);
        SimpleBean proxy = (SimpleBean) pf.getProxy();
        System.out.println("Running CGLIB (Frozen) Tests");
        test(proxy);
    }

    private static void runJdkTests(Advisor advisor, SimpleBean target) {
        ProxyFactory pf = new ProxyFactory();
        pf.setTarget(target);
        pf.addAdvisor(advisor);
        pf.setInterfaces(SimpleBean.class);
        SimpleBean proxy = (SimpleBean) pf.getProxy();
        System.out.println("Running JDK Tests");
        test(proxy);
    }

    private static void test(SimpleBean bean) {
        long before = 0;
        long after = 0;

        int times = 500000;

        System.out.println("Testing Advised Method");
        before = System.currentTimeMillis();
        for (int x = 0; x < times; x++) {
            bean.advised();
        }
        after = System.currentTimeMillis();
        System.out.println("Took " + (after - before) + " ms");
        System.out.println("Testing Unadvised Method");
        before = System.currentTimeMillis();
        for (int x = 0; x < times; x++) {
            bean.unadvised();
        }
        after = System.currentTimeMillis();
        System.out.println("Took " + (after - before) + " ms");
        System.out.println("Testing equals() Method");
        before = System.currentTimeMillis();
        for (int x = 0; x < times; x++) {
            bean.equals(bean);
        }
        after = System.currentTimeMillis();
        System.out.println("Took " + (after - before) + " ms");
        System.out.println("Testing hashCode() Method");

        before = System.currentTimeMillis();
        for (int x = 0; x < times; x++) {
            bean.hashCode();
        }
        after = System.currentTimeMillis();
        System.out.println("Took " + (after - before) + " ms");
        Advised advised = (Advised) bean;
        System.out.println("Testing Advised.getProxyTargetClass() Method");
        before = System.currentTimeMillis();
        for (int x = 0; x < times; x++) {
            advised.getTargetClass();
        }
        after = System.currentTimeMillis();
        System.out.println("Took " + (after - before) + " ms");
        System.out.println(">>>\n");
    }
}

我们使用了三种代理:

  • 标准的CGLIB代理
  • 使用frozen advice链的CGLIB代理(通过setFrozen()方法),advice不能修改
  • JDK代理

每种代理,执行了五个测试用例:

  • Advised method:是一个before advice,其中没有任何操作,减少了advice的影响
  • Unadvised method:unadvised的方法
  • equals():执行equals()的开销
  • hashCode()
  • Advised接口的执行方法:用来修改代理和查询代理信息
用例 CGLIB (Standard) CGLIB (Frozen) JDK
Advised method 75 14 78
Unadvised method 53 15 98
equals() 16 6 94
hashCode() 17 9 57
Advised.getProxyTargetClass() 9 18 39

猜你喜欢

转载自blog.csdn.net/weixin_43364172/article/details/84540912