Java中的动态反射机制和动态代理
一、什么是反射机制?
在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。简单来说,就是Java对每一个类和类中的所有成员都进行了封装,这样每个类都有一个与之对应的Class对象,通过这个对象可以直接访问类中的所有成员。
二、Class类
1.在一个Class类中,将被封装类的方法(构造器之外)封装为Method类,将字段封装为Filed类,将构造器封装为Constructor类。这三个类都是定义在java.lang.reflect这个类库中。
2.(定义一个Person类,需要得到Person类对应的Class对象)要想通过Class对象对被封装类进行访问,就必须首先获得一个Class对象。获取Class对象有三种方式:(1)通过Person类的对象,调用getClass方法获得;(2)通过Person类的class常量直接获得;(3)通过Class类中的forName(String className)方法。注意这种方法中需要传入类的全名(包名.类名),而且会抛出异常。同时,这三种方式得到的Class对象是一样的。代码示例如下:
1 //Person类定义 2 public class Person { 3 private String name; 4 private int age; 5 6 public Person() { 7 } 8 9 public Person(String name, int age) { 10 this.name = name; 11 this.age = age; 12 } 13 14 public String getName() { 15 return name; 16 } 17 18 public void setName(String name) { 19 this.name = name; 20 } 21 22 public int getAge() { 23 return age; 24 } 25 26 public void setAge(int age) { 27 this.age = age; 28 } 29 30 void eat(String food) { 31 System.out.println("eat " + food); 32 } 33 34 protected void drink(String drink) { 35 System.out.println("drink " + drink); 36 } 37 38 @Override 39 public String toString() { 40 // TODO Auto-generated method stub 41 return ("name: " + name + "age: " + age); 42 } 43 }
1 /** 2 * 前两种方式必须指定类名,而第三种方式虽然也需要类名),但是可以通过一个字符串传入 3 * @author hr 4 * 5 */ 6 public class getClassName { 7 public static void main(String[] args) throws Exception { 8 Class clazz1 = getClass1(); 9 Class clazz2 = getClass2(); 10 Class clazz3 = getClass3(); 11 System.out.println(((clazz1 == clazz2) && (clazz2 == clazz3))); 12 } 13 14 /** 15 * 第一种方法:通过Person类的对象,调用getClass方法获得 16 * 17 * @return 18 */ 19 public static Class getClass1() { 20 // TODO Auto-generated method stub 21 Person person = new Person(); 22 return person.getClass(); 23 } 24 25 /** 26 * 第二种方法:通过Person类的class常量直接获得 27 * 28 * @return 29 */ 30 public static Class getClass2() { 31 // TODO Auto-generated method stub 32 return Person.class; 33 } 34 35 /** 36 * 第三种方法:通过Class类中的forName(String className)方法。 37 * 注意这种方法中需要传入类的全名(包名.类名),而且会抛出异常。 38 * @return 39 * @throws Exception 40 */ 41 public static Class getClass3() throws Exception { 42 // TODO Auto-generated method stub 43 return Class.forName("reflect.Person"); 44 } 45 }
3.如上所述,可以通过一个类的Class对象得到这个类中的方法、数据和构造器。对于类中的所有对象,Class都提供了返回单个对象或者是对象集合的方法。例如,getMethod()方法,通过参数返回类中的某一个public方法(包括继承方法),而getMethods()方法则返回类中所有的public方法(包括继承方法)——通过getDeclaredMethod()和getDeclaredMethods()方法则可以返回本类中的所有方法(包括private方法,但是不包括继承来的方法)。对于类中的构造器和数据也是一样。
有两种方法可以用来新建类的对象:通过Class对象的newInstance()方法,这样得到的对象没有初始参数;首先通过Class对象getConstructor()方法得到对应的构造器(注意传入的参数代表该构造器的参数类型),然后通过得到的构造器创建新的对象(可以传入初始化参数)。
类中的方法可以通过Class中的invoke方法直接执行。对于非private方法,都是先通过getDeclaredMethod()或者getMethod()方法得到方法名(两种方法传入的参数,第一个是方法名,后面是方法需要的参数类型),然后通过方法名调用invoke()方法执行该方法(传入形式参数);对于private()方法,在得到方法名之后,需要先通过setAccessible()方法设置访问权限,然后通过方法名调用invoke()方法执行该方法(传入形式参数)。
代码示例如下:
1 public static void main(String[] args) throws Exception { 2 // TODO Auto-generated method stub 3 Class<Person> clazz = (Class<Person>) Class.forName("reflect.Person"); 4 // 获取除父类方法之外的所有方法 5 Method[] declaredMethods = clazz.getDeclaredMethods(); 6 // 获取所有的public方法,包括继承的方法 7 Method[] publicMethods = clazz.getMethods(); 8 // 获取一个除父类方法之外的方法 9 Method read = clazz.getDeclaredMethod("read", String.class); 10 // 获取一个本类中的public方法,包括继承的方法 11 Method toString = clazz.getMethod("toString", null); 12 // 创建类的一个无参对象 13 Object obj1 = clazz.newInstance(); 14 // 创建类的一个带参对象 15 // 需要首先得到该类的一个带参构造器,getConstructor传入的参数代表该构造器需要的数据类型 16 // 然后通过构造器构造该类的一个对象 17 Constructor<Person> constructor = clazz.getConstructor(String.class, int.class); 18 Object obj2 = constructor.newInstance("Sara", 14); 19 // 显示两个对象 20 System.out.println(obj1); 21 System.out.println(obj2); 22 // 运行类中的一个public方法 23 // 需要先得到该方法(pay) 24 Method pay = clazz.getMethod("pay", double.class); 25 pay.invoke(obj2, 5.4); 26 // 运行类中的一个protect方法 27 Method drink = clazz.getDeclaredMethod("drink", String.class); 28 drink.invoke(obj2, "water"); 29 // 运行类中的一个default方法 30 Method eat = clazz.getDeclaredMethod("eat", String.class); 31 eat.invoke(obj2, "noodles"); 32 // 运行类中的一个private方法,read方法之前已得到 33 // 需要先将read方法的访问权限设置为可见 34 read.setAccessible(true); 35 read.invoke(obj2, "Thinking in Java"); 36 37 for (Method method : declaredMethods) { 38 System.out.println(method); 39 } 40 41 System.out.println("---------------"); 42 43 for (Method method : publicMethods) { 44 System.out.println(method); 45 } 46 }
三、反射机制的作用
1.在运行时判断任意一个对象所属的类;
2.在运行时构造任意一个类的对象;
3.在运行时判断任意一个类所具有的成员变量和方法;
4.在运行时调用任意一个对象的方法;
5.生成动态代理。
前四个作用已经在上文中介绍了,下面主要说一下动态代理。
四、静态代理
在了解动态代理之前,我们首先需要介绍一下静态代理。简单来说,代理就是用一个代理类来封装一个委托类,这样做有两个好处:可以隐藏委托类的具体实现;可以在不改变委托类的情况下增加额外的操作。而静态代理,就是在程序运行之前,代理类就已经存在了。静态代理一般的实现方式为:委托类和代理类都实现同一个接口或者是继承自同一个父类,然后在代理类中保存一个委托类的对象引用(父类或者父类接口的对象引用),通过给构造器传入委托类的对象进行初始化,在同名方法中通过调用委托类的方法实现静态代理。除此之外,在代理类同名方法中还可以实现一些额外的功能。代码示例如下(借用Java编程思想中的实例),RealObject类为委托类,SimpleProxy类为代理类:
1 interface Interface { 2 void doSomething(); 3 4 void somethingElse(String arg); 5 } 6 7 class RealObject implements Interface { 8 @Override 9 public void doSomething() { 10 // TODO Auto-generated method stub 11 System.out.println("doSomething"); 12 } 13 14 @Override 15 public void somethingElse(String arg) { 16 // TODO Auto-generated method stub 17 System.out.println("somethingElse " + arg); 18 } 19 } 20 21 class SimpleProxy implements Interface { 22 // 保存委托类(父接口的引用) 23 private Interface proxied; 24 25 // 传入委托类的对象用于初始化 26 public SimpleProxy(Interface proxied) { 27 this.proxied = proxied; 28 } 29 30 // 两个同名方法中还实现了其他的功能 31 @Override 32 public void doSomething() { 33 // TODO Auto-generated method stub 34 System.out.println("SimpleProxy doSomething"); 35 proxied.doSomething(); 36 } 37 38 @Override 39 public void somethingElse(String arg) { 40 // TODO Auto-generated method stub 41 System.out.println("SimpleProxy somethingElse " + arg); 42 proxied.somethingElse(arg); 43 } 44 } 45 46 public class SimpleProxyDemo { 47 public static void main(String[] args) { 48 consumer(new RealObject()); 49 consumer(new SimpleProxy(new RealObject())); 50 } 51 52 public static void consumer(Interface iface) { 53 iface.doSomething(); 54 iface.somethingElse("bonobo"); 55 } 56 }
五、动态代理
静态代理的局限性在于,代理类需要在程序运行之前就编写好,而动态代理则可以在程序运行的过程中动态创建并处理对所代理方法的调用。在动态代理中,需要定义一个中介类,这个类实现InvocationHandle接口(主要是里面的invoke方法)。这个中介类位于委托类和代理类之间,作为一个调用处理器而存在。它保存一个委托类的引用,通过传入委托类对象进行初始化;然后在invoke方法中,实现对委托类方法的调用,并增加需要的额外操作。在需要使用动态代理时,首先通过Proxy类中的newProxyInstance方法得到代理类对象(方法的三个参数分别是:(通常是委托类实现接口的)类加载器,希望代理类实现的接口列表(通常也是委托类实现的接口),以及一个调用处理器的对象),然后通过这个代理类对象直接调用代理类的方法。这种调用实际上会通过调用处理器调用invoke方法,进而实现对委托类相应方法的调用。
注意在动态代理中,只实现了一个调用处理器,而没有真正实现代理类。代理类对象是通过Proxy类中的newProxyInstance方法得到的。这样,不管你在调用委托类任何方法时需要加入的额外操作都可以仅仅在调用处理器中的invoke方法中实现就可以了。代码示例如下(来自Java编程思想):
1 public class SimpleDynamiProxyDemo { 2 public static void consumer(Interface iface) { 3 iface.doSomething(); 4 iface.somethingElse("bonobo"); 5 } 6 7 public static void main(String[] args) { 8 RealObject real = new RealObject(); 9 consumer(real); 10 // 通过Proxy.newProxyInstance方法得到代理类对象 11 Interface proxy = (Interface) Proxy.newProxyInstance(Interface.class.getClassLoader(), 12 new Class[] { Interface.class }, new DynamicProxyHandler(real)); 13 // 通过代理类对象直接调用方法,会被重定向到调用处理器上的invoke方法 14 consumer(proxy); 15 16 } 17 } 18 19 // 中介类(调用处理器) 20 class DynamicProxyHandler implements InvocationHandler { 21 // 保存一个委托类的对象 22 private Object proxied; 23 24 public DynamicProxyHandler(Object proxied) { 25 this.proxied = proxied; 26 } 27 28 // 三个参数:代理类的引用,方法名和方法的参数列表 29 @Override 30 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 31 // TODO Auto-generated method stub 32 System.out.println("**** proxy: " + proxy.getClass() + ", method: " + method + ", args: " + args); 33 if (args != null) { 34 for (Object arg : args) { 35 System.out.println(" " + arg); 36 } 37 } 38 // 实现对委托类方法的调用,参数表示委托类对象和参数 39 return method.invoke(proxied, args); 40 } 41 }