Java 在web应用程序中动态加载不在类路径中的类-不使用自定义类加载器

Java 在web应用程序中动态加载不在类路径中的类-不使用自定义类加载器,java,classloader,Java,Classloader,我正在开发一个web应用程序 web应用程序动态生成java类。例如,它生成类com.people.Customer.java 在我的代码中,我动态编译它以获取com.people.Customer.class并存储在某个目录中,比如repository/com/people/Customer.class,它不在我的应用服务器的类路径上。我的应用服务器(我使用的是WebSphere application server/Apache Tomcat等)从WEB-INF/classes目录中拾取类。

我正在开发一个web应用程序

  • web应用程序动态生成java类。例如,它生成类
    com.people.Customer.java
  • 在我的代码中,我动态编译它以获取com.people.Customer.class并存储在某个目录中,比如
    repository/com/people/Customer.class
    ,它不在我的应用服务器的类路径上。我的应用服务器(我使用的是WebSphere application server/Apache Tomcat等)从
    WEB-INF/classes
    目录中拾取类。类加载器将使用它来加载类
  • 编译之后,我需要加载这个类,以便在创建之后,其他使用它的类可以访问它
  • 当我使用
    Thread.currentThread().getContextClassLoader().loadClass(com.people.Customer)
    时,显然类加载器无法加载该类,因为它不在类路径上(不在
    WEB-INF/classes
    )。由于类似的原因,
    getResource(..)
    getResourceAsStream(..)
    也不起作用
  • 我需要一种方法:

    阅读class
    Customer.class
    可能作为流(或任何其他方式)并加载它。以下是限制条件:

  • 我无法将存储库文件夹添加到
    WEB-INF/classes
    文件夹
  • 我无法创建新的自定义类加载器。如果我创建一个新的类加载器并加载该类,则它的父类加载器将无法访问该类
  • 有没有办法做到这一点

    如果不是这样,在更糟糕的情况下,是否有一种方法可以用web应用程序的自定义类加载器覆盖默认类加载器?应该使用相同的类加载器在web应用程序的整个生命周期中加载应用程序


    感谢任何解决方案:)

    简短回答:

    如果没有自定义类加载器,则无法动态加载类

    但是,由于WebApp类加载器加载的其他对象将无法使用这些新加载的类,因此您不能使用自定义类加载器的假设是不正确的。您所需要的只是使用这些新创建的类的通用方法——比如公共接口或元描述(用于访问bean属性的bean内省器)


    但是,如果您使用的是第三方库,如Hibernate,并且您在运行时动态加载要持久化的实体,那么您将有一段困难的时间,但我认为这是可能的。

    确实可以做到这一点。只需获取web类加载器并使用反射调用defineClass()方法(它受保护,因此请确保对该方法调用setAccessible(true)。defineClass()获取一个字节数组,因此它与类的来源没有任何区别。请确保类名是唯一的,并且只加载一次,否则您将遇到复杂的类加载问题。

    您需要一个自定义类加载程序来执行此操作,并且在此类加载程序中,您需要重新定义一个方法
    findClass(字符串名称)

    例如:

    public class CustomClassLoader extends ClassLoader {
    
        final String basePath = "/your/base/path/to/directory/named/repository/";
    
        @Override
        protected Class<?> findClass(final String name) throws ClassNotFoundException {
            String fullName = name.replace('.', '/');
            fullName += ".class";
    
            String path = basePath + fullName ;
            try {
                FileInputStream fis = new FileInputStream(path);
                byte[] data = new byte[fis.available()];
                fis.read(data);
                Class<?> res = defineClass(name, data, 0, data.length);
                fis.close();
    
                return res;
            } catch(Exception e) {
                return super.findClass(name);
            }
        }
    }
    
    公共类CustomClassLoader扩展了ClassLoader{
    最终字符串basePath=“/your/base/path/to/directory/named/repository/”;
    @凌驾
    受保护类findClass(最终字符串名称)引发ClassNotFoundException{
    字符串fullName=name.replace('.','/');
    全名+=“.class”;
    字符串路径=基本路径+全名;
    试一试{
    FileInputStream fis=新的FileInputStream(路径);
    字节[]数据=新字节[fis.available()];
    fis.读取(数据);
    Class res=defineClass(名称,数据,0,数据长度);
    fis.close();
    返回res;
    }捕获(例外e){
    返回super.findClass(名称);
    }
    }
    }
    
    然后,您将从自定义位置加载类。例如:

    Class<?> clazz = Class.forName("my.pretty.Clazz", true, new CustomClassLoader());
    Object obj = clazz.newInstance();
    
    Class clazz=Class.forName(“my.pretty.clazz”,true,new CustomClassLoader());
    Object obj=clazz.newInstance();
    
    这样做,您告诉JVM名为
    my.pretty.Clazz
    的类应该由您的自定义类装入器装入,它知道如何装入自定义类以及从何处装入自定义类。 它将完整的类名(如
    my.pretty.Clazz
    )解析为文件名(在本例中为:
    /your/base/path/to/directory/named/repository/my/pretty/Clazz.class
    ),然后将获得的资源作为字节数组加载,最后将该数组转换为
    class
    实例

    这个示例非常简单,并演示了一种有关如何像您的案例那样加载自定义类的通用技术。
    例如,我建议您阅读一些关于类加载的文章。

    为什么不能将其添加到WEB-INF/classes文件夹中?这是真正的解决方案。在任何情况下都是“不能”还是“不会”或“宁愿不”?@duffymo:你的假设是webapp是在扩展模式下部署的,并且webapp对文件系统具有写入权限。在生产环境中并不总是这样。将文件写入WEB-INF/类不是一个好的解决方案不,我没有任何这样的假设-你的透视界面已关闭,mhaller。我认为e-WAR应该重新打包以包含所有必需的.class文件并重新部署。我希望看到这样一个用例,即有必要创建一些像Customer on the fly这样显而易见的东西。实例?是的。类?不,我不买。嗯,我知道这个用例,因为我们正在这样做。我们的客户部署新的生成代码在运行时,独立于运行此代码的web应用程序。可以说是appserver中的appserver。这就是为什么我理解swdeveloper的请求,尽管我必须承认这是不寻常的,当然我不会在“正常”web应用程序中使用类似的内容。类“Customer”如果您认为“静态”域模型可能不是一个合适的例子