与Drools 5.6.0共享ClassLoader以允许运行时在内存中编译类
我在写一个简单的实用程序,它读取XML文件,将它们的节点转换为POJO,将它们加载到Drools的WM中并最终应用一些规则给他们。你可以在我的GitHub profile上找到整个项目。不幸的是,尽管我做了所有的努力,但我无法让Drools“喜欢”在运行时编译的任何类的实例。我看到许多人也遇到ClassLoader的问题,所以我怀疑它可能是它的错......我准备了一个最小工作示例,供您尝试使用GitHub以及下面的这些示例。它需要一些其他小文件(MemoryFileManager
,MemoryJavaClassObject
和MemoryJavaFileObject
),为简洁起见,它们仅在GitHub上可用。为了正常工作,此示例要求您的JVM是JDK> = 6,并且您的classpath
上有tools.jar
或classes.jar
。该示例如下:与Drools 5.6.0共享ClassLoader以允许运行时在内存中编译类
public class Example {
/**
* @param args
*/
public static void main(String[] args) {
// Setting the strings that we are going to use...
String name = "Person";
String content = "public class " + name + " {\n";
content += " private String name;\n";
content += " public Person() {\n";
content += " }\n";
content += " public Person(String name) {\n";
content += " this.name = name;\n";
content += " }\n";
content += " public String getName() {\n";
content += " return name;\n";
content += " }\n";
content += " public void setName(String name) {\n";
content += " this.name = name;\n";
content += " }\n";
content += " @Override\n";
content += " public String toString() {\n";
content += " return \"Hello, \" + name + \"!\";\n";
content += " }\n";
content += "}\n";
String value = "HAL";
String rules = "rule \"Alive\"\n";
rules += "when\n";
rules += "then\n";
rules += " System.out.println(\"I'm alive!\")\n";
rules += "end\n";
rules += "\n";
rules += "rule \"Print\"\n";
rules += "when\n";
rules += " $o: Object()\n";
rules += "then\n";
rules += " System.out.println(\"DRL> \" + $o.toString())\n";
rules += "end\n";
// Compiling the given class in memory
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
JavaFileManager manager = new MemoryFileManager(compiler.getStandardFileManager(null, null, null));
ClassLoader classLoader = manager.getClassLoader(null);
List<JavaFileObject> files = new ArrayList<JavaFileObject>();
files.add(new MemoryJavaFileObject(name, content));
compiler.getTask(null, manager, null, null, null, files).call();
try {
// Instantiate and set the new class
Class<?> person = classLoader.loadClass(name);
Method method = person.getMethod("setName", String.class);
Object instance = person.newInstance();
method.invoke(instance, value);
System.out.println(instance);
System.out.println("We get a salutation, so Person is now a compiled class in memory loaded by the given ClassLoader.");
// Use the same instance in Drools (by means of the shared ClassLoader)
KnowledgeBuilderConfiguration config1 = KnowledgeBuilderFactory.newKnowledgeBuilderConfiguration(null, classLoader);
KnowledgeBuilder builder = KnowledgeBuilderFactory.newKnowledgeBuilder(config1);
builder.add(ResourceFactory.newByteArrayResource(rules.getBytes()), ResourceType.DRL);
if (builder.hasErrors()) {
for (KnowledgeBuilderError error : builder.getErrors())
System.out.println(error.toString());
System.exit(-1);
}
KnowledgeBaseConfiguration config2 = KnowledgeBaseFactory.newKnowledgeBaseConfiguration(null, classLoader);
KnowledgeBase base = KnowledgeBaseFactory.newKnowledgeBase(config2);
base.addKnowledgePackages(builder.getKnowledgePackages());
StatefulKnowledgeSession session = base.newStatefulKnowledgeSession();
session.insert(instance);
session.fireAllRules();
} catch (ClassNotFoundException e) {
System.out.println("Class not found!");
} catch (IllegalAccessException e) {
System.out.println("Illegal access!");
} catch (InstantiationException e) {
System.out.println("Instantiation!");
} catch (NoSuchMethodException e) {
System.out.println("No such method!");
} catch (InvocationTargetException e) {
System.out.println("Invocation target!");
}
System.out.println("Done.");
}
}
如果我运行的例子中,我得到以下输出:
Hello, HAL!
We get a salutation, so Person is now a compiled class in memory loaded by the given ClassLoader.
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
Exception in thread "main" java.lang.NoClassDefFoundError: Object (wrong name: Person)
at java.lang.ClassLoader.defineClass1(Native Method)
at java.lang.ClassLoader.defineClass(ClassLoader.java:760)
at java.lang.ClassLoader.defineClass(ClassLoader.java:642)
at bragaglia.skimmer.core.MemoryFileManager$1.findClass(MemoryFileManager.java:33)
at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
at java.lang.Class.forName0(Native Method)
at java.lang.Class.forName(Class.java:340)
at org.drools.util.CompositeClassLoader$CachingLoader.load(CompositeClassLoader.java:258)
at org.drools.util.CompositeClassLoader$CachingLoader.load(CompositeClassLoader.java:237)
at org.drools.util.CompositeClassLoader.loadClass(CompositeClassLoader.java:88)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
at org.drools.base.ClassTypeResolver.resolveType(ClassTypeResolver.java:155)
at org.drools.rule.builder.PatternBuilder.build(PatternBuilder.java:174)
at org.drools.rule.builder.PatternBuilder.build(PatternBuilder.java:135)
at org.drools.rule.builder.GroupElementBuilder.build(GroupElementBuilder.java:67)
at org.drools.rule.builder.RuleBuilder.build(RuleBuilder.java:85)
at org.drools.compiler.PackageBuilder.addRule(PackageBuilder.java:3230)
at org.drools.compiler.PackageBuilder.compileRules(PackageBuilder.java:1038)
at org.drools.compiler.PackageBuilder.compileAllRules(PackageBuilder.java:946)
at org.drools.compiler.PackageBuilder.addPackage(PackageBuilder.java:938)
at org.drools.compiler.PackageBuilder.addPackageFromDrl(PackageBuilder.java:470)
at org.drools.compiler.PackageBuilder.addKnowledgeResource(PackageBuilder.java:698)
at org.drools.builder.impl.KnowledgeBuilderImpl.add(KnowledgeBuilderImpl.java:51)
at org.drools.builder.impl.KnowledgeBuilderImpl.add(KnowledgeBuilderImpl.java:40)
at bragaglia.skimmer.core.Example.main(Example.java:91)
正如你所看到的,Person
类成功编译在内存和实例化(见消息Hello, HAL!
在输出中),但是如果我将它添加到WM中,即使没有规则明确依赖于Person
s,我也会得到Exception in thread "main" java.lang.NoClassDefFoundError: Object (wrong name: Person)
。现在,我调查了一些异常,并意识到在Drools使用的ClassLoader
中找不到给定类(Person
)时会被解雇。因此,我通过添加一个配置来引用ClassLoader
来修改代码HAL
到KnowledgeBuilder
和KnowledgeBase
,但是我可能做错了什么,因为我仍然得到相同的异常。
你知道为什么发生这种情况以及如何解决它吗?提前谢谢了!
感谢与我的朋友讨论(@dsotty,如果您阅读本文,我指的是您),我找到了解决方案。 问题出在我的MemoryFileManager
,它只保存最后编译的类的字节码。任何时候我在稍后尝试检索一个类时,我只能找到最后编译的结果。 Drools需要访问每个进入其WM的类的字节码,因此MemoryFileManager
正在提高阻止执行的ClassNotFoundException
。
现在,解决方案就像使用private Map<String, MemoryJavaClassObject> objects;
替换其中的private MemoryJavaClassObject object;
一样简单,其中可以成功存储所有类的字节码。所以,当我尝试编译某些东西时,我还将字节码存储在objects
中,当我尝试查找某个类时,首先必须查找name
中objects
以内的条目。而已!
代码为MemoryFileManager
如下,为了您的方便和易于理解:
public class MemoryFileManager extends ForwardingJavaFileManager<StandardJavaFileManager> {
private Map<String, MemoryJavaClassObject> objects;
public MemoryFileManager(StandardJavaFileManager manager) {
super(manager);
this.objects = new HashMap<>();
}
@Override
public ClassLoader getClassLoader(Location location) {
return new SecureClassLoader() {
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
MemoryJavaClassObject object = objects.get(name);
if (null == object)
throw new ClassNotFoundException("Class '" + name + "' not found.");
byte[] b = object.getBytes();
return super.defineClass(name, b, 0, b.length);
}
};
}
@Override
public JavaFileObject getJavaFileForOutput(Location location, String name, Kind kind, FileObject sibling) throws IOException {
MemoryJavaClassObject object = new MemoryJavaClassObject(name, kind);
objects.put(name, object);
return object;
}
}
进一步的改善是可能的,但不包括为简洁。