最近处理了一个通过类名、方法名、参数值远程调用的功能。在处理的过程中,使用反射的方式进行动态调用,其中的难点是对泛型的处理,特别是多层泛型的情况。现将开发过程中的思路以及遇到的问题进行总结。
方法声明:public synchronized String invoke(String beanName, String methodName, List<Map<String,String>> params)
beanName:调用的Service在Spring容器中的名称
methodName:调用的Service方法名
params:调用方法参数,
每个参数对应一个Map,Map的key为参数的类型,如java.lang.String,
Map的value为JSON格式
return:JSON字符串:{"code":"1","result":""},code=1,执行成功,其他执行失败
一、现有方案的流程如下:
1、判断当前参数是否为空,如果为空,则调用的方法没有参数,直接进行反射调用。
2、参数不为空,遍历所有的参数列表,获取参数的类型字符串和值JSON串
3、判断参数的类型字符串中是否包含泛型,如果不包括,直接使用ReflectUtils.name2class(paramType)将参数字符串类型转换为class类型,并通过class将值JSON串转换为值对象。
4、如果参数的类型字符串中包含泛型,再次判断包含的泛型层数,如果是一层泛型,则调用handleOnlyParameterizedType进行处理。
5、如果是多层泛型,则将多层泛型进行拆分,从最里层开始封装JavaType。调用方法splitParameterizedClassName进行拆分泛型,调用constructJavaTypeByName进行构造JavaType。在其中存在一种情况,如Map等一层泛型中有多个的情况,在拆分时,如果遇到这种情况,就停止拆分,获取如A<B,C>的情况,分别递归创建B,C的JavaType,然后再创建A的JavaType。
/**
* 处理泛型的情况,如果只存在一层泛型如List<String>,Map<String,Integer>等
* 使用handleOnlyParameterizedType进行处理,
* 如果存在多层泛型如A<B<C>>等,构建JavaType进行处理
* @param className
* @param value
* @param typeList
* @param valueList
* @throws Exception
*/
private void handleParameterizedType(String className,String value,List<Class<?>> typeList,List<Object> valueList) throws Exception {
//存在多层泛型
if(StringUtils.countMatches(className, "<") > 1) {
String baseClsName = className.substring(0,className.indexOf("<"));
Class<?> baseCls = ReflectUtils.name2class(baseClsName);
typeList.add(baseCls);
List<String> baseClsList = new ArrayList<String>();
String childClsName = splitParameterizedClassName(className, baseClsList);
//倒叙遍历list,从内层开始构建javaType
/**
* Map<String,List<Object>>===>
* 1.type = (List.class,Object.javatype)
* 2.type = Map(String.javatype,1.type)
*/
JavaType type = constructJavaTypeByName(childClsName);
for(int i=baseClsList.size()-1;i>=0;i--) {
String listBaseClsName = baseClsList.get(i);
Class<?> listBaseCls = ReflectUtils.name2class(listBaseClsName);
type = mapper.getTypeFactory().constructParametricType(listBaseCls, type);
}
Object obj = mapper.readValue(value, type);
valueList.add(obj);
} else {
handleOnlyParameterizedType(className, value, typeList, valueList);
}
}
/**
* 处理泛型的情况,该方法只处理存在一层泛型的情况
* @param className 类型名:如java.util.List<java.lang.String>、java.util.Map<java.lang.String,java.lang.Object>
* @param value 参数值
* @param typeList
* @param valueList
* @throws Exception
*/
private void handleOnlyParameterizedType(String className,String value,List<Class<?>> typeList,List<Object> valueList) throws Exception {
String baseClsName = className.substring(0,className.indexOf("<"));
String genericClsNames = className.substring(className.indexOf("<")+1,className.length()-1);
String[] genericClsNameArr = genericClsNames.split(",");
if(genericClsNameArr.length <= 0) {
throw new Exception("none parameterizedType");
}
Class<?> baseCls = ReflectUtils.name2class(baseClsName.trim());
typeList.add(baseCls);
JavaType javaType;
Class<?>[] genericClses = new Class<?>[genericClsNameArr.length];
int i = 0;
for(String genericClsName : genericClsNameArr) {
genericClses[i++] = ReflectUtils.name2class(genericClsName.trim());
}
javaType = mapper.getTypeFactory().constructParametricType(baseCls, genericClses);
Object obj = mapper.readValue(value, javaType);
valueList.add(obj);
}
/**
* 解析两层以上的泛型如A<B<C<D>>>
* 如果均为单个泛型,则返回最内层的泛型 C<D>,baseClsList=[A,B]
* 如果里面存在多个泛型,如Map<String,Object>则遇到这种情况,直接返回
* @param str
* @param baseClsList
* @return
* @throws ClassNotFoundException
*/
private String splitParameterizedClassName(String str,List<String> baseClsList) throws ClassNotFoundException {
String childrenClsName = str;
while(StringUtils.countMatches(str, "<") >= 2) {
String baseClsName = str.substring(0,str.indexOf("<"));
Class<?> baseCls = ReflectUtils.name2class(baseClsName);
//如果该类的泛型个数超过1个,如Map,直接返回
if(baseCls.getTypeParameters().length > 1) {
break;
}
baseClsList.add(baseClsName);
childrenClsName = str.substring(str.indexOf("<")+1,str.lastIndexOf(">"));
str = childrenClsName;
}
return childrenClsName;
}
/**
* 通过类型构造JavaType,多个泛型的情况如Map<String,Object>,
* 分别构建String的JavaType和Object的JavaType,在构建Map的JavaType
* 此时使用递归调用
* @param className
* @return
*/
private JavaType constructJavaTypeByName(String className) throws Exception {
if(StringUtils.countMatches(className, "<") == 0) {
return mapper.constructType(ReflectUtils.name2class(className));
}
String baseClsName = className.substring(0,className.indexOf("<"));
String genericClsNames = className.substring(className.indexOf("<")+1,className.length()-1);
String[] genericClsNameArr = genericClsNames.split(",");
if(genericClsNameArr.length <= 0) {
//不存在泛型,即className为List<>,此种情况不会发生
throw new Exception("none parameterizedType");
}
Class<?> baseCls = ReflectUtils.name2class(baseClsName.trim());
JavaType javaType;
JavaType[] genericTypes = new JavaType[genericClsNameArr.length];
int i = 0;
for(String genericClsName : genericClsNameArr) {
genericTypes[i++] = constructJavaTypeByName(genericClsName.trim());
}
javaType = mapper.getTypeFactory().constructParametricType(baseCls, genericTypes);
return javaType;
}
二、曾经尝试的方法:Java动态编译
在尝试的过程中,发现jackson的TypeReference可以实现多层泛型的反射,但是发现通过构造嵌套构造JavaType不能使用(不知道当时是什么原因不能用,后来又可以使用),考虑使用Java动态编译,动态创建类TypeReference。代码完成后,在本地测试没有问题。但是部署后,远程调用,发现动态创建的类编译出错,不认识其中import的类。经排查,感觉可能是部署的时候,其他类是是由容器进行的加载,而Java动态编译时,在不指明classpath的情况下,是不会自动使用容器加载过的类的,造成ClassNotFound,但是有一些类的classpath是无法指定的,没有源文件,仍然无法编译成功。故该方案失败。但仍然学习了一些关于java动态编译的知识。在其中要特别注意多线程的情况,生成的类名要不相同,否则会出问题,动态生成的类使用完毕后要及时删除,否则会出现很多无效的临时文件。
public class TypeReferenceUtils {
private static int count = 0;
@SuppressWarnings({ "rawtypes" })
public static TypeReference getTypeReference(String paramType) throws Exception {
if(count == Integer.MAX_VALUE) {
count = 0;
}
count ++;
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager fileMgr = compiler.getStandardFileManager(null, null, null);
String className = "DynamicTypeReference"+count;
StringBuilder dynaCls = new StringBuilder();
dynaCls.append("public class ").append(className).append(" {")
.append(" public com.alibaba.fastjson.TypeReference getTypeReference() {")
.append(" return new com.alibaba.fastjson.TypeReference<")
.append(paramType)
.append(">(){};")
.append(" }")
.append("}");
StringObject so = new StringObject(className,dynaCls.toString());
JavaFileObject file = so;
Iterable<JavaFileObject> files = Arrays.asList(file);
System.out.println(TypeReferenceUtils.class.getResource("/"));
String path = TypeReferenceUtils.class.getResource("/").getPath();
File filePath = new File(path);
String webinfPath = filePath.getParent();
//,"-classpath",webinfPath+"/lib/fastjson-1.1.37.jar"
Iterable< String> options = Arrays.asList("-d", path);
JavaCompiler.CompilationTask t = compiler.getTask(null, fileMgr, null, options, null, files);
Boolean r=t.call();
TypeReference typeReference = null;
if(r.booleanValue() == true) {
Class<?> clazz = TypeReferenceUtils.class.getClassLoader().loadClass(className);
Object instance = clazz.newInstance();
typeReference = (TypeReference)clazz.getMethod("getTypeReference").invoke(instance, new Object[]{});
}
try {
return typeReference;
} finally {
//删除Java动态编译生成的临时class文件
File f = new File(path+className+".class");
if(f.exists()) {
f.delete();
}
f = new File(path+className+"$1.class");
if(f.exists()) {
f.delete();
}
}
}
}
@SuppressWarnings("restriction")
class StringObject extends SimpleJavaFileObject {
private String contents = null;
public StringObject(String className, String contents) throws Exception {
super(URI.create("string:///" + className.replace('.', '/')
+ Kind.SOURCE.extension), Kind.SOURCE);
this.contents = contents;
}
public CharSequence getCharContent(boolean ignoreEncodingErrors)
throws IOException {
return contents;
}
}