CLOVER🍀

That was when it all began.

JARファイルやJARファイルを指すInputStreamなどからクラスをロードできる、JCLで遊ぶ

ちょっと遊びでやってみたいことについて調べてたら、それができそうなものがあったので遊んでみました。

JCL(Jar Class Loader)
https://github.com/kamranzafar/JCL

Kamran
http://kamranzafar.github.io/

要はClassLoaderなのですが、こちらを使うとロード対象のクラスファイルを追加することができます。

追加する方法は、パッと見た感じ

  • JARファイルのパスをString、もしくはURLで指定
  • JARファイルをInputStreamとして指定
  • クラスファイルが配置されたディレクトリをString、もしくはURLで指定

が使えそうです。

ちょっと試してみましょう。

こちらを使うために、以下のようなMavenマルチプロジェクトを作成しました。

pom.xml … 親pom

core/pom.xml
core/src/main/java/org/littlewings/example/App.java … Mainクラス
core/src/main/java/org/littlewings/example/MyInterface.java … サンプルのインターフェース

impl-a/pom.xml
impl-a/src/main/java/org/littlewings/example/MyImplA.java … MyInterfaceの実装

impl-b/pom.xml
impl-b/src/main/java/org/littlewings/example/MyImplB.java … MyInterfaceの実装

主な部分を紹介。

Mainのpom。JCLへの依存関係を書いています。
core/pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>jar-classloader-example</artifactId>
        <groupId>org.littlewings</groupId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>core</artifactId>

    <dependencies>
        <dependency>
            <groupId>org.xeustechnologies</groupId>
            <artifactId>jcl-core</artifactId>
            <version>2.6</version>
        </dependency>
    </dependencies>
</project>

Mainクラス。
core/src/main/java/org/littlewings/example/App.java

package org.littlewings.example;

import java.io.FileInputStream;
import java.io.IOException;

import org.xeustechnologies.jcl.JarClassLoader;
import org.xeustechnologies.jcl.JclObjectFactory;

public class App {
    public static void main(String... args) throws IOException {
        String targetClassName = args[0];

        JarClassLoader jcl = new JarClassLoader();
        jcl.add(new FileInputStream("impl-a/target/impl-a-0.0.1-SNAPSHOT.jar"));
        jcl.add("impl-b/target/classes");

        JclObjectFactory factory = JclObjectFactory.getInstance();
        MyInterface itf = (MyInterface) factory.create(jcl, targetClassName);

        System.out.printf("Loaded message = %s%n", itf.message());

        itf = null;
        jcl = null;

        System.gc();

        System.console().readLine("Please Enter!");
    }
}

今回は、JARファイルのパスおよびコンパイルされたクラスファイルが配置されたディレクトリを指定してみました。

        JarClassLoader jcl = new JarClassLoader();
        jcl.add(new FileInputStream("impl-a/target/impl-a-0.0.1-SNAPSHOT.jar"));
        jcl.add("impl-b/target/classes");

このパスに登場するプロジェクトは、それぞれ「impl-a」、「impl-b」として作成しています。

あと、MyInterfaceインターフェースの実装を引数でもらってロード後、インスタンス化するようにしてみました。

        JclObjectFactory factory = JclObjectFactory.getInstance();
        MyInterface itf = (MyInterface) factory.create(jcl, targetClassName);

インスタンスの生成自体は、JCL提供のクラスを使用しています。

最後はClassLoaderおよびMyInterface型の変数にnull参照を代入し、GCかけてアンロードを行っています。これはオマケですが。

サンプルのインターフェース。こちらの実装を、サブプロジェクトで作成します。
core/src/main/java/org/littlewings/example/MyInterface.java

package org.littlewings.example;

public interface MyInterface {
    String message();
}

Jar Class Loaderで動的にロード対象となる、サブプロジェクト。
impl-a/pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>jar-classloader-example</artifactId>
        <groupId>org.littlewings</groupId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>impl-a</artifactId>

    <dependencies>
        <dependency>
            <groupId>org.littlewings</groupId>
            <artifactId>core</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>
    </dependencies>
</project>

MyInterfaceインターフェースの実装。
impl-a/src/main/java/org/littlewings/example/MyImplA.java

package org.littlewings.example;

public class MyImplA implements MyInterface {
    @Override
    public String message() {
        return "MyImplA!!";
    }
}

impl-b/pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>jar-classloader-example</artifactId>
        <groupId>org.littlewings</groupId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>impl-b</artifactId>

    <dependencies>
        <dependency>
            <groupId>org.littlewings</groupId>
            <artifactId>core</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>
    </dependencies>
</project>

こちらに対して、coreプロジェクトに対して以下のように実行してみます。
Mavenの出力は省いています

$ mvn -pl core exec:java -Dexec.mainClass=org.littlewings.example.App -Dexec.args=org.littlewings.example.MyImplA
Loaded message = MyImplA!!
Please Enter!

$ mvn -pl core exec:java -Dexec.mainClass=org.littlewings.example.App -Dexec.args=org.littlewings.example.MyImplB
Loaded message = MyImplB!!
Please Enter!

coreプロジェクトから、「impl-a」ないし「impl-b」には直接の依存関係は付与されていません。というか、インターフェースの提供側と実装側だから、その方向はありえませんが…。

というわけで、JarClassLoaderに追加したJARファイルやディレクトリからクラスがロードできていることがわかります、と。

このプログラムは、Enterを打つと終了します。System.gc後に止めているのですが、自分はこの間に

$ jmap -clstats [PID]

を使って以下の部分でクラスがアンロードされたかどうか見ていました。

        itf = null;
        jcl = null;

        System.gc();

ここはオマケですけどね。

実際の仕事とかで使うかどうかというと微妙ですが、ちょっと試してみたいところがあるなーというライブラリでした。