我曾经见过一个“规则引擎”,是在应用系统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();
}
}
}