结合框架理解Java反射 - blckder02

admin 2021年12月31日14:55:16评论51 views字数 8003阅读26分40秒阅读模式

概述

反射:框架设计的灵魂

  • 框架:半成品软件;可以在框架的基础上进行软件开发,简化编码。
  • 反射:将类的各个组成部分封装为其他对象。

下面大概介绍一下Java代码在计算机中经历的三个阶段:

  • 第一阶段——Source源代码阶段:
    首先写一个类文件Person.java,类里面可以写一些成员变量、构造方法、成员方法等;
    经过javac编译后在硬盘上生成一个字节码文件Person.class,里面保存了成员变量、构造方法、成员方法等;

  • 第二阶段——Class类对象阶段
    通过类加载器把字节码文件加载到内存里,内存中有一个Class 类对象(描述所有字节码文件的共同特征和行为),将成员变量封装为Field[ ]对象,构造方法封装为Constructor[ ]对象,成员方法封装为Method[ ]对象

  • 第三阶段——Runtime运行时阶段
    new一个Person对象

反射的好处:

  1. 可以在程序运行过程中,操作这些对象;
  2. 可以降低程序的一些耦合性,提高程序的可扩展性。

获取Class对象

三种方式

  1. Class.forName("全类名"):将字节码文件加载进内存,返回Class对象;
    • 多用于配置文件,将类名定义在配置文件中;读取文件,加载类。
  2. 类名.class:通过类名的属性class获取;
    • 多用于参数的传递。
  3. 对象.getClass():getclass()方法在Object类中定义;
    • 多用于对象的获取字节码的方式。

示例代码

cn.blckder02.domain下创建一个Person类,定义两个成员变量:

再创建一个student类,作对比,暂时不用写内容;
cn.blckder02.reflect下创建一个ReflectDemo1类,分别用以上三种方法获取Class对象,代码如下:

package cn.blckder02.reflect;

import cn.blckder02.domain.Person;
import cn.blckder02.domain.student;

public class ReflectDemo1 {

    /**
     获取Class对象的方式
         1. Class.forName("全类名"):将字节码文件加载进内存,返回Class对象;
         2. 类名.class:通过类名的属性class获取;
         3. 对象.getClass():getclass()方法在Object类中定义;
     */
    public static void main(String[] args) throws ClassNotFoundException {

        //1. Class.forName("全类名")
        Class cls1 = Class.forName("cn.blckder02.domain.Person");
        System.out.println(cls1);

        //2. 类名.class
        Class cls2 = Person.class;
        System.out.println(cls2);

        //3. 对象.getClass()
        Person p = new Person();
        Class cls3 = p.getClass();
        System.out.println(cls3);

        //比较三个对象是否为同一个对象(比较的是内存地址)
        System.out.println(cls1 == cls2);
        System.out.println(cls1 == cls3);

        //获取student
        Class cls = student.class;
        System.out.println(cls);

        //比较cls与上一个对象是否为同一个对象
        System.out.println(cls1 == cls);
    }

}

程序运行结果如下,前两个比较都为true,说明同一个字节码文件(*.class)在一次程序运行过程中,只会被加载一次,不论通过哪一种方式获取的Class对象都是同一个;
第三个比较为false,说明每个字节码文件对应的Class类对象都不相同。

使用Class对象

Class对象的获取功能

  1. 获取成员变量们

    • Field[ ] getFields() 获取所有public修饰的成员变量
    • Field getField(String name) 获取指定名称public的成员变量

    • Field[ ] getDeclaredFields() 获取所有成员变量,不考虑修饰符

    • Field getDeclaredField(String name) 获取所有指定名称的成员变量,不考虑修饰符
  2. 获取构造方法们

    • Constructor<?>[ ] getConstructors() 获取所有public修饰的构造方法
    • Constructor< T> getConstructor(类<?>... parameterTypes) 获取指定名称public的构造方法

    • Constructor<?>[ ] getDeclaredConstructors() 获取所有构造方法,不考虑修饰符

    • Constructo< T>r getDeclaredConstructor(类<?>... parameterTypes) 获取所有指定名称的构造方法,不考虑修饰符
  3. 获取成员方法们

    • Method[ ] getMethods() 获取所有public修饰的成员方法
    • Method getMethod(String name, 类<?>... parameterTypes) 获取指定名称public的成员方法

    • Method[ ] getMethods() 获取所有成员方法,不考虑修饰符

    • Method getDeclaredMethod(String name, 类<?>... parameterTypes) 获取所有指定名称的成员方法,不考虑修饰符
  4. 获取类名
    • String getName()

示例

1. 获取成员变量们
在Person类中按如下属性添加a、b、c、d四个成员变量,并重新设置toString()方法;

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

获取成员变量后可做操作:

  • 设置值:void set(Object obj, Object value)
  • 获取值:get(Object obj)
  • 忽略访问权限修饰符的安全检查:setAccessible(true)(暴力反射)

cn.blckder02.reflect下创建ReflectDemo2类,代码如下:

package cn.blckder02.reflect;

import cn.blckder02.domain.Person;

import java.lang.reflect.Field;

public class ReflectDemo2 {

    public static void main(String[] args) throws Exception {
        //获取Person的Class对象
        Class personClass = Person.class;

    /*
        1. 获取成员变量们
          - Field[ ]  getFields()
          - Field  getField(String name)

          - Field[ ]  getDeclaredFields()
          - Field  getDeclaredField(String name)
     */
        //1.Field[ ]  getFields()
        Field[] fields = personClass.getFields();
        for (Field field  : fields){
            System.out.println(field);
        }

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

        //2. Field  getField(String name)
        //获取a成员变量
        Field a = personClass.getField("a");
        //获取成员变量a的值,a初始值为null
        Person p = new Person();
        Object value1 = a.get(p);
        System.out.println(value1);
        //设置a的值
        a.set(p, "blckder02");
        System.out.println(p);

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

        //3. Field[ ]  getDeclaredFields()
        Field[] declaredFields = personClass.getDeclaredFields();
        for (Field declaredField : declaredFields){
            System.out.println(declaredField);
        }

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

        //4. Field  getDeclaredField(String name)
        //获取私有成员变量d
        Field d = personClass.getDeclaredField("d");
        //忽略访问权限修饰符的安全检查
        d.setAccessible(true);  //暴力反射
        Object value2 = d.get(p);
        System.out.println(value2);
    }

}

由于getFields()只能获取public修饰的成员变量,所以第一个只输出a;
getDeclaredFields()能获取所有成员变量,所以输出每个成员变量;
getDeclaredField(String name)不能直接访问私有成员变量,但是可以使用setAccessible()方法,忽略访问权限修饰符的安全检查来访问。
程序运行结果如下:

2. 获取构造方法们
getConstructors()方法同样只能获取public修饰的构造方法。
构造方法的名称都与类名相同,唯一的区别就是参数不同,所以在获取带参构造方法时要传入相应的参数。
通过构造器来创建对象,需要用到newInstance()方法;
如果使用空参构造方法创建对象,可以直接使用Class对象的newInstance()方法,简化代码,获取到的结果是一样的。
同样可以通过暴力反射setAccessible()方法获取私有构造方法。

cn.blckder02.reflect下创建ReflectDemo3类,代码如下:

package cn.blckder02.reflect;

import cn.blckder02.domain.Person;

import java.lang.reflect.Constructor;

public class ReflectDemo3 {

    public static void main(String[] args) throws Exception {
        //获取Person的Class对象
        Class personClass = Person.class;

        /*
            2. 获取构造方法们
              - Constructor<?>[ ]  getConstructors()
              - Constructor<T>  getConstructor(类<?>... parameterTypes)

              - Constructor<?>[ ]  getDeclaredConstructors()
              - Constructor<T>  getDeclaredConstructor(类<?>... parameterTypes)
         */
        //Constructor<?>[ ]  getConstructors()
        Constructor[]  constructor0 = personClass.getConstructors();
        for (Constructor constructor : constructor0){
            System.out.println(constructor);
        }

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

        //Constructor<T>  getConstructor(类<?>... parameterTypes)
        Constructor constructor1 = personClass.getConstructor(String.class, int.class);
        System.out.println(constructor1);

        //用构造器创建对象
        Object person1 = constructor1.newInstance("blckder02",20);
        System.out.println(person1);

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

        //获取空参构造方法
        Constructor constructor2 = personClass.getConstructor();
        System.out.println(constructor2);

        //用构造器创建对象
        Object person2 = constructor2.newInstance();
        System.out.println(person2);

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

        //简化
        Object person3 = personClass.newInstance();
        System.out.println(person3);
    }

}

程序运行结果如下:

3. 获取成员方法们
在person类中添加一个空参成员方法eat(),一个带参成员方法eat(String food)

public void eat(){
        System.out.println("eat eat eat ...");
    }
public void eat(String food){
        System.out.println("I like eat "+food);
    }

指定名称获取成员方法时,需要传入方法名以及参数。
执行方法时,需要用到invoke()方法,要传入方法对象和实际的参数列表;

Object invoke(Object obj, Object...args)

获取所有public修饰的成员方法时,不仅会得到自己设置的几个方法,还有Object类、父类等的方法。
调用方法的getName()可以获取到方法名称。
同样可以通过暴力反射setAccessible()方法获取私有成员方法。

在在cn.blckder02.reflect下创建ReflectDemo4类,代码如下:

package cn.blckder02.reflect;

import cn.blckder02.domain.Person;

import java.lang.reflect.Method;

public class ReflectDemo4 {

    public static void main(String[] args) throws Exception {
        //获取Person的Class对象
        Class personClass = Person.class;

        /*
            3. 获取成员方法们
              - Method[ ]  getMethods()
              - Method  getMethod(String name, 类<?>... parameterTypes)

              - Method[ ]  getMethods()
              - Method  getDeclaredMethod(String name, 类<?>... parameterTypes)
         */

        //Method  getMethod(String name, 类<?>... parameterTypes)
        //获取空参方法
        Method eat_method1 = personClass.getMethod("eat");
        Person p = new Person();
        //执行方法
        eat_method1.invoke(p);

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

        //获取带参方法
        Method eat_method2 = personClass.getMethod("eat", String.class);
        //执行方法
        eat_method2.invoke(p, "Apple");

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

        //Method[ ]  getMethods()
        Method[] methods = personClass.getMethods();
        for (Method method :methods){
            String name = method.getName();
            System.out.println(name+" / "+method);
        }
    }
}

程序运行结果如下:

4. 获取类名
调用类的getName()方法,获取到的是全类名;

案例

目的: 写一个类似“框架”的程序,在不改变程序任何代码的情况下,我们可以创建任意类对象,并执行其中方法。
可以通过写配置文件反射技术来实现;
步骤:

  1. 将需要创建的对象的全类名和需要执行的方法定义在配置文件中
  2. 在程序中加载读取配置文件
  3. 使用反射技术来加载文件进内存
  4. 创建对象
  5. 执行方法

示例代码:
配置文件config,类名参数值要写全类名:

className = cn.blckder02.domain.Person
methodName = eat

测试程序ReflectTest.java

package cn.blckder02.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 {

        //1.加载配置文件
        Properties pro = new Properties();  //创建Properties对象
        //获取class目录下的配置文件
        ClassLoader classLoader = ReflectTest.class.getClassLoader();   //获取字节码文件对应的类加载器
        InputStream bs = classLoader.getResourceAsStream("config"); //获取对应配置文件的字节流
        pro.load(bs);   //加载字节流

        //2.获取配置文件中定义的数据
        String className = pro.getProperty("className");
        String methodName = pro.getProperty("methodName");

        //3.加载该类进内存
        Class cls = Class.forName(className);

        //4.创建对象
        Object obj = cls.newInstance();

        //5.获取方法对象
        Method method = cls.getMethod(methodName);

        //执行方法
        method.invoke(obj);

    }
}

程序运行结果:

在不改动ReflectTest.java代码的情况下,修改配置文件,来调用student类中的sleep方法;
结合框架理解Java反射 -  blckder02
此时程序运行结果:

这就是框架的基本形式,只用修改少量地方,就能实现多种方法;
而反射就是去获取我们看不见的构造器、对象、方法等。

有什么写得不对的地方还请各位师傅指教。

BY:先知论坛

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2021年12月31日14:55:16
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   结合框架理解Java反射 - blckder02http://cn-sec.com/archives/710137.html

发表评论

匿名网友 填写信息