一、反射介绍
定义
在运行时期,能够动态访问、检测完整类结构信息甚至修改类本身的一种能力
特点
- 优点
- 灵活性高,因为是动态编译。(静态编译即编译器是确定类型&绑定对象;动态编译即运行期确定类型&绑定对象)
- 动态编译体现了Java的灵活性、多态特性&降低类之间耦合
- 缺点
- 执行效率低,主要通过JVM执行,所以时间成本会 高于 直接执行相同操作
- 反射调用方法是通过invoke方法需要传入Object object和Object… args,
基本数据类型需要拆装箱,产生大量额外的对象和内存开销,频繁出发GC - 异常处理:invoke方法会抛出InvokeTargetException,从而导致无法在原来的try/catch中捕捉到自定义异常,
这里是作者自己遇到的问题,觉得反射导致异常处理比较麻烦
二、具体内容
Class对象
万物皆可对象,所以所有的类都有一个类对象。
java.lang.Class类是反射机制的基础,存放着对应类型对象的运行时信息。
获取类对象的方法如下
- Object.getClass():先new出一个对象object,然后object.getClass()
- T.class:直接通过类(Class)获取,Class.class
- Class.forName():Class.forName(“类全包名”),例如最熟悉的JDBCClass.forName(“com.mysql.jdbc.Driver”)
主要使用类和方法
Class类
Constructor类(构造器)
// a. 获取指定的构造函数 (公共 / 继承)
Constructor<T> getConstructor(Class<?>... parameterTypes)
// b. 获取所有的构造函数(公共 / 继承)
Constructor<?>[] getConstructors();
// c. 获取指定的构造函数 ( 不包括继承)
Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes)
// d. 获取所有的构造函数( 不包括继承)
Constructor<?>[] getDeclaredConstructors();
Fields类(属性或者叫域)
// a. 获取指定的属性(公共 / 继承)
Field getField(String name) ;
// b. 获取所有的属性(公共 / 继承)
Field[] getFields() ;
// c. 获取指定的所有属性 (不包括继承)
Field getDeclaredField(String name) ;
// d. 获取所有的所有属性 (不包括继承)
Field[] getDeclaredFields() ;
// 最终都是获得一个Field类对象
Method类(方法)
// a. 获取指定的方法(公共 / 继承)
Method getMethod(String name, Class<?>... parameterTypes) ;
// b. 获取所有的方法(公共 / 继承)
Method[] getMethods() ;
// c. 获取指定的方法 ( 不包括继承)
Method getDeclaredMethod(String name, Class<?>... parameterTypes) ;
// d. 获取所有的方法( 不包括继承)
Method[] getDeclaredMethods() ;
// 最终都是获得一个Method类对象
父类
getSuperclass();
创建对象
Object newInstance();
获取类名
String getName();
三、应用实例
问题说明
为了重构一个很古老的项目,我们并不想进行大量的重复工作。
很难描述具体项目结构,打个比方,我们现在就是想通过方法名来调用某个对象的某个方法。
我不管你要什么参数,我把我有的所有参数都给你,当然参数名字和类型是一致的。
问题要点
要执行一个方法,首先要确定方法,执行对象和参数列表,重中之重就是参数列表
脑海里设想一下这个大致的流程
- 反射获取到该method,然后去获取参数的参数名称列表
- 把需要的参数值从所有的参数值通过名字一致过滤出来,放进一个Object[]
- 然后menthod.invoke(Object object,Object… args)
- 大家都知道,java被编译后,参数名称是被擦除的,class文件里面是用arg0,arg1代替,
幸好1.8后,可以设置编译器参数,将入参参数名保存,这也是这个思路之所以能存在的根本 - 反射调用方法的时候,参数不是按照名字对应的,而是根据顺序,
所以你一定要保证参数值列表内的参数前后顺序正确,否则就会方法参数不匹配
幸好method.getParameters(),获得的参数名称值就是按着顺序来着的。
这两个幸好下来,自己都感觉自己这个方法啊。真的是非常巧合之下产生。
四、代码示例
public class MethodPool {
//做一个方法池,这样可以避免多次创建方法,减少开支
private static Map<String, Method> methodMap = new HashMap<>();
public static Response invoke(Object sourceObject, CommonParameters inParameters) throws IllegalAccessException, IOException {
//包括父类的域也要统计出来,CommonParameters是一个父类被其他的controller统一参数类继承
//其实这里感觉很神奇,其实应该说是向上转型,子转父,但是依旧保存了子的域,多态很厉害
Field[] fields = getFieldFromClass(inParameters.getClass());
Method method = getMethodByName(sourceObject, inParameters.getMethodName());
if (method == null) {
throw new RuntimeException("no such method");
}
//Get the parameters required for method execution
//获取到方法执行需要的参数值
Parameter[] parameters = method.getParameters();
//set access and to map
//设置域访问限制和映射到map
Map<String, Field> paramFileMap = Stream.of(fields).peek(it -> it.setAccessible(true))
.collect(Collectors.toMap(Field::getName, Function.identity()));
Object[] obejects = new Object[parameters.length];
//get all the parameters you need by filter
//根据参数名称过滤并且获得参数值放入objects
for (int i = 0; i < parameters.length; i++) {
//System.out.println(parameters[i].getName());
Field field = paramFileMap.get(parameters[i].getName());
if (ObjectUtils.isEmpty(fields)) {
throw new RuntimeException("no parameter named " + parameters[i].getName() + "can be found");
}
obejects[i] = field.get(inParameters);
String log = ObjectUtils.isEmpty(obejects[i]) ? "null" :
" type->" + obejects.getClass().getTypeName() +
" value->" + obejects[i];
System.out.println("objects[" + i + "]:"+log);
}
//这里就是异常处理的问题,表示很无奈
//反射调用的异常覆盖了我的原有自定义异常
//导致我try/catch无法捕捉我的自定义异常
Response response = new Response();
Object object = null;
try {
object = method.invoke(sourceObject, obejects);
if(ObjectUtils.isEmpty(object)){
object = "true";
}
response.setData(object);
}catch (InvocationTargetException e){
response.setData("");
response.setCode(-1);
if(e.getTargetException().getClass() == NoRecordsReturned.class){
response.setMsg(((NoRecordsReturned)e.getTargetException()).msg);
response.setData("");
response.setCode(0);
}else if(e.getTargetException().getClass() == ProfileError.class){
response.setMsg(((ProfileError)e.getTargetException()).msg);
}
}
return response;
}
/**
* get the Filed[] of the class and its parent class
*
* @param clazz
* @return
*/
private static Field[] getFieldFromClass(Class clazz) {
Field[] fields = clazz.getDeclaredFields();
Field[] supFileds = clazz.getSuperclass().getDeclaredFields();
return concat(fields, supFileds);
}
/**
* merge two arrays
*
* @param afields
* @param bfields
* @return
*/
private static Field[] concat(Field[] afields, Field[] bfields) {
Field[] fields = new Field[afields.length + bfields.length];
System.arraycopy(afields, 0, fields, 0, afields.length);
System.arraycopy(bfields, 0, fields, afields.length, bfields.length);
return fields;
}
/**
* Find the corresponding method in the object by the method name
*
* @param obj
* @param methodName
* @return
*/
private static Method getMethodByName(Object obj, String methodName) {
Class<?> aClass = obj.getClass();
String key = aClass.getName() + "." + methodName;
//System.out.println(key);
Method method = methodMap.get(key);
if (ObjectUtils.isEmpty(method)) {
Method[] declaredMethods = aClass.getDeclaredMethods();
for (Method m :
declaredMethods) {
if (methodName.equals(m.getName())) {
methodMap.put(key, m);
method = m;
}
}
}
return method;
}
五、结束语
可能读者读起来有些云里雾里,我的确很难讲清楚项目结构,不过我也就自己存个档
也有可能说我写的很简陋,那就太好了,希望大家能指正和指导,让知识运用的更加完美
不过第一次为一个项目写了个工具,我反正感觉还是挺兴奋的,而且的确有减少不少工作量,很有意思。