事实上,大多数人在工作中从未使用过Java的动态编译功能。 事实上,我有机会学习和使用它只是一个偶然。
这还得说起我刚来我们部门时的故事。 当时我接了一个项目,主要是把部门的各项业务和外部第三方打通。 接手后apache源码编译问题,我遇到了一些问题:
1、项目是一个大杂烩,包括各种业务的代码。 经常提出需求却一直找不到对应的负责人(要么辞职,要么不再负责这个业务),最后会被要求改,但又不知道相关的生意很好。 我恨它!
2、各业务方每次做出变更都需要找我进行发布和分支管理,需要花费精力处理与我负责的业务无关的事情。 我很烦人!
为了解决这个问题,我动用了我聪明的大脑。 为什么不把这个项目中的代码分成小的代码块呢? 之后你只需要管理这个代码块就可以了,这就解决了代码归属的问题。
但仍然存在一个问题,每次需求到来时都需要更改并发版本,这绝对违背了所需的稳定组件系统的最初设计意图。 这时候我想到了动态编译,好像就解决了!
1.什么是动态编译
在Java中,动态编译是指在运行时动态编译Java源代码,生成字节码,并加载到JVM中执行。 动态编译可以用来实现动态代码生成、动态加载、插件等功能。
1.1. 动态编译的相关概念
DiagnosticListener对象:用于在编译时收集诊断信息。
JavaFileObject对象:代表要编译的Java源代码。
1.2. 如何简单实现动态编译
下面是一个简单的例子演示如何使用动态编译:
public class DynamicCompiler {
public static void main(String[] args) throws Exception {
// 创建 JavaCompiler 对象
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
// 创建 DiagnosticCollector 对象,用于收集编译时的诊断信息
DiagnosticCollector diagnostics = new DiagnosticCollector();
// 创建 JavaFileManager 对象,用于管理编译过程中的文件
StandardJavaFileManager fileManager = compiler.getStandardFileManager(diagnostics, null, null);
// 创建 JavaFileObject 对象,用于表示要编译的 Java 源代码
String code = "public class HelloWorld { public static void main(String[] args) { System.out.println("Hello World!"); } }";
JavaFileObject source = new JavaSourceFromString("HelloWorld", code);
// 获取 CompilationTask 对象
Iterable compilationUnits = Arrays.asList(source);
CompilationTask task = compiler.getTask(null, fileManager, diagnostics, null, null, compilationUnits);
// 编译 Java 源代码
boolean success = task.call();
// 获取诊断信息
List<Diagnostic> messages = diagnostics.getDiagnostics();
for (Diagnostic message : messages) {
System.out.println(message.getMessage(null));
}
// 处理编译结果
if (success) {
System.out.println("Compilation was successful.");
} else {
System.out.println("Compilation failed.");
}
fileManager.close();
}
}
class JavaSourceFromString extends SimpleJavaFileObject {
final String code;
JavaSourceFromString(String name, String code) {
super(URI.create("string:///" + name.replace('.', '/') + Kind.SOURCE.extension), Kind.SOURCE);
this.code = code;
}
@Override
public CharSequence getCharContent(boolean ignoreEncodingErrors) {
return code;
}
}
运行结果:
Hello World!
Compilation was successful.
2.如何结合springboot项目使用
它展示了如何简单地使用Java的动态编译功能,你在日常项目开发中将会面临更多的场景。 结合前言中我遇到的问题,给大家简单介绍一下我是如何在项目中使用Java的动态编译功能来解决我遇到的问题的。
我当时的看法是这样的:
动态编译图片(3)
这样每个业务方都可以管理自己的代码块apache源码编译问题,并且在与外界连接或者更改代码时不需要释放应用,彻底解放了我,让我有更多的精力去做更重要的事情为了公司!
2.1. 2.1.1. 动态编译项目中遇到的问题必须重绘类加载器才能使新编译的代码生效
在Java中使用动态编译特性时,需要重绘类加载器。 这是因为动态编译生成的类需要加载到JVM中执行,而默认的类加载器很难加载动态生成的类。
在Java中,类加载器分为三种类型:启动类加载器、扩展类加载器和应用程序类加载器。 默认情况下,Java 使用应用程序类加载器来加载类。 应用程序类加载器只能加载预编译的类,很难加载动态生成的类。 因此,我们需要重新绘制类加载器,使其能够加载动态生成的类。
重绘类加载器有两种形式:继承ClassLoader类或实现ClassLoader套接字。 一般情况下,我们推荐使用继承ClassLoader类的方法,因为这样可以更方便地控制类加载过程。
当我们重绘类加载器时,我们需要实现findClass技术。 findClass 方法用于查找具有指定名称的类。 如果类已经加载,则可以直接返回加载的类; 否则,需要使用动态编译生成类的字节码,并通过defineClass加载到JVM中执行。
2.1.2. 没有依赖关系的简单代码可以编译成功,但是一旦有依赖关系,编译就会失败。
Java编译器通过JavaFileManager加载相关的依赖类。 如果不重绘,使用默认的JavaFileManager获取springboot的jarFile来读取嵌套的jar,自然是获取不到的。 我们需要重绘JavaFileManager来获取它。 编译代码所需依赖,具体编写方法请参见2.2代码示例。
2.2. 代码示例
// 通过调用这个方法即可实现 java 的动态编译功能啦
public static Class compile(String className, String code) {
try (MemoryClassLoader loader = MemoryClassLoader.genInstance()) {
loader.registerJava(className, code);
return MemoryClassLoader.getInstance().loadClass(className);
} catch (Exception e) {
// ignore
}
}
}
public class MemoryClassLoader extends URLClassLoader {
private static final Map<String, byte[]> classBytes = new ConcurrentHashMap();
private MemoryClassLoader() {
super(new URL[0], MemoryClassLoader.class.getClassLoader());
}
private static final Map CLASSLOADER_MAP = new ConcurrentHashMap() {{
put(KEY_CLASSLOADER, new MemoryClassLoader());
}};
private static final String KEY_CLASSLOADER = "key_classloader";
/**
* 注册 Java 字符串到内存类加载器中
*/
public void registerJava(String className, String javaCode) {
try {
Map<String, byte[]> compile = compile(className, javaCode);
if (null != compile) {
classBytes.putAll(compile);
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 编译 Java 代码
*/
private static Map<String, byte[]> compile(String className, String javaCode) {
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager stdManager = getStandardFileManager(null, null, null);
try (MemoryJavaFileManager manager = new MemoryJavaFileManager(stdManager)) {
JavaFileObject javaFileObject = manager.makeStringSource(className, javaCode);
JavaCompiler.CompilationTask task = compiler.getTask(null, manager, null, null, null, Collections.singletonList(javaFileObject));
Boolean result = task.call();
if (result != null && result) {
return manager.getClassBytes();
}
}
return null;
}
@Override
public Class findClass(String name) throws ClassNotFoundException {
byte[] buf = classBytes.get(name);
if (buf == null) {
return super.findClass(name);
}
return defineClass(name, buf, 0, buf.length);
}
@Override
public void close() {
classBytes.clear();
CLASSLOADER_MAP.clear();
}
/**
* 自定义 Java 文件管理器
*/
public static SpringJavaFileManager getStandardFileManager(DiagnosticListener<? super JavaFileObject> var1, Locale var2, Charset var3) {
Context var4 = new Context();
var4.put(Locale.class, var2);
if (var1 != null) {
var4.put(DiagnosticListener.class, var1);
}
PrintWriter var5 = var3 == null ? new PrintWriter(System.err, true) : new PrintWriter(new OutputStreamWriter(System.err, var3), true);
var4.put(Log.outKey, var5);
return new SpringJavaFileManager(var4, true, var3);
}
/**
* 获取实例
*/
public static MemoryClassLoader getInstance() {
return CLASSLOADER_MAP.get(KEY_CLASSLOADER);
}
/**
* 生成新的实例
*/
public static MemoryClassLoader genInstance() {
MemoryClassLoader classLoader = new MemoryClassLoader();
CLASSLOADER_MAP.put(KEY_CLASSLOADER, new MemoryClassLoader());
return classLoader;
}
public static String getPath() {
ApplicationHome home = new ApplicationHome(MemoryJavaFileManager.class);
String path = home.getSource().getPath();
return path;
}
public static boolean isJar() {
return getPath().endsWith(".jar");
}
}
class MemoryJavaFileManager extends ForwardingJavaFileManager<JavaFileManager> {
// compiled classes in bytes:
final Map<String, byte[]> classBytes = new HashMap();
final Map<String, List> classObjectPackageMap = new HashMap();
private JavacFileManager javaFileManager;
/**
* key 包名 value javaobj 主要给 jdk 编译 class 的时候找依赖 class 用
*/
public final static Map<String, List> CLASS_OBJECT_PACKAGE_MAP = new HashMap();
private static final Object lock = new Object();
private boolean isInit = false;
public void init() {
try {
String jarBaseFile = MemoryClassLoader.getPath();
JarFile jarFile = new JarFile(new File(jarBaseFile));
List entries = jarFile.stream().filter(jarEntry -> jarEntry.getName().endsWith(".jar")).collect(Collectors.toList());
JarFile libTempJarFile;
List onePackageJavaFiles;
String packageName;
for (JarEntry entry : entries) {
libTempJarFile = jarFile.getNestedJarFile(jarFile.getEntry(entry.getName()));
if (libTempJarFile.getName().contains("tools.jar")) {
continue;
}
Enumeration tempEntriesEnum = libTempJarFile.entries();
while (tempEntriesEnum.hasMoreElements()) {
JarEntry jarEntry = tempEntriesEnum.nextElement();
String classPath = jarEntry.getName().replace("/", ".");
if (!classPath.endsWith(".class") || jarEntry.getName().lastIndexOf("/") == -1) {
continue;
} else {
packageName = classPath.substring(0, jarEntry.getName().lastIndexOf("/"));
onePackageJavaFiles = CLASS_OBJECT_PACKAGE_MAP.containsKey(packageName) ? CLASS_OBJECT_PACKAGE_MAP.get(packageName) : new ArrayList();
onePackageJavaFiles.add(new MemorySpringBootInfoJavaClassObject(jarEntry.getName().replace("/", ".").replace(".class", ""),
new URL(libTempJarFile.getUrl(), jarEntry.getName()), javaFileManager));
CLASS_OBJECT_PACKAGE_MAP.put(packageName, onePackageJavaFiles);
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
isInit = true;
}
MemoryJavaFileManager(JavaFileManager fileManager) {
super(fileManager);
this.javaFileManager = (JavacFileManager) fileManager;
}
public Map<String, byte[]> getClassBytes() {
return new HashMap(this.classBytes);
}
@Override
public void flush() {
}
@Override
public void close() {
classBytes.clear();
classObjectPackageMap.clear();
CLASS_OBJECT_PACKAGE_MAP.clear();
}
public List getLibJarsOptions(String packgeName) {
synchronized (lock) {
if (!isInit) {
init();
}
}
return CLASS_OBJECT_PACKAGE_MAP.get(packgeName);
}
@Override
public Iterable list(Location location,String packageName, Set kinds,
boolean recurse) throws IOException {
if ("CLASS_PATH".equals(location.getName()) && MemoryClassLoader.isJar()) {
List result = getLibJarsOptions(packageName);
if (result != null) {
return result;
}
}
Iterable it = super.list(location, packageName, kinds, recurse);
if (kinds.contains(JavaFileObject.Kind.CLASS)) {
final List javaFileObjectList = classObjectPackageMap.get(packageName);
if (javaFileObjectList != null) {
if (it != null) {
for (JavaFileObject javaFileObject : it) {
javaFileObjectList.add(javaFileObject);
}
}
return javaFileObjectList;
} else {
return it;
}
} else {
return it;
}
}
@Override
public String inferBinaryName(Location location, JavaFileObject file) {
if (file instanceof MemoryInputJavaClassObject) {
return ((MemoryInputJavaClassObject) file).inferBinaryName();
}
return super.inferBinaryName(location, file);
}
@Override
public JavaFileObject getJavaFileForOutput(Location location, String className, JavaFileObject.Kind kind,
FileObject sibling) throws IOException {
if (kind == JavaFileObject.Kind.CLASS) {
return new MemoryOutputJavaClassObject(className);
} else {
return super.getJavaFileForOutput(location, className, kind, sibling);
}
}
JavaFileObject makeStringSource(String className, final String code) {
String classPath = className.replace('.', '/') + JavaFileObject.Kind.SOURCE.extension;
return new SimpleJavaFileObject(URI.create("string:///" + classPath), JavaFileObject.Kind.SOURCE) {
@Override
public CharBuffer getCharContent(boolean ignoreEncodingErrors) {
return CharBuffer.wrap(code);
}
};
}
void makeBinaryClass(String className, final byte[] bs) {
JavaFileObject javaFileObject = new MemoryInputJavaClassObject(className, bs);
String packageName = "";
int pos = className.lastIndexOf('.');
if (pos > 0) {
packageName = className.substring(0, pos);
}
List javaFileObjectList = classObjectPackageMap.get(packageName);
if (javaFileObjectList == null) {
javaFileObjectList = new LinkedList();
javaFileObjectList.add(javaFileObject);
classObjectPackageMap.put(packageName, javaFileObjectList);
} else {
javaFileObjectList.add(javaFileObject);
}
}
class MemoryInputJavaClassObject extends SimpleJavaFileObject {
final String className;
final byte[] bs;
MemoryInputJavaClassObject(String className, byte[] bs) {
super(URI.create("string:///" + className.replace('.', '/') + Kind.CLASS.extension), Kind.CLASS);
this.className = className;
this.bs = bs;
}
@Override
public InputStream openInputStream() {
return new ByteArrayInputStream(bs);
}
public String inferBinaryName() {
return className;
}
}
class MemoryOutputJavaClassObject extends SimpleJavaFileObject {
final String className;
MemoryOutputJavaClassObject(String className) {
super(URI.create("string:///" + className.replace('.', '/') + Kind.CLASS.extension), Kind.CLASS);
this.className = className;
}
@Override
public OutputStream openOutputStream() {
return new FilterOutputStream(new ByteArrayOutputStream()) {
@Override
public void close() throws IOException {
out.close();
ByteArrayOutputStream bos = (ByteArrayOutputStream) out;
byte[] bs = bos.toByteArray();
classBytes.put(className, bs);
makeBinaryClass(className, bs);
}
};
}
}
}
class MemorySpringBootInfoJavaClassObject extends BaseFileObject {
private final String className;
private URL url;
MemorySpringBootInfoJavaClassObject(String className, URL url, JavacFileManager javacFileManager) {
super(javacFileManager);
this.className = className;
this.url = url;
}
@Override
public Kind getKind() {
return Kind.valueOf("CLASS");
}
@Override
public URI toUri() {
try {
return url.toURI();
} catch (URISyntaxException e) {
e.printStackTrace();
}
return null;
}
@Override
public String getName() {
return className;
}
@Override
public InputStream openInputStream() {
try {
return url.openStream();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
@Override
public OutputStream openOutputStream() throws IOException {
return null;
}
@Override
public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
return null;
}
@Override
public Writer openWriter() throws IOException {
return null;
}
@Override
public long getLastModified() {
return 0;
}
@Override
public boolean delete() {
return false;
}
@Override
public String getShortName() {
return className.substring(className.lastIndexOf("."));
}
@Override
protected String inferBinaryName(Iterable iterable) {
return className;
}
@Override
public boolean equals(Object o) {
return false;
}
@Override
public int hashCode() {
return 0;
}
@Override
public boolean isNameCompatible(String simpleName, Kind kind) {
return false;
}
}
// 自定义 springboot 的类加载器
class SpringJavaFileManager extends JavacFileManager {
public SpringJavaFileManager(Context context, boolean b, Charset charset) {
super(context, b, charset);
}
@Override
public ClassLoader getClassLoader(Location location) {
nullCheck(location);
Iterable var2 = this.getLocation(location);
if (var2 == null) {
return null;
} else {
ListBuffer var3 = new ListBuffer();
Iterator var4 = var2.iterator();
while (var4.hasNext()) {
File var5 = (File) var4.next();
try {
var3.append(var5.toURI().toURL());
} catch (MalformedURLException var7) {
throw new AssertionError(var7);
}
}
return this.getClassLoader((URL[]) var3.toArray(new URL[var3.size()]));
}
}
protected ClassLoader getClassLoader(URL[] var1) {
ClassLoader var2 = this.getClass().getClassLoader();
try {
Class loaderClass = Class.forName("org.springframework.boot.loader.LaunchedURLClassLoader");
Class[] var4 = new Class[]{URL[].class, ClassLoader.class};
Constructor var5 = loaderClass.getConstructor(var4);
return (ClassLoader) var5.newInstance(var1, var2);
} catch (Throwable var6) {
}
return new URLClassLoader(var1, var2);
}
}
总结
动态编译在日常工作中很多场景可能不会用到,但是它可以很好的解决我们在特定场景中遇到的问题。 这篇文章可以为您提供一些观点。 当遇到类似的场景时,动态编译其实也是可以很好解决的!
最后希望大家能够在平常的工作中从编程中收获一些快乐~
读完两件事后
如果你觉得这篇内容对你有启发,我想请你帮我做两件小事。
1.点击“正在看”,让更多人看到此内容(点击“正在看”,bug-1)
2.关注公众号“正财云科技”,持续推送您精选的好文章
招聘
西安基地正财云技术团队(零)是一支充满激情和技术匠心的成长型团队。 规模500人左右。 除了日常业务开发外,还在云原生、区块链、人工智能、低代码平台、中间件、大数据、素材系统、工程平台、性能体验、可视化等领域进行技术探索和实践,推广和实施一系列内部技术产品,并不断探索技术新边界。 据悉,团队还参与社区建设,已经是googleflutter、scikit-learn、ApacheDubbo、ApacheRocketmq、ApachePulsar、CNFDapr、ApacheDolphinScheduler、alibabaSeata等多个优秀开源社区的贡献者。
如果你想改变,你还在被事情折腾,你希望开始折腾; 如果你想改变你想要达到的目标,你需要一个团队来支持,但没有地方让你带领人; 如果你想改变原有的智慧,却总有一层模糊的阳台纸……如果你相信信仰的力量,我相信平凡的人也能成就不平凡的事,我相信我能遇见更好的人我自己的版本。 如果你想随着你的业务腾飞而参与到腾飞的过程中,亲自推动一支对业务有深入理解、有完善技术体系、有技术创造价值、有溢出影响力的技术团队的成长过程,我想我们应该谈谈。 任何时候,我都在等你写点东西并发送给zcy-tc@cai-inc.com