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

栏目: Hibernate · 发布时间: 5年前

内容简介:在之后的几篇文章,我会讲解我自己的hibernate、spring、beanutils框架,但讲解这些框架之前,我需要讲解RTTI和反射。工作将近一年了,我们公司项目所使用的框架是SSH,或者,其他公司使用的是SSM框架。不管是什么样的框架,其都涉及到反射。那么,什么是反射?我们在生成对象时,事先并不知道生成哪种类型的对象,只有等到项目运行起来,框架根据我们的传参,才生成我们想要的对象。比如,我们从前端调用后端的接口,查询出这个人的所有项目,我们只要传递这个人的id即可。当然,数据来源于数据库,那么,问题来

导读

在之后的几篇文章,我会讲解我自己的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框架,来讲解更好地讲解反射。


以上所述就是小编给大家介绍的《框架与RTTI的关系,RTTI与反射之间的关系》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Computer Age Statistical Inference

Computer Age Statistical Inference

Bradley Efron、Trevor Hastie / Cambridge University Press / 2016-7-21 / USD 74.99

The twenty-first century has seen a breathtaking expansion of statistical methodology, both in scope and in influence. 'Big data', 'data science', and 'machine learning' have become familiar terms in ......一起来看看 《Computer Age Statistical Inference》 这本书的介绍吧!

MD5 加密
MD5 加密

MD5 加密工具

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器