反射

反射

环境:
Idea:2019.3.1
系统:windows10 家庭版
Jdk: 8
java中文api

一、 Java代码在计算机中经历的三个阶段

1. Source源代码阶段

我们编写的.java文件,通过javac.exe翻译成.class字节码文件,字节码文件中包含着

  • 成员变量
  • 构造方法
  • 成员方法
    还有比如类名等
2. Class类对象阶段

.class文件通过类加载器ClassLoader,加载为类对象
在java中有一个Class类,java.lang.Class

它是来描述所有.class字节码文件的共同特征和行为的类,不管是什么样的字节码文件都应该或多或少有以下特征

  • 成员变量
    将成员变量封装为Field []对象,因为可能有多个成员变量,所以它是一个数组
  • 构造方法
    将构造方法封装为Constructor []对象,因为可能有多个构造器,所以它是一个数组
  • 成员方法
    将成员方法封装为Method []对象,因为可能有多个成员方法,所以它是一个数组
    这三条即我们所说的反射机制
    
3. Runtime运行时阶段

通过第2阶段的class类的一些行为,比如构造方法,我们可以创建一个实例对象,然后执行相关操作

二、 概念

将类的各个组成部分封装为其他对象,这就是反射机制,反射是框架的灵魂

    好处:
        1.可以在程序运行过程中,操作这些变量
            很明显的一个例子:
                idea在运行中可以知道我们创建的变量中有哪些方法

                这就是因为使用了反射,将String的所有方法封装到了method对象里面,然后遍历method数组就能帮我们显示所有的成员方法
        2.可以解耦,提高程序的可扩展性

三、 获取Class对象的方法

1. java代码处于Source源代码阶段时

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

    Class.forName("全限定类名")
        将字节码文件加载入内存,返回Class对象
        全限定类名,即包名加类名的完整路径,我们加载jdbc的时候,使用过这个
        示例:
            Class.forName("com.jdbc.mysql.Driver");
    ClassLoader.loadClass("全限定类名")

参考资料:ClassLoader.loadClass()与Class.forName()的区别

2. java代码处于Class类对象阶段时

多用于参数的传递

    类名.class
        通过类名的属性class获取
3. java代码处于Runtime运行阶段时

多用于通过对象获取字节码对象

    对象.getClass()
            getClass()方法在Object对象中定义

4. 测试

新建一个Person类

新建一个测试类,测试

package com.cbw;

import com.cbw.domain.Person;

public class test {
    public static void main(String[] args) throws Exception {
        //1.第一种方式创建Class类,Class.forName("全限定类名")
            Class<?> aClass1 = Class.forName("com.cbw.domain.Person");
            System.out.println("第一种方式创建:" + aClass1);

        //2.第二种方式创建,类名.class获取
           Class<Person> aClass2 = Person.class;
            System.out.println("第二种方式创建:" + aClass2);

        //3.第三种方式创建,对象.getClass()
            Person person = new Person();
            Class<? extends Person> aClass3 = person.getClass();
            System.out.println("第三种方式创建:" + aClass3);


        //4.比较这几个引用的地址是否相同
        System.out.println(aClass1 == aClass2);
        System.out.println(aClass1 == aClass3);
    }
}

运行结果

这里有个细节,就是比较了这三个Class对象的引用地址,发现是一样的,所以是同一个Class对象
我们得出一个结论

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

四、 Class对象的方法

1. 获取方法
1.获取成员变量
       * Field [] getFields():获取所有public修饰的成员变量
       * Field getField(String name):获取指定名称的public修饰的成员变量
       * Field [] getDeclaredFields():获取所有的成员变量, 不考虑修饰符
       * Field getDeclaredField(String name):获取指定名称的成员变量,不考虑修饰符

        Field对象方法:
            1.设置值
                void set(Object obj, Object value)
            2.获取值
                get(Object obj)
            示例:
                //1.获取Class对象
                     Class<?> aClass = Class.forName("com.cbw.domain.Person");
                //2.获取成员变量对象 (使用的这个方法获取的成员变量必须是public修饰的)
                    Field name = aClass.getField("name");
                //3.设置成员变量的值
                    //3.1 创建person对象
                        Person p = new Person();
                    //3.2 设置p的成员变量a的值
                        name.set(p,"张三")
                //4.获取成员变量的值
                    Object value = name.get(p);
        注意:
            如果使用getDeclaredField这种方法,无视修饰符获取的时候,我们需要加入一行代码
            name.setAccessible(true);//设置忽略访问权限修饰符的安全检查,这也叫作暴力反射,不这样设置会报错


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

        Constructor对象方法:
            1.构造实例
                T newInstance(Object...initargs) //这里面就传获取构造器的时候获取的哪个构造器对应的参数
            示例:
                //1.获取Class对象
                    Class<?> aClass = Class.forName("com.cbw.domain.Person");        
                //2.获取构造器对象
                    Constructor constructor = aClass.getConstructor(String.class, int.class);//这里我们使用的是有参构造方法,参数是什么,就要传什么类型的字节码文件
                //3.使用构造器创建实例
                    Object person = constructor.newInstance("张三",15);
                    //也可以使用空构造方法创建实例
                        Constructor constructor = aClass.getConstructor();
                        Object person = constructor.newInstance();
                    //硬要使用空构造器的话,还有一种方法更简单,使用Class对象的方法,不过,在我写这篇文章的时候,这个方法已经过期了,但是也能够使用
                        Object person = aClass.newInstance();
                //补充:
                    获取构造方法的这些方法和获取成员变量的是类似的,前两个只能获取public类型的构造方法,后两个可以无视修饰符获取构造方法,但是别忘了执行:
                        constructor.setAccessible(true);


3.获取成员方法
       * Method [] getMethods()
       * Method getMethod(String name,<?>... parameterTypes)
       * Method [] getDeclaredMethods()
       * Method getDeclaredMethod(String name,<?>... parameterTypes)

        Method对象方法
            1.执行方法
                Object invoke(Object obj,Object......args);//第一个参数是该方法所属类的示例对象,后面的参数是该方法的参数
            2.获取方法名
                String getName();
            示例:
                //1.获取Class对象
                    Class<?> aClass = Class.forName("com.cbw.domain.Person");        
                //2.获取成员方法对象
                    //aClass.getMethod(方法名,参数列表的各个参数的字节码文件);无参方法可以只传方法名
                    Method eat_method1 = aClass.getMethod("eat");
                    Method eat_method2 = aClass.getMethod("eat",String.class);
                //3.执行
                    Person p = new Person();
                    eat_method1.invoke(p);//p吃东西
                    eat_method2.invoke(p,"水果");//p吃水果
            //补充:
                1.还是一样,没带declared则只能获取public修饰的成员方法,带了的方法可以获取任意修饰符修饰的成员方法,但是别忘了
                    method.setAccessible(true);
                2.获取多个方法的方法会将自己的,还有继承的类的方法都获取到,Object类的都能获取到


4.获取类名
       * String getName()
       示例:
           //1.获取Class对象
               Class<?> aClass = Class.forName("com.cbw.domain.Person");        
           //2.获取类名
               //这个类名是全限定类名,从包名到类都有
                   String aClassName = aClass.getName();

五、 模拟写一个简易框架

Person类

package com.cbw.domain;
/**
 * 用户类
 */
public class Person {
        private String name;
        private int age;

    public Person() {
    }

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

    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 "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

    public void eat(){
        System.out.println("吃烧烤");
    }
}

ReflectFactory类

package com.cbw.domain;

import java.lang.reflect.InvocationTargetException;

/**
 * 反射工厂类,可以创建任意实例,执行任意方法
 */
public class ReflectFactory {

    /**
     * 获取一个实例
     * @param classpath 
     * @return
     */
    public static Object getClassObject(String classpath) {
        try {
            //获取Class类
            Class<?> aClass = Class.forName(classpath);
            //返回一个实例对象
            return aClass.newInstance();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 执行obj对象的methodName方法
     * @param obj 
     * @param methodName
     */
    public static void methodInvoke(Object obj,String methodName){
        try {
            //执行方法
            obj.getClass().getMethod(methodName).invoke(obj);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

propertie.properties配置文件

className=com.cbw.domain.Person
methodName=eat

Test测试类

package com.cbw;

import com.cbw.domain.ReflectFactory;

import java.io.InputStream;
import java.util.Properties;

public class test {
    public static void main(String[] args) throws Exception {
        //1.加载配置文件
            Properties properties = new Properties();
                //1.1获取类加载器
                    ClassLoader classLoader = test.class.getClassLoader();
                //1.2获取配置文件的字节流
                    InputStream resourceAsStream = classLoader.getResourceAsStream("propertie.properties");
                //1.3加载字节流
                    properties.load(resourceAsStream);
        //2.获取配置文件下定义的数据
        String className = properties.getProperty("className");
        String methodName = properties.getProperty("methodName");
        //3.创建实例
        Object classObject = ReflectFactory.getClassObject(className);
        //4.执行方法
        ReflectFactory.methodInvoke(classObject,methodName);

    }
}

运行结果

我们可以新建一个Student类

package com.cbw.domain;

public class Student {
    public void study(){
        System.out.println("学生学习");
    }
}

然后修改配置文件

className=com.cbw.domain.Student
methodName=study

再次执行test方法

可以发现仅仅修改了配置文件,执行的对象和方法都能改变,这也就是利用反射实现了解耦的思想


  转载请注明: 我所希冀的未来 反射

 上一篇
集合 集合
集合 环境:Idea:2019.3.1系统:windows10 家庭版Jdk: 8java中文api connection接口 jdk中的详细描述 概述: 是集合类的顶级接口 位置: java.util.Coll
2020-04-22
下一篇 
spring框架的概述以及spring中基于xml的IOC配置 spring框架的概述以及spring中基于xml的IOC配置
一.spring框架的概述以及spring中基于xml的IOC配置 环境:Idea:2019.3.1系统:windows10 家庭版Jdk: 8spring:5.0.3 releasespring文档项目代码 1.spring的概述spr
2020-04-10
  目录