文章目录
0.反射个人理解
下图是生活中比较常见的镜面反射,Java中的反射,也是类似于这种情况, 镜面反射是相对于镜面而形成的,主要表现为实体和虚像。
而Java中,则是相对于 .class文件(字节码文件) 而形成的,主要表面为 Class类和 Class**<?>**
1.反射定义
1.1 什么是反射?
Java反射就是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;并且能改变它的属性。
1.2 反射能做什么?
我们知道反射机制允许程序在运行时取得任何一个已知名称的class的内部信息,包括其modifiers(修饰符),fields(属性),methods(方法) 等,并可于运行时改变fields内容或调用methods。那么我们便可以更灵活的编写代码,代码可以在运行时装配,无需在组件之间进行源代码链接,降低代码的耦合度;还有动态代理的实现等等;但是需要注意的是反射使用不当会造成很高的资源消耗!
1.3 反射有哪些API
1.3.1 得到 Class 的三种方式
//1、通过对象调用 getClass() 方法来获取, 比如你传过来一个 Object通常应用在:
// 类型的对象,而我不知道你具体是什么类,用这种方法
Person p1 = new Person();
…
Class c1 = p1.getClass();
//2、直接通过 类名.class 的方式得到,该方法最为安全可靠,程序性能更高
// 这说明任何一个类都有一个隐含的静态成员变量 class
Class c2 = Person.class;
//3、通过 Class 对象的 forName() 静态方法来获取,用的最多,
// 但可能抛出 ClassNotFoundException 异常
Class c3 = Class.forName("com.ys.reflex.Person");
需要注意的是:一个类在 JVM 中只会有一个 Class 实例,即我们对上面获取的 c1,c2,c3进行 equals 比较,发现都是true
1.3.2 通过 Class 类获取成员变量、成员方法、接口、超类、构造方法等
查阅 API 可以看到 Class 有很多方法:
getName():获得类的完整名字。
getFields():获得类的public类型的属性。
getDeclaredFields():获得类的所有属性。包括private 声明的和继承类。
getMethods():获得类的public类型的方法。
getDeclaredMethods():获得类的所有方法。包括private 声明的和继承类。
getMethod(String name, Class[] parameterTypes):获得类的特定方法,name参数指定方法的名字,parameterTypes 参数指定方法的参数类型。
getConstructors():获得类的public类型的构造方法。
getConstructor(Class[] parameterTypes):获得类的特定构造方法,parameterTypes 参数指定构造方法的参数类型。
newInstance():通过类的不带参数的构造方法创建这个类的一个对象。
1.4实际应用
1.4.1什么场景会用到反射技术?
用到反射的场景如下:
1. 逆向代码 ,例如反编译
2. 各大框架,如spring,
3. 单纯的反射机制应用框架
4. 动态生成类框架 , 例如Gson
1.4.2用这种东西能给我们带来什么好处?
运行期类型的判断,动态类加载,动态代理使用反射。
1.4.3这种方式有什么弊端?
缺点:它的缺点是对性能有影响。使用反射基本上是一种解释操作,我们可以告诉 JVM,我们希望做什么并且它满足我们的要求。这类操作总是慢于只直接执行相同的操作。
总结: 优点和缺点都是在比较之下,才体现出来的。因此说反射动态类加载,指的是相对于 我们自定义的 类 源代码而言的,缺点执行速度较慢,指的是相对于,直接执行源代码而言的。
2.内省
2.1 什么是内省?
内省(Introspector)是Java语言对Bean类属性、事件的一种缺省处理方法。例如类A中有属性name,那我们可以通过getName,setName来得到其值或者设置新的值。通过getName/setName来访问name属性,这就是默认的规则。
Java中提供了一套API用来访问某个属性的getter/setter方法,通过这些API可以使你不需要了解这个规则(但你最好还是要搞清楚),这些API存放于包java.beans中。
一般的做法是通过类Introspector来获取某个对象的BeanInfo信息,然后通过BeanInfo来获取属性的描述器(PropertyDescriptor),通过这个属性描述器就可以获取某个属性对应的getter/setter方法,然后我们就可以通过反射机制来调用这些方法。
2.2内省的解释
内省在wiki上的解释:
在计算机科学中,内省是指计算机程序在运行时(Run time)检查对象(Object)类型的一种能力,通常也可以称作运行时类型检查。 不应该将内省和反射混淆。
相对于内省,反射更进一步,是指计算机程序在运行时(Run time)可以访问、检测和修改它本身状态或行为的一种能力。
2.3内省的核心内容
BeanInfo (java.beans.BeanInfo包中的) 实例详情
Introspector(java.beans.Introspector包中的)内省
PropertyDescriptor(java.beans.PropertyDescriptor包中的)属性描述
通过类 Introspector 来获取某个对象的 BeanInfo 信息,然后通过 BeanInfo 来获取属性的描述器( PropertyDescriptor ),通过这个属性描述器就可以获取某个属性对应的 getter/setter 方法(getReadMethod()/getWriteMethod()),然后我们就可以通过反射机制来调用这些方法。
3.区分:内省和反射区别
区别:反射是在运行状态把Java类中的各种成分映射成相应的Java类,可以动态的获取所有的属性以及动态调用任意一个方法,强调的是运行状态。
内省机制是通过反射来实现的,BeanInfo用来暴露一个bean的属性、方法和事件,以后我们就可以操纵该JavaBean的属性。
总结: 内省机制是通过反射实现的(jdk封装好的工具而已)只具备检查的能力,而反射则在内省之上具有修改的能力。在实际应用过程中二者要相互结合方能发挥真正的智能化以及高度可扩展性。
4.反射内省在处理数据同步的应用
场景描述:实际问题场景,当一个对象拥有100+属性的时候,这时候,想要对比属性值是否相同?如何处理?遇到需求变更,限定某些字段不需要比较的时候如何处理?
我这里是这样的思考的,程序代码要尽可能的做到业务部分和逻辑部分分离,以这一思路为出发点,我想到了反射,动态处理类的属性,从而将实际问题分离成两部分:
第一部分:(业务部分) 计算,比较属性值是否发生变化 ,进而对数据进行 增删改的 操作。
第二部分:(逻辑部分) 逻辑部分需要处理的就是相同属性名的属性值是否相同。(动态获取类的属性进行处理)
4.1具体代码实现
将逻辑部分封装成比较工具,如下:
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class CompareFieldsUtil {
/**
* 比较两个对象的相同属性名的属性值是否相同(不分区对象类型,单纯的比较属性值)
* @param obj1 待比较对象1
* @param obj2 待比较对象2
* @param ignoreArr 忽略比较属性
* @return boolean 排除忽略属性后,两个对象的属性值是否相同(true 相同,false不同)
*/
public static boolean compareFields(Object obj1, Object obj2, String[] ignoreArr) {
boolean returnFlag = false;
String name = null;
try{
List<String> ignoreList = null;
if(ignoreArr != null && ignoreArr.length > 0){
// array转化为list
ignoreList = Arrays.asList(ignoreArr);
}
Class<? extends Object> class1 = obj1.getClass();
Class<? extends Object> class2 = obj2.getClass();
PropertyDescriptor[] class1PropertyDescriptors = Introspector.getBeanInfo(class1,Object.class).getPropertyDescriptors();
PropertyDescriptor[] class2PropertyDescriptors = Introspector.getBeanInfo(class2,Object.class).getPropertyDescriptors();
Map<String,PropertyDescriptor> class1PropertyDescriptorsMap = new HashMap<String,PropertyDescriptor>();
for (PropertyDescriptor propertyDescriptor : class1PropertyDescriptors) {
class1PropertyDescriptorsMap.put(propertyDescriptor.getName(), propertyDescriptor);
}
for (PropertyDescriptor propertyDescriptor : class2PropertyDescriptors) {
// 属性名
name = propertyDescriptor.getName();
// 如果当前属性选择忽略比较,跳到下一次循环
if(ignoreList != null && ignoreList.contains(name)){
continue;
}
Method class1readMethod = class1PropertyDescriptorsMap.get(name).getReadMethod();
Method class2readMethod = propertyDescriptor.getReadMethod();
Object o1 = class1readMethod.invoke(obj1);
Object o2 = class2readMethod.invoke(obj2);
if (o1 instanceof String) {
if (String.valueOf(o1).equals("")) {
o1 = null;
}
}
if (o2 instanceof String) {
if (String.valueOf(o2).equals("")) {
o2 = null;
}
}
if (o1 instanceof Date) {
o1 = ((Date) o1).getTime();
o1 = String.valueOf(o1);
}
if (o2 instanceof Date) {
o2 = ((Date) o2).getTime();
o2 = String.valueOf(o2);
}
if(o1 == null && o2 == null){
continue;
}else if(o1 == null && o2 != null){
returnFlag = true;
continue;
}else if (o1 != null && o2 == null) {
returnFlag = true;
continue;
}
// 比较两个值是否相等
if (o1.equals(o2)) {
}else {
returnFlag = true;
}
}
}catch(Exception e){
e.printStackTrace();
}
return returnFlag;
}
}
上述方法是使用jdk 封装的,并没有使用第三方jar,可以直接 C V,在处理数据同步时,用这样的一个工具可以具有如下几种便利性:
- 不需要在乎比较数据的类型,只要属性名相同即可,非常适合敏捷开发的需要。
- 可以通过 ignoreArr(String[])对不需要过滤属性进行排除,这里需要注意的是多个不需要过滤的属性需要使用 “,” 进行分割。
- 在不需要写很长,很长的业务比较代码,手动判断值是否相等。
当然上述代码并不完整,目前只支持了几种数据类型的比较,如时间类型,字符串类型,可以根据自己的需要,和责任链模式进行一波拓展,这里对自定义类型数据的支持没有想到太好的处理办法。
缺点:
-
支持数据类型的拓展是个问题,需要维护
-
对自定数据类型的支持是个问题,需要做定制化开发