源码地址前端
在以后的几篇文章,我会讲解我本身的hibernate、spring、beanutils框架,但讲解这些框架以前,我须要讲解RTTI和反射。java
工做将近一年了,咱们公司项目所使用的框架是SSH,或者,其余公司使用的是SSM框架。不论是什么样的框架,其都涉及到反射。那么,什么是反射?咱们在生成对象时,事先并不知道生成哪一种类型的对象,只有等到项目运行起来,框架根据咱们的传参,才生成咱们想要的对象。git
好比,咱们从前端调用后端的接口,查询出这我的的全部项目,咱们只要传递这我的的id便可。固然,数据来源于数据库,那么,问题来了,数据是怎么从持久态转化成咱们想要的顺时态的?这里面,就涉及到了反射。可是,一提到反射,咱们势必就提到RTTI,即运行时类型信息(runtime Type Infomation)。spring
/** * 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才有了动态绑定的概念。bootstrap
/** * 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这个特殊对象。见名知其意,即类对象,其包含了类的全部信息,包括属性、方法、构造器。mybatis
咱们都知道,类是程序的一部分,每一个类都有一个Class对象。每当编写而且执行了一个新类,就会产生一个Class对象(更恰当地说,是被保存在一个同名的.class文件中)。为了生成这个类的对象,运行当前程序的jvm将使用到类加载器。jvm首先调用bootstrap类加载器,加载核心文件,jdk的核心文件,好比Object,System等类文件。而后调用plateform加载器,加载一些与文件相关的类,好比压缩文件的类,图片的类等等。最后,才用applicationClassLoader,加载用户自定义的类。app
反射正是利用了Class来建立、修改对象,获取和修改属性的值等等。那么,反射是怎么建立当前类的呢?
/** * 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; }
框架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();这种通常是无参构造器,而且建立对对象后,能够获取其属性,经过属性赋值和方法赋值,如如代码所示:
/** * 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); }
叔叔出结果为:
你会发现一个神奇的地方,就是名字没有输出来,但咱们写了名字呀,为何没有输出来?由于,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> 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。可是,咱们的日常的工做,通常以业务为主。每每都是使用别人封装好的框架,好比spring、hibernate、mybatis、beanutils等框架。因此,咱们不大会关注反射,可是,你若是想要往更高的方向去攀登,仍是要把基础给打捞。不然,基础不稳,爬得越高,摔得越重。
我会之后的篇章中,经过介绍我写的spring、hibernate框架,来说解更好地讲解反射。