2017-05-07 56 views
0

目标是将一堆jar文件作为来自远程位置的插件加载,并在CDI上下文中初始化它们。带有动态类加载的java CDI扩展

然后一个servlet可以触发事件,比如像这样:

testEvent.fire(new EventTest("some message")); 

哪个插件将能够观察到。示例插件将如下所示:

public class Plugin{ 
    public void respond (@Observes EventTest e){ 
     //does something with the even object 
    } 
} 

以下是假设加载插件的代码。从https://jaxenter.com/tips-for-writing-pluggable-java-ee-applications-105281.html采取并重构此类与servlet类位于相同的包中。它具有必需的META-INF/services目录,其javax.enterprise.inject.spi.Extension文件具有单行 - 扩展类的完全限定名:main.initplugins.InitPlugins。

package main.initplugins; 

import java.sql.SQLException; 
import java.sql.Connection; 
import java.sql.Statement; 

import java.util.jar.JarInputStream; 
import java.util.jar.JarEntry; 

import java.lang.ClassLoader; 
import java.lang.reflect.Method; 

import java.util.logging.Level; 
import java.util.logging.Logger; 

import javax.enterprise.event.Observes; 
import javax.enterprise.inject.spi.BeforeBeanDiscovery; 
import javax.enterprise.inject.spi.BeanManager; 

public class InitPlugins implements javax.enterprise.inject.spi.Extension{ 
    Logger log = Logger.getLogger(""); 
    private java.util.Set<Class<?>> classes; 

    public void beforeBeanDiscovery(@Observes BeforeBeanDiscovery bbd, BeanManager bm){ 
     log.log(Level.INFO, "LOAD PLUGINS HERE"); 
     loadFromFiles(); 

     try{ 
      for (Class<?> cl: classes){ 
       final javax.enterprise.inject.spi.AnnotatedType<?> at = bm.createAnnotatedType(cl); 
       bbd.addAnnotatedType(at); 
       log.log(Level.INFO, "ADD ANNOTATED TYPE FOR: " + cl.getName()); 

      } 
      log.log(Level.INFO, "ANNOTATED TYPE CREATION COMPLETE"); 
     } catch (Exception ex){ 
      log.log(Level.INFO, "FAIL TO CREATE ANNOTATED TYPE: " + ex.getMessage()); 
     } 
    } 
    public void loadFromFiles() { 

     classes = new java.util.LinkedHashSet<Class<?>>(); 

     try{ 

      //connect to a remote location. In this case it will be a database that holds the bytes of the .jar files 
      Connection dbConnection = java.sql.DriverManager.getConnection("jdbc:mysql://localhost/testdb?user=user&password=passwd"); 
      Statement statement = dbConnection.createStatement(); 
      java.sql.ResultSet plugins = statement.executeQuery("select * from plugins"); //the plugins table contain 2 columns: 1) fileName as primary key, 2) longblob that hold raw byte of the jar file 

      while (plugins.next()){ 
       JarInputStream js = new JarInputStream(new java.io.ByteArrayInputStream(plugins.getBytes(2))); //load them as jar files, 2 is the index for the raw byte column that holds the jar file 

       JarEntry je; 
       while((je = js.getNextJarEntry()) != null){ 
       //open each jar file, scan through file contents and find the .class files, then extract those bytes and pass them in the ClassLoader's defineClass method 

        if(!je.isDirectory() && je.getName().endsWith(".class")){ 
         String className = je.getName().substring(0, je.getName().length() - 6).replace("/", "."); 
         log.log(Level.INFO, "class name is: " + className); 

         java.io.ByteArrayOutputStream classBytes = new java.io.ByteArrayOutputStream(); 
         byte[] bytes; 

         try{ 
          byte[] buffer = new byte[2048]; 
          int read = 0; 
          while(js.available() > 0){ 
           read = js.read(buffer, 0, buffer.length); 
           if(read > 0){ 
            classBytes.write(buffer, 0, read); 
           } 
          } 
          bytes = classBytes.toByteArray(); 

          //code below taken from: https://jaxenter.com/tips-for-writing-pluggable-java-ee-applications-105281.html 
          java.security.ProtectionDomain protDomain = getClass().getProtectionDomain(); 
          ClassLoader cl = Thread.currentThread().getContextClassLoader(); 
          Method tempDefineClassMethod = null; 
          for (Method tempMethod : ClassLoader.class.getDeclaredMethods()){ 
           if(tempMethod.getName().equals("defineClass") && tempMethod.getParameterCount() == 5){ 
            tempDefineClassMethod = tempMethod; 
            break; 
           } 
          } 
          final Method defineClassMethod = tempDefineClassMethod; 
          try{ 
           java.security.AccessController.doPrivileged(new java.security.PrivilegedExceptionAction(){ 
            @Override 
            public java.lang.Object run() throws Exception{ 
             if (!defineClassMethod.isAccessible()){ 
              defineClassMethod.setAccessible(true); 
             } 
             return null; 
            } 
           }); 
           log.log(Level.INFO, "Attempting load class: " + className + " with lenght of: " + bytes.length); 
           defineClassMethod.invoke(cl, className, bytes, 0, bytes.length, protDomain); 
           classes.add(cl.loadClass(className)); 
           log.log(Level.INFO, "Loaded class: " + je.getName()); 

          } catch (Exception ex){ 
           log.log(Level.INFO, "Error loading class: " + ex.getMessage()); 
           ex.printStackTrace(); 
          } 
         } catch (Exception ex){ 
          log.log(Level.INFO, "Error loading bytes: " + ex.getMessage()); 
         } 
        } 
       } 
      } 

     } catch (SQLException ex){ 
      log.log(Level.SEVERE, "Fail to get db connection or create statement in plugin ejb: ".concat(ex.getMessage())); 
     } catch (Exception ex){ 
      log.log(Level.SEVERE, "Fail to get db connection or create statement in plugin ejb: ".concat(ex.getMessage())); 
     } 
    } 
} 

而且由于某种原因它不起作用。任何阶段都不会出现错误。当我从servlet中触发事件时,加载的插件不会提取它。我究竟做错了什么?

+0

你看到你的日志消息指出该类已加载?另外,你如何部署你的应用程序?什么容器(包括版本) –

+0

是的,显示所有日志消息,包括@Observable方法中的内容:“添加注释类型:”+ ...和“注释类型创建完成”。 使用wildfly 10.1 – hrs

+0

只是想知道,你的'beans.xml'看起来像什么?当你向'Plugin'添加一个作用域时,这是否正常工作,比如'@ ApplicationScoped'? –

回答

2

从CDI的角度来看,您的方法应该工作得很好。

这里的问题是类加载,特别是在考虑任何非平面部署(除纯SE之外的任何其他部分)时。

您选择使用TCCL,例如,你所做的:

ClassLoader cl = Thread.currentThread().getContextClassLoader(); 

,在某些应用服务器/ servlet的,可能会给你一个不同的类加载器比加载的扩展本身(InitPlugin)之一。

相反,你应该使用相同的CL加载扩展,因为这将成为处理CDI bean的CL。所以,只要做到这一点:

ClassLoader cl = InitPlugins.class.getClassLoader() 

注:要知道,你正在航行水域不确定。这种行为/修复可能不是可移植的。