2017-05-07 10 views
0

目的は、リモートロケーションから多数のjarファイルをプラグインとしてロードし、CDIコンテキスト内で初期化することです。動的クラスロードを使用するJava CDI拡張機能

その後、サーブレットはそうのようなのようなイベントを発生することができますプラグインが観察することができるようになります

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このクラスは、サーブレットクラスと同じパッケージ内にあります。拡張クラスの完全修飾名main.initplugins.InitPluginsという1行のjavax.enterprise.inject.spi.Extensionファイルを持つ必要なMETA-INF/servicesディレクトリがあります。

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())); 
     } 
    } 
} 

そして何らかの理由で動作しません。どの段階でもエラーは発生しません。サーブレットからイベントを発生させると、ロードされたプラグインはそれを取得しません。何が間違っているのですか?

+0

クラスがロードされたことを示すログメッセージが表示されますか?また、アプリをどのように配備していますか?どのコンテナ(バージョンを含む) –

+0

はい、@ Observableメソッドのものも含めて、すべてのログメッセージが表示されます: "AND:ANNOTATED TYPE FOR:" + ... "ANNOTATED TYPE CREATION COMPLETE"。 wildflyを使用する10.1 – hrs

+0

あなたの 'beans.xml'はどのように見えますか?これは '@ ApplicationScoped'のように' Plugin'にスコープを追加するとうまくいくのでしょうか? –

答えて

2

CDIの観点からは、あなたのアプローチはうまくいくはずです。

ここでの問題は、特に平らでない展開(純粋なSE以外のもの)を検討する場合は、クラスローディングです。

あなたはTCCLを使用することを選択しました。あなたがやった:特定のアプリケーションサーバー/サーブレットでは、あなたの拡張機能自体(InitPlugin)をロードされたものとは異なるクラスローダを与えるかもしれない、

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

を。

代わりに、CDI Beanを処理するものと同じように拡張子をロードした同じCLを使用する必要があります。それでは、次の操作を行います。

ClassLoader cl = InitPlugins.class.getClassLoader() 

注:あなたは未定義の海域を航行するされていることに注意してください。この動作/修正は移植性がない可能性があります。

関連する問題