转载

框架与RTTI的关系,RTTI与反射之间的关系

导读

在之后的几篇文章,我会讲解我自己的hibernate、spring、beanutils框架,但讲解这些框架之前,我需要讲解RTTI和反射。

工作将近一年了,我们公司项目所使用的框架是SSH,或者,其他公司使用的是SSM框架。不管是什么样的框架,其都涉及到反射。那么,什么是反射?我们在生成对象时,事先并不知道生成哪种类型的对象,只有等到项目运行起来,框架根据我们的传参,才生成我们想要的对象。

比如,我们从前端调用后端的接口,查询出这个人的所有项目,我们只要传递这个人的id即可。当然,数据来源于数据库,那么,问题来了,数据是怎么从持久态转化成我们想要的顺时态的?这里面,就涉及到了反射。但是,一提到反射,我们势必就提到RTTI,即运行时类型信息(runtime Type Infomation)。

RTTI

  • po类
/**
 * Created By zby on 16:53 2019/3/16
 */
@AllArgsConstructor
@NoArgsConstructor
public class Pet {

    private String name;

    private String food;

    public void setName(String name) {
        this.name = name;
    }

    public void setFood(String food) {
        this.food = food;
    }

    public String getName() {
        return name;
    }

    public String getFood() {
        return food;
    }
}

/**
 * Created By zby on 17:03 2019/3/16
 */
public class Cat extends Pet{

    @Override
    public void setFood(String food) {
        super.setFood(food);
    }
}

/**
 * Created By zby on 17:04 2019/3/16
 */
public class Garfield extends Cat{

    @Override
    public void setFood(String food) {
        super.setFood(food);
    }
}


/**
 * Created By zby on 17:01 2019/3/16
 */
public class Dog extends Pet{

    @Override
    public void setFood(String food) {
        super.setFood(food);
    }
}

以上是用来说明的persistent object类,也就是,我们在进行pojo常用的javabean类。其有继承关系,如下图:

框架与RTTI的关系,RTTI与反射之间的关系

  • 展示信息

如一下代码所示,方法eatWhatToday有两个参数,这两个参数一个是接口类,一个是父类,也就是说,我们并不知道打印出的是什么信息。只有根据接口的实现类来和父类的子类,来确认打印出的信息。 这就是我们输的运行时类型信息,正因为有了RTTI,java才有了动态绑定的概念。

/**
 * Created By zby on 17:05 2019/3/16
 */
public class FeedingPet {

    /**
     * Created By zby on 17:05 2019/3/16
     * 某种动物今天吃的是什么
     *
     * @param baseEnum 枚举类型 这里表示的时间
     * @param pet      宠物
     */
    public static void eatWhatToday(BaseEnum baseEnum, Pet pet) {
        System.out.println( pet.getName() + "今天" + baseEnum.getTitle() + "吃的" + pet.getFood());
    }
    
}
  • 测试类
@Test
public void testPet(){
    Dog dog=new Dog();
    dog.setName("宠物狗京巴");
    dog.setFood(FoodTypeEnum.FOOD_TYPE_BONE.getTitle());

    FeedingPet.eatWhatToday(DateTypeEnum.DATE_TYPE_MORNING,dog);

    Garfield garfield=new Garfield();
    garfield.setName("宠物猫加菲猫");
    garfield.setFood(FoodTypeEnum.FOOD_TYPE_CURRY.getTitle());
    FeedingPet.eatWhatToday(DateTypeEnum.DATE_TYPE_MIDNIGHT_SNACK,garfield);
}

打印出的信息为:

框架与RTTI的关系,RTTI与反射之间的关系

那么,这和反射有什么关系呢?

反射获取当前类信息

正如上文提到的运行时类型信息,那么,类型信息在运行时是如何表示的?此时,我们就想到了Class这个特殊对象。见名知其意,即类对象,其包含了类的所有信息,包括属性、方法、构造器。

我们都知道,类是程序的一部分,每个类都有一个Class对象。每当编写并且执行了一个新类,就会产生一个Class对象(更恰当地说,是被保存在一个同名的 .class 文件中)。为了生成这个类的对象,运行当前程序的jvm将使用到类加载器。jvm首先调用bootstrap类加载器,加载核心文件,jdk的核心文件,比如Object,System等类文件。然后调用plateform加载器,加载一些与文件相关的类,比如压缩文件的类,图片的类等等。最后,才用applicationClassLoader,加载用户自定义的类。

加载当前类信息

反射正式利用了Class来创建、修改对象,获取和修改属性的值等等。那么,反射是怎么创建当前类的呢?

  • 第一种,可以使用当前上下文的类路径来创建对象,如我们记载jdbc类驱动的时候,如以下代码:
/**
 * Created By zby on 18:07 2019/3/16
 * 通过上下文的类路径来加载信息
 */
public static Class byClassPath(String classPath) {
    if (StringUtils.isBlank(classPath)) {
        throw new RuntimeException("类路径不能为空");
    }
    classPath = classPath.replace(" ", "");
    try {
        return Class.forName(classPath);
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    }
    return null;
}
  • 第二种,通过类字面常量,这种做法非常简单,而且更安全。因为,他在编译时就会受到检查,我们不需要将其置于try catch的代码快中,而且,它根除了对forName的方法调用,所以,更高效。这种是spring、hibernate等主流框架使用的。

框架hibernate的内部使用类字面常量去创建对象后,底层通过jdbc获取数据表的字段值,根据数据表的字段与当前类的属性进行一一匹配,将字段值填充到当前对象中。匹配不成功,就会报出相应的错误。

类字面常量获取对象信息,如代码所示。下文,也是通过类字面常量创建对象。

/**
 * Created By zby on 18:16 2019/3/16
 * 通过类字面常量加载当前类的信息
 */
public static void byClassConstant() {
    System.out.println(Dog.class);
}
  • 第三种,是通过对象来创建当前类,这种会在框架内部使用。
/**
* Created By zby on 18:17 2019/3/16
* 通过类对象加载当前类的信息
*/
public static Class byCurrentObject(Object object) {
    return object.getClass();
}

反射创建当前类对象

我们创建当前对象,一般有两种方式,一种是通过clazz.newInstance();这种一般是无参构造器,并且创建对对象后,可以获取其属性,通过属性赋值和方法赋值,如如代码所示:

  • 第一种,通过clazz.newInstance()创建对象
/**
 * Created By zby on 18:26 2019/3/16
 * 普通的方式创建对象
 */
public static <T> T byCommonGeneric(Class clazz, String name, BaseEnum baseEnum) {
    if (null == clazz) {
        return null;
    }
    try {
        T t = (T) clazz.newInstance();
        
        //通过属性赋值,getField获取公有属性,获取私有属性
        Field field = clazz.getDeclaredField("name");
        //跳过检查,否则,我们没办法操作私有属性
        field.setAccessible(true);
        field.set(t, name);
        
        //通过方法赋值
        Method method1 = clazz.getDeclaredMethod("setFood", String.class);
        method1.setAccessible(true);
        method1.invoke(t, baseEnum.getTitle());

        return t;
    } catch (Exception e) {
        e.printStackTrace();
    }
    return null;
}

测试:
 @Test
public void testCommonGeneric() {
    Dog dog= GenericCurrentObject.byCommonGeneric(Dog.class,
            "宠物狗哈士奇", 
            FoodTypeEnum.FOOD_TYPE_BONE);
    FeedingPet.eatWhatToday(DateTypeEnum.DATE_TYPE_NOON,dog);
}

叔叔出结果为:

框架与RTTI的关系,RTTI与反射之间的关系

你会发现一个神奇的地方,就是名字没有输出来,但我们写了名字呀,为什么没有输出来?因为,dog是继承了父类Pet,当我们在创建子类对象时,首先,会加载父类未加载的构造器、静态代码块、静态属性、静态方法等等。但是,Dog在这里是以无参构造器加载的,当然,同时也通过无参构造器的实例化了父类。我们在给dog对象的name赋值时,、并没有给父类对象的name赋值,所以,dog的name是没有值的。父类引用指向子类对象,就是这个意思。

如果我们把Dog类中的 @Override public void setFood(String food) {super.setFood(food); } super.setFood(food); 方法去掉,属性food也是没有值的。如图所示:

框架与RTTI的关系,RTTI与反射之间的关系

  • 通过构造器创建对象
/**
     * Created By zby on 18:26 2019/3/16
     * 普通的方式创建对象
     */
    public static <T> T byConstruct(Class clazz, String name, BaseEnum baseEnum) {
        if (null == clazz) {
            return null;
        }
//        参数类型,
        Class paramType[] = {String.class, String.class};
        try {
//          一般情况下,构造器不止一个,我们根据构器的参数类型,来使用构造器创建对象
            Constructor constructor = clazz.getConstructor(paramType);
//            给构造器赋值,赋值个数和构造器的形参个数一样,否则,会报错
            return (T) constructor.newInstance(name, baseEnum.getTitle());
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
    
    测试:
    
   @Test
    public void testConstruct() {
        Dog dog= GenericCurrentObject.byConstruct(Dog.class,
                "宠物狗哈士奇",
                FoodTypeEnum.FOOD_TYPE_BONE);
        System.out.println("输出宠物的名字:"+dog.getName()+"/n");
        System.out.println("宠物吃的什么:"+dog.getFood()+"/n");
        FeedingPet.eatWhatToday(DateTypeEnum.DATE_TYPE_MIDNIGHT_SNACK,dog);
    }

测试结果:

框架与RTTI的关系,RTTI与反射之间的关系

这是通过构造器创建的对象。但是注意的是,形参类型和和参数值的位数一定要相等,否则,就会报出错误的。

总结

为什么写这篇文章,前面也说了,很多框架都用到了反射和RTTI。但是,我们的平常的工作,一般以业务为主。往往都是使用别人封装好的框架,比如spring、hibernate、mybatis、beanutils等框架。所以,我们不大会关注反射,但是,你如果想要往更高的方向去攀登,还是要把基础给打捞。否则,基础不稳,爬得越高,摔得越重。

我会以后的篇章中,通过介绍我写的spring、hibernate框架,来讲解更好地讲解反射。

原文  https://segmentfault.com/a/1190000018530551
正文到此结束
Loading...