草庐IT

动态代理入门必看

Math210 2023-08-02 原文

基本介绍

代理

就是让代理角色帮助真实角色完成一件事情。比如说,我叫我们朋友帮我完成博客,那么这个朋友就是代理,朋友不是我,但是和我完成同样的事,并且还可以对这件事加入他的行为逻辑。
不论是什么代理方法,其根本目的就是为了拓展一个类的方法,也就是想要加入自己的行为逻辑。
当然,我们可以直接修改类的方法,但是一旦类的方法多了起来,就会造成代码的冗余,而且极难进行维护。

静态代理

静态代理相当于是多写了一个代理类,在调用的时候调用的是代理类,在代理类中的处理还是原生的处理逻辑,不过在前后添加上需要添加的代码。
其最根本的特征就是这个代理类需要程序员自己写出来。即在编译阶段就确定了代理对象是谁。

实例说明:

我们编写如下的测试案例:

public class StaticProxyTest {  
    @Test  
    public void Test01() {  
        Vehicle proxyForCar = new ProxyForCar(new Car());  
        proxyForCar.run("托马斯");  
        proxyForCar.fly("托马斯");  
    }  
}

编写接口:

public interface Vehicle {  
    public void run(String s);  
  
    public void fly(String s);  
}

解读:

  • 这里编写接口纯粹是为了符合模板设计模式,没有其他的原因;
  • 这样就可以让代理对象调用原类的方法,不会造成原类的浪费。最大限度的保存原类的功能;
    编写实现类:
public class Car implements Vehicle {  
    @Override  
    public void run(String name) {  
        System.out.println(name + "is running");  
    }  
  
    @Override  
    public void fly(String name) {  
        System.out.println(name + "is flying");  
    }  
}

编写代理类:

public class ProxyForCar implements Vehicle {  
    Vehicle vehicle;  
  
    public ProxyForCar(Vehicle vehicle) {  
        this.vehicle = vehicle;  
    }  
  
    @Override  
    public void run(String arg) {  
        System.out.println("before invoke...");  
        vehicle.run(arg);  
        System.out.println("after invoke...");  
    }  
  
    @Override  
    public void fly(String arg) {  
        System.out.println("before invoke...");  
        vehicle.fly(arg);  
        System.out.println("after invoke...");  
    }  
}

以上就是一个简单的静态代理的实现。可以发现缺点是显然的,静态代理无法实现切面编程,一旦我们需要代理一个新的接口方法,就要重新写一个方法,甚至重新写一个代理类,这样的开销成本无疑是很大的。
于是我们引入了动态代理。

动态代理

我们这里将主要介绍基于接口实现的 JDK 的动态代理方式
见名知义,动态即我们不需要在自己写代理类了,而是由 JVM 在运行阶段自动的帮我生成一个代理类,在程序结束的时候自动销毁。优势在于:

  • 可延展性高,便于实现切面编程。可以批量的代理类的方法;
  • 极大的简化了代码工程量;
    接下来,我将简单的演示一下动态代理的实现过程,深入分析我们新开一章单独解决。

实例说明

编写测试案例:

public class DynamicProxyTests {  
    @Test  
    public void Test01() {  
        Car car = new Car();  
        VehicleDynamicProxy vehicleDynamicProxy = new VehicleDynamicProxy(car);  
        Vehicle proxy = vehicleDynamicProxy.getProxy();  
        String fly = proxy.fly(10000);  
        System.out.println("result:\t" + fly);  
    }  
}

编写接口:

public interface Vehicle {  
    public String fly(int height);  
  
    public void run();  
}

实现类 ship :

public class Ship implements Vehicle {  
    @Override  
    public String fly(int height) {  
        System.out.println("wdnmd");  
        return "ship 的高度为" + height;  
    }  
  
    @Override  
    public void run() {  
        System.out.println("ship 在run");  
    }  
}

实现类 Car:

public class Car implements Vehicle{  
    @Override  
    public String fly(int height) {  
        System.out.println("wdnmdCar");  
        return "car 的高度为" + height;  
    }  
  
    @Override  
    public void run() {  
        System.out.println("car 在run");  
    }  
}

动态代理类:

public class VehicleDynamicProxy {  
    /**  
     * 被代理对象  
     */  
    public Vehicle targetVehicle;  
  
    public VehicleDynamicProxy(Vehicle targetVehicle) {  
        this.targetVehicle = targetVehicle;  
    }  
  
    public Vehicle getProxy() {  
        //获取类的加载器  
        ClassLoader classLoader = targetVehicle.getClass().getClassLoader();  
        //获取类的接口数组  
        Class<?>[] interfaces = targetVehicle.getClass().getInterfaces();  
        //调用函数,用来的调用方法  
        InvocationHandler invocationHandler = new InvocationHandler() {  
            @Override  
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {  
                Object result = null;  
                try {  
                    System.out.println("前置通知 before");  
                    //调用被代理对象的方法  
                    result = method.invoke(targetVehicle, args);  
                    System.out.println("返回通知 afterReturning");  
                } catch (Exception e) {  
                    //异常通知 AfterThrowing                    
                    throw new RuntimeException(e);  
                } finally {  
                    //最终通知 After                }  
                return result;  
            }  
        };  
        //此时的proxy对象是实现了Vehicle接口,继承了Proxy类的代理对象  
        Vehicle proxy = (Vehicle) Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);  
        return proxy;  
    }  
}

解读:

  • JVM 何以动态的创建动态数组?从以上的代码可以看出,我们获得了被代理对象的类加载器和接口数组。显然底层是根据反射动态生成代理类,并且创建实例的;
  • 为了实现调用原对象中的方法,在我们调用代理对象的相应方法的时候,一定会利用 InvocationHandler 中进行调用的转发。也就是说,方法的实际调用不在代理对象中,而由 InvocationHandler 转发到了原对象处理;
  • 这样我们就可以在 invoke 方法中实现自己想要拓展的逻辑,在 Spring 中,就有了前置通知,后置通知等切面编程的思想;

总结

  1. 代理:让代理角色帮助真实角色完成任务,如朋友帮忙完成博客。
  2. 代理方法目的:拓展类的方法,加入自己的行为逻辑。
  3. 静态代理:编写代理类,调用时调用代理类,前后添加需要的代码,需要程序员在编译阶段自行编写代理类。
    • 缺点:无法实现切面编程,需要代理新接口方法时需要重新写方法或代理类。
  4. 动态代理:JVM在运行阶段自动生成代理类,程序结束时销毁。
    • 优势:高可延展性,便于实现切面编程,简化代码工程量。
  5. 动态代理实现过程:
    • 获取被代理对象的类加载器和接口数组。
    • 使用InvocationHandler进行调用转发。
    • 实现自己想要拓展的逻辑。

接下来,我们将从源码的角度,详细的看看 JDK 动态代理的机制。
我们再抛出一个问题,那就是为什么在生成代理对象的时候一定要传入接口数组呢?似乎没有接口,我们也可以通过反射,来转发方法的执行。
【源码解析】JDK 动态代理实现 - yelanyanyu - 博客园 (cnblogs.com)

有关动态代理入门必看的更多相关文章

  1. LC滤波器设计学习笔记(一)滤波电路入门 - 2

    目录前言滤波电路科普主要分类实际情况单位的概念常用评价参数函数型滤波器简单分析滤波电路构成低通滤波器RC低通滤波器RL低通滤波器高通滤波器RC高通滤波器RL高通滤波器部分摘自《LC滤波器设计与制作》,侵权删。前言最近需要学习放大电路和滤波电路,但是由于只在之前做音乐频谱分析仪的时候简单了解过一点点运放,所以也是相当从零开始学习了。滤波电路科普主要分类滤波器:主要是从不同频率的成分中提取出特定频率的信号。有源滤波器:由RC元件与运算放大器组成的滤波器。可滤除某一次或多次谐波,最普通易于采用的无源滤波器结构是将电感与电容串联,可对主要次谐波(3、5、7)构成低阻抗旁路。无源滤波器:无源滤波器,又称

  2. 微信小程序开发入门与实战(Behaviors使用) - 2

    @作者:SYFStrive @博客首页:HomePage📜:微信小程序📌:个人社区(欢迎大佬们加入)👉:社区链接🔗📌:觉得文章不错可以点点关注👉:专栏连接🔗💃:感谢支持,学累了可以先看小段由小胖给大家带来的街舞👉微信小程序(🔥)目录自定义组件-behaviors    1、什么是behaviors    2、behaviors的工作方式    3、创建behavior    4、导入并使用behavior    5、behavior中所有可用的节点    6、同名字段的覆盖和组合规则总结最后自定义组件-behaviors    1、什么是behaviorsbehaviors是小程序中,用于实现

  3. 【Java入门】使用Java实现文件夹的遍历 - 2

    遍历文件夹我们通常是使用递归进行操作,这种方式比较简单,也比较容易理解。本文为大家介绍另一种不使用递归的方式,由于没有使用递归,只用到了循环和集合,所以效率更高一些!一、使用递归遍历文件夹整体思路1、使用File封装初始目录,2、打印这个目录3、获取这个目录下所有的子文件和子目录的数组。4、遍历这个数组,取出每个File对象4-1、如果File是否是一个文件,打印4-2、否则就是一个目录,递归调用代码实现publicclassSearchFile{publicstaticvoidmain(String[]args){//初始目录Filedir=newFile("d:/Dev");Datebeg

  4. ES基础入门 - 2

    ES一、简介1、ElasticStackES技术栈:ElasticSearch:存数据+搜索;QL;Kibana:Web可视化平台,分析。LogStash:日志收集,Log4j:产生日志;log.info(xxx)。。。。使用场景:metrics:指标监控…2、基本概念Index(索引)动词:保存(插入)名词:类似MySQL数据库,给数据Type(类型)已废弃,以前类似MySQL的表现在用索引对数据分类Document(文档)真正要保存的一个JSON数据{name:"tcx"}二、入门实战{"name":"DESKTOP-1TSVGKG","cluster_name":"elasticsear

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

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

  6. 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

  7. 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)

  8. 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

  9. 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

  10. 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命令使用我的“虚拟

随机推荐