2

Java JDK Proxy和CGLib动态代理示例讲解

 1 year ago
source link: https://www.pkslow.com/archives/jdk-cglib-proxy
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

代理模式在Java中有很多应用场景,而代理又分静态代码和动态代理。静态代理是编写、编译或加载时织入代码实现,而动态代理则在运行时实现。简单而言,静态代理是在运行前就已经存在,而动态代理则在运行时才存在的。而常用的动态代理有两种实现:

  • JDK Proxy: JDK Proxy是JDK自带的,不需要引入外部库,通过实现接口进行代理;
  • CGLib: CGLib是引入第三方库,通过ASM技术来实现字节码的生成;通过继承的方式来实现。

现在我们来通过代码分别展示一下两种方式。

JDK Proxy

JDK Proxy是通过实现接口来实现代理的,我们先定义一个接口:

public interface Flyable {
    String fly(String route);
}

接着有一个实现类:

public class Bird implements Flyable {
    @Override
    public String fly(String route) {
        System.out.println("Route: " + route);
        return route;
    }
}

然后我们需要定义一个InvocationHandler来改动方法的逻辑,就是目标被代理后有什么不同:

public class FlyableInvocation  implements InvocationHandler {
    private final Flyable target;

    public FlyableInvocation(Flyable target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        long start = System.nanoTime();
        System.out.println(target + ": ===JDK proxy===");
        Object result = method.invoke(this.target, args);
        System.out.println(target + ": ===JDK proxy===");
        long end = System.nanoTime();
        System.out.println("Executing time: " + (end - start) + " ns");
        return result;
    }
}

这里我们在方法调用前后加了日志,同时也计算了一下方法的执行时间。

最终在调用的时候如下:

public class JDKDynamicProxy {
    public static void main(String[] args) {
        ClassLoader classLoader = JDKDynamicProxy.class.getClassLoader();
        Class<?>[] interfaces = Bird.class.getInterfaces();
        Bird bird = new Bird();
        Flyable flyable = (Flyable) Proxy.newProxyInstance(classLoader, interfaces, new FlyableInvocation(bird));
        flyable.fly("Go to pkslow.com");
    }
}

通过Proxy.newProxyInstance方法会生成一个代理的实例,执行这个实例的方法,而原有实例bird被代理了。

执行结果如下:

com.pkslow.basic.jdk.Bird@3551a94: ===JDK proxy===
Route: Go to pkslow.com
com.pkslow.basic.jdk.Bird@3551a94: ===JDK proxy===
Executing time: 18195736 ns

查看代理类

我们还可以查看生成的代理类,可以通过添加VM参数:

# JDK 8
-Dsum.misc.ProxyGenerator.saveGeneratedFiles=true
# JDK 11
-Djdk.proxy.ProxyGenerator.saveGeneratedFiles=true

当然,也可以在Java代码中设置系统属性来实现。

设置完成,再执行程序,就会生成代理类的.class文件。

jdk-cglib-proxy.jdk-class-file.png

CGLib

CGLib是通过继承来实现的,我们先来定义一个类:

public class Animal {
    public String talk(String str) {
        System.out.println("Talking: " + str);
        return str;
    }
}

然后定义一个Interceptor,这个类的作用就是生成代理实例,且定义如何改变目标方法的执行:

public class CGLibProxy<T> implements MethodInterceptor {
    private T target;

    public T getInstance(T target) {
        this.target = target;
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(this.target.getClass());
        enhancer.setCallback(this);
        return (T) enhancer.create();
    }

    @Override
    public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {

        long start = System.nanoTime();
        System.out.println(target + ": ===CGLib proxy===");
        Object result = methodProxy.invoke(this.target, args);
        System.out.println(target + ": ===CGLib proxy===");
        long end = System.nanoTime();
        System.out.println("Executing time: " + (end - start) + " ns");

        return result;
    }
}

这里同样是在方法前后加了日志,同时记录时长。

调用如下:

public class CGLibDynamicProxy {
    public static void main(String[] args) {
        System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "/Users/larry/IdeaProjects/pkslow-samples/java-basic/jdk-cglib-proxy/target/cglib_proxy_classes");
        CGLibProxy<Animal> cgLibProxy = new CGLibProxy<>();
        Animal animal = cgLibProxy.getInstance(new Animal());
        animal.talk("Hi, pkslow");
    }
}

这里设置系统属性是为了把生成的代理类输出到.class文件中,方便学习查看。

执行结果如下:

com.pkslow.basic.cglib.Animal@57855c9a: ===CGLib proxy===
Talking: Hi, pkslow
com.pkslow.basic.cglib.Animal@57855c9a: ===CGLib proxy===
Executing time: 28396871 ns

JDK Proxy本质上使用的是反射的机制,而CGLib使用的是ASM,CGLib速度会更好。但它们都不支持final的类和方法,因为通过接口和继承都无法改变final方法。

代码请看GitHub: https://github.com/LarryDpk/pkslow-samples


References:

代理模式——JDK动态代理与CGLib原理及对比分析

https://www.baeldung.com/java-dynamic-proxies

https://www.baeldung.com/cglib

动态代理类写到.class文件中

https://www.cnblogs.com/carpenterlee/p/8241042.html

https://www.jianshu.com/p/e2917b0b9614

https://blog.csdn.net/xiewenfeng520/article/details/107554411


Code for all: GitHub

欢迎关注微信公众号<南瓜慢说>,将持续为你更新...

file

Recommendations:
Cloud Native
Terraform
Container: Docker/Kubernetes
Spring Boot / Spring Cloud
Https
如何制定切实可行的计划并好好执行


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK