了解Java反射机制前需要先了解Java代码在计算机中的三个阶段

  • 源代码阶段:Java代码还存储在硬盘阶段的时候,由.java文件经javac编译形成.class文件存储在硬盘上。
  • 类对象阶段:JVM的类加载器将.class文件加载到内存中形成Class对象,此时java代码处在类对象阶段。
  • 运行阶段:开始运行程序创建对象,进入了代码运行阶段。

引入反射:框架设计的灵魂

框架:半成品的软件,可在框架的基础上进行软件开发,简化代码。
反射:将类的各个组成部分封装成其它对象,这就是反射机制。
1、可以在程序运行时操作这些对象。
2. 可以解耦,提高程序的可扩展性。

获取Class对象的方法:
1)Class.forName(“包名.全类名”); 将字节码文件加载到内存中返回Class对象。

  • 多用于配制文件,在类名定义到配置文件中,读取文件加载类

2)类名.class: 返回Class对象,通过类名的属性class获取

  • 多用于参数的传递

3)对象.getClass():返回Class对象,getClass对象在Object类中定义着

  • 多用于对象的获取字节码文件

结论:同一字节码文件(.class)在一次程序运行过程中只会被加载一次,无论以什么样的方式获取Class对象,对象都是同一个。
测试Person.java

package com.liuzeyu12a.reflect;

public class Person {
   
    String name;
    int age;

    public String a;
    protected String b;
    String c;
    private String d;

    public Person(String name, int age) {
   
        this.name = name;
        this.age = age;
    }

    public Person() {
   
    }

    @Override
    public String toString() {
   
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", a='" + a + '\'' +
                ", b='" + b + '\'' +
                ", c='" + c + '\'' +
                ", d='" + d + '\'' +
                '}';
    }
}

/* 获取Class对象 */
public class ReflectDemo {
   
    public static void main(String[] args)throws Exception {
   
        //1. Class.forName("全类名"):将字节码文件加载进内存,返回Class对象
        Class cls1 = Class.forName("com.liuzeyu12a.reflect.Person");

        //2. 类名.class:通过类名的属性class获取
        Class cls2 = Person.class;

        //对象.getClass():getClass()方法在Object类中定义着。
        Person person = new Person();
        Class cls3 = person.getClass();

        System.out.println(cls1);	//class com.liuzeyu12a.reflect.Person
        System.out.println(cls2);	//class com.liuzeyu12a.reflect.Person
        System.out.println(cls3);	//class com.liuzeyu12a.reflect.Person

        //判断是否Class对象相同
        System.out.println(cls1 == cls2);	//true
        System.out.println(cls1 == cls3);	//true
    }
}

访问成员变量常用的方法

  • Field[] getFields() :获取所有public修饰的成员变量
  • Field getField(String name) 获取指定名称的 public修饰的成员变量
  • Field[] getDeclaredFields() 获取所有的成员变量,不考虑修饰符
  • Field getDeclaredField(String name)
public class ReflectDemo2 {
   
    public static void main(String[] args) throws Exception {
   
        Class<Person> cls = Person.class;
        //1、Field[] getFields() :获取所有public修饰的成员变量
        Field[] fields = cls.getFields();
        for (Field field : fields) {
   
            System.out.println(field); //输出:public java.lang.String com.liuzeyu12a.reflect.Person.a
        }
        //2、Field getField(String name) 获取指定名称的 public修饰的成员变量
        Field a = cls.getField("a");
        //获取指定变量a
        Person person = new Person();
        /** * 1、给a设置值 set(Object obj, Object value)将指定对象变量上此 Field 对象表示的字段设置为指定的新值。 * 2、获取a的值 get(Object obj)返回指定对象上此 Field 表示的字段的值。 */
        a.set(person,"liuzeyu12a");
        a.get(person);
        System.out.println(person);//输出:erson{name='null', age=0, a='liuzeyu12a', b='null', c='null', d='null'}
        System.out.println("-----------------------------");
        //3、Field[] getDeclaredFields() 获取所有的成员变量,不考虑修饰符
        Field[] declaredFields = cls.getDeclaredFields();
        for (Field declaredField : declaredFields) {
   
            System.out.println(declaredField);
            /* 输出: java.lang.String com.liuzeyu12a.reflect.Person.name int com.liuzeyu12a.reflect.Person.age public java.lang.String com.liuzeyu12a.reflect.Person.a protected java.lang.String com.liuzeyu12a.reflect.Person.b java.lang.String com.liuzeyu12a.reflect.Person.c private java.lang.String com.liuzeyu12a.reflect.Person.d */
        }
        //4、Field getDeclaredField(String name)
        Field d = cls.getDeclaredField("d");
        /** * * 1、给d设置值 set(Object obj, Object value)将指定对象变量上此 Field 对象表示的字段设置为指定的新值。 * 2、获取d的值 get(Object obj)返回指定对象上此 Field 表示的字段的值。 如果没有访问权限(private)则会抛出Class com.liuzeyu12a.reflect.ReflectDemo2 can not access */
        d.setAccessible(true);  //因此将private类型的设置为可访问
        d.set(person,"jay");
        d.get(person);
        System.out.println(person);//输出:Person{name='null', age=0, a='liuzeyu12a', b='null', c='null', d='jay'}

    }
}

访问构造器(默认都是保护的)

  • Constructor<?>[] getConstructors() 获取所有public修饰的构造器
  • Constructor getConstructor(类<?>… parameterTypes)
  • Constructor getDeclaredConstructor(类<?>… parameterTypes)
  • Constructor<?>[] getDeclaredConstructors()
public class ReflectDemo3 {
   
    public static void main(String[] args) throws Exception{
   
        Class<Person> cls = Person.class;
        //1、Constructor<?>[] getConstructors()
        Constructor<?>[] constructors = cls.getConstructors();
        for (Constructor<?> constructor : constructors) {
   
            System.out.println(constructor);
            /** * 输出: * public com.liuzeyu12a.reflect.Person(java.lang.String,int) public com.liuzeyu12a.reflect.Person() */
        }
        //2、Constructor<T> getConstructor(类<?>... parameterTypes)
        Constructor<Person> constructor1 = cls.getConstructor(String.class, int.class);
        System.out.println(constructor1);//输出:public com.liuzeyu12a.reflect.Person(java.lang.String,int)
        Constructor<Person> constructor2 = cls.getConstructor();
        System.out.println(constructor2);//输出:public com.liuzeyu12a.reflect.Person()

        //利用构造器创建对象
        Person person2 = cls.newInstance();  //空参构造
        Person person1 = constructor1.newInstance("liuzeyu", 33);   //有参构造
        System.out.println(person1);//输出:Person{name='liuzeyu', age=33, a='null', b='null', c='null', d='null'}
        System.out.println(person2);//输出:Person{name='null', age=0, a='null', b='null', c='null', d='null'}

        System.out.println("----------------------------");

        //3、Constructor<?>[] getDeclaredConstructors()
        Constructor<?>[] declaredConstructors = cls.getDeclaredConstructors();
        int length = declaredConstructors.length;
        System.out.println(length);

        //4、Constructor<?>[] getDeclaredConstructors()
        Constructor<Person> liuzeyu = cls.getDeclaredConstructor(String.class,int.class);
        liuzeyu.setAccessible(true);  //设置访问权限构造器一般protected
    }
}

访问成员方法

  • Method[] getMethods() 获取所有public修饰的成员方法
  • Method getMethod(String name, 类<?>… parameterTypes)
  • Method[] getDeclaredMethods()
  • Method getDeclaredMethod(String name, 类<?>… parameterTypes)
public class ReflectDemo4 {
   
    public static void main(String[] args) throws Exception{
   
        Class<Person> cls = Person.class;
        //1、Method[] getMethods()
        Method[] methods = cls.getMethods();
        for (Method method : methods) {
   
            System.out.println(method);
            /** * 输出: * public java.lang.String com.liuzeyu12a.reflect.Person.toString() public final void java.lang.Object.wait() throws java.lang.InterruptedException public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException public boolean java.lang.Object.equals(java.lang.Object) public native int java.lang.Object.hashCode() public final native java.lang.Class java.lang.Object.getClass() public final native void java.lang.Object.notify() public final native void java.lang.Object.notifyAll() */
        }

        //2、Method getMethod(String name, 类<?>... parameterTypes)
        Method method1 = cls.getMethod("eat");
        System.out.println(method1);
        method1.invoke(new Person());//执行方法-->输出:eat...
        Method method2 = cls.getMethod("eat", String.class); //String.class表示方法参数
        method2.invoke(new Person(),"饭饭!");//输出:eat...饭饭!

        //3、Method[] getDeclaredMethods() //无视修饰符
        Method[] declaredMethods = cls.getDeclaredMethods();
        for (Method declaredMethod : declaredMethods) {
   
            System.out.println(declaredMethod);
            /** * 输出: * public final void java.lang.Object.wait() throws java.lang.InterruptedException public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException public boolean java.lang.Object.equals(java.lang.Object) public native int java.lang.Object.hashCode() public final native java.lang.Class java.lang.Object.getClass() public final native void java.lang.Object.notify() public final native void java.lang.Object.notifyAll() public void com.liuzeyu12a.reflect.Person.eat() public java.lang.String com.liuzeyu12a.reflect.Person.toString() private void com.liuzeyu12a.reflect.Person.drink() public void com.liuzeyu12a.reflect.Person.eat() */
        }
        //4、Method getDeclaredMethod(String name, 类<?>... parameterTypes)
        Method drink = cls.getDeclaredMethod("drink");
        drink.setAccessible(true);      //private 可访问,又称暴力反射
        String name = drink.getName();
        System.out.println(name);   //drink
        //综上说述:对于反射机制来说,在反射面前没有公有私有,都可以通过暴力反射解决setAccessible(true)。
    }
}

模拟框架小案例:

辅助类Person.java

package com.liuzeyu12a.reflect;

public class Student {
   
    String name;
    int age;

    public Student(String name, int age) {
   
        this.name = name;
        this.age = age;
    }
    public Student() {
   
    }
    public String getName() {
   
        return name;
    }
    public void setName(String name) {
   
        this.name = name;
    }
    public int getAge() {
   
        return age;
    }
    public void setAge(int age) {
   
        this.age = age;
    }

    @Override
    public String toString() {
   
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

    public void sleep(){
   
        System.out.println("student sleep in the class");
    }
}

Student.java

package com.liuzeyu12a.reflect;

public class Person {
   
    String name;
    int age;

    public String a;
    protected String b;
    String c;
    private String d;

    public Person(String name, int age) {
   
        this.name = name;
        this.age = age;
    }

    public Person() {
   
    }

    @Override
    public String toString() {
   
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", a='" + a + '\'' +
                ", b='" + b + '\'' +
                ", c='" + c + '\'' +
                ", d='" + d + '\'' +
                '}';
    }
    public void sleep(){
   
        System.out.println("person sleep in the bed");
    }
}

现在要实现的是不改变java代码的前提下可以分别实现Person和Student的sleep()方法,在src文件夹下新建配置文件pro.properties文件,写入内容

className=com.liuzeyu12a.reflect.Person
methodName=sleep

使用反射机制如下:

package com.liuzeyu12a.reflect;

import java.io.InputStream;
import java.lang.reflect.Method;
import java.util.Properties;

public class ReflectTest {
   

    public static void main(String[] args) throws Exception{
   
        /** * 前提: * 不改变类该类的任何代码,可以创建任意类对象,执行任意类的方法 * 没有用反射之前,如果要同时指向Person和Student的sleep()方法就要同时创建两个对象 * 指向两次方法,如下 */
// Person person = new Person();
// person.sleep();
// Student student = new Student();
// student.sleep();

        //引入反射:
        //1.1、加载配置文件
        Properties pro = new Properties();
        //1.2加载配置文件,使其转换成一个集合
        //1.2.1获取class文件目录下的配置文件
        ClassLoader classLoader = ReflectTest.class.getClassLoader();
        InputStream is = classLoader.getResourceAsStream("pro.properties");
        pro.load(is);
        //1.3获取配置文件中定义的数据
        String className = pro.getProperty("className");
        String methodName = pro.getProperty("methodName");

        //1.4执行方法
        Class<?> cls = Class.forName(className);
        Method method = cls.getMethod(methodName);
        Object o = cls.newInstance();
        method.invoke(o); //输出 person sleep in the bed
    }
}

这样子就可以实现在配置文件中定义的类+方法所执行的程序了,如果想要执行Studnent类中的sleep()方法只需要修改配置文件即可,修改如下:

#类名+方法名一一对应
className=com.liuzeyu12a.reflect.Student
methodName=sleep

这样做有什么好处

对于Java常用的三大框架Spring、Struts、Hibernate而言,我们一般是拿过来直接用的,而不应该去修改框架内部java代码,像传统的new实例的方式来实现方***显得很冗余而且繁琐,特别是当遇到一个大工程的时候,修改代码还要经过测试,重新编译,和发布一下复杂的步骤,而如果我们直接修改配置文件,就会简单很多,因为配置文件是一个物理文件,修改起来比较方便。
此外使用反射还能达到解耦的效果,假设我们使用的是new这种形式进行对象的实例化。此时如果在项目的某一个小模块中我们的一个实例类丢失了,那么在编译期间就会报错,以导致整个项目无法启动。而对于反射创建对象Class.forName(“全类名”);这种形式,我们在编译期需要的仅仅只是一个字符串(全类名),在编译期不会报错,这样其他的模块就可以正常的运行,而不会因为一个模块的问题导致整个项目崩溃。