带有动态类加载的java CDI扩展

时间:2017-05-07 22:12:45

标签: java servlets java-ee dependency-injection cdi

目标是从远程位置加载一堆jar文件作为插件,在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中激活事件时,加载的插件无法获取它。我做错了什么?

1 个答案:

答案 0 :(得分:2)

从CDI的角度来看,你的方法应该可以正常工作。

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

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

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

在某些应用程序服务器/ servlet中,可能会为您提供与加载扩展本身(InitPlugin)不同的类加载器。

相反,您应该使用加载扩展名的相同CL,因为它将是处理CDI bean的那个。所以,就这样做:

ClassLoader cl = InitPlugins.class.getClassLoader()

注意: 请注意,您正在航行未定义的水域。此行为/修复可能无法移植。