java 动态编译 spring_springboot 2 下 java 代码 动态编译 动态加载 实现

先放出代码,再说有几个坑的地方:

package com.yrxd.telemarketing.service;

import org.apache.log4j.Logger;

import org.springframework.boot.loader.LaunchedURLClassLoader;

import javax.lang.model.element.Modifier;

import javax.lang.model.element.NestingKind;

import javax.tools.*;

import java.io.*;

import java.net.*;

import java.nio.CharBuffer;

import java.util.*;

import java.util.jar.JarEntry;

/**

* Created by xidongzhou1 on 2020/1/17.

*/

public class CusCompiler {

static Logger logger = Logger.getLogger(CusCompiler.class);

static class CustomJavaFileObject implements JavaFileObject {

private String binaryName;

private URI uri;

private String name;

public String binaryName() {

return binaryName;

}

public CustomJavaFileObject(String binaryName, URI uri) {

this.uri = uri;

this.binaryName = binaryName;

name = uri.getPath() == null ? uri.getSchemeSpecificPart() : uri.getPath();

}

@Override

public Kind getKind() {

return Kind.CLASS;

}

@Override

public boolean isNameCompatible(String simpleName, Kind kind) {

String baseName = simpleName + kind.extension;

return kind.equals(getKind()) && (baseName.equals(getName()) || getName().endsWith("/" + baseName));

}

@Override

public NestingKind getNestingKind() {

throw new UnsupportedOperationException();

}

@Override

public Modifier getAccessLevel() {

throw new UnsupportedOperationException();

}

@Override

public URI toUri() {

return uri;

}

@Override

public String getName() {

return name;

}

@Override

public InputStream openInputStream() throws IOException {

return uri.toURL().openStream();

}

@Override

public OutputStream openOutputStream() throws IOException {

throw new UnsupportedOperationException();

}

@Override

public Reader openReader(boolean ignoreEncodingErrors) throws IOException {

throw new UnsupportedOperationException();

}

@Override

public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {

throw new UnsupportedOperationException();

}

@Override

public Writer openWriter() throws IOException {

throw new UnsupportedOperationException();

}

@Override

public long getLastModified() {

return 0;

}

@Override

public boolean delete() {

throw new UnsupportedOperationException();

}

}

static class MemoryInputJavaFileObject extends SimpleJavaFileObject {

final String code;

MemoryInputJavaFileObject(String name, String code) {

super(URI.create(name.replaceAll("\\.", "/") + Kind.SOURCE.extension), Kind.SOURCE);

this.code = code;

}

@Override

public CharBuffer getCharContent(boolean ignoreEncodingErrors) {

return CharBuffer.wrap(code);

}

}

static class MemoryOutputJavaFileObject extends SimpleJavaFileObject {

final String name;

Map class_out;

MemoryOutputJavaFileObject(String name, Map out) {

super(URI.create(name.replaceAll("\\.", "/") + Kind.SOURCE.extension), Kind.CLASS);

this.name = name;

this.class_out = out;

}

@Override

public OutputStream openOutputStream() {

return new FilterOutputStream(new ByteArrayOutputStream()) {

@Override

public void close() throws IOException {

out.close();

ByteArrayOutputStream bos = (ByteArrayOutputStream) out;

class_out.put(name, bos.toByteArray());

}

};

}

}

static class SpringBootJarFileManager implements JavaFileManager {

private URLClassLoader classLoader;

private StandardJavaFileManager standardJavaFileManager;

final Map classBytes = new HashMap<>();

SpringBootJarFileManager(StandardJavaFileManager standardJavaFileManager, URLClassLoader systemLoader) {

this.classLoader = new URLClassLoader(systemLoader.getURLs(), systemLoader);

this.standardJavaFileManager = standardJavaFileManager;

}

@Override

public ClassLoader getClassLoader(Location location) {

return classLoader;

}

private List find(String packageName) {

List result = new ArrayList<>();

String javaPackageName = packageName.replaceAll("\\.", "/");

try {

Enumeration urls = classLoader.findResources(javaPackageName);

while (urls.hasMoreElements()) {

URL ll = urls.nextElement();

String ext_form = ll.toExternalForm();

String jar = ext_form.substring(0, ext_form.lastIndexOf("!"));

String pkg = ext_form.substring(ext_form.lastIndexOf("!") + 1);

JarURLConnection conn = (JarURLConnection) ll.openConnection();

conn.connect();

Enumeration jar_items = conn.getJarFile().entries();

while (jar_items.hasMoreElements()) {

JarEntry item = jar_items.nextElement();

if (item.isDirectory() || (!item.getName().endsWith(".class"))) {

continue;

}

if (item.getName().lastIndexOf("/") != (pkg.length() - 1)) {

continue;

}

String name = item.getName();

URI uri = URI.create(jar + "!/" + name);

String binaryName = name.replaceAll("/", ".");

binaryName = binaryName.substring(0, binaryName.indexOf(JavaFileObject.Kind.CLASS.extension));

result.add(new CustomJavaFileObject(binaryName, uri));

}

}

} catch (Exception e) {

e.printStackTrace();

}

return result;

}

@Override

public Iterable list(Location location, String packageName, Set kinds, boolean recurse) throws IOException {

Iterable ret = null;

if (location == StandardLocation.PLATFORM_CLASS_PATH) {

ret = standardJavaFileManager.list(location, packageName, kinds, recurse);

} else if (location == StandardLocation.CLASS_PATH && kinds.contains(JavaFileObject.Kind.CLASS)) {

ret = find(packageName);

if (ret == null || (!ret.iterator().hasNext())) {

ret = standardJavaFileManager.list(location, packageName, kinds, recurse);

}

} else {

ret = Collections.emptyList();

}

return ret;

}

@Override

public String inferBinaryName(Location location, JavaFileObject file) {

String ret = "";

if (file instanceof CustomJavaFileObject) {

ret = ((CustomJavaFileObject)file).binaryName;

} else {

ret = standardJavaFileManager.inferBinaryName(location, file);

}

return ret;

}

@Override

public boolean isSameFile(FileObject a, FileObject b) {

throw new UnsupportedOperationException();

}

@Override

public boolean handleOption(String current, Iterator remaining) {

return standardJavaFileManager.handleOption(current, remaining);

}

@Override

public boolean hasLocation(Location location) {

return location == StandardLocation.CLASS_PATH || location == StandardLocation.PLATFORM_CLASS_PATH;

}

@Override

public JavaFileObject getJavaFileForInput(Location location, String className, JavaFileObject.Kind kind) throws IOException {

throw new UnsupportedOperationException();

}

@Override

public FileObject getFileForInput(Location location, String packageName, String relativeName) throws IOException {

throw new UnsupportedOperationException();

}

@Override

public FileObject getFileForOutput(Location location, String packageName, String relativeName, FileObject sibling) throws IOException {

throw new UnsupportedOperationException();

}

@Override

public void flush() throws IOException {

}

@Override

public void close() throws IOException {

classBytes.clear();

}

@Override

public int isSupportedOption(String option) {

return -1;

}

public Map getClassBytes() {

return new HashMap(this.classBytes);

}

@Override

public JavaFileObject getJavaFileForOutput(Location location, String className, JavaFileObject.Kind kind,

FileObject sibling) throws IOException {

if (kind == JavaFileObject.Kind.CLASS) {

return new MemoryOutputJavaFileObject(className, classBytes);

} else {

return standardJavaFileManager.getJavaFileForOutput(location, className, kind, sibling);

}

}

}

private static class MemoryClassLoader extends LaunchedURLClassLoader {

Map classBytes = new HashMap<>();

public MemoryClassLoader(Map classBytes, ClassLoader classLoader) {

super(new URL[0], classLoader);

this.classBytes.putAll(classBytes);

}

@Override

protected Class> findClass(String name) throws ClassNotFoundException {

System.out.println("findClass: " + name);

byte[] buf = classBytes.get(name);

if (buf == null) {

return super.findClass(name);

}

classBytes.remove(name);

return defineClass(name, buf, 0, buf.length);

}

}

public Class> loadClass(String name, Map classBytes) throws Exception {

ClassLoader loader = new ClassLoader() {

@Override

public Class> loadClass(String name) throws ClassNotFoundException {

Class> r = null;

if (classBytes.containsKey(name)) {

byte[] buf = classBytes.get(name);

r = defineClass(name, buf, 0, buf.length);

} else {

r = systemClassLoader.loadClass(name);

}

return r;

}

};

return loader.loadClass(name);

}

private URLClassLoader systemClassLoader;

public CusCompiler(URLClassLoader loader) {

systemClassLoader = loader;

}

public Map compile(String className, String code) throws Exception {

JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();

StandardJavaFileManager stdManager = compiler.getStandardFileManager(null, null, null);

SpringBootJarFileManager springBootJarFileManager = new SpringBootJarFileManager(stdManager, systemClassLoader);

JavaFileObject javaFileObject = new MemoryInputJavaFileObject(className, code);

List options = new ArrayList<>();

//options.addAll(Arrays.asList("-classpath", System.getProperty("java.class.path"), "-bootclasspath", System.getProperty("sun.boot.class.path"), "-extdirs", System.getProperty("java.ext.dirs")));

JavaCompiler.CompilationTask task = compiler.getTask(null, springBootJarFileManager, null, null, null, Arrays.asList(javaFileObject));

Boolean compileRet= task.call();

if (compileRet == null || (!compileRet.booleanValue())) {

throw new RuntimeException("java filter compile error");

}

for (String key : springBootJarFileManager.getClassBytes().keySet()) {

logger.info("class: " + key + " len: " + Integer.valueOf(springBootJarFileManager.getClassBytes().get(key).length).toString());

}

return springBootJarFileManager.getClassBytes();

}

}

调用的时候 sample 如下:

class_name = "com.yrxd.telemarketing.script." + class_name;

//CusCompiler compiler = new CusCompiler((URLClassLoader)getClass().getClassLoader());

CusCompiler compiler = new CusCompiler((URLClassLoader)Thread.currentThread().getContextClassLoader());

Map results = compiler.compile(class_name, script);

Class> clazz = compiler.loadClass(class_name, results);

踩坑的地方如下:

启动 springboot 的时候,加上 -Xbootclasspath/a:/usr/local/java/lib/tools.jar

由于 springboot 特殊的机制,它的启动 launcher 是可以从 fat jar 里面直接读取资源的,所以要充分利用这一点,编译的时候,利用 springboot 的loader 来查找资源,大胆的假设这个 loader 的类型为 URLClassLoader

尽量不要直接用springboot 启动的 loader, loader 里面的资源读过一次之后,后面读不出来,这样导致的后果是: 在 springboot 的 main 函数里面,直接动态加载会有问题,需要新建一个loader,继承springboot 的loader

注意适用这种方式后,系统会有多个 loader 存在,系统的的loader 是无法loader 这里动态加载的class, 由此可能引发一系列的异常,需要 case by case分析

同一个classloader,对于相同的 classname, 不能 defineclass 调用多次,否则会出异常

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值