在java项目中我们会经常用它的反射机制做一些工具类或者代码层面的架构
比如:一些路由的场景,需要根据一个反射的工具类(参数需要反射的对象,调用的方法名,以及参数),取出路由的消息并且根据消息带的方法名字和一些参数来调用对应的业务处理的方法。
这个工具类大体如下:
public static <T> T methodInvoker(Object target, String methodName, Object[] args){
...
}
在方法里面可以根据传入args参数调用getClass()方法获取每个参数的类型,再根据target对象的getDeclaredMethod方法传入方法名字和参数类型数组得到该对象方法的引用,最后调用方法引用的invoke执行该对象的指定方法得到返回结果。
上面的思路很明确,但是实际存在两个问题:
1.如果调用的方法属于该对象的父类,那getDeclaredMethod就会抛异常NoSuchMethodException
2.如果我们有个方法里面包含基础数据类型(int、byte、boolean等等),在反射getClass()获取参数类型的时候会发现int变成了Integer、boolean变成了Boolean包装类,看似好像没问题,但是在调用getDeclaredMethod获取方法对象的时候发现也抛出异常:NoSuchMethodException,其实是因为java反射的时候会把基础数据类型获取的数据类型都变成包装类,但是你需要调用的那个方法却不是包装类而是基础数据类型,就会报找不到方法的异常,这里需要指定例如:int就是int.class Integer就是Integer.class。
解决思路:
解决(1)问题:可以通过target.getSuperclass()获取父类的Class对象,并且进行方法的获取,如果获取不到再通过target.getSuperclass()方法获取父类的父类Class对象,直到得到了该方法的引用或者最后的父类是Object结束(采用递归的方式)。
解决(2)问题:最简单就是提前将需要反射的类方法设计都不用基础类型但实际很多情况并非想象那么美好,那么就可以加上try{}catch(NoSuchMethodException e){} 将找不到方法这个异常捕获,在catch块中将包装类转换成基础数据类型或者将基础数据类型转换为包装类型,但是调用的方法既有包装类又有基础数据类型,比如:method(Integer arg1, int arg2,boolean flag) 这种情况就很难解决了,最后思路:就是在这个catch块中直接获取该对象的所有方法,并判断获取到的方法名字是否和传入的名字一样,且参数个数一样,如果都满足那么就获取到该方法的引用,但是这样也有问题:如果需要被反射那个方法用了基础数据类型且有个重载方法参个数和需要调用那个方法的参数个数一样,那么可能会造成用getDeclaredMethod得到的方法引用对象根本不是我们调用的那个,从而又会抛出参数不匹配的异常(可以不考虑,使用的时候注意就行)
下面是这个工具类的demo(包含几个其他方法):
测试工程:
其中A是B的父类B是C的父类
A:
public class A {
public String showA(String str) {
return A.class.getName() + ":" + str;
}
}
B:
public class B extends A {
public String showB(String str) {
return B.class.getName() + ":" + str;
}
}
C:
public class C extends B {
public String showC(String str) {
return C.class.getName() + ":" + str;
}
}
ReflectionUtils:
public class ReflectionUtils {
/**
* 工具方法入口
*
* @param target
* @param methodName
* @param args
* @param
* @return
* @throws Exception
*/
public static T methodInvoker(Object target, String methodName, Object[] args) throws Exception {
Object result = null;
Class clazz = target.getClass();
Class[] argtypes = new Class[0];
if (args != null) {
argtypes = new Class[args.length];
ArrayList invoker = new ArrayList();
int argLength = args.length;
for (int i = 0; i < argLength; i++) {
Object arg = args[i];
invoker.add(arg == null ? null : arg.getClass());
}
invoker.toArray(argtypes);
}
Method method = getMethod(clazz, methodName, argtypes);
return method == null ? null : (T) method.invoke(target, args);
}
/**
* 获取对象的属性
*
* @param fieldName
* @param target
* @param
* @return
* @throws Exception
*/
public static T getFieldValueByName(String fieldName, Object target) throws Exception {
String firstLetter = fieldName.substring(0, 1).toUpperCase();
String getter = "get" + firstLetter + fieldName.substring(1);
Method method = target.getClass().getMethod(getter, new Class[0]);
Object e = method.invoke(target, new Object[0]);
return (T) e;
}
/**
* 获取所有字段名字
*
* @param target
* @return
*/
public static String[] getFiledName(Object target) throws Exception {
Field[] fields = target.getClass().getDeclaredFields();
String[] fieldNames = new String[fields.length];
for (int i = 0; i < fields.length; ++i) {
System.out.println(fields[i].getType());
fieldNames[i] = fields[i].getName();
}
return fieldNames;
}
/**
* 获取所有属性的值
*
* @param target
* @return
* @throws Exception
*/
public static Object[] getFiledValues(Object target) throws Exception {
String[] fieldNames = getFiledName(target);
Object[] value = new Object[fieldNames.length];
for (int i = 0; i < fieldNames.length; ++i) {
value[i] = getFieldValueByName(fieldNames[i], target);
}
return value;
}
/**
* 递归获取方法引用
*
* @param target
* @param methodName
* @param argTypes
* @return
*/
private static Method getMethod(Class> target, String methodName, Class>[] argTypes) {
Method method = null;
try {
method = target.getDeclaredMethod(methodName, argTypes);
method.setAccessible(true);
} catch (NoSuchMethodException e) {
method = getCatchMethod(target, methodName, argTypes);
}
if (method == null && target != Object.class) {
return getMethod(target.getSuperclass(), methodName, argTypes);
}
return method;
}
/**
* 当含有基础类型抛出NoSuchMethodException异常循环所有方法
*
* @param target
* @param methodName
* @param argTypes
* @return
*/
private static Method getCatchMethod(Class> target, String methodName, Class>[] argTypes) {
Method method = null;
Method[] methods = target.getDeclaredMethods();
int methodsLength = methods.length;
for (int i = 0; i < methodsLength; i++) {
Method methodTmp = methods[i];
int argsLength = methodTmp.getParameterTypes() == null ? 0 : methodTmp.getParameterTypes().length;
if (methodTmp.getName().equals(methodName) && argsLength == argTypes.length) {
methodTmp.setAccessible(true);
method = methodTmp;
break;
}
}
return method;
}
}
主函数MyMain:
public class MyMain {
public static void main(String[] args) throws Exception {
Object[] obj01 = new Object[1];
obj01[0] = "test";
String res01 = ReflectionUtils.methodInvoker(new C(), "showA", obj01);
System.out.println(res01);
Object[] obj02 = new Object[1];
obj02[0] = "test";
String res02 = ReflectionUtils.methodInvoker(new C(), "showB", obj02);
System.out.println(res02);
Object[] obj03 = new Object[1];
obj03[0] = "test";
String res03 = ReflectionUtils.methodInvoker(new C(), "showC", obj03);
System.out.println(res03);
}
}
执行主函数,测试结果如下:
注意事项:
1.最好不要使用基础数据类型作为方法参数类型,尽量使用包装类。
2.定义重载方法的时候不要让重载方法和被重载方法参数个数相同,不然可能会抛异常。