【规则引擎】使用JavaPoet生成代码和编译代码

本人有使用过2个工具自动生成过过JAVA代码,一个是jdt,另一个是JavaPoet。

两个比较起来,jdt可以正向生成JAVA,也可以反向把JAVA结构化成对抽象语法树ast,但使用起来比较复杂,过程中要创建各种类型类。案例->用AST写一个HELLOWORLD类  JDT工具类的使用

而JavaPoet只支持正向生成JAVA,使用起来比较方便,以下为使用JavaPoet生成JAVA的逻辑。

import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.TypeSpec;

import javax.lang.model.element.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;

public class JavaPoetTest {
    public static void main(String[] args) {
        //构建execute方法
        MethodSpec.Builder executeMethodBuild = MethodSpec
                .methodBuilder("execute")
                .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
                .addException(Exception.class)
                //使用$T,会import对应的类,不管是在代码中还是在注释中
                .addComment("$T", Arrays.class)
                .returns(ArrayList.class)
                .addParameter(HashMap.class, "inParameter")
                .addStatement("$T outParameter = new $T()", ArrayList.class,ArrayList.class)
                //使用$L,会直接替换成代码
                //使用$S,会设置成字符串,自动加上""
                .addStatement("$T $L = inParameter.containsKey($S) ? $T.valueOf(($T)inParameter.get($S)) : $L",
                        Integer.class, "myAge", "myAge", Integer.class, String.class, "myAge", null)
                .beginControlFlow("if(myAge != null)")
                .addStatement("$T.out.println($S + myAge)",System.class, "my age is ")
                .endControlFlow()
                .addStatement("return outParameter");
        MethodSpec executeMethod = executeMethodBuild.build();

        MethodSpec mainMethod = MethodSpec
                .methodBuilder("main")
                .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
                .addException(Exception.class)
                .returns(void.class)
                .addParameter(String[].class, "args")
                .addStatement("$T inParameter = new $T()",HashMap.class,HashMap.class)
                .addStatement("inParameter.put($S,$S)","myAge","10")
                .addStatement("execute(inParameter)")
                .build();

        //构建类
        TypeSpec.Builder classBuilder = TypeSpec
                .classBuilder("Test")
                .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
                .addMethod(mainMethod)
                .addMethod(executeMethod);
        TypeSpec finalClass = classBuilder.build();
        JavaFile javaFile = JavaFile
                .builder("com.sephy.util.java.build",finalClass)
                .build();
        System.out.println(javaFile.toString());

    }
}

生成代码如下:

package com.sephy.util.java.build;

import java.lang.Exception;
import java.lang.Integer;
import java.lang.String;
import java.lang.System;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;

public final class Test {
  public static void main(String[] args) throws Exception {
    HashMap inParameter = new HashMap();
    inParameter.put("myAge","10");
    execute(inParameter);
  }

  public static ArrayList execute(HashMap inParameter) throws Exception {
    // Arrays
    ArrayList outParameter = new ArrayList();
    Integer myAge = inParameter.containsKey("myAge") ? Integer.valueOf((String)inParameter.get("myAge")) : null;
    if(myAge != null) {
      System.out.println("my age is " + myAge);
    }
    return outParameter;
  }
}

生成代码后,可以将其编译成class,编译方法如下:

import javax.tools.*;
import java.io.File;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;

public class JavaCompilerUtils {
    private static JavaCompiler javac = ToolProvider.getSystemJavaCompiler();

    public static void main(String[] args) {
        System.out.println(System.getProperty("path.separator"));
        String outPath = "D:/java_test/classPathJar/";
        File f1 = new File("D:/java_test/classPathJar/Test.java");
        String classPath = "D:/java_test/classPathJar/utils-0.0.1-SNAPSHOT.jar";
        List<String> paths = new ArrayList<>();
        paths.add(classPath);
        List<File> source = Arrays.asList(f1);
        compile(source,outPath,paths);
    }

    /**
     * 编译java文件
     * @param sourceFiles java源文件全路径
     * @param outPath class输出路径
     * @param classPaths 当被编译的java文件中有import非jdk的类,需要将该类达成jar包,路径放到classPaths中,否则会编译失败
     */
    public static void compile(List<File> sourceFiles, String outPath, List<String> classPaths){
        long t1 = System.currentTimeMillis();
        File outFile = new File(outPath);
        if(!outFile.exists()){
            outFile.mkdirs();
        }
        ArrayList<String> msgs = new ArrayList<>();
        classPaths.add(outPath);
        String paths = String.join(System.getProperty("path.separator"),classPaths);
        List<String> compileOptions = Arrays.asList("-classpath",paths);
        System.out.println("outPath="+outPath);
        System.out.println("classPath="+paths);
        try (StandardJavaFileManager fileManager = javac.getStandardFileManager(null,null, Charset.forName("UTF-8"))) {
            Iterable<? extends JavaFileObject> compilationUnits = fileManager.getJavaFileObjectsFromFiles(sourceFiles);
            DiagnosticCollector<JavaFileObject> collector = new DiagnosticCollector<>();
            JavaCompiler.CompilationTask task = javac.getTask(null,fileManager,collector,compileOptions,null,compilationUnits);
            boolean passed = task.call();
            if(!passed){
                if(collector != null && collector.getDiagnostics() != null && collector.getDiagnostics().size() > 0){
                    collector.getDiagnostics().forEach(dia -> {
                        boolean hasWarnings = false;
                        boolean hasErrors = false;
                        if(dia.getKind() == Diagnostic.Kind.NOTE
                                || dia.getKind() == Diagnostic.Kind.MANDATORY_WARNING
                                || dia.getKind() == Diagnostic.Kind.WARNING){
                            hasWarnings = true;
                        }
                        if(dia.getKind() == Diagnostic.Kind.ERROR || dia.getKind() == Diagnostic.Kind.OTHER){
                            hasErrors = true;
                        }
                        if(hasErrors){
                            String msg = dia.getKind().name()+" : "+dia.getSource().getName()+" : "+dia.getMessage(Locale.CHINESE);
                            System.out.println(msg);
                        }
                    });
                }
            }

        }catch (Exception e){
            e.printStackTrace();
        }
        long t2 = System.currentTimeMillis();
        System.out.println("编译结束,耗时:"+(t2-t1));
    }
}

编译class后,如果想将class动态读取到jvm中,可以使用以下方法:

import java.lang.reflect.Method;
import java.net.URI;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashMap;

public class JavaClassLoader extends URLClassLoader{
        public JavaClassLoader (URL[] urls){
            super(urls);
        }
    public static void main(String[] args) throws Exception{
        File file = new File("D:/java_test/classPathJar/utils-0.0.1-SNAPSHOT.jar");
                URL[] urls = new URL[]{file.toURI().toURL()};
                JavaClassLoader jcl = new JavaClassLoader(urls);
        String path = "/D:/java_test/classPathJar";
        String name = "Test";
        Class clazz = jcl.findClass(path,"com.sephy.util.java.build",name);
        Method method = clazz.getMethod("execute", HashMap.class);
        HashMap map = new HashMap();
        map.put("myAge","100");
        Object o = method.invoke(null,map);
        System.out.println("success");
    }

    /**
     * 通过class文件加载class
     * @param path
     * @param packageName
     * @param className
     * @return
     */
    public Class findClass(String path, String packageName, String className){
        byte[] classBytes = readByte(path,className);
        return findClass(classBytes,packageName,className);
    }
    /**
     * 通过字节加载class
     * @param classBytes
     * @param packageName
     * @param className
     * @return
     */
    public Class findClass(byte[] classBytes, String packageName, String className){
        try {
            Class<?> clazz = findClassByName(packageName, className);
            if(clazz == null){
                clazz = defineClass(packageName+"."+className,classBytes,0,classBytes.length);
            }
            return clazz;
        }catch (Exception e){
            e.printStackTrace();
        }
        return null;
    }
    /**
     * 查询classloader中的class
     * @param packageName
     * @param className
     * @return
     */
    public Class findClassByName(String packageName, String className){
        try {
            Class<?> clazz = loadClass(packageName+"."+className);
            return clazz;
        }catch (Exception e){
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 读取class文件的字节流
     * @param filePath
     * @param className
     * @return
     */
    public static byte[] readByte(String filePath, String className){
        long t1 = System.currentTimeMillis();
        try {
            String classFullPath = "file://"+filePath+"/"+className+".class";
            System.out.println("class路径为:"+classFullPath);

            Path path = Paths.get(new URI(classFullPath));
            byte[] classBytes = Files.readAllBytes(path);
            return classBytes;
        }catch (Exception e){
            e.printStackTrace();
        }
        return null;
    }
}

此一套构建JAVA->编译JAVA->加载JVM,实际上就是规则引擎的核心,把结构化规则配置转成JAVA代码后,编译加载到内存

{context}