资讯专栏INFORMATION COLUMN

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

icyfire / 1632人阅读

摘要:不管是什么样的框架,其都涉及到反射。见名知其意,即类对象,其包含了类的所有信息,包括属性方法构造器。为了生成这个类的对象,运行当前程序的将使用到类加载器。这种是等主流框架使用的。

导读

源码地址

在之后的几篇文章,我会讲解我自己的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类。其有继承关系,如下图:

展示信息

如下代码所示,方法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);
}

打印出的信息为:

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

反射获取当前类信息

正如上文提到的运行时类型信息,那么,类型信息在运行时是如何表示的?此时,我们就想到了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 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);
}

叔叔出结果为:

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

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

通过构造器创建对象

    /**
     * Created By zby on 18:26 2019/3/16
     * 普通的方式创建对象
     */
    public static  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()+"
");
        System.out.println("宠物吃的什么:"+dog.getFood()+"
");
        FeedingPet.eatWhatToday(DateTypeEnum.DATE_TYPE_MIDNIGHT_SNACK,dog);
    }

测试结果:

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

总结

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

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

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/73736.html

相关文章

  • 《Java编程思想》笔记14.类型信息

    摘要:接口与类型信息关键字的一种重要目标就是允许程序员隔离构件,进而降低耦合性。如果你编写接口,那么就可以实现这一目标,但是通过类型信息,这种耦合性还是会传播出去接口并非是对解耦的一种无懈可击的保障。 点击进入我的博客 运行时类型信息使得你可以在运行时发现和使用类型信息,主要有两种方式: 传统的RTTI,它假定我们在编译时已经知道了所有的类型; 反射机制,它允许我们在运行时发现和使用类的...

    Hwg 评论0 收藏0
  • 只因数据过滤,方可模拟beanutils框架

    摘要:因而,我从中也知道了,很多公司没有实现数据过滤。因为,那样将会造成数据的冗余。因而,我们这时需要过滤数据对象,如代码所示常用的把图片转成结构如上诉代码的转换,公司使用的是这个框架。而栈是存放数据的一种结构,其采用,即先进后出。 导读 上一篇文章已经详细介绍了框架与RTTI的关系,RTTI与反射之间的关系。尤其是对反射做了详细说明,很多培训机构也将其作为高级教程来讲解。 其实,我工作年限...

    yzzz 评论0 收藏0
  • 【Java学习】JDBC学习(了解CLass等)

    摘要:同时也有一些儿高级的处理,比如批处理更新事务隔离和可滚动结果集等。连接对象表示通信上下文,即,与数据库中的所有的通信是通过此唯一的连接对象。因为是针对类的关系而言,所以一个对象对应多个类的实例化。返回表示查询返回表示其它操作。 JDBC是什么? JDBC是一个Java API,用中文可以通俗的解释为,使用Java语言访问访问数据库的一套接口集合。这是调用者(程序员)和实行者(数据库厂商...

    cjie 评论0 收藏0
  • Thinking in Java学习笔记——Type Information

    摘要:找到字节码并创建一个对象。链接,检验字节码,为字段分配存储空间,解决其对他类的引用。初始化,如果有父类则初始化父类,执行静态初始化器和静态初始化区块直到第一次访问静态成员时初始化才执行。如果成员不是编译时常量由初始化器赋值,也会引起初始化。 有两种形式在运行时获取类型信息: 传统的RTTI 反射 Class对象 运行时的类型信息是通过Class对象表现的,它包含了类的信息。所有...

    liangzai_cool 评论0 收藏0
  • 反射机制原理笔记

    反射机制与原理笔记 声明 文章均为本人技术笔记,转载请注明出处https://segmentfault.com/u/yzwall 反射机制 反射:当程序无法获知对象类型时,在运行期间动态获取类的所有属性和方法,这种动态获取类信息和动态调用对象方法的功能称为反射机制;反射机制实现:Class类与java.lang.reflect类库一起实现机制,java.lang.reflect类库包含Field...

    fobnn 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<