动态创建bean,前面一篇介绍了通过cglib来创建的方式,虽然实现了动态创建java bean,但是有一个问题,java bean中的field name和我们预期的不太一致
接下来我们介绍一种直接通过拼接java代码,然后再将其编译成class并加载,从而实现动态类的创建
1. java代码动态编译创建
接下来我们主要借助jdk自带的JavaCompiler
来实现java源码的编译,生成class,然后交由ClassLoader来加载类
如果我们现在有个java文件,希望在项目运行时,动态编译并加载类,一般的操作流程如下
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); int compilationResult = compiler.run(null, null, null, '/path/Test.java'); 复制代码
相比于存在的java文件,我们更多的场景则是针对String的java源码,基于它来创建java类
测试则需要针对上面的逻辑进行扩展;如果不想关心细节,直接使用开源的三方包来实现
<dependency> <groupId>com.itranswarp</groupId> <artifactId>compiler</artifactId> <version>1.0</version> </dependency> 复制代码
同样我们来实现一个根据Map,来生成java bean的方式
基于javacode来生成源码
/** * 编译java代码,并生成对象 * * @param packageName 包名 * @param className 类名 * @param javaCode 源码 * @return * @throws Exception */ public Object createBean(String packageName, String className, String javaCode) throws Exception { JavaStringCompiler compiler = new JavaStringCompiler(); Map<String, byte[]> result = compiler.compile(className + ".java", "package " + packageName + ";\n" + javaCode); Class clz = compiler.loadClass(packageName + "." + className, result); return clz.newInstance(); } 复制代码
接下来我们需要实现的是根据map来生成对应的java code
private String genCode(String clzName, Map<String, Object> map) { StringBuilder builder = new StringBuilder("public class ").append(clzName).append("{\n"); for (Map.Entry<String, Object> obj: map.entrySet()) { String fieldType; if (obj.getValue() instanceof List) { fieldType = "java.util.List"; } else if (obj.getValue() instanceof Set) { fieldType = "java.util.Set"; } else if (obj.getValue() instanceof Map) { fieldType = "java.util.Map"; } else { fieldType = obj.getValue().getClass().getName().replace("$", "."); } builder.append("\tpublic ").append(fieldType).append(" ").append(obj.getKey()).append(";\n"); } builder.append("}"); return builder.toString(); } 复制代码
注意上面的实现,现在只是最基础的实现,需要注意,如果value的类型,是一个内部类,就可能有坑(比如上面针对容器类进行了特殊处理)
最后测试一下
@Test public void testGenMapBean() throws Exception { Map<String, Object> map = new HashMap<>(); map.put("hello", "world"); map.put("key", 12); map.put("list", Arrays.asList(1, 2, 3)); String packageName = "com.git.hui.dynamic"; String clzName = "MBean1"; String code = genCode(clzName, map); Object bean = createBean(packageName, clzName, code); // 初始化bean的成员 for(Map.Entry<String, Object> entry: map.entrySet()) { Field field = bean.getClass().getField(entry.getKey()); field.set(bean, entry.getValue()); } System.out.println("---------------- java code ---------------\n" + code + "\n------------------"); System.out.println(JSON.toJSONString(bean)); } 复制代码
上面给出了一个根据Map生成bean对象,并初始化成员值的实例
输出如下
---------------- java code --------------- public class MBean1{ public java.lang.String hello; public java.util.List list; public java.lang.Integer key; } ------------------ {"hello":"world","key":12,"list":[1,2,3]} 复制代码
输出和我们预期一致,直接通过组装java代码,然后来生成对象
那么这种方式应用场景在什么地方呢?
- 比如我之前做过一个动态脚本执行的框架,可以在控制台上写java代码,写完之后直接交给框架来运行;当然这种选择groovy来做脚本可能更优雅一些