这是我参与8月更文挑战的第23天,活动详情查看:8月更文挑战
动态创建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来做脚本可能更优雅一些
II. 其他
1. 一灰灰Blog: liuyueyi.github.io/hexblog
一灰灰的个人博客,记录所有学习和工作中的博文,欢迎大家前去逛逛
2. 声明
尽信书则不如,以上内容,纯属一家之言,因个人能力有限,难免有疏漏和错误之处,如发现bug或者有更好的建议,欢迎批评指正,不吝感激
- 微博地址: 小灰灰Blog
- QQ: 一灰灰/3302797840
- 微信公众号:一灰灰blog