14反射动态代理

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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);//true
System.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()方法了,我们可以看下它的源码:

@CallerSensitive
public 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反射动态代理的主要内容,如果未能解决你的问题,请参考以下文章

反射应用之动态代理

动态代理与反射在GraphQL客户端的应用

java反射机制动态代理初探

反射机制-动态代理

java 反射之静态and动态代理

Java高级特性—反射和动态代理