首页 > 代码库 > 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、反射、动态代理