Spring5框架之JDK动态代理与CGLIB代理两种代理方式性能比较(七)

1.前言

在Spring中有两种类型的代理:一个是使用JDK Proxy类生成的代理,另一种就是使用CGLIB Enhancer类创建的CGLIB代理,下面将简单介绍两种代理方式。

JDK代理

JDK是Spring中最基本的代理类型,与CGLIB不同的是JDK代理只能生成基于接口的代理,无法生成类的代理。所以Spring中使用JDK的代理以实现接口的代理,下面简单演示一下JDK动态代理使用如下所示:

  1. 新增Calculator接口以其实现如下所示:
public interface Calculator {

    Double divide(Double a,Double b);
}


@Service(value = "calculator")
public class CalculatorImpl implements Calculator {
    @Override
    public Double divide(Double a, Double b) {
        return a / b;
    }
}

  1. 新增CalculatorProxy类实现InvocationHandler接口如下所示:
@NoArgsConstructor
public class CalculatorJDKProxy implements InvocationHandler {

    private Object proxyTarget;

    public CalculatorJDKProxy(Object proxyTarget) {
        this.proxyTarget = proxyTarget;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("\n"+"JDK代理方法执行之前执行的程序逻辑");
        List<Object> objects = Arrays.asList(args);
        double sum = objects.stream().mapToDouble(e -> Double.parseDouble(e.toString().trim())).sum();
        if (sum > 50.0) {
            System.out.println("计算的值大于50了");
        }
        Object invoke = method.invoke(proxyTarget, args);
        System.out.println("JDK代理执行之后的程序逻辑");
        // 代理之和的执行程序
        return invoke;
    }
}
  1. 测试类方法使用如下所示:
    @Test
    public void testJdkProxy() {
        Calculator calculator = new CalculatorImpl();
        Calculator o = (Calculator) Proxy.newProxyInstance(calculator.getClass().getClassLoader(), calculator.getClass().getInterfaces(), new CalculatorJDKProxy(calculator));
        Double divide = o.divide(250.0, 5.0);
        System.out.println("输出的值为:" + divide);
    }

输出结果如下所示:

JDK代理方法执行之前执行的程序逻辑
计算的值大于50了
JDK代理执行之后的程序逻辑
输出的值为:50.0

当使用JDK代理时,所有的方法的调用都会被JVM拦截到代理的Invoke方法,然后Spring由这个方法以及切入点方法确定是否执行相关通知。

CGLIB代理

CGLIB会为每个代理动态生成新的字节码并尽可能重复使用已经生成的字节码对象。而且也完美解决了JDK动态代理只能代理接口对象的局限性,从Spring 3.2 开始后不需要在单独将CGLIB添加到项目路径中,因为CGLIB包含在Spring-core jar中。


import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;

public class CalculatorCGLIBProxy implements MethodInterceptor {
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("CGLIB代理之前执行的程序逻辑");
        // 执行被代理对象的方法
        Object invokeSuper = methodProxy.invokeSuper(o, objects);
        List<Object> list = Arrays.asList(objects);
        double sum = list.stream().mapToDouble(e -> Double.parseDouble(e.toString().trim())).sum();
        if (sum > 50.0) {
            System.out.println("计算的值大于50了");
        }
        System.out.println("CGLIB代理之后执行的程序逻辑");
        return invokeSuper;
    }
}

测试类方法代码如下所示;

    @Test
    public void testCGLIB() {
        Enhancer enhancer = new Enhancer();
        System.out.println();
        enhancer.setSuperclass(CalculatorImpl.class);
        enhancer.setCallback(new CalculatorCGLIBProxy());
        Calculator o = (Calculator) enhancer.create();
        Double divide = o.divide(500.0, 3.0);
        System.out.println(divide);
    }

运行结果如下所示:

CGLIB代理之前执行的程序逻辑
计算的值大于50了
CGLIB代理之后执行的程序逻辑
166.66666666666666

需要说明的是CGLIB生成代理是通过字节码生成的子类作为代理类,因此不能对private final方法代理;

两种代理执行性能比较

  1. 先分别测试两种标准代理实现方式的性能差异比较,详情代码如下所示:
 @Test
    public void testPerformance() {
        //TODO JDK代理使用
        Map<String,String> map = new HashMap<>();
        Calculator calculator = new CalculatorImpl();
        Random random = new Random();
        // 创建Spring的计算类对象
        StopWatch stopWatch = new StopWatch();
        String taskName="JDK代理";
        stopWatch.start(taskName);
        Calculator o = (Calculator) Proxy.newProxyInstance(calculator.getClass().getClassLoader(), calculator.getClass().getInterfaces(), new CalculatorJDKProxy(calculator));
        List<Double> list = new ArrayList<>(1000);
        addNumToList(o, random, list);
        Double aDouble = list.stream().max(Double::compareTo).get();
        System.out.println("JDK计算比较结束,集合最大的值为:"+aDouble);
        stopWatch.stop();
        map.put(taskName,stopWatch.prettyPrint());
        list.clear();

        //TODO CGLIB代理使用
        taskName="CGLIB代理";
        stopWatch = new StopWatch();
        stopWatch.start(taskName);
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(CalculatorImpl.class);
        enhancer.setCallback(new CalculatorCGLIBProxy());
        Calculator o1 = (Calculator) enhancer.create();
        addNumToList(o1, random, list);
        System.out.println("CGLIB计算比较结束,集合最大的值为:"+aDouble);
        stopWatch.stop();
        map.put(taskName,stopWatch.prettyPrint());
        System.out.println(map);
    }


    private void addNumToList(Calculator o, Random random, List<Double> list) {
        for (int i = 1; i < 10000; i++) {
            double a = (random.nextInt(i) + 1) * 1.0;
            double b = (random.nextInt(i) + 1) * 1.0;
            Double result = o.divide(a, b);
            list.add(result);
        }
    }

输出map的结果如下所示:

{CGLIB代理=StopWatch '': running time = 134290034 ns
---------------------------------------------
ns         %     Task name
---------------------------------------------
134290034  100%  CGLIB代理
, JDK代理=StopWatch '': running time = 114593231 ns
---------------------------------------------
ns         %     Task name
---------------------------------------------
114593231  100%  JDK代理
}

无论运行多少次testPerformance方法我们总是发现CGLIB的运行时间比JDK的时间长久一些,一般有接口的情况下可以选择使用JDK动态代理,这也是Spring对接口默认的代理方式。

  1. 下面测试在Spring中两种代理方式的性能比较,具体代码如下所示:
  public void runCGLIBTest(ProxyFactory proxyFactory, Calculator calculator, int num, Map<String, Long> map) {
        // 当结果为true将使用CGLIB代理否则为JDK代理,默认为false
        proxyFactory.setProxyTargetClass(true);
        proxyFactory.setFrozen(true);
        testCode(calculator, num, map);
    }

    public void runJDKTest(ProxyFactory proxyFactory, Calculator calculator, int num, Map<String, Long> map) {
        // 当结果为true将使用CGLIB代理否则为JDK代理,默认为false
        proxyFactory.setProxyTargetClass(false);
        proxyFactory.setInterfaces(calculator.getClass().getInterfaces());
        testCode(calculator, num, map);
    }

    public void testCode(Calculator calculator, int num, Map<String, Long> map) {

        StopWatch stopWatch = new StopWatch();
        Random random = new Random();
        stopWatch.start("divide");
        List<Double> list = new ArrayList<>();
        addNumToList(calculator, random, list);
        Double aDouble = list.stream().max(Double::compareTo).get();
        System.out.println("计算比较结束,集合最大的值为:" + aDouble);
        stopWatch.stop();
        map.put("divide", stopWatch.getLastTaskTimeMillis());
        stopWatch.start("equals");
        for (int i = 0; i < num; i++) {
            calculator.equals(calculator);
        }
        stopWatch.stop();
        map.put("equals", stopWatch.getLastTaskTimeMillis());

        stopWatch.start("hashCode");
        for (int i = 0; i < num; i++) {
            calculator.hashCode();
        }
        stopWatch.stop();
        map.put("hashCode", stopWatch.getLastTaskTimeMillis());


        Advised advised = (Advised) calculator;
        stopWatch.start("hashCode");
        for (int i = 0; i < num; i++) {
            advised.getTargetClass();
        }
        stopWatch.stop();
        map.put("advised.getTargetClass()", stopWatch.getLastTaskTimeMillis());
    }

    @Test
    public void testProxyPerformance() {
        Map<String, Long> map = new HashMap<>();
        Calculator calculator = applicationContext.getBean("calculator", Calculator.class);
        NameMatchMethodPointcut pointcut = new NameMatchMethodPointcut();
        pointcut.addMethodName("divide");
        PointcutAdvisor pointcutAdvisor = new DefaultPointcutAdvisor(pointcut, new SimpleBeforeAdvice());
        ProxyFactory proxyFactory = new ProxyFactory();
        proxyFactory.addAdvisor(pointcutAdvisor);
        proxyFactory.setTarget(calculator);
        Calculator proxy = (Calculator) proxyFactory.getProxy();
        runCGLIBTest(proxyFactory, proxy, 5000000, map);
        System.out.println("CGLIB---map:"+map);
        map.clear();
        runJDKTest(proxyFactory, proxy, 5000000, map);
        System.out.println("JDK---map:"+map);
    }

运行结果输出map结果如下所示:

CGLIB---map:{hashCode=96, equals=24, divide=455, advised.getTargetClass()=12}

JDK---map:{hashCode=113, equals=45 divide=261, advised.getTargetClass()=0}

正如你上面看到的那样CGLIB代理与JDK代理两种性能差异不大,唯一就是CGLIB代理不仅可以代理接口也可以代理类,不过需要注意的是如果开启CGLIB代理接口,需要调用ProxyFactory 实例中的setOptimize方法将optimize 的值设置为true。

源码

以上代码均在 https://github.com/codegeekgao 可以下载查看。

猜你喜欢

转载自blog.csdn.net/javaee_gao/article/details/106596492