反射与内省
一、反射 (Reflect)
在框架开发中,都是基于配置文件开发的,在配置文件中配置了类,可以通过读取配置文件中的类名,然后通过反射得到类中的所有内容,或是让类中的某个方法来执行。
也就是说,反射是在运行时获取一个类的所有信息,可以获取到 .class 的任何定义的信息(包括成员 变量,成员方法,构造器等)可以操纵类的字段、方法、构造器等部分。
1.1、反射的原理
我们将通过下图对反射的原理进行说明:
-
得到Class类
- 把 java 文件保存到本地硬盘,得到 .java。
- 编译 java 文件,得到 .class 文件。
- JVM 把 .class 文件加载到内存中,class 文件在内存中使用 Class 类表示。
-
通过 class 文件得到 Class 类,可以通过以下 3 种方式获得 Class 类
- 通过成员变量获得: 类名 .class
- 通过具体对象获得: 对象 .getClass()
- 通过 Class 的静态方法获取: Class.forName("classFilePath")
-
通过 Class 类获取 class 文件中的内容,包括:成员变量,构造方法,普通方法,它们都可以用相应的类表示:
- 成员方法:Field
- 构造方法:Constructor
- 普通方法:Method
1.2、使用反射操作属性
1 public void test3() { 2 try { 3 Class c2 = Class.forName("cn.itcast.test09.Person"); // 得到Class类 4 Person p11 = (Person) c2.newInstance(); // 得到Person类的对象,返回 5 Field[] fields = c2.getDeclaredFields(); // 得到所有的属性,返回一个Field数组 6 Field f1 = c2.getDeclaredField("name"); // 得到属性,参数是属性的名称 7 8 // 如果操作的是私有的属性,不让操作,可以通过setAccessible(true)操作私有属性 9 f1.setAccessible(true); 10 f1.set(p11, "wangwu"); // 设置name值,相当于p.name = "wangwu"; 11 System.out.println(f1.get(p11)); // 相当于 p.name 12 }catch(Exception e) { 13 e.printStackTrace(); 14 } 15 }
1.3、用反射操作构造函数
- 无参数构造方法
通过 Class 对象的 newInstance() 方法创建。
1 public void test1() throws Exception { 2 Class c3 = Class.forName("cn.itcast.test09.Person"); 3 // 无参数的构造方法就是直接使用newInstance()方法 4 Person p = (Person) c3.newInstance(); 5 p.setName("zhangsan"); 6 System.out.println(p.getName()); 7 }
- 有参数构造方法
不能再通过 Class 对象的 newInstance() 方法创建了,要先得到要调用的构造函数的 Consturctor 对象,然后通过 Constructor 对象的 newInstance() 方法创建。
1 public void test2() throws Exception { 2 Class c1 = Class.forName("cn.itcast.test09.Person"); 3 4 // 获取所有的构造方法 5 Constructor[] css = c1.getConstructors(); 6 // 获取特定的构造方法:传递是有参数的构造方法里面参数类型,类型使用class的形式传递 7 Constructor cs = c1.getConstructor(String.class, String.class); 8 9 // 通过有参数的构造方法创建Person实例,而不是通过Class的对象 10 Person p1 = (Person) cs.newInstance("lisi","100"); 11 System.out.println(p1.getId()+" "+p1.getName()); 12 }
1.4、使用反射操作方法
- 实例方法
1 public void test4() throws Exception { 2 Class c4 = Class.forName("cn.itcast.test09.Person"); 3 Person p4 = (Person) c4.newInstance(); 4 5 // 得到所有的普通方法 6 Method[] mds = c4.getDeclaredMethods(); 7 // 得到特定的普通方法,传递两个参数:第一个参数:方法名称;第二个参数:方法里面参数的类型 8 Method m1 = c4.getDeclaredMethod("setName", String.class); 9 10 // 使用invoke执行方法,传递两个参数:第一个参数:person实例;第二个参数:设置的值 11 // 在这里要传入person对象的原因是:我们需要知道到底是哪一个对象的setName方法执行了 12 // 如果要操作的是私有的方法 ,需要 m1.setAccessible(true); 13 m1.invoke(p4, "niuqi"); 14 System.out.println(p4.getName()); 15 }
- 静态方法
静态方法调用方式是 类名.方法名,不需要类的实例,
所以使用反射操作静态方式时候,也是不需要实例的,
在 invoke 方法的第一个参数传入 null 即可: m1.invoke(null, "niuqi");
二、内省 (Introspector)
内省是基于反射实现的,主要用于操作 JavaBean,相比反射使用起来要方便一些。可以获取 bean 的 getter/setter 方法,也就是说,只要 JavaBean 有 getXxx() 方法,不管这个 Bean 有没有 Xxx 属性,使用内省我们都认为它有。
为了更好的理解,我们先来介绍一下 JavaBean 的规范。
2.1、JavaBean 的规范
- 必须要有一个默认构造器。
- 提供 get/set 方法,如果只有 get 方法,那么这个属性是只读属性。
- 属性:有 get/set 方法的成员,还可以没有成员,只有 get/set 方法。属性名称由 get/set 方法来决定,而不是成员名称。
- 方法名称满足一定的规范,它就是属性!boolean 类型的属性,它的读方法可以是 is 开头,也可以是 get 开头。
2.2、Introspector 操作 JavaBean
操作示例:
1 BeanInfo beanInfo = Introspector.getBeanInfo(User.class); 2 PropertyDescriptor[] pds = beanInfo.getPropertyDescriptors();
每个 PropertyDescriptor 对象对应一个 JavaBean 属性:
- String getName():获取 JavaBean 属性名称;
- Method getReadMethod():获取属性的读方法;
- Method getWriteMethod():获取属性的写方法。
然后再调用invoke(params...)就可以操作 JavaBean 了。
2.3、使用 BeanUtils
我们并不需要真的自己通过 Introspector 来获取 JavaBean 的实例,
我们可以通过现成的工具:
BeanUtils 来操作 JavaBean。
想要使用 BeanUtils,
我们需要先导入 commons-beanutils.jar 包,
然后,我们便可以通过 BeanUtils 来操纵 JavaBean 了。
2.3.1、设置 JavaBean 属性:BeanUtils.setProperty(bean, propertyName, propertyValue)
1 User user = new User(); 2 BeanUtils.setProperty(user, "username", "admin"); 3 BeanUtils.setProperty(user, "password", "admin123");
2.3.2、获取 JavaBean 属性:BeanUtils.getProperty(bean, propertyName)
1 User user = new User("admin", "admin123"); 2 String username = BeanUtils.getProperty(user, "username"); 3 String password = BeanUtils.getProperty(user, "password");
2.3.3、封装 Map 数据到 JavaBean 对象中:BeanUtils.populate(bean, map)
1 Map<String, String> map = new HashMap<String,String>(); 2 map.put("username", "admin"); 3 map.put("password", "admin123"); 4 User user = new User(); 5 BeanUtils.populate(user, map);
三、反射和内省的区别
反射就像给类照镜子,这个的所有信息会毫无保留的反射到镜子中,将这个类的所有信息照出来,能照出来就是有,照不出来就是没有,得到的东西都是客观真实存在的。
而内省的目的是找出 bean 的 getter 和 setter 以便操作这个 bean,
所以只要看到有 getter 或者 setter 就认为这个类有那么一个字段,
比如看到 getName() 内省就会认为这个类中有 name 字段,
但事实上并不一定会有 name。
来源:https://github.com/TangBean/OnlineExecutor