最近处理了一个通过类名、方法名、参数值远程调用的功能。在处理的过程中,使用反射的方式进行动态调用,其中的难点是对泛型的处理,特别是多层泛型的情况。现将开发过程中的思路以及遇到的问题进行总结。
方法声明: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; } }
版权声明:本文为博主原创文章,未经博主允许不得转载。