java动态编译

我曾经见过一个“规则引擎”,是在应用系统web界面直接编写java代码,然后保存后,规则即生效,我一直很是奇怪,这是如何实现的呢?实际这就好像jsp,被中间件动态的编译成java文件,有被动态的编译成class,同时又动态的加载到classloader中。所以,本质上,纯java得规则引擎,是100%可以实现的。

 

1、动态生成java源代码。这个过程太过简单,直接略过。

2、动态编译。

我看我们自己的规则引擎也有动态编译,就是在生成BOM模型的时候。但是是调用Process执行javac。但这种方式坦白来说不好。因为javac,的命令参数写法和操作系统有关,也就是windows和linux的写法有少量不同。后来发现jdk提供一个动态编译的类。

JavaCompiler javac;

javac = ToolProvider.getSystemJavaCompiler();

int compilationResult = javac.run(null,null,null, "-g","-verbose",javaFile);

这样就可以动态进行编译。前两个参数是输入参数、输出参数,我觉得没有什么用,第三个参数是编译输出信息,默认输出到System.out.err里面。从第四个参数开始,就是javac的参数,可以用数组,也可以直接逗号分割。

3、动态加载。

动态加载实际就是调用ClassLoader。当然需要反射机制调用其中的一个内部分方法,使之变成外部可调用的方法。

File file = new File("/Users/yangming/Work/DevWorkSpace/ssac/gx_hx/test/"); URLClassLoader classloader = (URLClassLoader) ClassLoader.getSystemClassLoader();

Method add = URLClassLoader.class.getDeclaredMethod("addURL", new Class[]{URL.class});

add.setAccessible(true);

add.invoke(classloader, new Object[]{file.toURI().toURL()});

Class c = classloader.loadClass("Test");

Object o = c.newInstance();

Method m = c.getDeclaredMethod("getString");

m.invoke(o, null);

这样就完成了类的动态加载。

package javacomplie;

public interface MyJavaCompiler {

    Class<?> compile(String code);
}
package javacomplie;

import org.springframework.util.StringUtils;

import javax.tools.*;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class MyJavaCompilerImpl implements MyJavaCompiler {

    private static final Pattern PACKAGE_PATTERN=Pattern.compile("package\\s+([_a-zA-z][_a-zA-Z0-9\\.]*);");

    private static final Pattern CLASS_PATTERN=Pattern.compile("class\\s+([_a-zA-z][_a-zA-Z0-9]*)\\s+");

    private final JavaCompiler javaCompiler;

    private URLClassLoader urlClassLoader;

    private String classPath;

    protected String endcoding="UTF-8";

    public MyJavaCompilerImpl() {
        javaCompiler= ToolProvider.getSystemJavaCompiler();
        if(javaCompiler==null){
            throw new IllegalStateException("获取不到java编译器");
        }
        this.urlClassLoader=(URLClassLoader)this.getClass().getClassLoader();

        buildClassPath();
    }

    private void buildClassPath(){
        StringBuffer stringBuffer=new StringBuffer();
        for(URL url:this.urlClassLoader.getURLs()){
            String path=url.getFile();
            stringBuffer.append(path).append(File.pathSeparator);
        }
        this.classPath=stringBuffer.toString();
    }



    @Override
    public Class<?> compile(String code) {
        Matcher matcher=PACKAGE_PATTERN.matcher(code);
        String packageName=null;
        if(matcher.find()){
            packageName=matcher.group(1);
        }
        matcher=CLASS_PATTERN.matcher(code);
        String className;
        if(matcher.find()){
            className=matcher.group(1);
        }else{
            throw new IllegalArgumentException("class Name not find");
        }
        String fullClassName=null;
        if(StringUtils.isEmpty(packageName)){
            fullClassName=packageName+"."+className;
        }else{
            fullClassName=className;
        }
        return doCompile(fullClassName,code);
    }

    public Class<?> doCompile(String fullClassName,String code){
        DiagnosticCollector<JavaFileObject> diagnosticCollector=new DiagnosticCollector<JavaFileObject>();
        StandardJavaFileManager standardJavaFileManager=javaCompiler.getStandardFileManager(diagnosticCollector,null,null);

        ClassFileManager classFileManager=new ClassFileManager(standardJavaFileManager);

        CharJavaFileObject charJavaFileObject=new CharJavaFileObject(fullClassName,code);
        try{
            List<JavaFileObject> jfiles=new ArrayList<>();
            jfiles.add(charJavaFileObject);
            List<String> options=new ArrayList<>();
            options.add("-encoding");
            options.add(endcoding);
            options.add("-classpath");
            options.add(classPath);
            JavaCompiler.CompilationTask task=javaCompiler.getTask(null,classFileManager,diagnosticCollector,options,null,jfiles);
            boolean success=task.call();
            for(Diagnostic<? extends JavaFileObject> diagnostic : diagnosticCollector.getDiagnostics()){
                if(diagnostic.getKind()==Diagnostic.Kind.ERROR){
                    String errorCode=diagnostic.getLineNumber()+"--"+charJavaFileObject.getLineCode(diagnostic.getLineNumber());
                    throw new java.lang.Error(errorCode);
                }
            }
            MyClassLoader myClassLoader=new MyClassLoader(this.urlClassLoader);
            for(ByteJavaFileObject byteJavaFileObject:classFileManager.getInnerClassJavaClassObejct()){
                myClassLoader.defineClass(byteJavaFileObject);
            }
            ByteJavaFileObject bjfo=classFileManager.getByteJavaFileObject();
            Class<?> clazz=myClassLoader.defineClass(bjfo);
            try{
                myClassLoader.close();
            }catch (Exception e ){

            }
            return clazz;

        } finally {
             charJavaFileObject.delete();

            try {
                classFileManager.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }


    @Override
    public String toString() {
        return "MyJavaCompilerImpl{" +
                "javaCompiler=" + javaCompiler +
                ", urlClassLoader=" + urlClassLoader +
                ", classPath='" + classPath + '\'' +
                ", endcoding='" + endcoding + '\'' +
                '}';
    }
}
package javacomplie;


import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;

public class MyClassLoader extends URLClassLoader {

    public MyClassLoader(ClassLoader classLoader){
        super(new URL[0],classLoader);
    }

    public  String getClassName(ByteJavaFileObject byteJavaFileObject){
        String name=byteJavaFileObject.getName();
        name=name.substring(1,name.length()-6);
        name=name.replace("/",".");
        return name;
    }

    public  Class<?> defineClass(ByteJavaFileObject byteJavaFileObject)  {
        String name=getClassName(byteJavaFileObject);
        byte[] classData=byteJavaFileObject.getBytes();
        return super.defineClass(name,classData,0,classData.length);
    }

}
package javacomplie;

import org.springframework.util.CollectionUtils;

import javax.tools.FileObject;
import javax.tools.ForwardingJavaFileManager;
import javax.tools.JavaFileManager;
import javax.tools.JavaFileObject;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class ClassFileManager extends ForwardingJavaFileManager<JavaFileManager> {

    private ByteJavaFileObject byteJavaFileObject;

    private List<ByteJavaFileObject> innerObjects=new ArrayList<ByteJavaFileObject>();

    protected ClassFileManager(JavaFileManager javaFileManager){
        super(javaFileManager);
    }

    public ByteJavaFileObject getByteJavaFileObject() {
        return byteJavaFileObject;
    }
    public byte[] getJavaClass(){
        return byteJavaFileObject.getBytes();
    }

    public List<ByteJavaFileObject> getInnerClassJavaClassObejct(){
        if(CollectionUtils.isEmpty(innerObjects)){
            int size=innerObjects.size();
            if(size==1){
                return Collections.emptyList();
            }
            return this.innerObjects.subList(0,size-1);
        }
        return Collections.emptyList();

    }
    @Override
    public JavaFileObject getJavaFileForOutput(Location location, String className, JavaFileObject.Kind kind, FileObject sibling) throws IOException {
        this.byteJavaFileObject=new ByteJavaFileObject(className,kind);
        innerObjects.add(byteJavaFileObject);
        return byteJavaFileObject;
    }

    @Override
    public void close() throws IOException {
        if(byteJavaFileObject!=null){
            byteJavaFileObject.delete();
        }
        super.close();
    }
}
package javacomplie;

import javax.tools.SimpleJavaFileObject;
import java.io.IOException;
import java.io.LineNumberReader;
import java.io.StringReader;
import java.net.URI;

public class CharJavaFileObject extends SimpleJavaFileObject {
    //等待编译的源码字段
    private String contents;

    //java源代码 => CharJavaFileObject 的时候使用
    public CharJavaFileObject(String className, String contents) {
        super(URI.create("string:///" + className.replace(".", "/") + Kind.SOURCE.extension), Kind.SOURCE);
        this.contents = contents;
    }

    //字符串源码会调用该方法
    @Override
    public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
        return contents;
    }

    /**
     * 获取某个位置的代码
     * @param line
     * @return
     */
    public String getLineCode(long line){
        LineNumberReader reader=new LineNumberReader(new StringReader(contents));
        int num=0;
        String codeLine=null;
        try {
            while ((codeLine = reader.readLine()) != null) {
                num++;
                if(num==line){
                    break;
                }
            }
        }catch (Exception e){

        }finally {
            if(reader!=null){
                try {
                    reader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return codeLine;
    }
}
package javacomplie;

import javax.tools.SimpleJavaFileObject;
import java.io.ByteArrayOutputStream;
import java.io.OutputStream;
import java.net.URI;

/**
 * 自定义一个编译之后的字节码对象
 */
public class ByteJavaFileObject extends SimpleJavaFileObject {

    //存放编译后的字节码
    private ByteArrayOutputStream byteArrayOutputStream=new ByteArrayOutputStream();

    public ByteJavaFileObject(String className, Kind kind){
        super(URI.create("string:///" + className.replace(".", "/") + kind.extension), kind);
    }

    //StringJavaFileManage 编译之后的字节码输出会调用该方法(把字节码输出到outputStream)
    @Override
    public OutputStream openOutputStream() {
        return byteArrayOutputStream;
    }

    public byte[] getBytes(){
        return byteArrayOutputStream.toByteArray();
    }
}
package javacomplie;

public class Test {

    public static void main(String[] args){

        String ok = "public class HelloWorld {\n" +
                "    public static void main(String[] args) {\n" +
                "       System.out.println(\"Hello World!\");\n" +
                "    }\n" +
                "}";
       MyJavaCompilerImpl compiler= new MyJavaCompilerImpl();

        System.out.println(compiler);
        Class<?> clazz=compiler.compile(ok);
        System.out.println(clazz);
        try {
            Object obj=clazz.newInstance();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }

}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值