Java反射机制你还不会?那你怎么看Spring源码

文章目录

    • 1.Java代码在计算机中经历的阶段:三个阶段
    • 2.Java识别类和对象信息的两种方式
    • 3.什么是反射
    • 4.获取Class对象的方式
    • 5.Class对象的功能
    • 6.通过反射操作类中的成员变量、构造函数、方法
    • 7.案例
    • 8.反射的优缺点

1.Java代码在计算机中经历的阶段:三个阶段


Person.java经过javac编译,变为Person.class文件(字节码文件),字节码文件中,主要有类的属性、构造函数、方法,当然还有类的其他信息,这个阶段称为源码阶段,通过类加载器进入内存,在内存中生成一个Class对象,这个阶段为Class类对象阶段,一个类的Class对象中存储了类的全部信息,使用这个类对象的阶段称为Runtime运行时阶段。

2.Java识别类和对象信息的两种方式

  • 一种是传统的RTTI(Run-Time Type Identification),它假定我们在编译时已经知道了所有的类型信息;
  • 另一种是反射机制,它允许我们在运行时发现和使用类的信息。使用的前提条件:必须先得到代表的字节码的Class,Class类用于表示.class文件(字节码)

3.什么是反射

Java的反射(reflection)机制是指在程序的运行状态中,可以构造任意一个类的对象,可以了解任意一个对象所属的类,可以了解任意一个类的成员变量和方法,可以调用任意一个对象的属性和方法。这种动态获取程序信息以及动态调用对象的功能称为Java语言的反射机制。
要想解剖一个类,必须先要获取到该类的字节码文件对象。而解剖使用的就是Class类中的方法。所以先要获取到每一个字节码文件对应的Class类型的对象.。反射就是把Java类中的各种成分映射成一个个的Java对象。

4.获取Class对象的方式

  1. 类名.class:该方法最为安全可靠,程序性能更高。这说明任何一个类都有一个隐含的静态成员变量 class。这种方法只适合在编译前就知道操作的 Class,多用于传递参数。
  2. Class.forName(“类的全路径名”) :当知道某类的全路径名时,可以使用此方法获取 Class 类对象。用的最多,但可能抛出 ClassNotFoundException 异常,多用于加载配置文件。
  3. 对象名.getClass():getClass()是Object中的方法,java中所有类都直接或间接继承了Object类,所以都有getClass()方法。
//ewen包下的Person类
public class Person {
    private String name;
    private int age;

    public String a;
    protected String b;
    String c;
    private String d;
//构造函数、get、set方法省略。
 public void sleep(){
        System.out.println("sleep....");
    }
}
public class ReflectTest {
    public static void main(String[] args) throws Exception {
       //获取Person类的Class对象
        // 通过类名.class获取
        Class clazz1 = Person.class;
        
      //通过Class.forName("类的全路径名")
       Class clazz2 = Class.forName("ewen.Person");
       
       //通过对象名.getClass()方法获得
        Person person = new Person();
        Class clazz3 = person.getClass();

        System.out.println(clazz1 == clazz2);	//true
        System.out.println(clazz2 == clazz3);	//true

    }
}

一个类只有一个Class实例,无论通过哪种方式获取,得到的都是同一个Class对象。

5.Class对象的功能

1)获取类的成员变量

  • Field[] getFields() :获取所有public修饰的成员变量,返回一个含有所有public修饰的成员变量对象的数组。
  • Field getField(String name):获取指定名称的 public修饰的成员变量,返回一个成员变量对象。
  • Field[] getDeclaredFields() : 获取所有的成员变量,不考虑修饰符。
  • Field getDeclaredField(String name) : 获取指定名称的成员变量对象,不考虑修饰符。
 		//获取所有pubic修饰的成员变量
        Field[] fields = clazz1.getFields();
        for (Field field : fields) {
            System.out.println(field); //public java.lang.String ewen.Person.a,只有a是public修饰的
        }

        //获取指定的成员变量
        Field field = clazz1.getField("a"); 
        System.out.println("成员变量a:" + field);	/public java.lang.String ewen.Person.a

        //使用getField()方法获取非public修饰的方法,报NoSuchFieldException
       /* Field field2 = clazz1.getField("b");
        System.out.println("protected修饰的b" + field2);*/

        //获取所有成员变量
       Field[] fields2 =  clazz1.getDeclaredFields();
        for (Field field1 : fields2) {
            System.out.println(field1);	//所有成员变量
        }

        //获取指定成员变量,不考虑修饰符
        Field field3 = clazz1.getDeclaredField("d");
        System.out.println(field3);		/public java.lang.String ewen.Person.d

2)获取类的构造方法

  • Constructor[] getConstructors() :获取所有public 修饰的构造方法,返回一个含有所有public修饰的构造函数对象的数组。
  • Constructor getConstructor(类… parameterTypes) :获取含有指定参数public修饰的构造函数。
  • Constructor[] getDeclaredConstructors() :获取所有构造函数,不考虑修饰符。
  • Constructor getDeclaredConstructor(类… parameterTypes) :获取所有构造函数,不考虑修饰符,参数是构造器中参数类型对应的Class对象。
 //获取构造函数
       Constructor con =  clazz1.getDeclaredConstructor(String.class, int.class);
        System.out.println(con);	//private ewen.Person(java.lang.String,int)

3)获取类中的方法

  • Method[] getMethods() :获取所有public修饰的方法,返回一个含有所有public修饰的方法对象的数组。
  • Method getMethod(String name, 类… parameterTypes) :获取public修饰的含有指定参数的方法。
  • Method[] getDeclaredMethods() :获取所有方法,不考虑修饰符
  • Method getDeclaredMethod(String name, 类… parameterTypes) :获取指定方法,不考虑修饰符。

4)获取类的全路径名:String getName()

6.通过反射操作类中的成员变量、构造函数、方法

1)Field:成员变量对象
操作:

  • 设置成员变量值:field.set(类对象名,value)
		//获取指定的成员变量
        Field field = clazz1.getField("a");
        System.out.println("成员变量a:" + field);
        Person person2 = new Person();
        //设置值
        field.set(person2,"aaa");
        System.out.println("a的值:" + person2.getA());    //a的值:aaa
  • 获取成员变量值:field.get(类对象名)
 		//获取指定的成员变量
        Field field = clazz1.getField("a");
        System.out.println("成员变量a:" + field);
        Person person2 = new Person();
        //获取person2中的a的值
        Object value = field.get(person2);
        System.out.println("a的值:" + value);	//a的值为null,String类型的成员变量默认为null。
  • 忽略访问权限修饰符的安全检查:field.setAccessible(true)
		//获取指定成员变量,不考虑修饰符
        Field field3 = clazz1.getDeclaredField("d");
        System.out.println(field3);
        //设置值
        Person person1 = new Person();
        
        /*
        忽略访问权限修饰符的安全检查,d是private修饰的,如果不忽略
        访问权限修饰符的安全检查,会报IllegalAccessException异常。
         */
        field3.setAccessible(true);
        field3.set(person1,"111");
        System.out.println("d的值:" + person1.getD());    //d的值:111

2)Constructor:构造方法对象

  • 创建对象:T newInstance(Object… initargs) ,里面为可变参数,变量的值。
/获取构造函数
        Constructor con1 = clazz1.getConstructor(String.class);
        //创建对象
        Object person3 = con1.newInstance("张三");

如果使用空参数构造方法创建对象,操作可以简化:Class对象的newInstance方法。
如果构造函数是private修饰的,也可以调用setAccessible(true)来忽略访问权限的安全检查。

Object person4 = clazz1.newInstance();

3)Method:方法对象

  • 执行方法:Object invoke(类对象名, Object… args) ,参数必须得有类对象,变量参数根据方法参数来定
    如果方法不是public修饰的,调用method.setAccessible(true)来忽略访问权限的安全检查。
//获取方法对象
 Method method = clazz1.getMethod("sleep");
 //执行方法
 method.invoke(person1);

Method 调用 invoke() 的时候,存在许多细节:
invoke() 方法中第一个参数 Object 实质上是 Method 所依附的 Class 对应的类的实例,如果这个方法是一个静态方法,那么 obj 为 null,后面的可变参数 Object 对应的自然就是参数。
invoke() 返回的对象是 Object,所以实际上执行的时候要进行强制转换。
在对Method调用invoke()的时候,如果方法本身会抛出异常,那么这个异常就会经过包装,由Method统一抛InvocationTargetException。而通过InvocationTargetException.getCause() 可以获取真正的异常。

  • 获取方法名称 String getName()

7.案例

需求:写一个"框架",不能改变该类的任何代码的前提下,可以帮我们创建任意类的对象,并且执行其中任意方法
实现:
1. 配置文件
2. 反射
步骤:
1. 将需要创建的对象的全类名和需要执行的方法定义在配置文件中
2. 在程序中加载读取配置文件
3. 使用反射技术来加载类文件进内存
4. 创建对象
5. 执行方法

pro.properties文件:
pro.className=ewen.Person
pro.methodName=sleep

public class UseReflect {
    public static void main(String[] args) throws Exception {
        //加载配置文件
        /*
        创建Properties对象。
         Properties是Java中jdk自带的一个对象
        我们可以直接将后缀为properties的文件变为Properties对象,
        然后通过Porperties对象中的方法对.properties文件对象进行操作
         */
        Properties pro = new Properties();
        //获得配置文件(pro.properties)的字节流
        InputStream is = UseReflect.class.getClassLoader().getResourceAsStream("pro.properties");
        //从输入流中读取属性列表(键和元素对)。
        pro.load(is);

        //获取配置文件中定义的数据
        String className = pro.getProperty("pro.className");
        String methodName = pro.getProperty("pro.methodName");
        //获取Class对象
        Class clazz = Class.forName(className);
        //获取类对象
        Object obj = clazz.newInstance();
        //获取方法对象
        Method method = clazz.getMethod("sleep");
        //执行方法
        method.invoke(obj);

    }
}

只需更改配置文件pro.properties文件就可以,在不改变代码的前提下,执行任意类的任意方法。

8.反射的优缺点

优点

  1. 反射机制极大的提高了程序的灵活性和扩展性,降低模块的耦合性,提高自身的适应能力。
  2. 通过反射机制可以让程序创建和控制任何类的对象,无需提前硬编码目标类。
  3. 使用反射机制能够在运行时构造一个类的对象、判断一个类所具有的成员变量和方法、调用一个对象的方法。
  4. 反射机制是构建框架技术的基础所在,使用反射可以避免将代码写死在框架中。

正是反射有以上的特征,所以它能动态编译和创建对象,极大的激发了编程语言的灵活性,强化了多态的特性,进一步提升了面向对象编程的抽象能力,因而受到编程界的青睐

缺点
尽管反射机制带来了极大的灵活性及方便性,但反射也有缺点。反射机制的功能非常强大,但不能滥用。在能不使用反射完成时,尽量不要使用,原因有以下几点:

  1. 性能问题。

Java反射机制中包含了一些动态类型,所以Java虚拟机不能够对这些动态代码进行优化。因此,反射操作的效率要比正常操作效率低很多。我们应该避免在对性能要求很高的程序或经常被执行的代码中使用反射。而且,如何使用反射决定了性能的高低。如果它作为程序中较少运行的部分,性能将不会成为一个问题。

  1. 安全限制

使用反射通常需要程序的运行没有安全方面的限制。如果一个程序对安全性提出要求,则最好不要使用反射。

  1. 程序健壮性

反射允许代码执行一些通常不被允许的操作,所以使用反射有可能会导致意想不到的后果。反射代码破坏了Java程序结构的抽象性,所以当程序运行的平台发生变化的时候,由于抽象的逻辑结构不能被识别,代码产生的效果与之前会产生差异。

参考:

  • Java反射机制
  • Java反射

如有不足之处,欢迎指正,谢谢!

你可能感兴趣的:(java基础)