与Drools 5.6.0共享ClassLoader以允许运行时在内存中编译类

问题描述:

我在写一个简单的实用程序,它读取XML文件,将它们的节点转换为POJO,将它们加载到Drools的WM中并最终应用一些规则给他们。你可以在我的GitHub profile上找到整个项目。不幸的是,尽管我做了所有的努力,但我无法让Drools“喜欢”在运行时编译的任何类的实例。我看到许多人也遇到ClassLoader的问题,所以我怀疑它可能是它的错......我准备了一个最小工作示例,供您尝试使用GitHub以及下面的这些示例。它需要一些其他小文件(MemoryFileManager,MemoryJavaClassObjectMemoryJavaFileObject),为简洁起见,它们仅在GitHub上可用。为了正常工作,此示例要求您的JVM是JDK> = 6,并且您的classpath上有tools.jarclasses.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来修改代码HALKnowledgeBuilderKnowledgeBase,但是我可能做错了什么,因为我仍然得到相同的异常。

你知道为什么发生这种情况以及如何解决它吗?提前谢谢了!

感谢与我的朋友讨论(@dsotty,如果您阅读本文,我指的是您),我找到了解决方案。 问题出在我的MemoryFileManager,它只保存最后编译的类的字节码。任何时候我在稍后尝试检索一个类时,我只能找到最后编译的结果。 Drools需要访问每个进入其WM的类的字节码,因此MemoryFileManager正在提高阻止执行的ClassNotFoundException

现在,解决方案就像使用private Map<String, MemoryJavaClassObject> objects;替换其中的private MemoryJavaClassObject object;一样简单,其中可以成功存储所有类的字节码。所以,当我尝试编译某些东西时,我还将字节码存储在objects中,当我尝试查找某个类时,首先必须查找nameobjects以内的条目。而已!

代码为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; 
    } 

} 

进一步的改善是可能的,但不包括为简洁。