目录

反射概述

使用反射生成并操作对象

创建对象

调用方法

访问成员变量值


反射概述

spring源码中有很多地方涉及到反射的知识,这里还是有必要再复习一下。

Java程序中的许多对象在运行时都会出现两种类型,编译时类型和运行时类型,例如代码Person p = new Student(), 这行代码将会生成一个p变量,该变量的编译时类型为Person,运行时类型为Student,除此之外,还有更极端的情形,程序在运行时接收到外部传入的一个对象,该对象的编译时类型是Object,但程序又需要调用该对象运行时类型的方法。

解决上述问题最好的办法就是利用反射。每个类被加载后,系统就会为该类生成一个对应的对象,通过该Class对象就可以访问到JVM中这个类,在Java程序中获得Class对象通常有三种方式。

  • 使用Class类的forName(String clazzName) 静态方法。该方法需要传入字符串参数,该字符串参数的值是某个类的全限定类名
  • 调用某个类的class属性来获取该类对应的Class对象,例如Person.class将会返回Person类对应的Class对象
  • 调动某个对象的getClass()方法,该方法是java.lang.Object 类中一个方法,所有的Java对象都可以调用该方法,该方法将会返回该对象所属对象所属类的Class对象。

相比之下,第二种方式有如下两种优势

  • 代码更安全,程序在编一阶段就可以检查需要访问的Class对象是否存在
  • 程序性能更好,因为这种方式无需调用方法

Class类提供了大量的实例方法来获取该Class对象所对应类的详细信息,具体可以通过查询Api。Java-Api文档

获取构造方法:

springboot 反射调用指定interface_配置文件

获取Method:

springboot 反射调用指定interface_成员变量_02

获取Field:

springboot 反射调用指定interface_配置文件_03

使用反射生成并操作对象

创建对象

通过反射来生成对象需要先使用Class对象获取指定的Constructor对象,再调用Constructor对象的newInstance()方法来创建该Class对象对应类的实例,通过这种方式可以选择使用指定的构造器来创建实例。

下面实现一个简单的根据配置文件创建对象的操作:(代码地址https://github.com/lizhjian/crazyJava.git    ObjectPoolFactory类)

public class ObjectPoolFactory {

    private Map<String, Object> objectPoool = new HashMap<>();

    private Object createObject(String clazzName) throws Exception {
        //根据类名进行实例化
        Class<?> clazz = Class.forName(clazzName);
        return clazz.getConstructor().newInstance();
    }

    public void initPool(String fileName) throws  Exception{
        try {
            //读取和加载配置文件
            FileInputStream fis = new FileInputStream(fileName);
            Properties props = new Properties();
            props.load(fis);
            for (String name : props.stringPropertyNames()){
                //实例化类对应Spring框架中的beanDefinitionMap
                objectPoool.put(name, createObject(props.getProperty(name)));
            }
        }catch (Exception ex){
            System.out.println("读取异常");
        }
    }

    public Object getObject(String name){
        return objectPoool.get(name);
    }

    public static void main(String[] args) throws  Exception{
        ObjectPoolFactory pf = new ObjectPoolFactory();
        pf.initPool("obj.txt");

        System.out.println(pf.getObject("a"));
        System.out.println(pf.getObject("b"));
    }

}

 配置文件如下:

a=java.util.Date
b=java.lang.String

输出:

springboot 反射调用指定interface_字符串_04

可以看到上面成功的实例化了配置文件中 a 及 b对应的类。

调用方法

当获得某个类对应的Class对象后,就可以通过该class对象的getMethods()方法或者getMethod()方法来获取全部方法或指定的方法。

具体代码如下:(github:    https://github.com/lizhjian/crazyJava.git    ExtendedObjectPoolFactory类)

/**
 * 1.加载配置文件读取key-value
 * 2.将value实例化对应的实体
 * 3.拼setTitle字符串
 * 4.根据setTitle字符串找到对应的setTitle的方法
 * 5.取出a对应的实例 及a%setitle对应的value 调用setTitle.invoke(a, value)
 */
public class ExtendedObjectPoolFactory {

    private Map<String, Object> objectPoool = new HashMap<>();
    private Properties config = new Properties();

    private Object createObject(String clazzName) throws Exception {
        Class<?> clazz = Class.forName(clazzName);
        return clazz.getConstructor().newInstance();
    }

    public void initPool(String fileName) throws  Exception{
        try {
            FileInputStream fis = new FileInputStream(fileName);

            config.load(fis);

            for (String name : config.stringPropertyNames()){
                if(!name.contains("%")){
                    objectPoool.put(name, createObject(config.getProperty(name)));
                }
            }
        }catch (Exception ex){
            System.out.println("读取异常");
        }
    }
    public void  initProperty() throws InvocationTargetException, IllegalAccessException, NoSuchMethodException{
        for(String name : config.stringPropertyNames()){
            if(name.contains("%")){
               //将配置文件中的key按照%分割
                String[] objAndProp = name.split("%");
                Object target = getObject(objAndProp[0]);
                // 获取setXxx方法字符串
                String mtdName = "set"+objAndProp[1].substring(0, 1).toUpperCase()
                        + objAndProp[1].substring(1);

                Class<?> targetClass = target.getClass();
                Method mtd = targetClass.getMethod(mtdName, String.class);
                mtd.invoke(target, config.getProperty(name));
            }
        }
    }


    public Object getObject(String name){
        return objectPoool.get(name);
    }

    public static void main(String[] args) throws  Exception{
        ExtendedObjectPoolFactory pf = new ExtendedObjectPoolFactory();
        pf.initPool("objExt.txt");
        pf.initProperty();

        System.out.println(pf.getObject("a"));
    }

}

配置文件如下:

a=javax.swing.JFrame
b=javax.swing.JLabel
a%title=Test Title

过程分为以下几步

 1.加载配置文件读取key-value
 2.将value实例化对应的实体
 3.拼setTitle字符串
 4.根据setTitle字符串找到对应的setTitle的方法
 5.取出a对应的实例 及a%setitle对应的value 调用setTitle.invoke(a, value)

结果:

springboot 反射调用指定interface_成员变量_05

Spring框架就是通过这种方式将成员变量值以及依赖对象等都放在配置文件中进行管理的,从而实现了很好的解耦,这也是Spring框架IoC的秘密。

访问成员变量值

通过Class对象的getFields()和getField()方法可以获取该类所包含的全部成员变量或指定成员变量,Field提供了两组方法来设置和获取成员变量

getXxx(Object o)和setXxx(Object o, value),当字段为非8种基本类型时,去掉Xxx,代码如下所示(https://github.com/lizhjian/crazyJava.git  FieldTest)

springboot 反射调用指定interface_字符串_06

以上就是反射的全部内容。后一节我们将介绍基于反射的代理,万丈高楼平地起,打好基础才是关键。