クラスローダの名前空間を利用する
コンポーネントコンテナフレームワークという概念は、jarまたはzipアーカイブで定義されたコンポーネントとそれらのコンポーネントが必要とする補助クラスをロードする役割を持ったコンテナエンティティとして実装できます。このフレームワークの目的は次のとおりです。
- インスタンス化するコンポーネントのバージョンを開発者が明確に指定できるようにすること
- 各コンポーネント用の適切な補助クラスを、コンポーネントと同じjarファイル内の情報に基づいて正しくロードすること
- 補助クラスとアーカイブをコンポーネント全体で共有すること
コンポーネントとそれに関連する補助ファイルを定義するには、次のような設定ファイルが必要です。
<?xml version="1.0"?> <component name= "com.jeffhanson.components.HelloWorld"> <component-archive> HelloWorldComponentV1.jar </component-archive> <ancillary-resources> <ancillary-resource> log4j-1.2.12.jar </ancillary-resource> <ancillary-resource> concurrent-1.3.4.jar </ancillary-resource> </ancillary-resources> </component>
設定ファイルをもう1つ紹介します。上の例と、次の例の要素を比べてみてください。変更されている唯一の点は、component-archive
要素の値だけです。それぞれのバージョンのコンポーネントを格納するアーカイブの名前は、この要素の値で定義します。
<?xml version="1.0"?> <component name= "com.jeffhanson.components.HelloWorld"> <component-archive> HelloWorldComponentV2.jar </component-archive> <ancillary-resources> <ancillary-resource> log4j-1.2.12.jar </ancillary-resource> <ancillary-resource> concurrent-1.3.4.jar </ancillary-resource> </ancillary-resources> </component>
フレームワークがクラスをロードする際に、指定した場所からのみロードするよう保証するためには、URLClassLoader
を拡張して新しいクラスローダを作成する必要があります。まず、loadClass
メソッドをオーバーライドして、そのメソッドへの呼び出しが既定のクラスローダの親に伝播されることを防止します(これは結果的に、標準のクラスパスからのロードを防止することにつながります)。これにより、クラスローダに指定したURLでのみクラス検索が行われるようになるので、後はコンポーネントのロード元となる特定のjarファイルの場所をクラスローダに与えればよいことになります。
次のコードは、この制限付きクラスローダのメカニズムを示しています。
package com.jeffhanson.components; import java.net.URL; import java.net.URLClassLoader; public class RestrictedURLClassLoader extends URLClassLoader { public RestrictedURLClassLoader( URL[] urls) { super(urls, null); } public Class loadClass(String name) throws ClassNotFoundException { Class cls = super.loadClass(name); if (cls == null) { throw new ClassNotFoundException( "Restricted ClassLoader" + " is unable to find class: " + name); } return cls; } }
この制限付きクラスローダは、コンポーネントコンテナがコンポーネントや補助クラスをロードする際に使われます。
コンポーネントコンテナは、現在のスレッドのコンテキストクラスローダを利用して、目的のコンポーネントのURLを見つけます。その後、このURLを制限付きクラスローダに与え、コンポーネントのインスタンス作成に使用します。さらに、このコンポーネントクラスを以後の呼び出しに備えてキャッシュします。リスト1にコンポーネントコンテナのコードを示し、図2にコンポーネントコンテナフレームワークの各クラス間の関係を示します。
package com.jeffhanson.components; import org.apache.commons.configuration. ConfigurationException; import org.apache.commons.configuration. XMLConfiguration; import org.apache.commons.configuration.Configuration; import org.apache.log4j.Logger; import java.net.URL; import java.util.*; import java.io.IOException; public class ComponentContainer { // =============================================== // static fields // =============================================== private static Logger log = Logger.getLogger(ComponentContainer.class); // =============================================== // member fields // =============================================== private String componentArchive = ""; private URL[] ancillaryClassPathURLs = null; private HashMap componentCache = new HashMap(); // =============================================== // constructors // =============================================== /** * Constructs a new ComponentContainer for * components named by the specified component * names. The names are assumed to * refer to JAR/zip files which will be downloaded * and opened as needed. * * @param componentConfigFileName the name of the * configuration * file for the * component from * which to * load classes * and resources */ public ComponentContainer(String componentConfigFileName) throws ConfigurationException { XMLConfiguration config = new XMLConfiguration( componentConfigFileName); this.componentArchive = config.getString("component-archive"); Configuration subConfig = config.subset("ancillary-resources"); if (subConfig != null) { String[] resNameArr = subConfig.getStringArray( "ancillary-resource"); if (resNameArr != null && resNameArr.length > 0) { this.ancillaryClassPathURLs = new URL[resNameArr.length]; for (int i = 0; i < resNameArr.length; i++) { this.ancillaryClassPathURLs[i] = Thread.currentThread(). getContextClassLoader(). getResource(resNameArr[i]); } } } } /** * Constructs a new ComponentContainer for * components named by the specified component * names. The names are assumed to refer to JAR/zip * files which will be downloaded and opened * as needed. * * @param componentArchive the name of the * archive from which * to load the component, * classes and resources */ public ComponentContainer(String componentArchive, URL[] ancillaryClassPathURLs) { this.componentArchive = componentArchive; if (ancillaryClassPathURLs != null && ancillaryClassPathURLs.length > 0) { this.ancillaryClassPathURLs = new URL[ancillaryClassPathURLs.length]; System.arraycopy(ancillaryClassPathURLs, 0, this.ancillaryClassPathURLs, 0, ancillaryClassPathURLs.length); } } // ============================================== // non-public methods // ============================================== protected Class findLoadedComponent(String name) { if (componentCache.get(name) != null) { return (Class)componentCache.get(name); } return null; } /** * Finds a component by name. This method will be * invoked by the loadComponent method. * * @param name The name of the component * @return The component's <tt>Class</tt> object * @throws ComponentNotFoundException If the * component * could not * be found */ protected Class findComponent(String name) throws ComponentNotFoundException { ClassLoader ctxClsLoader = Thread.currentThread(). getContextClassLoader(); URL url = ctxClsLoader.getResource( componentArchive); if (url != null) { URL[] urls = null; if (ancillaryClassPathURLs != null && ancillaryClassPathURLs.length > 0) { urls = new URL[ ancillaryClassPathURLs.length + 1]; System.arraycopy(ancillaryClassPathURLs, 0, urls, 0, ancillaryClassPathURLs.length); urls[ancillaryClassPathURLs.length] = url; } else { urls = new URL[1]; urls[0] = url; } RestrictedURLClassLoader urlClassLoader = new RestrictedURLClassLoader(urls); try { Class cls = urlClassLoader.loadClass( name); if (cls != null) { return cls; } } catch (ClassNotFoundException e) { // ignore } } throw new ComponentNotFoundException(name); } // =============================================== // public methods // =============================================== /** * Loads the component with the specified name. * * @param name The name of the component * @return The component <tt>Class</tt> object * @throws ComponentNotFoundException If the * component * was not * found */ public Class loadComponent(String name) throws ComponentNotFoundException { Class cls = findLoadedComponent(name); if (cls != null) { return cls; } cls = findComponent(name); if (cls != null) { componentCache.put(name, cls); return cls; } throw new ComponentNotFoundException( "Definition for " + "component: " + name + " was not found"); } /** * Loads and instantiates the component with the * specified name. * * @param componentName * @return The component <tt>Class</tt> object * @throws ComponentNotFoundException */ public Object createComponent(String componentName) throws ComponentNotFoundException { Class cls = loadComponent(componentName); try { Object componentObj = cls.newInstance(); return componentObj; } catch (Exception e) { throw new ComponentNotFoundException( "Unable to" + " instantiate " + " component: " + componentName); } } }