3

Java サーブレット コンテナー (Tomcat が望ましいですが、別のコンテナーで実行できる場合はそう言ってください) では、理論的に可能なものが必要です。ここでの私の質問は、それをサポートするツールが存在するかどうか、存在する場合はどのツール (またはさらに調査する必要がある名前) かです。

これが私の問題です。1 つのサーブレット コンテナで、多数の異なる WAR ファイルを実行したいと考えています。それらはいくつかの大規模な共通ライブラリ (Spring など) を共有しています。一見したところ、受け入れがたい選択肢が 2 つあります。

  1. 各 WAR ファイルに大きなライブラリ (Spring など) を含めます。これは、Spring の多数のコピーをロードし、サーバーのメモリを使い果たすため、受け入れられません。

  2. 大きなライブラリをコンテナ クラスパスに配置します。これで、すべての WAR ファイルがライブラリの 1 つのインスタンスを共有します (良い)。しかし、すべての WAR ファイルを一度にアップグレードしないと Spring バージョンをアップグレードできないため、これは容認できません。

ただし、理論的には、機能する代替手段があります。

  1. 大きなライブラリの各バージョンをコンテナ レベルのクラスパスに配置します。各WARファイルが使用したいバージョンを宣言し、クラスパスでそれを見つけるように、コンテナレベルの魔法を行います。

「魔法」はコンテナレベルで実行する必要があります(私はそう思います)。これは、ライブラリの各バージョンを異なるクラスローダーでロードし、各WARファイルに表示されるクラスローダーを調整することによってのみ達成できるためです。

それで、これを行うことについて聞いたことがありますか?もしそうなら、どのように?または、さらに調査できるように、それが何と呼ばれているか教えてください.

4

3 に答える 3

5

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 を Web アプリケーションの META-INF に入れることを忘れないでください。

桟橋やその他のコンテナについても、同じ手法を使用できます。唯一の違いは、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);
    }
}

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

}

最初のアプリの context.xml

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

2 番目のアプリの 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
于 2014-09-26T00:26:00.987 に答える
1

JBoss には、この問題を解決するモジュールと呼ばれるフレームワークがあります。共有ライブラリをそのバージョンとともに保存し、war ファイルから参照できます。

Tomcat で動作するかどうかはわかりませんが、Wildfly ではチャームとして動作します。

于 2014-09-30T10:58:27.607 に答える
1

これをTomcatに実装できました(Tomcat 7.0.52でテスト済み)。私の解決策には、標準の Tomcat の WebAppLoader を拡張する WebAppLoader のカスタム バージョンを実装することが含まれます。このソリューションのおかげで、カスタム クラスローダーを渡して、各 Web アプリケーションのクラスをロードできます。

この新しいローダーを利用するには、アプリケーションごとに宣言する必要があります (各 war に配置された Context.xml ファイルまたは Tomcat の server.xml ファイルのいずれか)。このローダーは追加のカスタム パラメータwebappNameを受け取ります。このパラメータは後で LibrariesStorage クラスに渡され、どのライブラリをどのアプリケーションで使用するかを決定します。

    <Context  path="/pl-app" >
        <Loader className="web.DynamicWebappLoader" webappName="pl-app"/>
    </Context>      
    <Context  path="/my-webapp" >
        <Loader className="web.DynamicWebappLoader" webappName="myApplication2"/>
    </Context>

これを定義したら、この DynamicWebappLoader を Tomcat にインストールする必要があります。これを行うには、コピーしたすべてのクラスをTomcat のlibディレクトリにコピーします (したがって、次のファイルが必要です [tomcat dir]/lib/web/DynamicWebappLoader.class, [tomcat dir]/lib/web/LibrariesStorage.class, [tomcat dir]/ lib/web/LibraryAndVersion.class、[Tomcat ディレクトリ]/lib/web/WebAppAwareClassLoader.class)。

また、xbean-classloader-4.0.jar をダウンロードして、Tomcat の lib ディレクトリに配置する必要があります (したがって、[tomcat dir]/lib/xbean-classloader-4.0.jar が必要です。注:xbean-classloader はクラスローダーの特別な実装を提供します ( org.apache.xbean.classloader.JarFileClassLoader) を使用すると、実行時に必要な jar をロードできます。

主なトリックは LibraryStorgeClass で行われます (完全な実装は最後にあります)。各アプリケーション ( webappNameで定義) と、このアプリケーションがロードできるライブラリとの間のマッピングを格納します。現在の実装では、これはハードコーディングされていますが、これを書き直して、各アプリケーションに必要なライブラリのリストを動的に生成できます。各ライブラリには JarFileClassLoader の独自のインスタンスがあり、各ライブラリが 1 回だけロードされるようにします (ライブラリとそのクラスローダーの間のマッピングは静的フィールド「libraryToClassLoader」に格納されるため、このマッピングはすべての Web アプリケーションで同じです。フィールド)

class LibrariesStorage {
    private static final String JARS_DIR = "D:/temp/idea_temp_proj2_/some_jars";

  private static Map<LibraryAndVersion, JarFileClassLoader> libraryToClassLoader = new HashMap<>();

  private static Map<String, List<LibraryAndVersion>> webappLibraries = new HashMap<>();

  static {
    try {
      addLibrary("commons-lang3", "3.3.2", "commons-lang3-3.3.2.jar"); // instead of this lines add some intelligent directory scanner which will detect all jars and their versions in JAR_DIR
      addLibrary("commons-lang3", "3.3.1", "commons-lang3-3.3.1.jar");
      addLibrary("commons-lang3", "3.3.0", "commons-lang3-3.3.0.jar");

      mapApplicationToLibrary("pl-app", "commons-lang3", "3.3.2"); // instead of manually mapping application to library version, some more intelligent code should be here (for example you can scann Web-Inf/lib of each application and detect needed jars

      mapApplicationToLibrary("myApplication2", "commons-lang3", "3.3.0");

     (...)    
 } 

上記の例では、すべての jar を含むディレクトリ (ここでは JARS_DIR で定義) にcommons-lang3-3.3.2.jar ファイルしかないとします。これは、「pl-app」名 (名前は上記の Context.xml のタグの webappName 属性に由来する) で識別されるアプリケーションが、commons-lang jar からクラスをロードできることを意味します。「myApplication2」で識別されるアプリケーションは、commons-lang3-3.3.0.jar にしかアクセスできないため、この時点で ClassNotFoundException を取得しますが、このファイルは JARS_DIR ディレクトリに存在しません。

完全な実装はこちら:

package web;

import org.apache.catalina.loader.WebappLoader;
import org.apache.xbean.classloader.JarFileClassLoader;
import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;


public class DynamicWebappLoader extends WebappLoader {
  private String webappName;
  private WebAppAwareClassLoader webAppAwareClassLoader;

  public static final ThreadLocal lastCreatedClassLoader = new ThreadLocal();

  public DynamicWebappLoader() {
    super(new WebAppAwareClassLoader(Thread.currentThread().getContextClassLoader()));

    webAppAwareClassLoader = (WebAppAwareClassLoader) lastCreatedClassLoader.get(); // unfortunately I did not find better solution to access new instance of WebAppAwareClassLoader created in previous line so I passed it via thread local
    lastCreatedClassLoader.remove();

  }

  // (this method is called by Tomcat because of Loader attribute in Context.xml - <Context> <Loader className="..." webappName="myApplication2"/> )
  public void setWebappName(String name) {
    System.out.println("Setting webapp name: " + name);
    this.webappName = name;
    webAppAwareClassLoader.setWebAppName(name); // pass web app name to ClassLoader 
  }


}


class WebAppAwareClassLoader extends ClassLoader {
  private String webAppName;

  public WebAppAwareClassLoader(ClassLoader parent) {
    super(parent);
    DynamicWebappLoader.lastCreatedClassLoader.set(this); // store newly created instance in ThreadLocal .. did not find better way to access the reference later in code
  }

  @Override
  public Class<?> loadClass(String className) throws ClassNotFoundException {
    System.out.println("Load class: " + className + " for webapp: " + webAppName);
    try {
      return LibrariesStorage.loadClassForWebapp(webAppName, className);
    } catch (ClassNotFoundException e) {
      System.out.println("JarFileClassLoader did not find class: " + className + " " + e.getMessage());
      return super.loadClass(className);
    }

  }

  public void setWebAppName(String webAppName) {
    this.webAppName = webAppName;
  }
}

class LibrariesStorage {
  private static final String JARS_DIR = "D:/temp/idea_temp_proj2_/some_jars";

  private static Map<LibraryAndVersion, JarFileClassLoader> libraryToClassLoader = new HashMap<>();

  private static Map<String, List<LibraryAndVersion>> webappLibraries = new HashMap<>();

  static {
    try {
      addLibrary("commons-lang3", "3.3.2", "commons-lang3-3.3.2.jar"); // instead of this lines add some intelligent directory scanner which will detect all jars and their versions in JAR_DIR
      addLibrary("commons-lang3", "3.3.1", "commons-lang3-3.3.1.jar");
      addLibrary("commons-lang3", "3.3.0", "commons-lang3-3.3.0.jar");

      mapApplicationToLibrary("pl-app", "commons-lang3", "3.3.2"); // instead of manually mapping application to library version, some more intelligent code should be here (for example you can scann Web-Inf/lib of each application and detect needed jars
      mapApplicationToLibrary("myApplication2", "commons-lang3", "3.3.0");

    } catch (MalformedURLException e) {
      throw new RuntimeException(e.getMessage(), e);
    }

  }

  private static void mapApplicationToLibrary(String applicationName, String libraryName, String libraryVersion) {
    LibraryAndVersion libraryAndVersion = new LibraryAndVersion(libraryName, libraryVersion);
    if (!webappLibraries.containsKey(applicationName)) {
      webappLibraries.put(applicationName, new ArrayList<LibraryAndVersion>());
    }
    webappLibraries.get(applicationName).add(libraryAndVersion);
  }

  private static void addLibrary(String libraryName, String libraryVersion, String filename)
                          throws MalformedURLException {
    LibraryAndVersion libraryAndVersion = new LibraryAndVersion(libraryName, libraryVersion);
    URL libraryLocation = new File(JARS_DIR + File.separator + filename).toURI().toURL();

    libraryToClassLoader.put(libraryAndVersion,
      new JarFileClassLoader("JarFileClassLoader for lib: " + libraryAndVersion,
        new URL[] { libraryLocation }));
  }

  private LibrariesStorage() {
  }


  public static Class<?> loadClassForWebapp(String webappName, String className) throws ClassNotFoundException {
    System.out.println("Loading class: " + className + " for web application: " + webappName);

    List<LibraryAndVersion> webappLibraries = LibrariesStorage.webappLibraries.get(webappName);
    for (LibraryAndVersion libraryAndVersion : webappLibraries) {
      JarFileClassLoader libraryClassLoader = libraryToClassLoader.get(libraryAndVersion);

      try {
        return libraryClassLoader.loadClass(className); // ok current lib contained class to load
      } catch (ClassNotFoundException e) {
        // ok.. continue in loop... try to load the class from classloader connected to next library
      }
    }

    throw new ClassNotFoundException("Class " + className + " was not found in any jar connected to webapp: " +
      webappLibraries);

  }

}


class LibraryAndVersion {
  private final String name;
  private final String version;

  LibraryAndVersion(String name, String version) {
    this.name = name;
    this.version = version;
  }

  @Override
  public boolean equals(Object o) {
    if (this == o) {
      return true;
    }
    if ((o == null) || (getClass() != o.getClass())) {
      return false;
    }

    LibraryAndVersion that = (LibraryAndVersion) o;

    if ((name != null) ? (!name.equals(that.name)) : (that.name != null)) {
      return false;
    }
    if ((version != null) ? (!version.equals(that.version)) : (that.version != null)) {
      return false;
    }

    return true;
  }

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

  @Override
  public String toString() {
    return "LibraryAndVersion{" +
      "name='" + name + '\'' +
      ", version='" + version + '\'' +
      '}';
  }
}
于 2014-09-29T23:52:34.883 に答える