首页 > 代码库 > 14、反射、动态代理
14、反射、动态代理
一、反射
反射其实就是对字节码进行操作,构造函数对应Constructor类,成员变量对应Field类,成员方法对应Method类,其实这些类有一个特性,那就是通过字节码获取的。
1.1、反射解析
1、获取字节码的三种方式:
- 类名.class 例如:System.class
- 对象.class 例如:new Date().getClass()
- Class.forName("类名"),例如:Class.forName("java.util.Date");
String str = "abc";Class cls1 = str.getClass();//通过调用getClass方法获取字节码。Class cls2 = String.class;//通过类名.class获取字节码。Class cls3 = Class.forName("完整类名");//通过Class.forName()获取字节码,会出现异常。System.out.println(cls1 == cls2);//trueSystem.out.println(cls1 == cls3);//true
总结:获取字节码的三种方式,主要用第三种。可以将"java.util.Date"看成一个变量。
2、反射获取构造函数
Constructor类:
a) 得到某个类所有的构造方法。
Constructor[] cons = Class.forName("java.lang.String").getConstructors();
b) 得到某一个构造方法:
Constructor constructor = Class.forName("java.lang.String").getConstructors(StringBuffer.class);
c) 创建实例对象:
通常方式: String str = new String(new StringBuffer("abc"));反射方式: Constrctor cons = String.class.getConstructor(StringBuffer.class); String str = (String)cons.newInstance(new StringBuffer("abc"));//调用获得的方法时要用到上面相同类型的实例对象
String.class.getConstructor("可变参数列表");//StringBuffer.class,int.class.....
常用方法:
- getConstructor(字节码...)://获取类中public修饰构造函数,获取时需要指定参数列表来确定具体哪一个.注意它是可变参数列表。
- getDeclaredConstructor(字节码...)://获取类中public或private修饰的构造函数,获取时需要指定参数列表来确定具体哪一个.注意它是可变参数列表。
- getConstructors()://获取类中所有被public修饰的构造函数,返回的是Constructor类型的数组。
- getDeclaredConstructors()://获取类中所有被public以及private修饰的构造函数,返回值是Constructor类型的数组。
使用详解:
1.Contructor类中的newInstance()的返回值是Object类型,参数列表对应getConstruct()方法的参数列表锁指定的类型。
Constructor cons = Class.forName("java.lang.String").getConstructor(StringBuffer.class);
String str = (String)cons.newInstance(new StringBuffer("abc"));
类型转换是因为编译时所指定的,运行时却不知道,所以我们必须再次指定,也可以简单的理解newInstance()返回的是Object类型。
注意:Class类中也有newInstance()方法,其实只是Constructor类的省略写法,省去了获取字节码的过程,它只能访问无参的构造函数。
3、成员变量的反射
Field类:Field类代表某个类中的一个成员变量。
- getField(String name)://获取类中public修饰的且变量名和参数列表相同的成员变量。
- getDeclaredField(String name)://获取类中public或者private修饰的且变量名和参数列表相同的成员变量。
- getFields()://获取类中所有被public修饰的成员变量,返回值是Field类型的数组。
- getDeclaredFields()://获取类中所有被public以及private修饰的成员变量,返回值是Field类型的数组。
使用详解:
(1) get和set方法:
Object get(Object obj)://返回指定对象上此 Field 表示的字段的值。根据返回值注意类型转换。void set(Object obj, Object value)://将指定对象变量上此 Field 对象表示的字段设置为指定的新值。拓展:这系列方法还有很多,比如:getInt()、getBoolean()、setInt()、setBoolean().....等等
(2) Class<?> getType():返回一个 Class 对象,它标识了此 Field 对象所表示字段的声明类型。
例:field.getType == String.class
注:这里判断用==,当然也可以用equals,只是==的话语意上才正确。
实例演示:
class ReflectPoint { private int x; public int y; ReflectPoint(int x,int y){ this.x = x; this.y = y; }}//主函数ReflectPoint rep = new ReflectPoint(3,5);//Field不是对象身上的变量,而是类上,要用它去取对象身上的值。//获取public修饰的成员变量。Field fieldY = rep.getClass().getField("y");System.out.println(fieldY.get(rep)); //获取被private修饰的成员变量(暴力反射)Field fieldX = rep.getClass().getDeclaredField("x");fieldX.setAccessible(true);//设置为可以访问System.out.println(fieldX.get(rep));/*我的写法:ReflectPoint rep = new ReflectPoint(3,5);Class cls = rep.getClass();Field fieldY = cls.getField("y");//暴力反射Field fieldX = cls.getDeclaredField("x");fieldX.setAccessible(true);System.out.println(fieldX.get(rep)+"::"+fieldY.get(rep));
练习:将一个类中所有String类型的成员变量的值中的a改成b。
(接上面,具体参考反射源码示例)//获取类中所有的String类型成员变量,并将‘a‘改成‘b‘。 public static void changeValue(Object obj) throws Exception{ Field[] fields = obj.getClass().getFields(); //迭代器 for(Field field : fields){ if(field.getType() == String.class){ String oldValue = (String)field.get(obj); String newValue = oldValue.replace(‘a‘,‘b‘); //替换后再将新值存进去 field.set(obj,newValue); } }}
4、成员方法的反射
Method类:Method代表某个类中的一个成员方法。
Mehod charAt = Class.forName("java.lang.String").getMethod("charAt",int.class);
调用方法:
1)通常方式: String str = "abc"; System.out.println(str.charAt());(2)反射方式: String str = "abc"; Mehod Me = Class.forName("java.lang.String").getMethod("charAt",int.class); System.out.println(Me.invoke(str,1)); //注:getMethod(name,type.class):第一个参数是方法名,第二个参数是数据类型的字节码。 //invoke(Object obj,参数):第一个参数为对象,第二个参数为getMethod方法中所对应的方法名的参数。(charAt的参数)
如果传递给Method对的invoke()方法的第一个参数为null,说明该Method的对象对应的是一个静态方法。
常用方法:
- getMethod(String name,字节码...)://获取类中被public修饰的方法,获取时需要指定方法名(name),以及字节码是该方法名的参数列表类型的字节码。
- getDeclaredMethod(String name,字节码...)://获取类中被public或者private修饰的方法,获取时需要指定方法名(name),以及字节码是该方法名的参数列表类型的字节码。
- getMethods()://获取类中被public修饰的所有方法,返回值是Method类型的数组。
- getDeclaredMethods()://获取类中被public以及private修饰的所有方法,返回值是Method类型的数组
使用详解:
invoke(Object obj, Object... args):
(1) 它的返回值是Object类型,所以调用传值过程中注意类型转换。
(2) 它第一个参数对应被反射的类的引用(对象),如果被反射获取的方法是静态的,则参数值为null。
(3) 它第二个参数是可变参数列表,对应被反射获取的方法的参数列表,如果为无参方法,则invoke第二个参数值为0的数组或者null。
(4) 它具备了所有被反射的成员的方法的功能,可以破坏方法的限制,主要指定该方法所在类的对象,以及方法名。
5、数组的反射:
- 具有相同维数和元素类型的数组属于同一个类型,即具有相同的Class实例对象(此处比较与值无关)。
- 代表数组的Class实例对象的getSuperClass()方法返回的父类为Object类对应的Class。
- 基本类型的一维数组可以被当作Object类型使用,不能当作Object[]类型使用;非基本类型的一维数组,既可以当做Object类型使用,又可以当做Object[]类型使用。
- Arrays.asList()方法处理int[]和String[]时的差异。
- Array工具类用于完成对数组的反射操作。
反射的方式操作数组。
public static void printObject(Object obj){ //获取数组的字节码 Class cls = obj.getClass(); //判断是否是数组 if(cls.isArray()){ //是数组的话我们取其长度并打印 int len = Array.getLength(obj); for(int x=0;x<len;x++){ System.out.ptintln(Array.get(obj,x)); } }else{ //不是数组直接打印值 System.out.println(obj); }}
总结:数组的反射操作是通过数组的字节码和Array工具类结合来判断长度和获取元素。
6、反射总结
a) 总结一:
1.getFields()和getMethods()依次获得权限为public的成员变量和方法,将包含从父类(接口)中继承到的成员变量和方法;
2.而通过方法getDeclaredFields()和getDeclaredMethods()只是获得本类中定义的所有成员变量和方法,包括private修饰的。
3.getField以及getMethod和getFields()和getMethods()也是一样,带s的返回的是数组,不带s的可以在参数列表中指定具体某一个。
b) 总结二:
1.Constructor、Filed、以及Method类都有一个父类AccessibleObject,从中继承了一系列的方法,比如:setAccessible(boolean flag)。
2.setAccessible(boolean flag)方法主要是在访问被private修饰的构造函数、成员变量以及成员方法的时候,必须指定setAccessible(true)。
3.setAccessible(true)并不是将访问权限改成了public,而是取消java的权限控制检查。所以即使是public方法,其属性默认也是false。
了解:getFiled和getMethod以及带s的都是通过递归去查找公共成员变量或者方法,成员变量先接口再父类,成员方法先父类后接口。
带Declared的能访问一个类中所有的方法,不论是公开还是私有。
二、动态代理
动态代理:用来修改已经具有的对象的方法,控制方法是否执行,或在方法执行之前和执行之后做一些额外的操作
Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h);loader -- 类加载器interfaces -- 指定代理对象实现哪些接口,通常代理对象要和被代理对象实现相同的接口,从而保证和被代理者具有相同的方法InvocationHandler -- 处理器对象,当调用代理对象的任何方法时,都会导致此对象中的invoke方法执行,在这个方法中可以编写是否允许方法执行, 以及在方法执行之前和之后做那些额外的操作
其中有一个比较重要的方法:
{ Object invoke (Object proxy, Method method, Object[] args) proxy -- 代理者对象 method -- 当前调用到的方法 args -- 方法的参数 返回值 -- 就是这个方法要返回什么}
1、使用动态代理
a) 我们首先创建一个Person类,并实现接口IAction,接口中有两个方法sing和dance。
public class Person implements IAction { public void singe() { System.out.println("唱歌"); } public void dance() { System.out.println("跳舞"); }}interface IAction { public void singe(); public void dance();}
b) 接下来我们创建Person的代理类PersonProxy
public class PersonProxy { private Person person = new Person(); public Object newPersonProxy(){ IAction iAction = (IAction) Proxy.newProxyInstance(person.getClass().getClassLoader(), person.getClass().getInterfaces(), new InvocationHandler() { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 当返回代理对象,调用代理对象的任何方法都会走invoke里面的逻辑 System.out.println("----------------------"); return null; } }); return iAction; }}
c) 然后我们创建TestDynamicProxy类来测试下,运行可以看出-----被打印出来,并且调用任意方法都会走invoke的逻辑。
public class TestDynamicProxy { public static void main(String[] args){ PersonProxy personProxy = new PersonProxy(); IAction iAction = (IAction) personProxy.newPersonProxy(); iAction.dance(); }}
d) 再次处理下invoke中的逻辑,完善代理类的操作
public class PersonProxy { private Person person = new Person(); public Object newPersonProxy(){ IAction iAction = (IAction) Proxy.newProxyInstance(person.getClass().getClassLoader(), person.getClass().getInterfaces(), new InvocationHandler() { // 当返回代理对象,调用代理对象的任何方法都会走invoke里面的逻辑 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 调用真实的person去执行行为操作 if(method.getName().equals("dance")){ // 第一个参数为代理对象,第二个参数是方法的参数列表,如果不想让程序继续执行,则直接retuen return method.invoke(person, args); }else if (method.getName().equals("sing")) { return method.invoke(person, args); }else{ System.out.println("该方法暂时未开通"); return null; } } }); return iAction; }}
e) 接下来,我们对方法进行改造,比如加入点歌的操作,并且还带返回值,以表达观众的热情
public class Person implements IAction { public String sing(String name) { System.out.println("唱歌"); return "歌唱完了,谢谢!"; } public String dance(String name) { System.out.println("跳舞"); return "跳舞完了,谢谢"; }}interface IAction { public String sing(String name); public String dance(String name);}
f) 可以看到运行没有问题,其实传递的参数会封装在invoke的args参数列表中,它是一个Object类型的可变数组,我们可以在invoke中打印查看args列表
public class TestDynamicProxy { public static void main(String[] args){ PersonProxy personProxy = new PersonProxy(); IAction iAction = (IAction) personProxy.newPersonProxy(); String dance = iAction.dance("爵士舞"); String sing = iAction.sing("黑色毛衣"); System.out.println(dance); System.out.println(sing); }}
g) 之后,假如唱歌和跳舞需要一些特殊的要求,比如要钱或者掌声等等,那么我们可以在invoke中处理这个逻辑
public class PersonProxy { private Person person = new Person(); public Object newPersonProxy(){ IAction iAction = (IAction) Proxy.newProxyInstance(person.getClass().getClassLoader(), person.getClass().getInterfaces(), new InvocationHandler() { // 当返回代理对象,调用代理对象的任何方法都会走invoke里面的逻辑 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 调用真实的person去执行行为操作 if(method.getName().equals("dance")){ // 第一个参数为代理对象,第二个参数是方法的参数列表,如果不想让程序继续执行,则直接retuen System.out.println(args[0]); System.out.println("跳舞需要三千块钱"); return method.invoke(person, args); }else if (method.getName().equals("sing")) { System.out.println(args[0]); System.out.println("唱歌需要二千块钱"); return method.invoke(person, args); }else{ System.out.println("该方法暂时未开通"); return null; } } }); return iAction; }}
总结:动态代理最大的作用是通过反射在不改变源代码的情况下,对方法进行增强操作。
2、动态代理原理
动态代理其实是在运行时,通过反射获取到字节码,在运行时期的内存中创建被代理类的实例对象,并返回。
动态代理中最重要的方法就是newProxyInstance()方法了,我们可以看下它的源码:
@CallerSensitivepublic static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) ..... Class<?> cl = getProxyClass0(loader, intfs); try { final Constructor<?> cons = cl.getConstructor(constructorParams); final InvocationHandler ih = h; if (!Modifier.isPublic(cl.getModifiers())) { AccessController.doPrivileged(new PrivilegedAction<Void>() { public Void run() { cons.setAccessible(true); return null; } }); } return cons.newInstance(new Object[]{h}); } catch (Exception e) { }}
通过源码我们可以看出,首先拿到被代理类的字节码,通过反射获取到构造函数,然后创建出被代理对象的实例并返回给代理对象。
简而言之,动态代理其实就是被代理类的实例构建出该类的另一个实例,这个另外的实例就是所谓的代理对象。
不过日常开发中,动态代理很少使用,一般主要用于方法增强和框架开发等,不过方法增强的方式有很多,不一定
非要使用动态代理,比如使用继承和装饰者模式,但是动态代理有一个优点,它可以不改变源代码来实现功能加强。
14、反射、动态代理