Java之反射(是框架设计的灵魂,.properties配置文件的解耦)

目录

反射:框架设计的灵魂

获取Class对象的方式

Class对象功能

1.class的成员变量们

2.class的构造方法们

3.class的成员方法们

 4. class的全类名    

为什么框架要使用反射


反射:框架设计的灵魂

在后期使用框架的时候,是否掌握反射关系不大,因为框架已经写好了;如果是自己开发一个框架,让别人使用,那么反射需要深入理解和掌握。同时,如果已经理解反射原理,那么可以更好的使用已有的框架。 

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


好处:
        1. 可以在程序运行过程中,操作这些对象。
        2. 可以解耦,提高程序的可扩展性。

获取Class对象的方式

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

    * 结论:
        同一个字节码文件(*.class)在一次程序运行过程中,只会被加载一次,即不论通过哪一种方式获取的Class对象都是同一个

// 定义一个Person类
public class Person {
    private String name;
    private int age;


    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    // 后面省略不写了
}
// 返回获取Class对象的方式
public class ReflectTest {
    public static void main(String[] args) throws Exception {
        // 1. Class.forName("全类名"),全类名=包名.类名
        Class cls1 = Class.forName("demo23.Person");
        System.out.println(cls1); // class demo23.Person
        // 2. 类名.class
        Class cls2 = Person.class;
        System.out.println(cls2); // class demo23.Person
        // 3. 对象.getClass()
        Person person = new Person();
        Class cls3 = person.getClass();
        System.out.println(cls3); // // class demo23.Person
        // 比较三个获取得到的对象是否同一个
        System.out.println(cls1==cls2); // true
        System.out.println(cls1==cls3); // true
    }
}

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<T> getDeclaredConstructor(类<?>... parameterTypes)   获取所有的构造方法,不考虑修饰符
            * Constructor<?>[] getDeclaredConstructors()  
        3. 获取成员方法们:
            * Method[] getMethods()  获取所有public修饰的成员方法
            * Method getMethod(String name, 类<?>... parameterTypes)   获取指定名称,该方法对应的参数的 public修饰的成员方法

            * Method[] getDeclaredMethods()  获取所有的成员方法,不考虑修饰符
            * Method getDeclaredMethod(String name, 类<?>... parameterTypes)  

        4. 获取全类名    
            * String getName() 

1.class的成员变量们

首先通过上述class获取功的方法来获取成员变量们,然后进行下面的操作

Field:成员变量
    * 操作:
        1. 设置值
            * void set(Object obj, Object value)  
        2. 获取值
            * get(Object obj)

        3. 忽略访问权限修饰符的安全检查
            * setAccessible(true):暴力反射

// 定义一个Person类
public class Person {
    private String name;
    private byte age;
    public int a;  // public修饰
    protected float b; // protected修饰
    char c;  // 默认修饰符
    private boolean d; // private修饰
    // 其余不重要的省略
}
// 获取class的成员变量,并对其操作
public class ReflectTest {
    public static void main(String[] args) throws Exception {
        // 0.获取class对象
        Class personCls = Person.class;
        // 1.获取成员变量
        // 方法一:Field[] getFields() 获取所有public修饰的成员变量,其他修饰符不包含在里面
        Field[] fields = personCls.getFields();
        // 下面只打印了a变量,public int demo23.Person.a
        for (Field field : fields) {
            System.out.println(field);
        }
        // 方法二:Field getField(String name)  获取指定名称的 public修饰的成员变量
        Field a = personCls.getField("a");
        Person person = new Person();
        // 1.1获取成员变量a的值
        Object valueA = a.get(person);
        System.out.println(valueA); // int默认初始化是0
        // 1.2设置成员变量a的值
        a.set(person,10);
        System.out.println(person); // Person{name='null', age=0, a=10, b=0.0, c= , d=false} 因为字符类型c,默认为‘\u0000’ 打印不可见。
        // 方法三、Field[] getDeclaredFields() 获取所有的成员变量,不考虑修饰符
        Field[] declaredFields = personCls.getDeclaredFields();
        // 打印了全部的变量
        /*
            private java.lang.String demo23.Person.name
            private byte demo23.Person.age
            public int demo23.Person.a
            protected float demo23.Person.b
            char demo23.Person.c
            private boolean demo23.Person.d
         */
        for (Field field : declaredFields) {
            System.out.println(field);
        }
        // 方法四、Field getDeclaredField(String name)  获取指定名称的成员变量,不考虑修饰符
        Field d = personCls.getDeclaredField("d");
        // 忽视访问权限修饰符的安全检查,暴力反射。
        d.setAccessible(true);
        Object valueD = d.get(person);
        System.out.println(valueD);  // false  由于变量d是一个private变量,所以需要暴力反射再访问。否则会报错IllegalAccessException
    }
}

2.class的构造方法们

首先通过上述class获取功的方法来获取成员变量们,然后进行下面的操作

Constructor:构造方法
    * 创建对象:
        * T newInstance(Object... initargs)  

        * 如果使用空参数构造方法创建对象,操作可以简化:  Class对象的newInstance方法

        * 忽略访问权限修饰符的安全检查:setAccessible(true):暴力反射

// 定义一个Person类
public class Person {
    private String name;
    public int a;  // public修饰
    protected float b; // protected修饰
    private boolean d; // private修饰
    public Person() {
    }
    public Person(String name, int a) {
        this.name = name;
        this.a = a;
    }
    public Person(float b, boolean d) {
        this.b = b;
        this.d = d;
    }
    // 后面的省略了
}
// 构造器对象的使用
public class ReflectTest {
    public static void main(String[] args) throws Exception {
        // 0.获取class对象
        Class personCls = Person.class;
        // 1.获取构造方法们
        // 方法一:获取构造方法Constructor<T> getConstructor(类<?>... parameterTypes)
        // 创建对象,有参数
        Constructor constructor = personCls.getConstructor(float.class, boolean.class);
        System.out.println(constructor); // public demo23.Person(float,boolean)
        Object person = constructor.newInstance(10.0F, true);
        System.out.println(person); // Person{name='null', a=0, b=10.0, d=true}
        // 创建对象,无参数
        Constructor constructor1 = personCls.getConstructor();
        Object person1 = constructor1.newInstance();
        System.out.println(person1); // Person{name='null', a=0, b=0.0, d=false}
        // 无参数的时候,建议使用Class对象的newInstance方法
        Object person2 = personCls.newInstance();
        System.out.println(person2); // Person{name='null', a=0, b=0.0, d=false}
    }
}

3.class的成员方法们

Method:方法对象
    * 执行方法:
        * Object invoke(Object obj, Object... args)  

    * 获取方法名称:
        * String getName:获取方法名

// 定义一个Person类
public class Person {
    private String name;
    public int a;  // public修饰
    protected float b; // protected修饰
    private boolean d; // private修饰
    public void watch(){
        System.out.println("前无古人,后无来者!");
    }
    // 方法的重载,有关的因素(1)参数个数不同。(2)参数类型不同。(3)参数的多类型顺序不同。
    public void watch(String name){
        System.out.println("前方隐隐约约来了一位丁香姑娘:"+name);
    }
    // 后面省略了
}
// 成员方法对象的使用
import java.lang.reflect.Method;
public class ReflectTest {
    public static void main(String[] args) throws Exception {
        // 0.获取class对象
        Class personCls = Person.class;
        // 1.获取成员方法们
        // 获取指定名称的成员方法:Method getMethod(String name, 类<?>... parameterTypes) 获取指定名称,该方法对应的参数的 public修饰的成员方法
        // 指定的方法无参数
        Method watch1 = personCls.getMethod("watch");
        // 执行
        Person person = new Person();
        watch1.invoke(person); // 前无古人,后无来者!
        // 指定的方法有参数
        Method watch2 = personCls.getMethod("watch", String.class);
        watch2.invoke(person, "王昭君"); // 前方隐隐约约来了一位丁香姑娘:王昭君
        // 获取所有public修饰的方法,不仅仅是对象的自己的方法,还包括了Object父类的一些方法
        Method[] methods = personCls.getMethods();
        for (Method method : methods) {
            System.out.println(method); // 打印 public void demo23.Person.watch()...
            // 获取方法对象的名字
            String methodName = method.getName();
            System.out.println(methodName); // 打印对应的方法名字watch...
        }
    }
}

 4. class的全类名    

            String getName()

// 获取class对象的全类名
public class ReflectTest {
    public static void main(String[] args) throws Exception {
        // 0.获取class对象
        Class personCls = Person.class;
        // 1.获取class对象的全类名:包.类名
        String className = personCls.getName();
        System.out.println(className); // demo23.Person  是一个全类名
    }
}

为什么框架要使用反射

以前要使用方法,则创建对象,然后调用对象方法。但是,如果是一个框架类,那么这样存在一个弊端:既然是一个框架,那么是一个半成品的软件,代码是提前写好的、不允许修改。框架的前提就是:不能改变任何代码,但是可以创建类一类的对象,可以执行任意方法。

案例:

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

如果框架要改,代码要改,那么需要重新测试,重新上线。该配置文件的方式能使得程序的拓展性更强。
当一个配置文件出现了全类名,那么这个配置文件用到了反射技术。

//配置文件pro.properties
className=demo23.Person  // 配置文件中可以修改类和方法等等,而不需要修改框架中的代码
methodName=watch

// 定义一个Person类
public class Person {
    private String name;
    public int a;  // public修饰
    protected float b; // protected修饰
    private boolean d; // private修饰
    public void watch(){
        System.out.println("前无古人,后无来者!");
    }
    // 方法的重载,有关的因素(1)参数个数不同。(2)参数类型不同。(3)参数的多类型顺序不同。
    public void watch(String name){
        System.out.println("前方隐隐约约来了一位丁香姑娘:"+name);
    }
    public Person() {
    }
}

// 反射的魅力--框架的灵魂
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.加载配置文件

        //1.1.手动新建一个.properties文件
        // 1.2.创建Properties对象,因为可以加载properties文件形成一个集合。
        Properties properties = new Properties();
        // 1.3加载配置文件,转换为一个集合
        // 1.3.1获取字节码文件对应的类加载器,是这个类加载器将字节码加载进内存的。
        ClassLoader classLoader = ReflectTest.class.getClassLoader(); // ReflectTest.class表示当前类的字节码文件
        // 1.3.2.既然类加载器可以找到class目录下的class文件,那么也可以找到同目录下的配置文件
        // classLoader.getResource(String name)方法返回的是URL即资源的完整路径
        System.out.println(classLoader.getResource("pro.properties")); // file:/C:/Users/jupy/IdeaProjects/basic_code/out/production/code01/pro.properties
        // classLoader.getResourceAsStream(String name)方法返回的是字节流InputStream
        InputStream resourceAsStream = classLoader.getResourceAsStream("pro.properties");
        System.out.println(resourceAsStream); // java.io.BufferedInputStream@677327b6
        // 1.3.3.加载配置文件的字节流
        properties.load(resourceAsStream);

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

        // 3.使用反射技术,加载该类进内存
        Class cls = Class.forName(className);

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

        // 5.获取方法对象并且执行方法
        Method method1 = cls.getMethod(methodName);
        method1.invoke(obj); // 前无古人,后无来者!
        // 如果所有的方法都不需要输入参数,那么该框架可以不修改,这里只是为了测试其他方法
        Method method2 = cls.getMethod(methodName, String.class);
        method2.invoke(obj,"高圆圆"); // 前方隐隐约约来了一位丁香姑娘:高圆圆
    }
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值