Java提升五:反射与动态代理

1、反射

关于反射,个人理解就是,对于每一个已定义的Java类对象,都可以通过获取该类对应的Class类来在程序运行时动态地对于该类进行操作。

可以说,反射就是通过类名或类路径等字符串信息来直接创建和改变对象的操作。

1.1、 Class类的获取

首先,需要知道的是Java代码的运行会经历一下三个阶段:
java代码运行的三个阶段
而反射要做的就是,在不直接创建对象的情况下,通过对象的全类名等信息获取要创建对象的Class类型对象。接下来看一个示例。
首先,将要获取的类的定义如下:

class Person{//全类名为:reflection.work.Person
    public int age;
    private String name;
    public Person(){
        System.out.println("无参构造方法");
    }
    public void eat(){
        System.out.println("吃饭");
    }
}

要获取该Person对象所对应的Class类对象有以下三种方式:

  • ①通过类的全类名获取,多用于配置文件中。

获取方式:Class.forName(“全类名”);
该方式一般适用于Java运行代码的第一阶段 Source阶段

代码示例:

 //1、全类名:多用于配置文件
 Class<?> class1 = Class.forName("reflection.work.Person");
 System.out.println(class1);       
  • ②通过类名直接获取,多用于方法的参数传递。

获取方式:类名.class
该方式一般适用于Java运行代码的第二阶段 Class阶段

代码示例:

 //2,类名:多用于参数传递
 Class class2=Person.class;
 System.out.println(class2);      
  • ③通过创建的对象实例进行获取,多用于获取对象实例的字节码。

获取方式:类名 p=new 类名(); p.getClass();
该方式一般适用于Java运行代码的第三阶段 Runtime阶段

代码示例:

 //3.对象:多用于对象的获取字节码方式
 Person p=new Person();
 Class class3=p.getClass();
 System.out.println( class3);     

需要注意的是:通过以上三种方法获取的类对象是相同的,因为每个Java对象只有唯一一个与其对应的Class类对象

1.2 、Class类对象功能

在获取到Java对象所对应的Class类对象后,实际上就可以通过该Class对象在代码中动态地创建Java对象以及Java对象中的方法。
在此,将Class对象的功能分为以下两大类:

  • ①通过Class对象直接获取Class所对应的Java对象的成员属性、成员方法以及构造器方法;
  • ②获取到Java对象的成员属性、成员方法以及构造器方法后,根据获取元素不同调用不同方法进行不同的操作;

接下来就逐一分析如何通过Class对象获取Class所对应的Java对象的成员属性、成员方法、构造器方法并进行操作的。

1.2.1、 获取成员属性并操作

在获取到Person对象的Class类对象后,可以直接使用Class类对象中方法获取Person对象的成员属性。可以使用的方法及功能如下:

方法 功能
getFields() 获取Public属性的成员变量
getField(“变量名”) 根据变量名获取指定成员变量
getDeclaredFields() 获取所有成员变量(包括私有)
getDeclaredField (“变量名”) 根据变量名获取指定成员变量(包括私有)

在通过上述方法获取到Person对象的成员属性在Class类中所封装成的Field对象后,利用Field对象中的方法,可以获取或设置已经创建的Person对象实例中相对应的成员属性的值。

方法 功能
get(实例对象) 获取已创建的对象实例中的相对应的成员属性的值
set(实例对象, 所赋值) 给已创建的对象实例中的相对应的成员属性赋值
setAccessible(true) 将Method对象中封装的不可访问的私有成员属性设置为可访问(暴力反射)

代码示例:

public static void main(String[] args) throws Exception {
        //获取类的成员变量
        Class<Person> class1= Person.class;

        //获取成员变量(public)
        Field[] fields = class1.getFields();
        for (Field f : fields) {
        //输出值为:public int reflection.work.Person.age
        //对应Person类中:public int age;属性
            System.out.println(f);
        }

        //获取指定成员变量(public)
        Field field = class1.getField("age");
        //获取成员变量后的操作
        //获取值
        Person p=new Person();
        Object value = field.get(p);//获取p中的age属性的值
        System.out.println(value);//获取age的值

        //设置值
        field.set(p,45);//给p中age属性赋值为45
        System.out.println(p.toString());
    }

1.2.2 、获取成员方法并操作

在获取到Person对象的Class类对象后,可以直接使用Class类对象中方法获取Person对象的成员方法。可以使用的方法及功能如下:

方法 功能
getMethods() 获取Public属性的成员方法
getMethod(“方法名”) 根据方法名获取指定成员方法
getDeclaredMethods() 获取所有成员方法(包括私有)
getDeclaredMethod (“方法名”) 根据方法名获取指定成员方法(包括私有)

在通过上述方法获取到Person对象的成员方法在Class类中所封装成的Method对象后,利用Method对象中的方法,可以调用已经创建的Person对象实例中相对应的成员方法的值。

方法 功能
invoke(对象实例,方法参数) 调用已创建的对象实例中的相对应的成员方法
getName() 获取Method对象中封装的成员方法的名称
setAccessible(true) 将Method对象中封装的不可访问的私有成员方法设置为可访问(暴力反射)

代码示例:

public static void main(String[] args) throws Exception{
        Class<Person> class3 = Person.class;
        //获取类的方法
        Method eat = class3.getMethod("eat");
        Person p=new Person();
        //执行方法
        eat.invoke(p);//执行p中的eat方法
        //获取方法
        String name = eat.getName();
        System.out.println(name);//eat->方法名
    }

1.2.3 、获取构造器方法并操作

以上两种操作都需要先创建对象的实例,这就使得反射失去了意义,接下来就通过获取Java类所对应的Class类中所封装的构造器方法对象Constructor,并通过Constructor对象中的方法来创建Java类的实例。通过Class类对象获取Constructor对象的方法如下:

方法 功能
getConstructors() 获取Public属性的构造器方法
getConstructor(构造器方法参数的Class类) 根据构造器方法参数的Class类获取指定构造器方法
getDeclaredConstructors() 获取所有构造器方法(包括私有)
getDeclaredConstructor (构造器方法参数的Class类) 根据构造器方法参数的Class类获取指定构造器方法(包括私有)

通过上述方法获取到Person对象的构造器方法在Class类中所封装成的Constructor对象后,利用Constructor对象中的方法,可以创建Person对象的实例。

方法 功能
newInstance(实例对象,方法参数) 创建的Class类对象所对应的Java对象的实例

代码示例:

 public static void main(String[] args) throws Exception{
        Class<Person> class2 = Person.class;
         //获取class类的构造方法
        Constructor<Person> con = class2.getConstructor();
        System.out.println(con);
        //创建对象实例
        Person p1 = con.newInstance();
        System.out.println(p1);
    }

结合使用Class类对象获取Constructor对象创建Person对象实例,以及Method对象和Field对象的获取及使用,可以完全通过获取Person类的Class类对象对于该对象进行实现和操作,而无需创建一个确定性的对象,这就是反射的魅力。也就是说,我们只需要知道一个类的全类名甚至只需知道类名,就可以动态地在代码中使用该类,而无需创建它。

2、动态代理

代理模式,就是在不需要更改目标对象的前提下,对目标对象进行增强、包装校验等操作后形成一个新的对象,这就是代理类。
代理的方式一般分为三种:

  • 静态代理:要求被代理类和代理类要实现同一个接口,同时代理类和被代理类都需要事先创建
  • 动态代理:要求被代理类实现接口,通过Java中的Proxy类在需要对被代理类进行方法增强时动态地创建代理类。
  • Cglib代理:对于被代理类无要求,使用第三方cglib库中的Enhancer类为被代理对象动态地创建代理类。

这里我们并不展开去讲述三种代理,而是只对其中最常用的动态代理做一些探究。

首先,可以创建动态代理的被代理对象必须要实现一个接口,所以需要事先准备好接口和被代理对象,代码如下:

//接口
public interface Producer {
	//方法
    public void saleProduct(float money);

    public void afterService(float money);
}

//被代理对象
public class ProducerImpl implements Producer{
   //接口中的方法实现
    public void saleProduct(float money){
        System.out.println("销售产品,并拿到钱"+money);
    }

    public void afterService(float money){
        System.out.println("提供售后服务,并拿到钱"+money);
    }
}

在准备好接口和被代理对象之后,当我们需要对被代理对象中的方法进行增强后再使用时,就需要使用动态代理。具体的实现代理如下:

public static void main(String[] args) {
		//创建被代理对象的的实例
        final Producer producer = new ProducerImpl();
        //创建动态代理对象
        Producer proxyInstance = (Producer) Proxy.newProxyInstance(producer.getClass().getClassLoader(),
                producer.getClass().getInterfaces(),
                new InvocationHandler() {
                  
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        //提供增强的代码
                        Object returnValue = null;
                        //1 获取方法执行的参数
                        Float money = (Float) args[0];
                        //2 判断当前方法是不是销售
                        if ("saleProduct".equals(method.getName())) {

                            returnValue = method.invoke(producer, money * 0.8f);
                        }

                        return returnValue;
                    }
                }
        );
        producer.saleProduct(10000f);
        proxyInstance.saleProduct(10000f);
    }

对于以上代码,作出如下分析
动态代理的实现
这就是动态代理类被创建和使用的过程,总的来说,动态代理可以在不改变源代码的基础上,对被代理对象中的所有方法进行增强,这就是动态代理的作用。这也是之后很多框架所需要使用的原理。

3、反射与动态代理的关联

要探究反射和动态代理的关联,就需要去研究动态代理实现的源代码。在理解源代码之前,先将生成的动态代理类的大致情况做一下介绍:

  • 动态代理类继承了Proxy类
  • 动态代理类实现了被代理对象所实现的接口
  • 动态代理类对于接口中方法的具体实现都是通过调用InvocationHandler的匿名内部类中实现的invoke()方法;

有了以上的认知基础,我们从Proxy.newProxyInstance()开始追溯源代码并做出图解分析如下:
JDK代理源代码分析
可见,在动态代理的源代码实现中,利用反射和获取Class类对象的操作随处可见,可以说动态代理就是反射应用的典范。

4、总结

反射和动态代理是很多框架设计的底层原理基础,深入理解和挖掘反射以及动态代理的源代码,有助于之后的框架学习以及架构师之路。

posted @ 2020-02-29 23:08  zjL1997  阅读(162)  评论(0编辑  收藏  举报