草庐IT

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

南瓜慢说 www.pkslow.com 2023-04-16 原文

简介

代理模式在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文件。

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

有关Java JDK Proxy和CGLib动态代理示例讲解的更多相关文章

  1. 世界前沿3D开发引擎HOOPS全面讲解——集3D数据读取、3D图形渲染、3D数据发布于一体的全新3D应用开发工具 - 2

    无论您是想搭建桌面端、WEB端或者移动端APP应用,HOOPSPlatform组件都可以为您提供弹性的3D集成架构,同时,由工业领域3D技术专家组成的HOOPS技术团队也能为您提供技术支持服务。如果您的客户期望有一种在多个平台(桌面/WEB/APP,而且某些客户端是“瘦”客户端)快速、方便地将数据接入到3D应用系统的解决方案,并且当访问数据时,在各个平台上的性能和用户体验保持一致,HOOPSPlatform将帮助您完成。利用HOOPSPlatform,您可以开发在任何环境下的3D基础应用架构。HOOPSPlatform可以帮您打造3D创新型产品,HOOPSSDK包含的技术有:快速且准确的CAD

  2. postman——集合——执行集合——测试脚本——pm对象简单示例02 - 2

    //1.验证返回状态码是否是200pm.test("Statuscodeis200",function(){pm.response.to.have.status(200);});//2.验证返回body内是否含有某个值pm.test("Bodymatchesstring",function(){pm.expect(pm.response.text()).to.include("string_you_want_to_search");});//3.验证某个返回值是否是100pm.test("Yourtestname",function(){varjsonData=pm.response.json

  3. ruby - 在 Ruby 中动态创建数组 - 2

    有没有办法在Ruby中动态创建数组?例如,假设我想遍历用户输入的书籍数组:books=gets.chomp用户输入:"TheGreatGatsby,CrimeandPunishment,Dracula,Fahrenheit451,PrideandPrejudice,SenseandSensibility,Slaughterhouse-Five,TheAdventuresofHuckleberryFinn"我把它变成一个数组:books_array=books.split(",")现在,对于用户输入的每一本书,我想用Ruby创建一个数组。伪代码来做到这一点:x=0books_array.

  4. ruby - 是否可以将 IRB 提示配置为动态更改? - 2

    我想在IRB中浏览文件系统并让提示更改以反射(reflect)当前工作目录,但我不知道如何在每个命令后进行提示更新。最终,我想在日常工作中更多地使用IRB,让bash溜走。我在我的.irbrc中试过这个:require'fileutils'includeFileUtilsIRB.conf[:PROMPT][:CUSTOM]={:PROMPT_N=>"\e[1m:\e[m",:PROMPT_I=>"\e[1m#{pwd}>\e[m",:PROMPT_S=>"FOO",:PROMPT_C=>"\e[1m#{pwd}>\e[m",:RETURN=>""}IRB.conf[:PROMPT_MO

  5. Ruby-vips 图像处理库。有什么好的使用示例吗? - 2

    我对图像处理完全陌生。我对JPEG内部是什么以及它是如何工作一无所知。我想知道,是否可以在某处找到执行以下简单操作的ruby​​代码:打开jpeg文件。遍历每个像素并将其颜色设置为fx绿色。将结果写入另一个文件。我对如何使用ruby​​-vips库实现这一点特别感兴趣https://github.com/ender672/ruby-vips我的目标-学习如何使用ruby​​-vips执行基本的图像处理操作(Gamma校正、亮度、色调……)任何指向比“helloworld”更复杂的工作示例的链接——比如ruby​​-vips的github页面上的链接,我们将不胜感激!如果有ruby​​-

  6. ruby - HTTP 请求中的用户代理,Ruby - 2

    我是Ruby的新手。我试过查看在线文档,但没有找到任何有效的方法。我想在以下HTTP请求botget_response()和get()中包含一个用户代理。有人可以指出我正确的方向吗?#PreliminarycheckthatProggitisupcheck=Net::HTTP.get_response(URI.parse(proggit_url))ifcheck.code!="200"puts"ErrorcontactingProggit"returnend#Attempttogetthejsonresponse=Net::HTTP.get(URI.parse(proggit_url)

  7. ruby-on-rails - capybara poltergeist - 覆盖用户代理 - 2

    有人知道如何将capybarapoltergeist的用户代理覆盖到移动用户代理以进行测试吗?我发现了一些有关为seleniumwebdriver配置它的信息:http://blog.plataformatec.com.br/2011/03/configuring-user-agents-with-capybara-selenium-webdriver/这在capybara闹鬼中怎么可能? 最佳答案 请参阅poltergeistgithub页面上的链接:https://github.com/teampoltergeist/polte

  8. ruby - 如何配置 Ruby Mechanize 代理以通过 Charles Web 代理工作? - 2

    我正在使用Ruby/Mechanize编写一个“自动填写表格”应用程序。它几乎可以工作。我可以使用精彩CharlesWeb代理以查看服务器和我的Firefox浏览器之间的交换。现在我想使用Charles查看服务器和我的应用程序之间的交换。Charles在端口8888上代理。假设服务器位于https://my.host.com。.一件不起作用的事情是:@agent||=Mechanize.newdo|agent|agent.set_proxy("my.host.com",8888)end这会导致Net::HTTP::Persistent::Error:...lib/net/http/pe

  9. ruby-on-rails - carrierwave:在序列化动态属性上安装 uploader - 2

    首先,我使用的是rails3.1.3和来自master的carrierwavegithub仓库的分支。我使用after_init钩子(Hook)来确定基于属性的字段页面模型实例并为这些字段定义属性访问器将值存储在序列化哈希中(希望它清楚我是什么谈论)。这是我正在做的事情的精简版:classPage省略mount_uploader命令让我可以访问我想要的属性。但是当我安装uploader时出现错误消息说“nil类的未定义新方法”我在源代码中读到有方法read_uploader和扩展模块中的write_uploader。我如何必须覆盖这些来制作mount_uploader命令使用我的“虚拟

  10. arrays - 如何在下面的示例中将两个值数组分组为 n 个值数组? - 2

    我已经有很多两个值数组,例如下面的例子ary=[[1,2],[2,3],[1,3],[4,5],[5,6],[4,7],[7,8],[4,8]]我想把它们分组到[1,2,3],[4,5],[5,6],[4,7,8]因为意思是1和2有关系,2和3有关系,1和3有关系,所以1,2,3都有关系我如何通过ruby​​库或任何算法来做到这一点? 最佳答案 这是基本Bron–Kerboschalgorithm的Ruby实现:classGraphdefinitialize(edges)@edges=edgesenddeffind_maximum_

随机推荐