一.前言
说到语言的动态性,这个是脚本语言的一大优势,没有中间环节,源码即时执行。大家一般不会把它和java联系在一起,从java本身语言来看,java确实具有脚本语言的一些特性,即可以即时编译和执行。java相关的动态加载技术也非常的成熟,在android客户端,可以用这种技术热修复,动态替换有bug的相关代码;在服务端也有广泛的应用,像java的插件技术,感兴趣的可以参考 https://github.com/pf4j/pf4j ;包括我们使用的开发工具,比如idea的热替换,帮助我们在开发过程中,修改完代码热替换文件,不用每次都重启项目。但是java的热替换有一些限制,比如不能修改方法的签名,只能修改方法体里面的内容。
二.demo
我们结合springboot框架简单展示下动态加载代码技术的具体实现。动态加载代码的核心是类加载引擎,该类的作用是动态加载类的源文件,编译成class文件并且加载到jvm中。
public class DynamicEngine {
public Class> compile(String className, String javaCodes) {
if (classReloadNum.containsKey(className)) {
classReloadNum.get(className).incrementAndGet();
} else {
classReloadNum.put(className, new AtomicLong(startTime));
}
String newClassName = className + "_" + classReloadNum.get(className);
String[] classNameArr = className.split("\\.");
String[] newClassNameArr = newClassName.split("\\.");
String newJavaCode = javaCodes.replaceAll(classNameArr[classNameArr.length
- 1], newClassNameArr[newClassNameArr.length -1]);
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null);
StrSrcJavaObject srcObject = new StrSrcJavaObject(newClassName, newJavaCode);
Iterable extends JavaFileObject> fileObjects = Collections.singletonList(srcObject);
String flag = "-d";
String outDir;
File classPath;
try {
classPath = new File(Objects.requireNonNull(Thread.currentThread().getContextClassLoader().getResource("")).toURI());
outDir = classPath.getAbsolutePath() + File.separator;
Iterable options = Arrays.asList(flag, outDir);
JavaCompiler.CompilationTask task = compiler.getTask(null, fileManager, null, options, null, fileObjects);
boolean result = task.call();
if (result) {
try {
return myClassLoader.loadClass(newClassName);
} catch (ClassNotFoundException e) {
System.out.println("加载失败!");
e.printStackTrace();
}
}
} catch (URISyntaxException e) {
e.printStackTrace();
}
return null;
}
}
再定义一个接口类,我们的动态加载的类都是继承于这个接口。
public interface BaseSay {
String say();
}
写个controller提供动态加载的入口和我们动态加载类的执行动作。
@RestController
public class IndexController {
@Resource
private DynamicEngine dynamicEngine;
private BaseSay baseSay;
@PostMapping("/loadClass")
public String loadClass(String className,String code) throws IllegalAccessException, InstantiationException {
Class baseSayClass = (Class) dynamicEngine.compile(className, code);
baseSay = baseSayClass.newInstance();
return "ok";
}
@GetMapping("/say")
public String say() {
if (baseSay == null) {
return "baseSay was null!";
}
return baseSay.say();
}
再做个html页面去测试下我们的动态加载好使不:
我们点击动态加载按钮,然后点击say:
say返回字符串修改下,动态加载:
我们点击动态加载按钮,然后点击say,可以看到返回值是我们动态定义的类了。
也可以在代码里面加任意的其他代码,比如我想在执行前打印一些日志:
我们点击动态加载按钮,然后点击say,可以看到控制台打印出我们的代码了
三.demo地址jsdman/dynamic-codegithub.com
四.应用场景
大家可以一起讨论下,是否有一些场景可以应用此方式进行开发。