S-JIS[2024-03-27] 変更履歴

WildFly31 Tsurugi

WildFly31でTsurugiにアクセスする方法。


概要

WildFlyのアプリケーションからTsurugiにアクセスする場合、TsurugiのクライアントライブラリーであるIceaxeTsubakuro)をwarファイルの中に入れておけば実行できる。

しかし、Iceaxeは言わばJDBCと同じ位置づけであり、通常、JDBCのjarファイルはwarファイルの中には含めず、APサーバーの静的ライブラリーとして配置しておくものらしい。
したがって、IceaxeやTsubakuroも同様にWildFlyの静的ライブラリーとして置いておくのが良さそう。
(特にTsubakuroはIPC接続する際にJNIを使うので、再デプロイと相性が悪い

※JDBCだとJNDIを使ってコネクションプールからコネクションを取得したりするが、IceaxeやTsubakuroでそれを作るのは難しそうなので、その方法はひとまず採らない。


warファイルの中にIceaxeを含める例

シンプルに、warファイルの中にIceaxeTsubakuro)を含める構成の例。

build.gradle:

plugins {
    id 'java'
    id 'war'
}

java {
    toolchain {
        languageVersion = JavaLanguageVersion.of(11)
    }
}

tasks.withType(JavaCompile) {
    options.encoding = 'UTF-8'
}

repositories {
    mavenCentral()
}

war {
    archiveFileName = 'example-wildfly.war'
}

dependencies {
    compileOnly 'jakarta.ws.rs:jakarta.ws.rs-api:3.1.0'

    implementation 'com.tsurugidb.iceaxe:iceaxe-core:1.2.0'
}

アプリケーションクラス:

import java.util.Set;

import jakarta.ws.rs.ApplicationPath;
import jakarta.ws.rs.core.Application;
@ApplicationPath("/api")
public class IceaxeApplication extends Application {

    @Override
    public Set<Class<?>> getClasses() {
        return Set.of(IceaxeResource.class);
    }
}

リソースクラス:

import java.io.IOException;

import com.tsurugidb.iceaxe.TsurugiConnector;
import com.tsurugidb.iceaxe.transaction.option.TgTxOption;

import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
@Path("/iceaxe")
public class IceaxeResource {

    private final TsurugiConnector connector = TsurugiConnector.of("tcp://localhost:12345");

    @GET
    @Produces(MediaType.TEXT_PLAIN)
    public String select() throws IOException, InterruptedException {
        try (var session = connector.createSession(); //
                var ps = session.createQuery("select * from test")) {
            var tm = session.createTransactionManager(TgTxOption.ofRTX());
            var list = tm.executeAndGetList(ps);
            return list.toString();
        }
    }
}

ビルド&デプロイ

$ ./gradlew war
$ ls build/libs/
example-wildfly.war
$ cd wildfly-31.0.1.Final/bin
$ ./jboss-cli.sh --connect
[standalone@localhost:9990 /] deploy --force=true /path/to/example-wildfly.war

実行

ブラウザーから「http://localhost:8080/example-wildfly/api/iceaxe」にアクセスする。


再デプロイ時のJMXエラー

Tsubakuroでは、初回のセッション作成時にJMXにオブジェクト(SessionInfo)を登録している。
warファイルにTsubakuroが入っている状態で再デプロイすると、実行時に「SessionInfoが既に登録済みである」というエラー メッセージが出る。

JMX経由でSessionInfoを参照しないなら、ひとまず無視しておけばよい。
(このエラーが出てもTsurugiへのアクセスは可能)

19:50:34,111 ERROR [stderr] (default task-1) javax.management.InstanceAlreadyExistsException: com.tsurugidb.tsubakuro.diagnostic.common:type=SessionInfo
19:50:34,113 ERROR [stderr] (default task-1)    at java.management/com.sun.jmx.mbeanserver.Repository.addMBean(Repository.java:436)
19:50:34,116 ERROR [stderr] (default task-1)    at java.management/com.sun.jmx.interceptor.DefaultMBeanServerInterceptor.registerWithRepository(DefaultMBeanServerInterceptor.java:1855)
19:50:34,119 ERROR [stderr] (default task-1)    at java.management/com.sun.jmx.interceptor.DefaultMBeanServerInterceptor.registerDynamicMBean(DefaultMBeanServerInterceptor.java:955)
19:50:34,119 ERROR [stderr] (default task-1)    at java.management/com.sun.jmx.interceptor.DefaultMBeanServerInterceptor.registerObject(DefaultMBeanServerInterceptor.java:890)
19:50:34,120 ERROR [stderr] (default task-1)    at java.management/com.sun.jmx.interceptor.DefaultMBeanServerInterceptor.registerMBean(DefaultMBeanServerInterceptor.java:320)
19:50:34,120 ERROR [stderr] (default task-1)    at java.management/com.sun.jmx.mbeanserver.JmxMBeanServer.registerMBean(JmxMBeanServer.java:522)
19:50:34,121 ERROR [stderr] (default task-1)    at org.jboss.as.jmx@23.0.3.Final//org.jboss.as.jmx.PluggableMBeanServerImpl$TcclMBeanServer.registerMBean(PluggableMBeanServerImpl.java:1512)
19:50:34,124 ERROR [stderr] (default task-1)    at org.jboss.as.jmx@23.0.3.Final//org.jboss.as.jmx.PluggableMBeanServerImpl.registerMBean(PluggableMBeanServerImpl.java:861)
19:50:34,125 ERROR [stderr] (default task-1)    at deployment.example-wildfly.war//com.tsurugidb.tsubakuro.diagnostic.JMXAgent.setUp(JMXAgent.java:28)
19:50:34,125 ERROR [stderr] (default task-1)    at deployment.example-wildfly.war//com.tsurugidb.tsubakuro.diagnostic.JMXAgent.sessionInfo(JMXAgent.java:42)
19:50:34,126 ERROR [stderr] (default task-1)    at deployment.example-wildfly.war//com.tsurugidb.tsubakuro.common.SessionBuilder.(SessionBuilder.java:45)
19:50:34,127 ERROR [stderr] (default task-1)    at deployment.example-wildfly.war//com.tsurugidb.tsubakuro.common.SessionBuilder.connect(SessionBuilder.java:77)
19:50:34,127 ERROR [stderr] (default task-1)    at deployment.example-wildfly.war//com.tsurugidb.iceaxe.TsurugiConnector.createLowSession(TsurugiConnector.java:244)
19:50:34,128 ERROR [stderr] (default task-1)    at deployment.example-wildfly.war//com.tsurugidb.iceaxe.TsurugiConnector.createSession(TsurugiConnector.java:224)
19:50:34,128 ERROR [stderr] (default task-1)    at deployment.example-wildfly.war//com.tsurugidb.iceaxe.TsurugiConnector.createSession(TsurugiConnector.java:188)
19:50:34,129 ERROR [stderr] (default task-1)    at deployment.example-wildfly.war//com.example.wildfly31.IceaxeResource.select(IceaxeResource.java:21)

再デプロイした後のIPC接続のエラー

warファイルにTsubakuroが入っている状態で、TsurugiへのアクセスにIPC接続を使用した後に再デプロイして再びIPC接続しようとすると、「libtsubakuro.soが既に別のクラスローダーでロードされている」というエラーが出て、続行不可能になる。
TCP接続しか使っていない場合はこのエラーは発生しない)

19:58:04,529 ERROR [io.undertow.request] (default task-1) UT005023: Exception handling request to /example-wildfly/api/iceaxe: java.lang.UnsatisfiedLinkError: Native Library /usr/lib/tsurugi-1.0.0-BETA3/lib/libtsubakuro.so already loaded in another classloader
        at java.base/java.lang.ClassLoader$NativeLibrary.loadLibrary(ClassLoader.java:2476)
        at java.base/java.lang.ClassLoader.loadLibrary0(ClassLoader.java:2705)
        at java.base/java.lang.ClassLoader.loadLibrary(ClassLoader.java:2635)
        at java.base/java.lang.Runtime.load0(Runtime.java:768)
        at java.base/java.lang.System.load(System.java:1850)
        at deployment.example-wildfly.war//com.tsurugidb.tsubakuro.channel.ipc.NativeLibrary.(NativeLibrary.java:84)
        at deployment.example-wildfly.war//com.tsurugidb.tsubakuro.channel.ipc.connection.IpcConnectorImpl.(IpcConnectorImpl.java:42)
        at deployment.example-wildfly.war//com.tsurugidb.tsubakuro.channel.ipc.connection.IpcConnectorFactory.tryCreate(IpcConnectorFactory.java:44)
        at deployment.example-wildfly.war//com.tsurugidb.tsubakuro.channel.common.connection.ConnectorHelper.create(ConnectorHelper.java:20)
        at deployment.example-wildfly.war//com.tsurugidb.tsubakuro.channel.common.connection.Connector.create(Connector.java:35)
        at deployment.example-wildfly.war//com.tsurugidb.iceaxe.TsurugiConnector.of(TsurugiConnector.java:104)
        at deployment.example-wildfly.war//com.tsurugidb.iceaxe.TsurugiConnector.of(TsurugiConnector.java:40)
        at deployment.example-wildfly.war//com.example.wildfly31.IceaxeResource.(IceaxeResource.java:17)

TsurugiへのアクセスにIPC接続を使用する場合、TsubakuroではIPC接続にJNIを使っているため、native libraryをロードしている。
ta_morimotoさんのJNIのライブラリ導入時にハマった事象によると、Javaのnative libraryはひとつのクラスローダーにしかロードできないらしい。

ひとまずの回避方法としては、再デプロイせず、WildFly自身を再起動すること。
根本的な解決策としては、Tsubakuroをwarファイルに含めず、WildFlyの静的ライブラリーとすること。


IceaxeをWildFlyの静的ライブラリーに入れる例

Iceaxeおよびその依存ライブラリー(主にTsubakuro)をWildFlyの静的ライブラリーに入れる例。


まず、Iceaxeと依存ライブラリーのjarファイルを集める。

dependenciesのiceaxe-coreはprovidedCompileにする。
そして、static_libsというディレクトリーにjarファイルを出力するGradleタスク(copyStaticLibs)を用意する。

build.gradle:

〜
dependencies {
    compileOnly 'jakarta.ws.rs:jakarta.ws.rs-api:3.1.0'

    providedCompile 'com.tsurugidb.iceaxe:iceaxe-core:1.2.0'
}

task copyStaticLibs(type: Copy) {
    from configurations.providedCompile
    into "${buildDir}/static_libs" 
}

実行(jarファイル収集)

$ ./gradlew copyStaticLibs
$ ls build/static_libs/
iceaxe-core-1.2.0.jar           tsubakuro-common-1.2.0.jar
jackson-annotations-2.13.3.jar  tsubakuro-connector-1.2.0.jar
jackson-core-2.13.3.jar         tsubakuro-explain-1.2.0.jar
jackson-databind-2.13.3.jar     tsubakuro-ipc-1.2.0.jar
java-jwt-3.19.2.jar             tsubakuro-proto-1.2.0.jar
jsr305-3.0.2.jar                tsubakuro-session-1.2.0.jar
protobuf-java-3.17.3.jar        tsubakuro-stream-1.2.0.jar
slf4j-api-1.7.36.jar

次に、出力されたjarファイルを、WildFlyのモジュール置き場にコピーする。

モジュールの置き場所は、wildfly-31.0.1.Final/modules/system/layers/base。
この下に、パッケージ名を逆順にしたディレクトリーを作ってそこに置くことになっているようだ。

つまり、Iceaxeはcom/tsurugidb/iceaxe、Tsubakuroはcom/tsurugidb/tsubakuroに置くのが本来の姿だと思われる。

しかし個別にコピーするのは面倒なので、今回はexample-wildflyというひとつのディレクトリー内に全部置くことにする。
jarファイルを置く場所は、具体的にはさらにmainというディレクトリーの下になる。

$ mkdir -p /path/to/wildfly-31.0.1.Final/modules/system/layers/base/example-wildfly/main
$ cp build/static_libs/* /path/to/wildfly-31.0.1.Final/modules/system/layers/base/example-wildfly/main/

WildFlyに静的ライブラリーとして認識させるためには、ただjarファイルを置けばいいだけではなく、module.xmlというファイルも必要になる。

wildfly-31.0.1.Final/modules/system/layers/base/example-wildfly/main/module.xml:

<?xml version="1.0" encoding="UTF-8"?>
<module name="example-wildfly" xmlns="urn:jboss:module:1.9">
    <resources>
        <resource-root path="iceaxe-core-1.2.0.jar"/>
        <resource-root path="tsubakuro-common-1.2.0.jar"/>
        <resource-root path="tsubakuro-connector-1.2.0.jar"/>
        <resource-root path="tsubakuro-explain-1.2.0.jar"/>
        <resource-root path="tsubakuro-ipc-1.2.0.jar"/>
        <resource-root path="tsubakuro-proto-1.2.0.jar"/>
        <resource-root path="tsubakuro-session-1.2.0.jar"/>
        <resource-root path="tsubakuro-stream-1.2.0.jar"/>
        <resource-root path="protobuf-java-3.17.3.jar"/>
        <resource-root path="slf4j-api-1.7.36.jar"/>
    </resources>

    <dependencies>
        <module name="java.management"/>
        <module name="java.logging"/>
    </dependencies>
</module>

moduleタグのname属性は、ディレクトリーに付けた名前と同じものにする必要がある。
(xxx/yyyというディレクトリーの場合、「name="xxx.yyy"」)

そして、使用するjarファイルをresourcesに列挙していく。
jackson系はWildFlyが自動的にインポートしているので、明示的なインポートは不要。(module.xmlに書かなくてよい)
java-jwtやjsr305も無くても大丈夫そう。


最後に、warファイル内の指定で、上記で用意したexample-wildflyモジュールを依存関係に含める必要がある。

jboss-deployment-structure.xmlというファイルを作り、その中にモジュール名を記載する。

src/main/webapp/WEB-INF/jboss-deployment-structure.xml:

<?xml version="1.0" encoding="UTF-8"?>
<jboss-deployment-structure>
    <deployment>
        <dependencies>
            <module name="example-wildfly"/>
        </dependencies>
    </deployment>
</jboss-deployment-structure>

これで、warファイルを作り直してWildFlyを起動し直せば、静的ライブラリーを利用して実行されるようになる。


NoClassDefFoundError

MalformedObjectNameExceptionはJREに含まれているのだが、どういうわけか静的ライブラリーからデフォルトでは参照できないようだ。
module.xmlのdependenciesに特に何も指定していないと実行時に以下のようなエラーが出る。

20:41:18,918 ERROR [org.jboss.resteasy.core.providerfactory.DefaultExceptionMapper] (default task-1) RESTEASY002375: Error processing request GET /example-wildfly/api/iceaxe - com.example.wildfly31.IceaxeResource.select: java.lang.NoClassDefFoundError: javax/management/MalformedObjectNameException
        at example-wildfly@1.7.36//com.tsurugidb.tsubakuro.common.SessionBuilder.(SessionBuilder.java:45)
        at example-wildfly@1.7.36//com.tsurugidb.tsubakuro.common.SessionBuilder.connect(SessionBuilder.java:77)
        at example-wildfly@1.7.36//com.tsurugidb.iceaxe.TsurugiConnector.createLowSession(TsurugiConnector.java:244)
        at example-wildfly@1.7.36//com.tsurugidb.iceaxe.TsurugiConnector.createSession(TsurugiConnector.java:224)
        at example-wildfly@1.7.36//com.tsurugidb.iceaxe.TsurugiConnector.createSession(TsurugiConnector.java:188)
        at deployment.example-wildfly.war//com.example.wildfly31.IceaxeResource.select(IceaxeResource.java:22)
〜
Caused by: java.lang.ClassNotFoundException: javax.management.MalformedObjectNameException from [Module "example-wildfly" version 1.7.36 from local module loader @36546a22 (finder: local module finder @285c08c8 (roots: 〜))]
        at org.jboss.modules.ModuleClassLoader.findClass(ModuleClassLoader.java:200)
        at org.jboss.modules.ConcurrentClassLoader.performLoadClassUnchecked(ConcurrentClassLoader.java:410)
        at org.jboss.modules.ConcurrentClassLoader.performLoadClass(ConcurrentClassLoader.java:398)
        at org.jboss.modules.ConcurrentClassLoader.loadClass(ConcurrentClassLoader.java:116)

module.xmlのdependenciesにjava.managementを指定すると通る。
(javax.management.MalformedObjectNameExceptionクラスは、JREのjava.managementモジュールに含まれている)


同様に、java.util.logging.Loggerが見つからないというエラーが出た場合、module.xmlのdependenciesにjava.loggingを指定すると通る。
(java.util.logging.Loggerクラスは、JREのjava.loggingモジュールに含まれている)

20:53:12,168 ERROR [org.jboss.resteasy.core.providerfactory.DefaultExceptionMapper] (default task-1) RESTEASY002375: Error processing request GET /example-wildfly/api/iceaxe - com.example.wildfly31.IceaxeResource.select: java.lang.NoClassDefFoundError: java/util/logging/Logger
        at example-wildfly@1.7.36//com.google.protobuf.CodedOutputStream.(CodedOutputStream.java:60)
        at example-wildfly@1.7.36//com.tsurugidb.endpoint.proto.EndpointRequest$Request.getSerializedSize(EndpointRequest.java:313)
        at example-wildfly@1.7.36//com.google.protobuf.AbstractMessageLite.writeDelimitedTo(AbstractMessageLite.java:89)
        at example-wildfly@1.7.36//com.tsurugidb.tsubakuro.channel.common.connection.wire.impl.WireImpl.toDelimitedByteArray(WireImpl.java:238)
        at example-wildfly@1.7.36//com.tsurugidb.tsubakuro.channel.common.connection.wire.impl.WireImpl.handshake(WireImpl.java:208)
        at example-wildfly@1.7.36//com.tsurugidb.tsubakuro.channel.stream.connection.StreamConnectorImpl.connect(StreamConnectorImpl.java:46)
        at example-wildfly@1.7.36//com.tsurugidb.tsubakuro.common.SessionBuilder.createAsync(SessionBuilder.java:155)
        at example-wildfly@1.7.36//com.tsurugidb.iceaxe.TsurugiConnector.createLowSession(TsurugiConnector.java:250)
        at example-wildfly@1.7.36//com.tsurugidb.iceaxe.TsurugiConnector.createSession(TsurugiConnector.java:224)
        at example-wildfly@1.7.36//com.tsurugidb.iceaxe.TsurugiConnector.createSession(TsurugiConnector.java:188)
        at deployment.example-wildfly.war//com.example.wildfly31.IceaxeResource.select(IceaxeResource.java:22)

WildFly31へ戻る / WildFlyへ戻る / 技術メモへ戻る
メールの送信先:ひしだま