动态代理(proxy)类和类加载器
作者:zhrb
学习该技术之前,需要对反射Method对象的使用有一定的了解。
可以在运行时创建一个实现了一组给定接口的新类(代理类)。不过该代理类和Hibernate中使用的代理类使用的是不一样的技术。
该代理类具有下列方法:
指定接口所需要的全部方法
Object类中的全部方法
但是该代理类不能定义其所实现接口的方法,即,不能编写实现该方法的代码。所以需要invocation handler,该invocation handler实现了InvocationHandler接口的类对象,该接口中方法的定义:
Object
invoke(Object proxy, Method method, Object[] args)
在代理实例上处理方法调用并返回结果。
在代理实例上处理方法调用并返回结果。
那么当我们调用新生成的代理对象的方法时,invocation handler的invoke方法就会被调用。从而间接达到了为代理类增加自己代码的目的。
以下面这个例子为例,我们希望实现Comparable接口的对象的compareTo方法被调用时执行一些额外的代码,如打印被调用的方法(compareTo)名称、调用该方法传递进来的参数。我们可以使用代理类实现。具体代码详见《java核心技术 卷1:基础知识》接口与内部类这章的代理小节中的例6-7, ProxyTest.java代码。
为了做实验我们编写自己的代码进行操作,有如下接口与类:
public interface ShapeInterface {
public double getArea();
}
Cricle、Square、Triangle类实现了ShapeInterface接口。
我们希望实现如下功能,当ShapeInterface的实现类的getArea()方法被调用的时候,打印出被代理对象的类型、该代理对象中被调用的方法名、参数个数、参数类型、参数值,如果没有参数传递进来则打印“该方法没有参数”,最后执行getArea()方法本身。这些功能可以算是为代理类新增的代码,但我们不能直接在代理类中编写代码,我们需要为ShapeInterface接口定义一个invocation handler,即下面的ShapeProxyHandler,见如下代码:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class ShapeProxyHandler implements InvocationHandler {
public ShapeProxyHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
System.out.print("你所要代理的对象类型是"+target.getClass().getName());
System.out.println(",要执行"+method.getName()+"方法");
if (args!=null){
System.out.print("该方法有"+args.length+"个参数,分别是");
for(Object e:args){
System.out.print(e.getClass().getName());
System.out.print(","+e);
}
}
else{
System.out.println("该方法没有参数");
}
return method.invoke(target, args);
}
private Object target;//存放要代理的对象
}
该invocation handler编写好以后,编写如下测试代码进行测试:
import java.lang.reflect.Proxy;
import proxy.ShapeInterface;
public class ProxyTestShape {
public static void main(String[] args) {
Object[] elements = new Object[3];//用于存放代理对象的数组
ShapeProxyHandler proxyHandler1 = new ShapeProxyHandler(new Circle());
Object proxy1 = Proxy.newProxyInstance(ShapeInterface.class.getClassLoader(), new Class[] { ShapeInterface.class } , proxyHandler1);
elements[0] = proxy1;
ShapeProxyHandler proxyHandler2 = new ShapeProxyHandler(new Square(10));
Object proxy2 = Proxy.newProxyInstance(ShapeInterface.class.getClassLoader(), new Class[] { ShapeInterface.class } , proxyHandler2);
elements[1] = proxy2;
ShapeProxyHandler proxyHandler3 = new ShapeProxyHandler(new Triangle(10, 20, 30));
Object proxy3 = Proxy.newProxyInstance(ShapeInterface.class.getClassLoader(), new Class[] { ShapeInterface.class } , proxyHandler3);
elements[2] = proxy3;
invokeGetArea(elements);
}
public static void invokeGetArea(Object[] elements){
for(Object e:elements){
System.out.println("返回结果是="+((ShapeInterface)e).getArea());
System.out.println("===============END=============");
}
}
}
其中下面这句代码:
Object proxy1 = Proxy.newProxyInstance(ShapeInterface.class.getClassLoader(), new Class[] { ShapeInterface.class } , proxyHandler1);
使用了Proxy类的newProxyInstance方法。
我们先来看一下该方法第一个参数:
类加载器(class loader),ShapeInterface.class.getClassLoader()就是ShapeInterface这个类型的类加载器。该处代码和java核心技术的参考代码又不一样,核心技术里面该处的参数为null,主要是因为核心技术里面实现的是Comparable接口,而该接口是被Extension类加载器。Extension类加载器用于从jre/lib/ext目录加载,而Comparable作为java.lang包中的接口正好位于jre/lib/ext目录。但我们自定义的接口ShapeInterface却不能被Extension类加载器加载而是被System类加载器加载,所以此处的代码不能像java核心技术中的那样设置为null。每个线程都有一个对类加载器的引用,称为上下文类加载器。主线程的上下文类加载器是System类加载器。在这里main方法所在的主线程的就使用了System类加载器。也就是说主线程的上下文类加载器和自定义接口的ShapeInterface类加载器刚好一样,都是System类加载器。那么上面这段代码可以改成如下这段代码:
Object proxy1 = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class[] { ShapeInterface.class } , proxyHandler1);
其中Thread.currentThread().getContextClassLoader()这句代码就是获得当前线程(主线程)的上下文类加载器。
我们再来看一下该方法第二个参数:
这个参数new Class[] { ShapeInterface.class }是一个数组,意味着我们实际上生成的代理类可以实现多个接口。
该方法的第三个参数:
proxyHandler1就是我们实现的invocation handler,也就是在这个invocation handler中我们编写了想要为代理类增加的代码。
使用代理类还应注意,我们只能生成接口的代理类而不能生成某个类的代理类。
代理类的其他案例:
实际开发中可能会遇到新旧版本的匹配问题,如旧版本接口的和新版本的接口不一样,但是希望新旧版本都可使用,也可以使用动态代理类实现。具体请参考附录的参考资料2.
代理技术为Java提供了极大的动态性可以使我们的编程更加灵活,所以有兴趣的话大家可以了解一下动态代理类。
参考代码见附件
参考资料:
1. JAVA核心技术 卷1:基础知识--接口与内部类--代理小节
2. JAVA核心技术 卷2:高级特性--安全--类加载器小节
3.深入理解Java 7:核心技术与最佳实践 成富 著
分享到:
相关推荐
FoxyProxy 是一款高级代理服务器管理工具,是 Firefox 火狐浏览器的代理插件,相比比 SwitchProxy、ProxyButton、QuickProxy、xyzproxy、ProxyTex 等扩展提供更多的功能。 FoxyProxy 通过使用通配符、正则表达式和...
* 动态代理中的静态方法:java.lang.reflect.Proxy.newProxyInstance (ClassLoader(类加载器),interface(接口),handler(监听处理器)) * * 代码示例:《帮爸爸买菜》 * 1.Father: 被代理类,必须需要实现接口 ...
代理经理代理管理器,用于从Internet下载具有所需参数(协议和匿名程度)的代理服务器列表,并与加载服务器一起工作(例如,从该列表中获取随机代理服务器)。安装从PyPi安装软件包$ pip3 install proxy-manager-g4...
react-proxy, 代理在不卸载或者丢失它的状态的情况下响应组件 代理 一种通用反应元件代理,通过热加载器作为新引擎。 1.x-和 2.x你正在查看 1.x 分支中广泛使用的自述文件。 然而,我们打算逐步过渡诸如 react-...
6.8.3 枚举类的属性、方法和构造器 220 6.8.4 实现接口的枚举类 223 6.8.5 包含抽象方法的枚举类 224 6.9 对象与垃圾回收 225 6.9.1 对象在内存中的状态 226 6.9.2 强制垃圾回收 227 6.9.3 finalize方法 228 ...
《Java 基础核心总结》 Java 概述 什么是 Java2 Java 的特点Java ...JDK Proxy 和 CGLIB 的对比动态代理的实际应用 Spring AOP 变量 变量汇总实例变量 实例变量的特点全局变量 静态变量 静态变量的特点类变量 局部变量
安装将此行添加到应用程序的 Gemfile 中: gem 'proxy_manager' 然后执行: $ bundle 或者自己安装: $ gem install proxy_manager用法加载代理列表从阵列 (IP:PORT) proxy = ProxyManager :: Proxy . new ( [ '...
React代理加载器 将React组件包装在代理组件中以启用代码拆分,这将按需加载React组件及其依赖项。 要求 此模块至少需要Node v6.9.0和Webpack v4.0.0。 入门 首先,您需要安装react-proxy-loader : $ npm install ...
不过我实现的这个版本有局限性: 只适用于对象,无法代理数组等基本数据类型(需要用 ArrayObject 一类的内置对象封装) 被代理之后,一些带有操作符重载性质的接口实现就失效了,例如 ArrayAccess 的索引器、...
一、什么是AOP 二、AOP相关概念 (1)切面 (Aspect) 交叉业务,也就是通用的业务逻辑,比如日志、事务。 (2)通知(Advice) ... 2、类加载器织入 3、动态代理织入 AspectJ:1,2 Spring AOP:3
Docker 中的 BrowserMob 代理 ... 默认情况下,它会在端口 9090 上加载 BrowserMob-Proxy,并在端口 9091 上启动代理侦听器。 用法 $ docker run -p 9090:9090 -p 9091:9091 shopigniter/browsermob-proxy
Proxy动态代理机制详解 从整体上观察对象 网络开发 Servlet基础,生命周期执行过程 Http请求详解,握手挥手流程简介 会话跟踪技术,Session和Cookie详解 过滤器、监听器、拦截器,应用详解 Servlet 集成 C3P0
: • 设计简单 • 易于使用 • 处理速度快 • 与平台无关 v1.5 中的新功能: • 错误修正(代理可以再次加载) • 显示自己的 ip、提供者和位置 说明: 1. 启动应用程序,输入密码“1234”,不带引号,然后回车确认...
语言:English,русский,中文 (简体) ...保存后重新加载代理自动设置 3.添加默认的中国旁路列表 4.完全重写UI 5.添加中文支持 2012-12-7(1.1.4) 1.支持直接模式,连接永远不会使用代理。 2.更新固定UI和错误。
设置安装节点克隆回购安装 API 和 Core 模块的依赖项运行主编辑过滤器将 DNS 流量重定向到运行此项目的设备自动重新加载默认情况下,将自动考虑 source.d 和 filters.d 中的所有更改。 您不必停止 DNSProxy添加更多...
节点代理注入器该脚本允许您代理远程服务器,并将css样式表和js脚本注入到远程服务器响应中。 该工具支持实时重新加载,但是您必须安装和配置浏览器扩展。 有关详细信息,请访问 。 请注意,该工具正在开发中!用法$...
内核驱动加载与调试符号服务器 _NT_SYMBOL_PATH代理服务器 _NT_SYMBOL_PROXY驱动的加载安装一个驱动等同于安装一个服务打开服务管理器成
保存和加载过滤器和数据包日志 定义参考 在选择数据包时单击定义按钮将打开用于解析该数据包的定义。 此窗口是可拖动和可调整大小的(从右下角调整大小) 六角工具 该工具将是一种快速计算工具,用于从数据包数据...
代理Proxy 底层实现 JDK动态代理(默认) 基于接口:代理对象与目标对象是兄弟关系,目标类必须实现接口 CGLIB动态代理 基于父类:代理对象与目标对象是父子关系.目标不能被final修饰 修改默认代理方法:...
ocramius / proxy-manager的维护者以及成千上万的其他软件包正在与Tidelift一起使用,以为您用于构建应用程序的开源依赖项提供商业支持和维护。 节省时间,降低风险并改善代码运行状况,同时向维护人员支付所使用的...