java - WAR 共享的容器级版本化库

标签 java tomcat classloader servlet-container

在 Java servlet 容器中(最好是 Tomcat,但如果这可以在不同的容器中完成,那么请这样说)我想要一些理论上可行的东西。我的问题是是否存在支持它的工具,如果有的话是什么工具(或者我应该进一步研究哪些名称)。

这是我的问题:在一个 servlet 容器中,我想运行大量不同的 WAR 文件。它们共享一些大型公共(public)库(例如 Spring)。乍一看,我有两个无法接受的选择:

  1. 在每个 WAR 文件中包含大型库(例如 Spring)。这是 Not Acceptable ,因为它会加载大量的 Spring 副本,耗尽服务器上的内存。

  2. 将大型库放在容器类路径中。现在所有的 WAR 文件共享库的一个实例(很好)。但这是 Not Acceptable ,因为我无法在不立即升级所有 WAR 文件的情况下升级 Spring 版本,而且如此大的更改几乎不可能。

不过,从理论上讲,还有一个可行的替代方案:

  1. 将大型库的每个版本 放入容器级别的类路径中。做一些容器级别的魔法,以便每个 WAR 文件都声明它希望使用哪个版本,并且它会在其类路径中找到该版本。

“魔法”必须在容器级别完成(我认为),因为这只能通过使用不同的类加载器加载库的每个版本,然后调整每个 WAR 文件可见的类加载器来实现。

那么,你听说过这样做吗?如果是这样,如何?或者告诉我它叫什么,以便我进一步研究。

最佳答案

关于 Tomcat,对于第 7 版,您可以使用 VirtualWebappLocader像这样

<Context>
    <Loader className="org.apache.catalina.loader.VirtualWebappLoader"
            virtualClasspath="/usr/shared/lib/spring-3/*.jar,/usr/shared/classes" />
</Context>

对于第 8 个版本 Pre- & Post- Resources应该改用

<Context>
    <Resources>
        <PostResources className="org.apache.catalina.webresources.DirResourceSet"
                       base="/usr/shared/lib/spring-3" webAppMount="/WEB-INF/lib" />
        <PostResources className="org.apache.catalina.webresources.DirResourceSet"
                       base="/usr/shared/classes" webAppMount="/WEB-INF/classes" />
    </Resources>
</Context>

不要忘记将相应的 context.xml 放入您的 webapp 的 META-INF 中。

For the jetty以及其他容器可以使用相同的技术。 唯一的区别在于如何为 webapp 指定额外的类路径元素。


更新 上面的示例不共享加载的类,但想法是一样的——使用自定义类加载器。这只是一个非常丑陋的示例,它还试图防止在取消部署期间类加载器泄漏。


SharedWebappLoader

package com.foo.bar;

import org.apache.catalina.LifecycleException;
import org.apache.catalina.loader.WebappLoader;

public class SharedWebappLoader extends WebappLoader {

    private String pathID;
    private String pathConfig;

    static final ThreadLocal<ClassLoaderFactory> classLoaderFactory = new ThreadLocal<>();

    public SharedWebappLoader() {
        this(null);
    }

    public SharedWebappLoader(ClassLoader parent) {
        super(parent);
        setLoaderClass(SharedWebappClassLoader.class.getName());
    }

    public String getPathID() {
        return pathID;
    }

    public void setPathID(String pathID) {
        this.pathID = pathID;
    }

    public String getPathConfig() {
        return pathConfig;
    }

    public void setPathConfig(String pathConfig) {
        this.pathConfig = pathConfig;
    }

    @Override
    protected void startInternal() throws LifecycleException {
        classLoaderFactory.set(new ClassLoaderFactory(pathConfig, pathID));
        try {
            super.startInternal();
        } finally {
            classLoaderFactory.remove();
        }
    }

}

SharedWebappClassLoader

package com.foo.bar;

import org.apache.catalina.LifecycleException;
import org.apache.catalina.loader.ResourceEntry;
import org.apache.catalina.loader.WebappClassLoader;

import java.net.URL;

public class SharedWebappClassLoader extends WebappClassLoader {

    public SharedWebappClassLoader(ClassLoader parent) {
        super(SharedWebappLoader.classLoaderFactory.get().create(parent));
    }

    @Override
    protected ResourceEntry findResourceInternal(String name, String path) {
        ResourceEntry entry = super.findResourceInternal(name, path);
        if(entry == null) {
            URL url = parent.getResource(name);
            if (url == null) {
                return null;
            }

            entry = new ResourceEntry();
            entry.source = url;
            entry.codeBase = entry.source;
        }
        return entry;
    }

    @Override
    public void stop() throws LifecycleException {
        ClassLoaderFactory.removeLoader(parent);
    }
}

类加载器工厂

package com.foo.bar;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

public class ClassLoaderFactory {

    private static final class ConfigKey {
        private final String pathConfig;
        private final String pathID;
        private ConfigKey(String pathConfig, String pathID) {
            this.pathConfig = pathConfig;
            this.pathID = pathID;
        }
        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;

            ConfigKey configKey = (ConfigKey) o;

            if (pathConfig != null ? !pathConfig.equals(configKey.pathConfig) : configKey.pathConfig != null)
                return false;
            if (pathID != null ? !pathID.equals(configKey.pathID) : configKey.pathID != null) return false;

            return true;
        }

        @Override
        public int hashCode() {
            int result = pathConfig != null ? pathConfig.hashCode() : 0;
            result = 31 * result + (pathID != null ? pathID.hashCode() : 0);
            return result;
        }
    }

    private static final Map<ConfigKey, ClassLoader> loaders = new HashMap<>();
    private static final Map<ClassLoader, ConfigKey> revLoaders = new HashMap<>();
    private static final Map<ClassLoader, Integer> usages = new HashMap<>();

    private final ConfigKey key;

    public ClassLoaderFactory(String pathConfig, String pathID) {
        this.key = new ConfigKey(pathConfig, pathID);
    }

    public ClassLoader create(ClassLoader parent) {
        synchronized (loaders) {
            ClassLoader loader = loaders.get(key);
            if(loader != null) {
                Integer usageCount = usages.get(loader);
                usages.put(loader, ++usageCount);
                return loader;
            }

            Properties props = new Properties();
            try (InputStream is = new BufferedInputStream(new FileInputStream(key.pathConfig))) {
                props.load(is);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }

            String libsStr = props.getProperty(key.pathID);
            String[] libs = libsStr.split(File.pathSeparator);
            URL[] urls = new URL[libs.length];
            try {
                for(int i = 0, len = libs.length; i < len; i++) {
                    urls[i] = new URL(libs[i]);
                }
            } catch (MalformedURLException e) {
                throw new RuntimeException(e);
            }

            loader = new URLClassLoader(urls, parent);
            loaders.put(key, loader);
            revLoaders.put(loader, key);
            usages.put(loader, 1);

            return loader;
        }
    }

    public static void removeLoader(ClassLoader parent) {
        synchronized (loaders) {
            Integer val = usages.get(parent);
            if(val > 1) {
                usages.put(parent, --val);
            } else {
                usages.remove(parent);
                ConfigKey key = revLoaders.remove(parent);
                loaders.remove(key);
            }
        }
    }

}

第一个应用的context.xml

<Context>
    <Loader className="com.foo.bar.SharedWebappLoader"
            pathConfig="${catalina.base}/conf/shared.properties"
            pathID="commons_2_1"/>
</Context>

第二个app的context.xml

<Context>
    <Loader className="com.foo.bar.SharedWebappLoader"
            pathConfig="${catalina.base}/conf/shared.properties"
            pathID="commons_2_6"/>
</Context>

$TOMCAT_HOME/conf/shared.properties

commons_2_1=file:/home/xxx/.m2/repository/commons-lang/commons-lang/2.1/commons-lang-2.1.jar
commons_2_6=file:/home/xxx/.m2/repository/commons-lang/commons-lang/2.6/commons-lang-2.6.jar

关于java - WAR 共享的容器级版本化库,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25897954/

相关文章:

java - 在没有源代码的情况下序列化为 JSON 时更改属性名称

JavaFX 8-停止 TableView 在 setAll() 调用上跳转?

macos - 在 mac 10.5.8 上运行 tomcat 7.0.16 时出现问题

tomcat使用pc名,页面显示改变

ubuntu - Magnolia 未在 Vanilla Tomcat 服务器上启动

java - 在不影响现有系统的情况下更新新的 Java 库

java - Firebase AuthUI - 仅在生产中出现未知错误代码

java - 使用 JSON Post 请求创建对象

java - Java中JVM如何加载父类

java - 通过 Spring 持久化数据时,不同的类加载器会导致 ClassCastException