第八章:反射与代理机制
8.1 Java反射机制
1.Java类型信息
(1)获取Java运行时的类型信息有两种方法:
①RTTI。在运行时,需要识别一个对象的类型。当从数组中取出元素时,会自动将结果转型回Shape,这是RTTI最基本的使用形式,因为在Java中,所有的类型转换都是在运行时进行正确性检查的。
在写代码时,大部分代码应尽可能少地了解对象的具体类型,而是只与对象家族中的一个通用表示打交道。
②Java反射机制。它指在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象的方法称为Java的反射机制。
2.类Class
Class类是Java一个基础类,每装载一个新类,java虚拟机就会在java堆中,创建一个Class实例,这个实例就代表Class这个类型,通过实例获取类型信息。该类中一些方法如下:
(1)Method[] getMethods():获得Class类中所有的方法,这个是方法类的数组。
(2)Field[] getFields():获得Class类中所有的成员变量,这是一个Field类的数组。
(3)Constructor<?>[] getDeclaredConstructors():获得Class类中所有的构造方法。
例:利用Class类创建一个实例
1.创建Class类的一个对象,返回一个类的引用
Class cls = Class.forName("Airplane");//返回一个类型
forName是Class的一个静态方法,实际上Airplane是一个类的名字,cls实际上是Class的一个实例,这个cls实际上是Airplane这个类型。
2.通过类的引用创建实例
cls.newInstace();//通过newinstace创建实例,一般调用默认构造函数
这样就可以用一个字符串“Airplane”创建出一个类的实例。
完整版:
Class Airplane{ public String toString(){ return("in airplane"); }}public class CreateInstance{ public static void main(String[] args)throws Exception{ Class c1=null; Object ap; c1=Class.forName("Airplane");//创建Class类的一个对象 System.out.println(c1); ap=c1.newInstance();//创建实例的另外一种方法 System.out.println(ap.toString()); }}
传统创建实例的方法:
Airplane ap = new Airplane();
Java反射的例子——Method类的invoke
如图1所示,首先要引出reflect这个包。然后定义add方法,add方法的功能是把两个参数转换成整数值,然后再把它们相加。再定义第二个方法StringAdd方法,输出“out”和abc的值。
接下来看主方法。第一个定义了一个Method类的实例mth,这句话代表获得了ClassA中add的这个方法,逗号后面的语句为提供的参数。接下来通过invoke方法来执行add方法。后面括号里是指一样的,前面生成了一个Class类的对象,后面就要用newInstance方法创建实例,然后两个参数里面的数字分别是1和2。后面获取StringAdd与前面同理。
图1
8.2 Java静态代理
1.代理模式
在某些情况下,一个客户不想或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介作用。
代理模式的作用是,为其他对象提供一种代理以控制这个对象的访问。如图2所示,Client要调用我们目标对象的request方法。左下角的代理和右下角的真实对象拥有同样的接口,只不过它可能还要做一些其它的事情。
图2
2.代理模式一般涉及到的角色
(1)抽象角色:声明真是对象和代理对象共同的接口。
(2)代理角色:代理对象角色内部含有对真是对象的引用,从而可以操作真是对象,同事代理对象提供与真是对象相同的接口以便在任何时刻能够代替真实对象。同事,代理对象可以在执行真实对象操作时,附加其他的操作,相当于对真是对象进行封装。
(3)真实角色:代理角色所代表的的真实对象,是我们最终要引用的对象。
例:静态代理的例子
//真实对象和代理对象的共同接口abstract class Subject{ public abstract void request();}//真实对象和代理对象的共同接口class RealSubject extends Subject{ public void request(){ System.out.println ("From Real Subject!"); }}//客户端public class Client{ public static void main(String[] args){ Subject subject = new ProxySubject(); subject.request(); }}//代理角色class ProxySubject extends Subject{ //代理角色对象内部含有对真是对象的引用 private RealSubject realSubject; @Overridepublic void request(){ //在真实角色操作之前所附加的操作 preRequest(); if(null == realSubject){ realSubject= new RealSubject(); }//真实角色所完成的事情 realSubject.request(); //在真是角色操作之后附加操作 postRequest(); } private void preRequest(){ System.out.println("Pre"); } private void postRequest(){ System.out.println("post"); }
这里就看出来了,代理角色可以实现真实角色的方法,也可以自己附加执行自己的一些操作。
3.静态代理的优缺点
优点:
业务类只需要关注业务逻辑本身,保证了业务类的可重用性,这是代理的共有优点。
缺点:
(1)代理对象的一个接口只服务于一种类型的对象,如果要代理的方法很多,势必要为每一种方法都进行代理,静态代理在程序规模稍大时就无法胜任。
(2)如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法,增加了代码维护的复杂度。
8.3 Java动态代理
1.Java动态代理
使用到的类:
(1)java.lang.reflect.Proxy
这是java动态代理机制的主类,它提供了一组静态方法来为一组接口动态地生成代理类及其对象。常用方法有以下几种:
①用于获取指定代理对象所关联的调用处理器:
static InvocationHandler getInvocationHandler(Object proxy)
②获取关联于指定类装载器和一组接口的动态代理类的类对象
static Class getProxyClass(ClassLoader loader, Class[] interfaces)
③判断指定类对象是否是一个动态代理类
static Boolean isProxyClass(Class cl)
④为指定类装载器、一组接口及调用处理器生成动态代理类实例
static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h)
(2)java.lang.reflect.InvocationHandler
这是调用处理器接口,它自定义了一个Invoke方法,用于集中处理在动态代理类对象上的方法嗲用,通常在该方法中实现对委托类的代理访问。
Object invoke(Object proxy, Method method, Object[] args)
该方法负责集中处理动态代理类上的所有方法调用。第一个参数是代理类实例,第二个参数是被调用的方法对象,第三个方法是调用参数。调用处理器根据这三个参数进行预处理或分配到委托类实例上执行。
例:Java动态代理实例
//抽象角色interface Subject{ public void request();}//真实角色,实现了Subject的request()方法class RealSubject implements Subject{ public RealSubject(){} public void request(){ System.out.println ("From real subject"); }}//代理角色,必须继承InvocationHandlerimport java.lang.reflect.Method;import java.lang.reflect.InvocationHandler;class DynamicSubject implements InvocationHandler{ private Object sub; public DynamicSubject(){} public DynamicSubject(Object obj){ sub = obj; } public Object invoke(Object proxy, Method method,Object[] args)throws Throwable{ System.out.println ("before calling"+method); method.invoke(sub,args); System.out.println ("after calling" + method); return null; }}//客户端调用import java.lang.reflect.Proxy;import java.lang.reflect.Constructor;import java.lang.reflect.Method;import java.lang.reflect.InvocationHandler;public class Client{ public static void main(String[] args) throws Throwable{ RealSubject rs = new RealSubject(); //在这里指定被代理类 InvocationHandler ds = new DynamicSubject(rs); Class cls = rs.getClass(); //以下是一次性生成代理 Subject subject = (Subject)Proxy.newProxyInstance(cls.getClassLoader(),cls.getInterfaces(),ds); subject.request(); }}
运行结果:
before calling public abstract void Subject.request()
From realsubject
before calling public abstract void Subject.request()
2.动态代理的特点
(1)包:如果所代理的接口都是public的,那么它将被定义在顶层包(即包路径为空),如果所代理的接口有非public的接口,那么它就被定义在该接口所在的包,这样设计的目的是为了最大程度的保证动态代理类不会因为包管理的问题而无法被成功定义并访问;
(2)类修饰符:该代理类具有final和public修饰符,意味着它可以被所有的类访问,但是不能被再度继承;
(3)类名:格式是“$ProxyN”,其中N是一个逐一递增的阿拉伯数字,代表Proxy类第N此生成的动态代理类。注意,并不是每次调用Proxy的惊天方法创建动态代理都会使N增加,原因是对同一组接口(包括接口排序的顺序相同)试图重复创建动态代理类,它会很聪明地返回先前已经创建好的代理类的类对象,而不会尝试去创建一个全新的代理类,这样可以节省不必要重复的代码生成,提高代理类的创建效率。
(4)类继承关系:该类的继承关系如图3所示。
图3
3.动态代理的优点和缺点
优点:接口中声明的所有方法都被转移到调用处理器一个集中的方法中进行处理(InvocationHandler.invoke)。这样,在借口方法数量较多的时候,可以进行灵活处理。
缺点:始终无法摆脱仅支持interface代理的桎梏。
8.4 Java反射扩展-JVM加载类原理
1.JVM类加载的种类
(1)JVM自带的默认加载器:
①跟类加载器:bootstrap,由C++编写,所有Java程序无法获得。
②扩展类加载器:Java编写。
③系统类、应用类加载器:Java编写。
(2)用户自定义加载器
Java.lang.ClassLoader的子类,用户可以定制类的加载方式。每一个类都包含了加载它的ClassLoader的一个引用——getClassLoader()。
2.类的加载方式
(1)本地编译好的class中直接加载。
(2)网络加载:java.net.URLClassLoader可以加载url指定的类。
(3)从jar、zip等压缩文件加载类,自动解析jar文件找到class文件去加载。
(4)从java源代码文件动态编译成class文件。
3.类加载的步骤
(1)加载。把class文件放到虚拟机。
(2)连接。包括以下小步骤:验证,验证class文件是否满足Java虚拟机对于自解码文件的规范;准备,把静态成员做内存分配;解析,把符号性引用转换成直接引用。
(3)类的初始化。
4.类加载器的加载顺序
根加载器->扩展类加载器->应用类加载器->用户自定义类加载器
注意:如果到最后一层都加载不了,就出现ClassNotFoundException异常。
5.类加载器加载Class的过程
第一步:检测此Class是否载入过,如果有跳到第8步,否则前进到第2步。
第二步:如果parent classloader不存在(没有parent,那parent一定是bootstrap classloader),则跳到第4步。
第三步:请求parent classloader载入,如果成功到底8步,否则第5步。
第四步:请求JVM从bootstrap classloader中载入,若成功到第8步。
第五步:寻找Class文件(从榆次classloader相关的路径查找),如果找不到则到第7步。
第六步:从文件载入Class,到第8步。
第七步:抛出ClassNotFoundException。
第八步,返回Class。