Java基础(十四)代理(Proxy)
1.为什么要使用代理
代理可以在运行时创建一个实现了一组给定接口的新类。这种功能只有在编译时无法确定需要实现哪个接口时才有必要使用。
假设有一个表示接口的Class对象(有可能只包含一个接口),它的确切类型在编译时无法知道,如果想要根据这个Class对象来构造一个实现这些接口的类,就需要使用newInstance方法或者反射找出类的构造器,但是,不能实例化一个接口,需要在程序处于运行状态时定义一个新类。
代理机制可以解决这个问题,代理类可以在运行时创建新的类,这样的代理类能够实现执行的接口。并且代理类具有指定接口中的全部方法以及Object类中的全部方法。
2.创建代理对象
要想创建一个代理对象,需要使用Proxy类的newProxyInstance方法。
这个方法有三个参数:
- ClassLoader loader:类加载器,用null表示默认的类加载器。
- Class<?>[] interfaces:Class对象数组,每个元素都是需要实现的接口
- InvocationHandler h:调用处理器
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
调用处理器是实现了InvocationHandler接口的类的对象,这个接口中只有一个invoke方法,无论什么时候调用代理对象的方法,调用处理器的invoke方法都会被调用,并向其传递Method对象和原始的调用参数。
public Object invoke(Object proxy, Method m, Object[] args)
以一个例子说明:
package proxy; import java.lang.reflect.*;
import java.util.*; public class ProxyTest
{
public static void main(String[] args)
{
Object[] elements = new Object[1000];
for (int i = 0; i < elements.length; i++)
{
Integer value = i + 1;
InvocationHandler handler = new TraceHandler(value);
Object proxy = Proxy.newProxyInstance(null, new Class[] { Comparable.class } , handler);
elements[i] = proxy;
} Integer key = new Random().nextInt(elements.length) + 1;
System.out.println("key: " + key); int result = Arrays.binarySearch(elements, key);
System.out.println("result: " + result); if (result >= 0) System.out.println(elements[result]);
}
} class TraceHandler implements InvocationHandler
{
private Object target; public TraceHandler(Object t)
{
target = t;
} public Object invoke(Object proxy, Method m, Object[] args) throws Throwable
{
// print implicit argument
System.out.print(target);
// print method name
System.out.print("." + m.getName() + "(");
// print explicit arguments
if (args != null)
{
for (int i = 0; i < args.length; i++)
{
System.out.print(args[i]);
if (i < args.length - 1) System.out.print(", ");
}
}
System.out.println(")"); // invoke actual method
return m.invoke(target, args);
}
}
以一个例子来分析代码执行过程,首先for循环,创建了1000个Interger对象value,将这些value对象传递到处理器的构造函数中,然后构造一个代理类对象,将这些代理类对象放入到element对象数组中。然后构造一个随机的Integer对象,假设key值为104,然后Arrays.binarySearch(elements, key);在elements对象数组中寻找对象值为104的索引,这个时候就发生了一些很令人难人寻味的事情。
这里就要看一下Arrays.binarySearch方法的具体实现:
public static int binarySearch(Object[] a, Object key) {
return binarySearch0(a, 0, a.length, key);
}
然后看一下binarySearch0方法的具体实现,注意参数传递关系,a是第一个参数,key是最后一个参数
// Like public version, but without range checks.
private static int binarySearch0(Object[] a, int fromIndex, int toIndex,
Object key) {
int low = fromIndex;
int high = toIndex - 1; while (low <= high) {
int mid = (low + high) >>> 1;
@SuppressWarnings("rawtypes")
Comparable midVal = (Comparable)a[mid];
@SuppressWarnings("unchecked")
int cmp = midVal.compareTo(key); if (cmp < 0)
low = mid + 1;
else if (cmp > 0)
high = mid - 1;
else
return mid; // key found
}
return -(low + 1); // key not found.
}
注意这个方法中的下面的代码,同时注意参数对应关系
Comparable midVal = (Comparable)a[mid];int cmp = midVal.compareTo(key);
有点乱,将思路再次梳理一下:
(1)Arrays.binarySearch(elements, key);其中,key是Integer对象104
(2)调用binarySearch(elements, 104);返回binarySearch0(elements, 0, elements.length, 104);
(3)然后开始调用binarySearch0(elements, 0, elements.length, 104);方法:mid=500,midVal是Object对象elements[500]采用强制类型转换得到的Comparable接口类型的对象,在int cmp = midVal.compareTo(104);这行代码执行的时候,由于数组中都是代理对象,而elements[500]也是(null, new Class[] { Comparable.class } , handler);且handler = new TraceHandler(value);中value是500对应的代理对象,当这个代理对象执行.compareTo(104)这个方法的时候,由于compareTo方法是Comparable接口中的唯一方法,如果代理对象调用了这个方法,就会去执行hander调用处理器对象对应的invoke(Object proxy, Method m, Object[] args)方法,此时构造函数中的target就是value也就是500,elements[500].compareTo(104)对应到invoke方法中就是,proxy=elements[500],m=compareTo,args={104},然后就简单了,首先打印500,然后打印.compareTo(,然后打印args中所有的对象即104,然后打印),最后返回的是m.invoke(target, args);对应着elements[500].compareTo(104);也就是又回到了原来的方法中...最后二分法结束之后返回的result的值才是索引值并且大于0,然后进入System.out.println(elements[result]);然后又开始了...又开始了,
System.out.println的实现是:
public void println(Object x) {
String s = String.valueOf(x);
synchronized (this) {
print(s);
newLine();
}
}
String.valueOf的实现是:
public static String valueOf(Object obj) {
return (obj == null) ? "null" : obj.toString();
}
这里就看出问题来了,toString方法也会被重定向到调用处理器上,这是为什么呢,明明toString都不属于Comparable接口,为什么还是要调用处理器。这是因为,即使不属于Comparable接口,toString方法也被代理了,因此就会...
总的输出为:
key: 104
500.compareTo(104)
250.compareTo(104)
125.compareTo(104)
62.compareTo(104)
93.compareTo(104)
109.compareTo(104)
101.compareTo(104)
105.compareTo(104)
103.compareTo(104)
104.compareTo(104)
result: 103
104.toString()
104
3.代理类的特性
下面就要解释一下上面的toString方法到底是怎么回事。
所有的代理类都扩展于Proxy类,一个代理类只有一个实例域--调用处理器,它定义在Proxy的父类中。为了履行代理对象的职责,所需要的任何附加数据都必须存储在调用处理器中。例如,代理Comparable对象时,TranceHandler包装了实际的对象。
所有的代理类都覆盖了Object类中的方法toString、equals和hashCode。如同所有的代理方法一样,这些方法仅仅调用了调用处理器的invoke。Object类中的其他方法没有被重新定义。
对于特定的类加载器和预设的一组接口来说,只能有一个代理类。即如果使用同一个类加载器和接口数组调用两次newProxyInstance方法的话,那么只能够得到同一个类的两个对象。
代理类一定是public和final,如果代理类实现的所有接口都是public,代理类就不属于某个特定的包;否则,所有非公有的接口都必须属于同一个包,同时,代理类与属于这个包。
可以通过Proxy类中的isProxyClass方法检测一个特定的Class对象是否代表着一个代理类。
代理类真的是很麻烦呀。
Java基础(十四)代理(Proxy)的更多相关文章
- java基础(十四)-----详解匿名内部类——Java高级开发必须懂的
在这篇博客中你可以了解到匿名内部类的使用.匿名内部类要注意的事项.匿名内部类使用的形参为何要为final. 使用匿名内部类内部类 匿名内部类由于没有名字,所以它的创建方式有点儿奇怪.创建格式如下: n ...
- Java基础(十四)--装箱、拆箱详解
Java中基本数据类型都有相对应的包装类 什么是装箱?什么是拆箱? 在Java SE5之前,Integer是这样初始化的 Integer i = new Integer(10); 而在从Java SE ...
- Java实习生常规技术面试题每日十题Java基础(四)
目录 1.String 和StringBuffer的区别. 2.数组有没有length()这个方法? String有没有length()这个方法? 3.final, finally, finalize ...
- 黑马程序员:Java基础总结----静态代理模式&动态代理
黑马程序员:Java基础总结 静态代理模式&动态代理 ASP.Net+Android+IO开发 . .Net培训 .期待与您交流! 静态代理模式 public class Ts { ...
- “全栈2019”Java第九十四章:局部内部类详解
难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java第 ...
- “全栈2019”Java第十四章:二进制、八进制、十六进制
难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java第 ...
- “全栈2019”Java第二十四章:流程控制语句中决策语句switch下篇
难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java第 ...
- Bootstrap<基础十四> 按钮下拉菜单
使用 Bootstrap class 向按钮添加下拉菜单.如需向按钮添加下拉菜单,只需要简单地在在一个 .btn-group 中放置按钮和下拉菜单即可.也可以使用 <span class=&qu ...
- Java基础十二--多态是成员的特点
Java基础十二--多态是成员的特点 一.特点 1,成员变量. 编译和运行都参考等号的左边. 覆盖只发生在函数上,和变量没关系. Fu f = new Zi();System.out.println( ...
- Java基础十--接口
Java基础十--接口 一.接口的定义和实例 /* abstract class AbsDemo { abstract void show1(); abstract void show2(); } 8 ...
随机推荐
- NABCD分析 [团队任务]
N(Need,需求) 学校有许多闲置的自己用不着或者想出手的二手物品,加群发消息寻找物品太过繁琐,同样兼职信息在QQ群混杂在一起尤为不便.因此我们打算做一个专门发布信息的App.包括发布闲置物品,兼职 ...
- HTML5-常用正则表达式
有关H5正则表达式的一些常用式子,希望热爱编程的同学们多多指教,还有也希望可以关注收藏本站哦!❤^_^❤ 一.校验数字的表达式 1. 数字:^[0-9]*$ 2. n位的数字:^\d{n}$ 3. 至 ...
- mysql数据库设计规则总结
MySQL数据库设计总结 规则1:一般情况可以选择MyISAM存储引擎,如果需要事务支持必须使用InnoDB存储引擎. 注意:MyISAM存储引擎 B-tree索引有一个很大的限制:参与一个索引的 ...
- Go标准库Context
在 Go http包的Server中,每一个请求在都有一个对应的 goroutine 去处理.请求处理函数通常会启动额外的 goroutine 用来访问后端服务,比如数据库和RPC服务.用来处理一个请 ...
- [ngclass]、[ngstyle]、管道
[ngclass] 动态改变一个元素的class ts: public classcolor:boolean=false; public list:any = [{title:"新闻1&qu ...
- Fast Earth - 文本 绘制,如何实现三维空间中绘制屏幕大小的文字?
如题:先上一张图,在说是如何实现的 实现上图效果,有如下三种方式: 1. 屏幕坐标绘制点要素,即将经纬度坐标转换成屏幕坐标方式绘制,大多数GIS系统都是采用这种方式: 优点:实现方式简单,效果较好 缺 ...
- 对BUG的分析与理解
对BUG的分析与理解 bug的分类 bug,其实就是软件期望的行为与实际行为的差异.从程序的角度来看,在软件整个生命周期中都会有bug的出现.需求分析过程中,需求理解的不足,导致的理解错位 ,遗漏甚至 ...
- Java 多线程爬虫及分布式爬虫架构探索
这是 Java 爬虫系列博文的第五篇,在上一篇 Java 爬虫服务器被屏蔽,不要慌,咱们换一台服务器 中,我们简单的聊反爬虫策略和反反爬虫方法,主要针对的是 IP 被封及其对应办法.前面几篇文章我们把 ...
- smp_processor_id()获取当前执行cpu_id
基于Linux 2.6.32内核进行分析,看本篇文章前,建议先看看percpu变量这篇文章 smp_processor_id()用来获取当前cpu的id,首先来看smp_processor_id的定义 ...
- Docker 安装Oracle
1.使用docker 命令搜索oracle 镜像,前提是已安装了Docker docker search oracle 2.下载相应版本的oracle 镜像 docker pull sath89/o ...