通过javassist,添加了动态编译解析参数类的功能。记录一下主要目的和原理:
目的:动态解析传入参数组合成类。
最后达到的效果:
1 2 3 |
@Get("/api/list") public String list(DataTablesParam param) { ... |
主要是为了能自动获取DataTables传入的参数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
/** * jQuery插件 DataTables 传递的参数 * <p> * https://blog.bbdd.ltd/api/list?draw=1 : draw=1 * &columns%5B0%5D%5Bdata%5D=id : columns[0][data]=id * &columns%5B0%5D%5Bname%5D= : columns[0][name]= * &columns%5B0%5D%5Bsearchable%5D=true : columns[0][searchable]=true * &columns%5B0%5D%5Borderable%5D=true : columns[0][orderable]=true * &columns%5B0%5D%5Bsearch%5D%5Bvalue%5D= : columns[0][search][value]= * &columns%5B0%5D%5Bsearch%5D%5Bregex%5D=false: columns[0][search][regex]=false * ... * &order%5B0%5D%5Bcolumn%5D=0 : order[0][column]=0 * &order%5B0%5D%5Bdir%5D=asc : order[0][dir]=asc * &start=0 * &length=10 * &search%5Bvalue%5D= : search[value]= * &search%5Bregex%5D=false : search[regex]=false * &_=1465394996767 */ |
过程:最初修改自wanghaomiao的blog(以下2-6条)。原文是用java的反射机制,我加入了7-9条,实现了动态编译解析器类。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
/** * 一个强大的 'form表单'-> 'Bean' 的参数封装器,支持对Bean对象内任意深度的递归封装。 * 以接收UserBasic有如下约定: * 1,只针对注解了 {@link WebBean} 的Model对象生效 * 2,基本类型属性直接按照属性名进行接收 * 3,对象类型属性的子属性按照 '{fieldName}.{childFieldName}' 形式接收 * 4,对象类型属性中的子对象属性的子属性按照 '{fieldName}.{childFieldName}.{childFieldName}',更深层次的子代对象以此类推 * 5,List对象类型属性按照 '{fieldName}.{index}.{childFieldName}' 其中{index}从0开始 * 6,属性的集合类型目前仅支持List,这基本足够了 * <p> * bianbian.org修改: * 7,上述子属性分隔符除了"."也可以用"["和"]",即 user.0.id 也可以用 user[0][id], 需要注解类 * 8,也可以支持 {object}.{fieldName}.{childFieldName} 形式, 需要传入特殊参数: __prefix * 9,动态编译类替代反射提高性能, 详见 {@link ResolverHelper} * * 修改自: http://www.wanghaomiao.cn/archives/11/ */ |
原理:rose的ParamResolver接口提供了supports和resolve方法,在supports判断某个类是否支持解析,resolve里实现解析。我引入了一个WebBean注解,通过是否加注了WebBean来判断。如果加注了,就用ResolverHelper来实现动态编译。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
public class WebBeanResolver implements ParamResolver { private static final Logger logger = LoggerFactory.getLogger(WebBeanResolver.class); private static final String PREFIX = "__prefix"; @Override public boolean supports(ParamMetaData metaData) { if (metaData.getParamType().isAnnotationPresent(WebBean.class)) { logger.debug("method: {}", metaData.getMethod()); ResolverHelper.init(metaData.getParamType()); return true; } else { return false; } } @Override public Object resolve(Invocation inv, ParamMetaData metaData) throws Exception { Class beanClass = metaData.getParamType(); String prefix = inv.getParameter(PREFIX); return ResolverHelper.resolve(beanClass, inv, prefix); } } |
动态编译核心代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 |
private BeanResolver createResolver() { BeanResolver obj = null; try { ClassPool cp = DynamicBeanUtils.getClassPool(); CtClass ctClass = cp.makeClass(GuavaUtil.JOIN_DOLLAR.join( _CreatedImpl, Math.abs(DynamicBeanUtils.random.nextInt()), System.currentTimeMillis())); ctClass.addInterface(cp.get(_BeanResolver)); ctClass.addField(CtField.make("private String $prefix;", ctClass)); ctClass.addField(CtField.make("private " + _Separator + " $separator;", ctClass)); ctClass.addMethod(CtMethod.make("public void setSeparator(" + _Separator + " sp){$separator=sp;}", ctClass)); CtMethod makePath = CtMethod.make( "private String makePath(String name) {" + " return ($prefix == null) ? name : $separator.createParamKey(new Object[] { $prefix, name });" + "}", ctClass); ctClass.addMethod(makePath); sb = new StringBuilder(1000); line("public Object resolve({0} $inv, String $prefix) throws Exception { ", _Invocation); line(" this.$prefix = $prefix; "); line(" boolean $save = false; "); //$save为false返回null line(" String $path, $value; "); line(" {0} $bean = new {0}(); ", mappedClass.getName()); appendFieldsResolver(); line(" return ($save ? $bean : null); "); line("} "); logger.debug("\n{}:\n{}", mappedClass, sb); CtMethod resolve = CtMethod.make(sb.toString(), ctClass); ctClass.addMethod(resolve); obj = (BeanResolver) ctClass.toClass().newInstance(); obj.setSeparator(this.separator); } catch (Exception e) { logger.error(e.getMessage(), e); } return obj; } private void appendFieldsResolver() { mappedFields.values().stream().forEach(fieldMeta -> { String fieldName = fieldMeta.getFieldName(); line(); line(" $path = makePath(\"{0}\"); ", fieldName); Class type = fieldMeta.getJavaType(); if (type.equals(List.class)) { appendList(fieldMeta, fieldName); } else if (type.isAnnotationPresent(WebBean.class)) { ResolverHelper.init(type); line(" {0} {1} = ({0}) {2}.resolve({0}.class, $inv, $path); ", type.getName(), fieldName, _ResolverHelper); appendSetter(fieldMeta, fieldName); } else { appendField(fieldMeta, type); } }); } private void appendList(BeanField fieldMeta, String fieldName) { Class pType = fieldMeta.getParameterizedType(); line(" java.util.List {0} = new java.util.ArrayList(); ", fieldName); line(" for (int $i = 0; ; $i++) { "); line(" String $_path = $separator.createParamKey(new Object[]{ $path, new Integer($i) });"); if (pType.isAnnotationPresent(WebBean.class)) { ResolverHelper.init(pType); line(" {1} _{0} = ({1}) {2}.resolve({1}.class, $inv, $_path);", fieldName, pType.getName(), _ResolverHelper); } else { // TODO: Int Long Boolean String 都支持valueOf, 其他类型的话得扩展valueOf line(" {1} _{0} = {1}.valueOf($inv.getParameter($_path));", fieldName, pType.getName()); } line(" if (_{0} == null) break; ", fieldName); line(" {0}.add(_{0});", fieldName); line(" } "); //end for line(" if ({0}.size() > 0) { ", fieldName); line(" $save = true; "); appendSetter(fieldMeta, fieldName); line(" } "); } private void appendField(BeanField fieldMeta, Class type) { line(" $value = $inv.getParameter($path); "); line(" if ($value != null) { "); line(" $save = true; "); appendSetter(fieldMeta, appendValue("$value", type)); line(" } "); } private void appendSetter(BeanField fieldMeta, String value) { line(" $bean.{0}({1});", fieldMeta.getWriteName(), value); } // TODO: 暂支持 String int long boolean, 需要其他类型再添加 private String appendValue(String value, Class type) { if (type.equals(String.class)) return value; else if (type.equals(int.class) || type.equals(Integer.class)) return MessageFormat.format("Integer.parseInt({0})", value); else if (type.equals(long.class) || type.equals(Long.class)) return MessageFormat.format("Long.parseLong({0})", value); else if (type.equals(boolean.class) || type.equals(Boolean.class)) return MessageFormat.format("org.apache.commons.lang3.BooleanUtils.toBoolean({0})", value); return value; } private void line(Object... args) { String line = ""; if (args.length > 0) { line = (String) args[0]; args = ArrayUtils.remove(args, 0); } if (args.length > 0) { line = StringUtil.format(line, args); } sb.append(line).append("\n"); } |
附动态生成的解析类主要方法(支持嵌套、支持List、暂支持int long boolean)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 |
auto.BeanResolverImpl$1529875889$1466525192610 for ...DataTablesParam: public Object resolve(net.paoding.rose.web.Invocation $inv, String $prefix) throws Exception { this.$prefix = $prefix; boolean $save = false; String $path, $value; DataTablesParam $bean = new DataTablesParam(); $path = makePath("search"); DataTablesSearch search = (DataTablesSearch) ResolverHelper.resolve(DataTablesSearch.class, $inv, $path); $bean.setSearch(search); $path = makePath("columns"); java.util.List columns = new java.util.ArrayList(); for (int $i = 0; ; $i++) { String $_path = $separator.createParamKey(new Object[]{ $path, new Integer($i) }); DataTablesColumn _columns = (DataTablesColumn) ResolverHelper.resolve(DataTablesColumn.class, $inv, $_path); if (_columns == null) break; columns.add(_columns); } if (columns.size() > 0) { $save = true; $bean.setColumns(columns); } $path = makePath("length"); $value = $inv.getParameter($path); if ($value != null) { $save = true; $bean.setLength(Integer.parseInt($value)); } $path = makePath("start"); $value = $inv.getParameter($path); if ($value != null) { $save = true; $bean.setStart(Integer.parseInt($value)); } $path = makePath("draw"); $value = $inv.getParameter($path); if ($value != null) { $save = true; $bean.setDraw(Integer.parseInt($value)); } $path = makePath("order"); java.util.List order = new java.util.ArrayList(); for (int $i = 0; ; $i++) { String $_path = $separator.createParamKey(new Object[]{ $path, new Integer($i) }); DataTablesOrder _order = (DataTablesOrder) ResolverHelper.resolve(DataTablesOrder.class, $inv, $_path); if (_order == null) break; order.add(_order); } if (order.size() > 0) { $save = true; $bean.setOrder(order); } return ($save ? $bean : null); } |