07 Java反射/动态代理
2014-01-13 23:04
501 查看
笔者仿照第5篇Java IO的笔记方式,主要通过小程序来总结反射和动态代理的有关知识。
以getDeclaredMethods()方法为例:
(1)客户接口:一组接口,委托类和代理类都实现这组接口。客户的作用是指示代理类的对象去操作委托类的对象;
(2)代理对象:代理类的对象,实现客户接口,也拥有委托类的对象的引用。因为代理对象和委托类的对象都实现客户接口,所以代理对象可以代替委托类的对象来满足客户要求。也因为代理对象拥有委托类的对象的引用,所以代理对象可以操作委托类的对象,比如调用委托类的方法。代理的作用是充当客户和委托类的中介,接收来自客户的要求,转给委托类的对象实际执行;
(3)委托类对象:委托类实现客户接口。委托类的对象接收代理对象转发来的客户要求,调用自己的方法来满足客户。
动态代理模式(Dynamic Proxy Pattern)就是指代理通过构造参数获取客户接口和委托类的对象。具体体现在代理类需要三个构造参数来关联客户和委托类,一个是委托类的类加载器(通过反射获得),一个是客户接口(通过反射获得),一个是Handler对象(实现InvocationHandler接口的类的对象,实际处理代理转发来的客户要求,Handler对象通过构造参数获取委托类的对象的引用)。这样的设计可以提高代理类的可重用性。所谓动态就是代理在运行时才关联客户接口和委托类,这种关联可以在运行时改变。下面是一个简单的例子:
委托的处理顺序,动态代理模式使用客户->代理->Handler->委托的处理顺序。动态代理不仅可以动态关联客户和委托,还可以再增加一层灵活性,动态关联Handler(不同的方法来重写Handler的invoke()方法,可以生成不同的Handler),相当于给委托类做不同的包装。
(1)需要考虑不同的权限:不同用户对同一对象的操作权限不同,相当于不同权限的同一客户(同样的方法调用但权限不同)要求该对象给出不同反应。这时可以用代理来判断客户权限,然后用不同方法操作对象;
(2)需要以简代繁,防止阻塞:在网络上访问某个很大的资源(比如图片),限于网速需要等待,会导致该资源后面需要访问的资源都被阻塞。如果访问顺序无关紧要,可以使用代理来响应资源的访问,使得后面的访问不再等待。
(3)其它需要用不同方法包装同一个对象,以便对于不同访问给出不同反应的场合。此时代理的作用就是避免修改对象本身,而是修改中介。
1. 反射
1.1 getClass()方法和getName()方法
public class Test { public static void main(String[] args) { Temp tmp = new Temp(); //getClass()方法返回tmp对象的运行时类,用Class类对象表示,getName()提取类名 System.out.println(tmp.getClass().getName()); } } class Temp { } //输出: //TempgetClass()方法属于Object类中的public final类型的方法,所以不可重写。
1.2 三种获取类对象的方式
public class Test { public static void main(String[] args) throws Exception { //声明三个类的类的引用 Class<?> tmp1 = null; Class<?> tmp2 = null; Class<?> tmp3 = null; //根据字符串:返回名字为字符串的类的类对象,静态方法 tmp1 = Class.forName("Temp"); //根据对象:返回对象所属类的类对象 tmp2 = new Temp().getClass(); //根据类:返回类的类对象 tmp3 = Temp.class; System.out.println(tmp1.getName()); System.out.println(tmp2.getName()); System.out.println(tmp3.getName()); } } class Temp { } //输出 //Temp //Temp //Temp获取类对象时,最好用Class.forName()方法,因为这时不必生成Temp类的对象,也可以将名字作为参数,便于修改。
1.3 根据类对象得到其所表示的类的对象
1.3.1 通过类对象的newInstance()方法
public class Test { public static void main(String[] args) throws Exception { //得到以字符串"Temp"为名字的类的类对象 Class<?> tmp = Class.forName("Temp"); //newInstance()方法创建类对象所表示的类的对象 //需要强制类型转换,将返回的泛型对象转换成类的对象 Temp n = (Temp) tmp.newInstance(); //对得到的对象进行操作 n.set("Name"); System.out.println(n); } } class Temp { public String word; void set(String word) { this.word = word; } @Override public String toString() { return word; } } //输出: //Name需要注意的一点是使用newInstance()方法生成的类的对象,一定要有无参构造函数,默认生成的和重载定义的皆可,否则会出现InstantiationException。
1.3.2 通过Constructor对象的newInstance()方法
import java.lang.reflect.*; public class Test { public static void main(String[] args) throws Exception { //获取类对象 Class<?> tmp = Class.forName("Temp"); //类对象的getConstructor()方法返回tmp所表示的类的对象的public无参构造方法,用对象表示 Constructor<?> cons = tmp.getConstructor(); //类对象的getConstructors()方法返回tmp所表示的类的对象的所有public构造方法,用对象数组表示 //数组中构造方法的顺序与它们在类中出现的顺序一致 Constructor<?>[] consArr = tmp.getConstructors(); //需要强制类型转换,将返回的泛型对象转换成类的对象,与newInstance()方法相似 //无参构造方法生成新对象 Temp n = (Temp) cons.newInstance(); System.out.println(n); //使用有参构造函数 //按照书写顺序选择参数 Temp m = (Temp) consArr[0].newInstance(); Temp k = (Temp) consArr[1].newInstance("word"); System.out.println(m); System.out.println(k); } } class Temp { public String word; //显式定义的无参构造函数 //第一个出现的构造方法,对应于consArr[0] public Temp() { } //重载的有参构造函数 //第二个出现的构造方法,对应于consArr[1] public Temp(String word) { this.word = word; } @Override public String toString() { return word; } } //输出: //null //null //word需要注意的是Constructor类属于java.lang.reflect包,使用前需要导入该包。
1.4 根据类对象得到其所表示的类所实现的接口
import java.lang.reflect.*; public class Test { public static void main(String[] args) throws Exception { Class<?> tmp = Class.forName("Temp"); //getInterface()方法提取tmp类对象所表示的类的所有接口到类对象数组 Class<?>[] intf = tmp.getInterfaces(); //得到所有接口的名字 for(Class<?> interf : intf) System.out.println(interf.getName()); } } interface INTF { public String tag = "tag"; public int number = 1; public void get(); } class Temp implements INTF { public String word; public Temp() { } public Temp(String word) { this.word = word; } @Override public void get() {} @Override public String toString() { return word; } } //输出: //INTF反射中类和接口都是用Class<?>类型的对象,也就是类对象表示的,因此都可以放入Class<?>[]类型的数组中。
1.5 根据类对象得到其所表示的类的父类
import java.lang.reflect.*; public class Test { public static void main(String[] args) throws Exception { Class<?> c = Class.forName("Child"); //getSuperclass()方法通过子类的类对象得到父类的类对象 Class<?> f = c.getSuperclass(); System.out.println(f.getName()); } } class Father{} class Child extends Father{} //输出 //Father
1.6 根据类对象得到其所表示的类的构造方法
import java.lang.reflect.*; public class Test { public static void main(String[] args) throws Exception { Class<?> tmp = Class.forName("Temp"); //根据类对象得到其所表示的类的构造方法的类对象数组 Constructor<?>[] consArr = tmp.getConstructors(); //遍历数组输出所有构造方法的签名 for(Constructor<?> c : consArr) System.out.println(c); } } class Temp { public String word; public Temp() {} public Temp(String word) { this.word = word; } } //输出: //public Temp() //public Temp(java.lang.String)有两点需要注意,一是getConstructor()和getConstructors()方法都只能找到public的构造方法,二是返回的构造方法的类对象中仅有访问修饰符和函数签名的信息,构造方法的方法体不包括在内。
1.7 根据类对象调用其所表示的类的方法
import java.lang.reflect.*; public class Test { public static void main(String[] args) throws Exception { Class<?> tmp = Class.forName("Temp"); //Method类也属于java.lang.reflect包,用来保存提取到的类方法或实例方法 //getMethod()方法按字符串返回名称相同的public方法的方法对象 Method mtdGet = tmp.getMethod("get"); //invoke()方法调用字符串内对象的方法对象表示的方法 mtdGet.invoke(tmp.newInstance()); //返回有参数的方法的方法对象 Method mtdSet = tmp.getMethod("set", String.class); //调用方法对象表示的方法,带参数 mtdSet.invoke(tmp.newInstance(), "word"); } } class Temp { public String word; public Temp() {} public Temp(String word) { this.word = word; } public void set(String word) { this.word = word; System.out.println(word); } public void get() { System.out.println(word); } } //输出 //null //word与getConstructor()相似,getMethod()只能返回public方法。与getConstructors()相似,getMethods()返回多个方法对象到方法对象数组中。
1.8 根据类对象调用其所表示的类的字段
import java.lang.reflect.*; public class Test { public static void main(String[] args) throws Exception { Class<?> tmp = Class.forName("Temp"); //Field类也属于java.lang.reflect包,用来保存提取到的类字段或实例字段 //getField()方法按字符串返回名称相同的public字段的字段对象 Field fid = tmp.getField("word"); //显示字段对象 System.out.println(fid); //生成实例 Temp n = (Temp) tmp.newInstance(); //显示字段对象所表示字段的值 System.out.println(fid.get(n)); //设置字段对象所表示字段的值 fid.set(n, "word"); //显示新值 System.out.println(fid.get(n)); } } class Temp { public String word; public Temp() {} public Temp(String word) { this.word = word; } public void set(String word) { this.word = word; System.out.println(word); } public void get() { System.out.println(word); } } //输出: //public java.lang.String Temp.word //null //word与getMethod()相似,getField()只能返回public方法。与getMethods()相似,getFields()返回多个字段对象到字段对象数组中。
1.9 getDeclaredConstructor(s), getDeclaredMethod(s), getDeclaredField(s)方法
笔者在上面强调了get...方法的适用范围仅限public构造方法,方法和字段。那怎样获得类对象所表示的类的所有(包括private)的方法和字段呢?这时需要getDeclared...方法。get...方法和getDeclared...方法的另一个重要区别是是否包含继承的字段/方法。以getDeclaredMethods()方法为例:
import java.lang.reflect.*; public class Test { public static void main(String[] args) throws Exception { Class<?> tmp = Class.forName("Temp"); Method[] mtdArr = tmp.getMethods(); //获取所有public方法包括继承的public方法和实现的接口的public方法 for(Method mtd : mtdArr) System.out.println(mtd); System.out.println(" = = = = = = = = "); //获取public,protected,default和private方法,但不包括继承的方法 Method[] mtdArrDeclared = tmp.getDeclaredMethods(); for(Method mtd: mtdArrDeclared) System.out.println(mtd); } } class Temp { public String word; public Temp() {} public Temp(String word) { this.word = word; } public void set(String word) { this.word = word; System.out.println(word); } private void get() { System.out.println(word); } } //输出: //public void Temp.set(java.lang.String) //public final native java.lang.Object.wait() throws java.lang.InterruptedException //public final void java.lang.Object.wait(long, int) throws java.lang.InterruptedException //public boolean java.lang.Object.equals(java.lang.Object) //public java.lang.String java.lang.Object.toString() //public native int java.lang.Object.hashCode() //public final native java.lang.Class java.lang.Object.getClass() //public final native void java.lang.Object.notify() //public final native void java.lang.Object.notifyAll() // = = = = = = = //private void Temp.get() //public void Temp.set(java.lang.String)容易看出getMethods()方法返回了Temp类自身的public方法和继承自Object类的8个方法,而getDeclaredMethods()方法仅返回了Temp类自身的一个public方法和一个private方法。注意它们都没有返回任何构造方法。另一个要注意的地方是数组元素的顺序不一定与方法在代码中出现的顺序一致。
1.10 基本类型和数组的反射
前面讨论的情形都是自定义的类或者接口,那java基本类型和数组能不能使用反射呢?public class Test { public static void main(String[] args) throws Exception { //Class类的isPrimitive()方法判断类对象是否表示基本类型 System.out.println(int.class.isPrimitive()); //包装类.TYPE是包装类对应基本类型的类对象 System.out.println(Integer.TYPE); //void像基本类型一样拥有预定义类对象,其预定义类对象是Void.TYPE System.out.println(void.class.isPrimitive()); System.out.println(Void.TYPE); } } //输出: //true //int //true //void由此可见java基本类型加上void类型都有各自的类对象。基本类型的类对象是包装类.TYPE,void类型的类对象是Void.TYPE。基本类型存储在栈中,存取速度更快。包装类存储在堆中,使用更灵活。因为基本类型需要装箱成包装类才能具备面向对象特性,所以基本类型的反射就是包装类的反射:
import java.lang.reflect.*; public class Test { public static void main(String[] args) throws Exception { Class<?> intTmp = Class.forName("java.lang.Integer"); Method[] mtdArr = intTmp.getMethods(); //这样会显示出Integer类的许多public方法 for(Method mtd : mtdArr) System.out.println(mtd); } }数组也是类,也能获得类对象。不同的是数组只能声明得到,而不能用构造方法得到,所以newInstance()对于数组的类对象无效。但是对于数组的元素,它们应当是基本类型或者Object类型的,所以可以反射:
import java.lang.reflect.*; public class Test { public static void main(String[] args) throws Exception { int[] temp = {1, 2, 3}; //获取数组的类对象 Class<?> tmp = temp.getClass(); //显示数组的类对象所表示的类的名字 System.out.println(tmp.getName()); //获取数组元素的类对象 Class<?> tmpElement = temp.getClass().getComponentType(); //显示数组元素的类对象所表示的类的名字 System.out.println(tmpElement.getName()); } } //输出: //[I //int数组的类对象所表示的类写作"[大写字母"形式,括号表示数组,字母表示元素的类型。
1.11 获取类加载器
类加载器是负责加载类的对象,是ClassLoader类的具体实现。import java.lang.reflect.*; public class Test { public static void main(String[] args) throws Exception { Test t = new Test(); //getClassLoader()方法被类对象调用,返回ClassLoader类的对象, //获取这个对象的类对象就是Test类的类加载器的类对象 System.out.println(t.getClass().getClassLoader().getClass().getName()); } } //输出: //sun.misc.Launcher$AppClassLoader
2. 动态代理
2.1 代理模式
代理模式(Proxy Pattern)是java设计模式中的一种。当一个客户不想或不能直接引用一个类的对象时,代理对象可以在客户和它要引用的对象间起到中介的作用。代理模式的参与方有三个:(1)客户接口:一组接口,委托类和代理类都实现这组接口。客户的作用是指示代理类的对象去操作委托类的对象;
(2)代理对象:代理类的对象,实现客户接口,也拥有委托类的对象的引用。因为代理对象和委托类的对象都实现客户接口,所以代理对象可以代替委托类的对象来满足客户要求。也因为代理对象拥有委托类的对象的引用,所以代理对象可以操作委托类的对象,比如调用委托类的方法。代理的作用是充当客户和委托类的中介,接收来自客户的要求,转给委托类的对象实际执行;
(3)委托类对象:委托类实现客户接口。委托类的对象接收代理对象转发来的客户要求,调用自己的方法来满足客户。
2.2 动态代理模式
根据上面的描述,代理要满足客户要求,必须实现客户接口,才能接收客户要求,必须拥有委托类的对象引用,才能将要求转给委托类的对象实际执行。这样的代理类非常不灵活,其实现的客户接口和拥有的委托类的对象是固定的。如果需要更换客户,或者用不同的委托类实际处理客户要求,就需要重写代理。将客户接口和委托类的对象作为构造参数传入,就可以重复利用同一个代理类关联不同的客户接口和委托类了。动态代理模式(Dynamic Proxy Pattern)就是指代理通过构造参数获取客户接口和委托类的对象。具体体现在代理类需要三个构造参数来关联客户和委托类,一个是委托类的类加载器(通过反射获得),一个是客户接口(通过反射获得),一个是Handler对象(实现InvocationHandler接口的类的对象,实际处理代理转发来的客户要求,Handler对象通过构造参数获取委托类的对象的引用)。这样的设计可以提高代理类的可重用性。所谓动态就是代理在运行时才关联客户接口和委托类,这种关联可以在运行时改变。下面是一个简单的例子:
import java.lang.reflect.*; public class Test { public static void main(String[] args) throws Exception { Worker worker = new Worker(); //java.lang.reflect.Proxy是代理类,Proxy.newProxyInstance()方法需要委托类的类加载器,委托类的一组接口和调用处理器三个参数来生成代理类的对象 //newProxyInstance()是Proxy类的一个静态方法,返回一个Object类型的代理类的对象转换成接口赋值给客户接口 Client client = (Client) Proxy.newProxyInstance(worker.getClass().getClassLoader(), worker.getClass().getInterfaces(), new ClientHandler(worker)); //接口中的方法调用代理类的对象中的invoke()方法,该方法利用委托类的类对象提取到的方法对象和方法参数来运行委托类的方法,并将委托类的方法运行结果返回给代理类 client.plan("jobNext"); client.solve("jobNow"); } } //使用代理的方法都被包含在一个接口中,成为客户接口 interface Client { public void plan(String content); public void solve(String content); } //委托类:实现了客户接口中的方法,被代理类的对象委托来执行客户接口的方法 class Worker implements Client { public void plan(String content) { System.out.println("Planning of " + content + " done."); } public void solve(String content) { System.out.println("Solution of " + content + " done."); } } //java.lang.reflect.InvocationHandler接口是代理类的对象的调用处理程序实现的接口 //客户接口通知代理执行某方法,代理调用InvocationHandler接口中的处理程序,通常写在invoke()方法中 class ClientHandler implements InvocationHandler { private Object proxyObject; //需要委托类的对象proxyObject作为构造参数,以便invoke()方法调用委托类的方法 public ClientHandler(Object proxyObject) { this.proxyObject = proxyObject; } //invoke()方法处理代理类实例接到的所有方法调用,第一个参数是代理类实例,第二个参数是委托类被调用的方法的方法对象,第三个参数是委托类被调用的方法的参数 @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("before working"); Object rslt = method.invoke(proxyObject, args); System.out.println("after working"); return rslt; } } //输出: //before working //Planning of jobNext done. //after working //before working //Solution of jobNow done. //after working上面的例子中Client是客户接口。Client调用它的方法就是在向代理发出要求。Proxy类是代理,代理通过构造参数获得委托类的类加载器,客户接口,和实际处理客户要求的ClientHandler类的对象。ClientHandler类通过构造参数得到委托类Worker的一个对象,代理转发客户要求给ClientHandler类的对象,后者转发给Worker类的对象来实际执行。相比于静态代理模式的客户->代理->
委托的处理顺序,动态代理模式使用客户->代理->Handler->委托的处理顺序。动态代理不仅可以动态关联客户和委托,还可以再增加一层灵活性,动态关联Handler(不同的方法来重写Handler的invoke()方法,可以生成不同的Handler),相当于给委托类做不同的包装。
2.3 动态代理的用途
设计模式是根据大量实践总结出来的程序模板。不同的设计模式实质上是使用不同模板操作对象,以满足不同的使用场合。代理模式也是用来满足某些场合的:(1)需要考虑不同的权限:不同用户对同一对象的操作权限不同,相当于不同权限的同一客户(同样的方法调用但权限不同)要求该对象给出不同反应。这时可以用代理来判断客户权限,然后用不同方法操作对象;
(2)需要以简代繁,防止阻塞:在网络上访问某个很大的资源(比如图片),限于网速需要等待,会导致该资源后面需要访问的资源都被阻塞。如果访问顺序无关紧要,可以使用代理来响应资源的访问,使得后面的访问不再等待。
(3)其它需要用不同方法包装同一个对象,以便对于不同访问给出不同反应的场合。此时代理的作用就是避免修改对象本身,而是修改中介。
3. 总结
反射和动态代理作为Java高新技术很重要的部分,应该给予充分的重视。动态代理使用反射技术,可以在不修改委托类的情况下对委托类做一些灵活包装,以适应不同的需求,同时也隐藏了委托类,相当于用设计模式实现了面向对象的隐藏和封装原则。相关文章推荐
- Java的反射机制和动态代理
- Java中反射动态代理接口的详解及实例
- Java深度历险——Java反射与动态代理
- Java反射学习总结四(动态代理使用实例和内部原理解析)
- JavaSE(10):Java反射技术及动态代理
- 模拟实现Struts拦截器-蕴含着代理模式,AOP,工厂模式,依赖注入,Java 反射,动态构造等机制
- java反射之基于JDK的动态代理的乐子
- java 动态代理,反射 回顾
- java反射与动态代理
- JavaVM,反射与动态代理
- Java中反射、静态代理、动态代理
- java动态代理反射剖析
- Java回炉之反射(二)动态代理
- (54)Java学习笔记——反射 / 动态代理
- Java深度历险(七)——Java反射与动态代理
- 【转】java的反射机制中的动态代理代理(二)--在远程方法调用中运用代理类
- Java反射之JDK动态代理实现简单AOP
- 黑马程序员 25 Java基础加强-07-动态代理篇
- Java基础 - 类的加载,类加载器,反射,动态代理,模板设计模式,JDK5新特性,枚举(类),JDK1.7新特性
- java中的反射三(反射机制深入---静态代理,动态代理及cglib动态代理)