2017-01-17 9 views
6

URLStreamHandlerというカスタムを登録して、Amazon S3 URLへのリクエストを一般的な方法で処理しようとしています。ハンドラの実装は、S3-URLStreamHandler (github)のようになります。 Handlerクラスをsun.net.www.protocol.s3パッケージに入れませんでしたが、カスタムパッケージcom.github.dpr.protocol.s3を使用しました。 Javaパッケージをこのパッケージに取り込むために、URL classのドキュメントに続いて、システムプロパティ-Djava.protocol.handler.pkgs="com.github.dpr.protocol"を提供しました。しかし、私はs3://my.bucket/some-awesome-file.txtのようなS3-URLを処理しようとした場合、私はMalformedURLExceptionを得る:Spring Webアプリケーション(Tomcat)でカスタムURLStreamHandlerを登録します

Caused by: java.net.MalformedURLException: unknown protocol: s3 
    at java.net.URL.<init>(URL.java:600) 
    at java.net.URL.<init>(URL.java:490) 
    at java.net.URL.<init>(URL.java:439) 
    at java.net.URI.toURL(URI.java:1089) 
    ... 

私のアプリケーションは、現在のTomcatで動作します春ベースのWebアプリケーション、ですが、およそあらゆる知識と雑然としてはなりません基本となるアプリケーションコンテナ

それぞれのコードを既にデバッグしていて、クラスをロードするために使用されたクラスローダーがそれを知らないため、URLStreamHandlerを初期化できないことがわかりました。これはjava.net.URLからそれぞれのコード(JDK 1.8.0_92)である:

1174: try { 
1175: cls = Class.forName(clsName); 
1176: } catch (ClassNotFoundException e) { 
1177: ClassLoader cl = ClassLoader.getSystemClassLoader(); 
1178: if (cl != null) { 
1179:  cls = cl.loadClass(clsName); 
1180: } 
1181: } 

Class.forNameによって使用される)java.net.URLクラスのクラスローダは、ブートストラップクラスローダと認識していないシステムクラスローダを示すnullある私クラス。ブレークポイントを入れて、現在のスレッドのクラスローダーを使用してハンドラクラスをロードしようとすると、正常に動作します。つまり、私のクラスは明らかにそこにあり、アプリケーションのクラスパスにありますが、Javaはハンドラーをルックアップするために「間違った」クラスローダーを使います。

私はthis question on SOFを認識してんだけど、Tomcatは、それはアプリケーションの起動時に自社工場(org.apache.naming.resources.DirContextURLStreamHandlerFactory)だと、単一のJVMに登録さ1つの工場が存在しなければなら登録するように私は、カスタムURLStreamHandlerFactoryを登録してはいけません。 TomcatのDirContextURLStreamHandlerFactoryはカスタムプロトコルの処理に使用できるユーザーファクトリを追加することができますが、アプリケーションを別のコンテナで実行する必要があるため、アプリケーションコード内のTomcatに依存関係を追加したくありません。

カスタムURLのハンドラをコンテナに依存しない方法で登録する方法はありますか?


UPDATE 2017年1月25日:

オプション1 - このオプションは、として働く反射
を使用してカスタムURLStreamHandlerFactoryのセットを:

私は別のオプション@Nicolas Filottoは試して提案しました期待される。しかし、リフレクションを使用することによって、java.net.URLクラスの内部動作に厳密な依存関係が導入されます。幸いなことに、Oracleは基本的なJavaクラスの利便性の問題を解決することにあまり熱心ではありません。実際に​​はほぼ14年(素晴らしい仕事Sun/Oracle)のためオープンしています。

オプション2 - {JAVA_HOME}/JRE/libに/ extに
このオプションは、同様に動作へのハンドラのJARを置きます。しかし、システム拡張としてハンドラjarを追加するだけでは、トリックはできません。ハンドラのすべての依存関係も追加する必要があります。これらのクラスは、このJavaインストールを使用するすべてのアプリケーションで表示されるため、クラスパス内の同じライブラリのバージョンが異なるため、望ましくない影響が発生する可能性があります。

オプション3 - {CATALINA_HOME}/libに
にハンドラJARを入れてこれは動作しません。 Tomcat's classloader documentationによると、このディレクトリに置かれるリソースは、Tomcatのコモンクラスローダーを使用してロードされます。このクラスローダーは、java.net.URLによってプロトコルハンドラをルックアップするために使用されません。

これらのオプションが指定されている場合は、反射バリアントを使用します。すべてのオプションはそれほど素晴らしいものではありませんが、少なくとも最初のものは簡単にテストすることができ、デプロイロジックを必要としません。

public static void registerFactory() throws Exception { 
    final Field factoryField = URL.class.getDeclaredField("factory"); 
    factoryField.setAccessible(true); 
    final Field lockField = URL.class.getDeclaredField("streamHandlerLock"); 
    lockField.setAccessible(true); 

    // use same lock as in java.net.URL.setURLStreamHandlerFactory 
    synchronized (lockField.get(null)) { 
    final URLStreamHandlerFactory urlStreamHandlerFactory = (URLStreamHandlerFactory) factoryField.get(null); 
    // Reset the value to prevent Error due to a factory already defined 
    factoryField.set(null, null); 
    URL.setURLStreamHandlerFactory(new AmazonS3UrlStreamHandlerFactory(urlStreamHandlerFactory)); 
    } 
} 
+1

CL問題の場合は、 'S3-URLStreamHandler'を専用のjarファイルに入れて、' tomcat/lib'または '$ {JAVA-HOME}/jre/lib/ext'にデプロイしようとしましたか? ?このようにして、あなたのクラスは見える階層内で十分に高いCLになります。 –

+0

@NicolasFilotto実際には、これがアプリケーションコード自体で解決できることを期待していました。つまり、ハンドラクラスを別の場所に配置しようとはしませんでした。もし別の可能性があると思われなければ、私は試してみましょう。 – dpr

+0

これは、アプリケーションコードのTomcatクラスに依存関係を導入します。実際には、異なるアプリケーションサーバーで同じコードを区別する必要なく実行できるように、私が避けたいものです。 – dpr

答えて

3

あなたがいずれかの可能性:しかし、私はありませんjava.net.URLと同期のために同じロックオブジェクトを使用するために少しのコードを適応

1.カスタムURLStreamHandlerFactoryを設定するには、デコレータ

一つの方法を使用して、すでに定義されているかもしれないURLStreamHandlerFactory(この場合はtomcatによって)をラップするためにタイプURLStreamHandlerFactoryのデコレータを使用することができます。トリッキーな部分は、潜在的に定義された現在のfactoryを取得してリセットするためにリフレクション(かなりハッキリです)を使用する必要があるという事実です。ここで

はあなたのデコレータの擬似コードである:ここで

public class S3URLStreamHandlerFactory implements URLStreamHandlerFactory { 

    // The wrapped URLStreamHandlerFactory's instance 
    private final Optional<URLStreamHandlerFactory> delegate; 

    /** 
    * Used in case there is no existing URLStreamHandlerFactory defined 
    */ 
    public S3URLStreamHandlerFactory() { 
     this(null); 
    } 

    /** 
    * Used in case there is an existing URLStreamHandlerFactory defined 
    */ 
    public S3URLStreamHandlerFactory(final URLStreamHandlerFactory delegate) { 
     this.delegate = Optional.ofNullable(delegate); 
    } 

    @Override 
    public URLStreamHandler createURLStreamHandler(final String protocol) { 
     if ("s3".equals(protocol)) { 
      return // my S3 URLStreamHandler; 
     } 
     // It is not the s3 protocol so we delegate it to the wrapped 
     // URLStreamHandlerFactory 
     return delegate.map(factory -> factory.createURLStreamHandler(protocol)) 
      .orElse(null); 
    } 
} 

はそれを定義するためのコードです:

// Retrieve the field "factory" of the class URL that store the 
// URLStreamHandlerFactory used 
Field factoryField = URL.class.getDeclaredField("factory"); 
// It is a package protected field so we need to make it accessible 
factoryField.setAccessible(true); 
// Get the current value 
URLStreamHandlerFactory urlStreamHandlerFactory 
    = (URLStreamHandlerFactory) factoryField.get(null); 
if (urlStreamHandlerFactory == null) { 
    // No factory has been defined so far so we set the custom one 
    URL.setURLStreamHandlerFactory(new S3URLStreamHandlerFactory()); 
} else { 
    // Retrieve the field "streamHandlerLock" of the class URL that 
    // is the lock used to synchronize access to the protocol handlers 
    Field lockField = URL.class.getDeclaredField("streamHandlerLock"); 
    // It is a private field so we need to make it accessible 
    lockField.setAccessible(true); 
    // Use the same lock to reset the factory 
    synchronized (lockField.get(null)) { 
     // Reset the value to prevent Error due to a factory already defined 
     factoryField.set(null, null); 
     // Set our custom factory and wrap the current one into it 
     URL.setURLStreamHandlerFactory(
      new S3URLStreamHandlerFactory(urlStreamHandlerFactory) 
     ); 
    } 
} 

はNB:は、Java 9の出発、あなたが追加する必要があります--add-opens java.base/java.net=myModuleNameを起動コマンドに追加して、モジュールにを含むパッケージを深く反映させることができます。myModuleNamesetAccessible(true)RuntimeExceptionを発行します。


2は、そのすべての依存関係(installed extensionとして${JAVA-HOME}/jre/lib/extでそれを専用のジャーにカスタムURLStreamHandlerを移動し、展開することができ、このClassLoder問題を回避すべきである拡張

もう一つの方法として、それを展開します)が拡張子ClassLoaderで利用できるようになります。このようにして、クラスは表示される階層で十分に高いClassLoaderで定義されます。

+0

注:これはJDK9では動作しません。 'setAccessible(true)'は失敗します。 OpenJDK側でUrlStreamHandlerFactoryへのアクセスを可能にするためのバグがオープンされました。 https://bugs.openjdk.java。net/browse/JDK-8168076 –

+0

@JoakimErdfelt FYI私は自分の答えにNBを追加しました。 –

関連する問題