プログラムでSpringBootアプリケーションを起動する

公開: 2022-03-11

この記事では、別のJavaプログラムからSpringBootアプリケーションを起動する方法について説明します。 Spring Bootアプリケーションは通常、単一の実行可能JARアーカイブに組み込まれています。 内部にすべての依存関係が含まれ、ネストされたJARとしてパッケージ化されています。

同様に、Spring Bootプロジェクトは通常、提供されたmavenプラグインによって実行可能なJARファイルとしてビルドされ、すべてのダーティな作業を実行します。 その結果、他のユーザーと共有したり、サーバーにデプロイしたりするのが簡単な、便利な単一のJARファイルが作成されます。

Spring Bootアプリケーションの起動は、 java -jar mySpringProg.jarと入力するのと同じくらい簡単で、アプリケーションはコンソールにいくつかの適切にフォーマットされた情報メッセージを出力します。

しかし、Spring Boot開発者が、人間の介入なしに別のJavaプログラムからアプリケーションを実行したい場合はどうでしょうか。

ネストされたJARのしくみ

すべての依存関係を持つJavaプログラムを単一の実行可能なJARファイルにパックするには、JARファイルでもある依存関係を提供し、最終的な実行可能なJARファイル内に何らかの方法で格納する必要があります。

「シェーディング」は1つのオプションです。 依存関係のシェーディングは、依存関係を含めて名前を変更し、クラスを再配置し、影響を受けるバイトコードとリソースを書き換えて、アプリケーション(プロジェクト)独自のコードと一緒にバンドルされるコピーを作成するプロセスです。

シェーディングを使用すると、ユーザーはすべてのクラスとリソースを依存関係から解凍し、実行可能なJARファイルにパックして戻すことができます。 これは単純なシナリオでは機能する可能性がありますが、2つの依存関係にまったく同じ名前とパスを持つ同じリソースファイルまたはクラスが含まれている場合、それらは重複し、プログラムが機能しない可能性があります。

Spring Bootは別のアプローチを取り、依存関係のJARをネストされたJARとして実行可能なJAR内にパックします。

 example.jar | +-META-INF | +-MANIFEST.MF +-org | +-springframework | +-boot | +-loader | +-<spring boot loader classes> +-BOOT-INF +-classes | +-mycompany | +-project | +-YourClasses.class +-lib +-dependency1.jar +-dependency2.jar

JARアーカイブは、標準のJava実行可能JARファイルとして編成されています。 SpringBootローダークラスはorg/springframework/boot/loaderパスにあり、ユーザークラスと依存関係はBOOT-INF/classesBOOT-INF/libにあります。

注: Springを初めて使用する場合は、SpringFrameworkの最も一般的な間違いトップ10の記事も参照してください

一般的なSpringBootJARファイルには、次の3種類のエントリが含まれています。

  • プロジェクトクラス
  • ネストされたJARライブラリ
  • SpringBootローダークラス

Spring Boot Classloaderは、最初にクラスパスにJARライブラリを設定し、次にプロジェクトクラスを設定します。これにより、IDE(Eclipse、IntelliJ)からのSpringBootアプリケーションの実行とコンソールからのSpringBootアプリケーションの実行にわずかな違いが生じます。

クラスオーバーライドとクラスローダーの詳細については、この記事を参照してください。

SpringBootアプリケーションの起動

コマンドラインまたはシェルから手動でSpringBootアプリケーションを起動するには、次のように入力します。

 java -jar example.jar

ただし、別のJavaプログラムからプログラムでSpring Bootアプリケーションを起動するには、より多くの労力が必要です。 org/springframework/boot/loader/*.classコードをロードし、Javaリフレクションを少し使用してJarFileArchiveJarLauncherをインスタンス化し、 launch(String[])メソッドを呼び出す必要があります。

これがどのように達成されるかについては、次のセクションでさらに詳しく見ていきます。

SpringBootLoaderクラスのロード

すでに指摘したように、SpringBootJARファイルは他のJARアーカイブとまったく同じです。 org/springframework/boot/loader/*.classをロードし、Classオブジェクトを作成し、それらを使用して後でSpringBootアプリケーションを起動することができます。

 import java.net.URLClassLoader; import java.util.Enumeration; import java.util.HashMap; import java.util.Map; import java.util.jar.JarEntry; import java.util.jar.JarFile; . . . public static void loadJar(final String pathToJar) throws IOException . . . { // Class name to Class object mapping. final Map<String, Class<?>> classMap = new HashMap<>(); final JarFile jarFile = new JarFile(pathToJar); final Enumeration<JarEntry> jarEntryEnum = jarFile.entries(); final URL[] urls = { new URL("jar:file:" + pathToJar + "!/") }; final URLClassLoader urlClassLoader = URLClassLoader.newInstance(urls);

ここでは、 classMapがそれぞれのパッケージ名にマップされたClassオブジェクトを保持していることがわかります。たとえば、文字列値org.springframework.boot.loader.JarLauncherJarLauncher.classオブジェクトにマップされます。

 while (jarEntryEnum.hasMoreElements()) { final JarEntry jarEntry = jarEntryEnum.nextElement(); if (jarEntry.getName().startsWith("org/springframework/boot") && jarEntry.getName().endsWith(".class") == true) { int endIndex = jarEntryName.lastIndexOf(".class"); className = jarEntryName.substring(0, endIndex).replace('/', '.'); try { final Class<?> loadedClass = urlClassLoader.loadClass(className); result.put(loadedClass.getName(), loadedClass); } catch (final ClassNotFoundException ex) { } } } jarFile.close();

whileループの最終結果は、SpringBootローダークラスオブジェクトが入力されたマップです。

実際の起動の自動化

読み込みが邪魔にならないようにすると、自動起動を完了し、それを使用して実際にアプリを起動できます。

Javaリフレクションを使用すると、ロードされたクラスからオブジェクトを作成できます。これは、チュートリアルのコンテキストで非常に役立ちます。

最初のステップは、 JarFileArchiveオブジェクトを作成することです。

 // Create JarFileArchive(File) object, needed for JarLauncher. final Class<?> jarFileArchiveClass = result.get("org.springframework.boot.loader.archive.JarFileArchive"); final Constructor<?> jarFileArchiveConstructor = jarFileArchiveClass.getConstructor(File.class); final Object jarFileArchive = jarFileArchiveConstructor.newInstance(new File(pathToJar));

JarFileArchiveオブジェクトのコンストラクターは、引数としてFile(String)オブジェクトを受け取るため、指定する必要があります。

次のステップは、 JarLauncherオブジェクトを作成することです。これには、コンストラクターにArchiveが必要です。

 final Class<?> archiveClass = result.get("org.springframework.boot.loader.archive.Archive"); // Create JarLauncher object using JarLauncher(Archive) constructor. final Constructor<?> jarLauncherConstructor = mainClass.getDeclaredConstructor(archiveClass); jarLauncherConstructor.setAccessible(true); final Object jarLauncher = jarLauncherConstructor.newInstance(jarFileArchive);

混乱を避けるために、 Archiveは実際にはインターフェースであり、 JarFileArchiveは実装の1つであることに注意してください。

プロセスの最後のステップは、新しく作成したjarLauncherオブジェクトでlaunch(String[])メソッドを呼び出すことです。 これは比較的簡単で、数行のコードが必要です。

 // Invoke JarLauncher#launch(String[]) method. final Class<?> launcherClass = result.get("org.springframework.boot.loader.Launcher"); final Method launchMethod = launcherClass.getDeclaredMethod("launch", String[].class); launchMethod.setAccessible(true); launchMethod.invoke(jarLauncher, new Object[]{new String[0]});

invoke(jarLauncer, new Object[]{new String[0]})メソッドは、最終的にSpringBootアプリケーションを起動します。 メインスレッドが停止し、SpringBootアプリケーションが終了するのをここで待機することに注意してください。

SpringBootクラスローダーについての一言

Spring Boot JARファイルを調べると、次の構造が明らかになります。

 +--- mySpringApp1-0.0.1-SNAPSHOT.jar +--- META-INF +--- BOOT-INF | +--- classes # 1 - project classes | | | | | +--- com.example.mySpringApp1 | | \--- SpringBootLoaderApplication.class | | | +--- lib # 2 - nested jar libraries | +--- javax.annotation-api-1.3.1 | +--- spring-boot-2.0.0.M7.jar | \--- (...) | +--- org.springframework.boot.loader # 3 - Spring Boot loader classes +--- JarLauncher.class +--- LaunchedURLClassLoader.class \--- (...)

次の3種類のエントリに注意してください。

  • プロジェクトクラス
  • ネストされたJARライブラリ
  • SpringBootローダークラス

プロジェクトクラス( BOOT-INF/classes )とネストされたJAR( BOOT-INF/lib )の両方が、同じクラスローダーLaunchedURLClassLoaderによって処理されます。 このローダーは、SpringBootJARアプリケーションのルートにあります。

LaunchedURLClassLoaderは、IDEとは異なるライブラリコンテンツ( BOOT-INF/lib )の後にクラスコンテンツ( BOOT-INF/classes )をロードします。 たとえば、Eclipseは最初にクラスコンテンツをクラスパスに配置し、次にライブラリ(依存関係)を配置します。

LaunchedURLClassLoaderは、クラスのロードに使用されるURLのセットを使用して作成されたjava.net.URLClassLoaderを拡張します。 URLは、JARアーカイブやクラスフォルダなどの場所を指している場合があります。 クラスの読み込みを実行する場合、URLで指定されたすべてのリソースは、URLが提供された順序でトラバースされ、検索されたクラスを含む最初のリソースが使用されます。

まとめ

従来のJavaアプリケーションでは、すべての依存関係をクラスパス引数に列挙する必要があるため、起動手順がやや面倒で複雑になります。

対照的に、Spring Bootアプリケーションは便利で、コマンドラインから簡単に起動できます。 それらはすべての依存関係を管理し、エンドユーザーは詳細について心配する必要はありません。

ただし、別のJavaプログラムからSpring Bootアプリケーションを起動すると、Spring Bootのローダークラスをロードし、 JarFileArchiveJarLauncherなどの特殊なオブジェクトを作成し、Javaリフレクションを使用してlaunchメソッドを呼び出す必要があるため、手順がより複雑になります。

結論:Spring Bootは、内部で多くの面倒なタスクを処理できるため、開発者は時間を解放して、新機能の作成やテストなど、より便利な作業に集中できます。