Java动态编译

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 不然会找不到类

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值