【规则引擎】使用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代码后,编译加载到内存