Java动态编译.java文件
最近在写一个实时ETL的时候为了增强清洗能力想着增加一个用户自定义方法的能力,自然而然的想到了使用动态编译。
以下是相关代码
public class UDFParse {
private URLClassLoader urlClassLoader = (URLClassLoader) Thread.currentThread().getContextClassLoader();
private String fullClassName;
private String sourceCode;
private static Map<String, ByteJavaFileObject> javaFileObjectMap = new ConcurrentHashMap<>();
private JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
private DiagnosticCollector<JavaFileObject> diagnosticsCollector = new DiagnosticCollector<>();
private long compilerTakeTime;
public UDFParse(String sourceCode) {
this.sourceCode = sourceCode;
this.fullClassName = getFullClassName(sourceCode);
}
public boolean compiler() {
long startTime = System.currentTimeMillis();
Object o = javaFileObjectMap.get(fullClassName);
if (o != null) {
compilerTakeTime = System.currentTimeMillis() - startTime;
return true;
}
StandardJavaFileManager standardFileManager = compiler.getStandardFileManager(diagnosticsCollector, null, null);
JavaFileManager javaFileManager = new StringJavaFileManage(standardFileManager);
JavaFileObject javaFileObject = new StringJavaFileObject(fullClassName, sourceCode);
List<String> options = new ArrayList<String>();
options.add("-classpath");
StringBuilder sb = new StringBuilder();
for (URL url:urlClassLoader.getURLs()){
sb.append(url.getFile()).append(File.pathSeparator);
}
options.add(sb.toString());
JavaCompiler.CompilationTask task = compiler.getTask(null, javaFileManager, diagnosticsCollector, options, null, Arrays.asList(javaFileObject));
compilerTakeTime = System.currentTimeMillis() - startTime;
Boolean call = task.call();
return call;
}
public Object getUDF() throws Exception {
StringClassLoader stringClassLoader = new StringClassLoader();
Class<?> obj = stringClassLoader.findClass(fullClassName);
Constructor<?> constructor = obj.getConstructor();
return constructor.newInstance();
}
public String getCompilerMessage() {
StringBuilder sb = new StringBuilder();
List<Diagnostic<? extends JavaFileObject>> diagnostics = diagnosticsCollector.getDiagnostics();
for (Diagnostic diagnostic : diagnostics) {
sb.append(diagnostic.toString()).append("\r\n");
}
return sb.toString();
}
private long getCompilerTakeTime() {
return compilerTakeTime;
}
private static String getFullClassName(String sourceCode) {
String className = "";
Pattern pattern = Pattern.compile("package\\s+\\S+\\s*;");
Matcher matcher = pattern.matcher(sourceCode);
if (matcher.find()) {
className = matcher.group().replaceFirst("package", "").replace(";", "").trim() + ".";
}
pattern = Pattern.compile("class((?:(?!extends).))+");
matcher = pattern.matcher(sourceCode);
if (matcher.find()) {
className += matcher.group().replaceFirst("class", "").replace("{", "").trim();
}
return className;
}
private class StringJavaFileObject extends SimpleJavaFileObject {
private String contents;
public StringJavaFileObject(String className, String contents) {
super(URI.create("string:///" + className.replaceAll("\\.", "/") + Kind.SOURCE.extension), Kind.SOURCE);
this.contents = contents;
}
@Override
public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
return contents;
}
}
private class ByteJavaFileObject extends SimpleJavaFileObject {
private ByteArrayOutputStream outPutStream;
public ByteJavaFileObject(String className, Kind kind) {
super(URI.create("string:///" + className.replaceAll("\\.", "/") + Kind.SOURCE.extension), kind);
}
@Override
public OutputStream openOutputStream() {
outPutStream = new ByteArrayOutputStream();
return outPutStream;
}
public byte[] getCompiledBytes() {
return outPutStream.toByteArray();
}
}
private class StringJavaFileManage extends ForwardingJavaFileManager {
StringJavaFileManage(JavaFileManager fileManager) {
super(fileManager);
}
@Override
public JavaFileObject getJavaFileForOutput(JavaFileManager.Location location, String className, JavaFileObject.Kind kind, FileObject sibling) throws IOException {
ByteJavaFileObject javaFileObject = new ByteJavaFileObject(className, kind);
javaFileObjectMap.put(className, javaFileObject);
return javaFileObject;
}
}
private class StringClassLoader extends ClassLoader {
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
ByteJavaFileObject fileObject = javaFileObjectMap.get(name);
if (fileObject != null) {
byte[] bytes = fileObject.getCompiledBytes();
return defineClass(name, bytes, 0, bytes.length);
}
try {
return urlClassLoader.loadClass(name);
} catch (Exception e) {
return super.findClass(name);
}
}
}
}
这块代码是从网上摘抄下来之后,自己适配了一些。主要适配的地方其实是动态加载的时候包环境问题,在本地测试可以调通但是上传到服务器无法加载到类
关键点在以下两点
1、要将class给传入到编译环境中
options.add("-classpath");
StringBuilder sb = new StringBuilder();
for (URL url:urlClassLoader.getURLs()){
sb.append(url.getFile()).append(File.pathSeparator);
}
options.add(sb.toString());
在编译的时候,服务器上尤其是我是使用的hdfs做文件的存储以及任务提交到yarn上没有自定义的一些类的执行环境,这时候就需要在编译的时候把这些类给声明进去
2、urlClassLoader是同一个对象
for (URL url:urlClassLoader.getURLs()){
sb.append(url.getFile()).append(File.pathSeparator);
}
ByteJavaFileObject fileObject = javaFileObjectMap.get(name);
if (fileObject != null) {
byte[] bytes = fileObject.getCompiledBytes();
return defineClass(name, bytes, 0, bytes.length);
}
try {
return urlClassLoader.loadClass(name);
} catch (Exception e) {
return super.findClass(name);
}
编译时和获取时使用的loader必须是同一个loader 不然会找不到类