Java – container level versioned library shared by war

In a java servlet container (preferably tomcat, but if this can be done in another container, then say so) I want something that works in theory My question is whether there are tools to support it and which tools exist (or which names I should study further)

This is my problem: in a servlet container, I want to run a large number of different war files They share some large public libraries (such as spring) At first glance, I have two unacceptable choices:

>Include large libraries (such as spring) in each war file This is not acceptable because it will load a large number of spring copies and run out of memory on the server. > Place large libraries in the container classpath Now all war files share a library instance (good) But this is unacceptable because I can't upgrade the spring version without upgrading all war files, and such a big change is difficult to achieve

But in theory, there is an alternative:

>Put each version of a large library into a container - level classpath Do some container level magic so that each war file declares which version it wants to use, and it will find it in its classpath

"Magic" must be done at the container level (I think), because this can only be achieved by loading each version of the library with different class loaders, and then adjusting the class loader that is visible in every WAR file.

So, have you ever heard of this? What if so? Or tell me what it's called so I can study it further

Solution

For tomcat, for version 7, you can use virtual web app locator like this

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

For version 8, you should use 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>

Don't forget to put the corresponding context XML into meta - inf of webapp

For the jetty and other containers can use the same technology The only difference is how to specify additional classpath elements for webapp

Update the above example does not share the loaded classes, but the idea is the same - use a custom class loader This is a very ugly sample that also attempts to prevent classloader leaks during undeployment

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

ClassLoaderFactory

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

}

The context of the first application xml

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

Context of the second application xml

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

$TOMCAT_ Home / conf directory / 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
The content of this article comes from the network collection of netizens. It is used as a learning reference. The copyright belongs to the original author.
THE END
分享
二维码
< <上一篇
下一篇>>