ソフトウェアエンジニアの雑記

日々思ったことをまとめます

Javaをスクリプト言語っぽくするJBang その3(SpringBootでAPIサーバを作る,実用プロジェクトに変換する)

Javaスクリプト言語っぽくするJBangその3です。今回はSpringBootを起動してみようと思います。その1その2の内容は説明しませんので、下記からみてください。

k-sasaki.net

k-sasaki.net

想定シナリオ

ちょっとSpringBootとかで試したいことがあるときやAPIサーバの挙動とかを軽くテストしたいときに使う感じです。モノリスなどで大きい場合はちょっとした検証をするにもDBやRedisなどが必要になったり、ユニットテストを書かないといけなかったりちょっと面倒です。それをJBangが解決してくれます。

成果物

成果物は下記になります。一つずつ説明します。(追加説明を希望の場合はコメントにお願いします。)

Gistにもあげておきます。

https://gist.github.com/ko-sasaki/f38dfe9fc7146268520cf72f333565e5

///usr/bin/env jbang "$0" "$@" ; exit $?

package api;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

//JAVA 21+
//DEPS org.springframework.boot:spring-boot-starter-web:3.5.0
//FILES application.yml
//REPOS central,jitpack,google


@SpringBootApplication
@RestController
public class SpringBootSample {

    @Value("${sample.message}")
    private String message;

    public static void main(String[] args) {
        SpringApplication.run(SpringBootSample.class, args);
    }

    /**
     * 固定メッセージを返却する.
     */
    @GetMapping("/hello")
    public String hello() {
        return "Hello, World!";
    }

    /**
     * URLパスで指定された文字列でメッセージを置換して返却する.
     */
    @GetMapping("/hello/{name}")
    public String hello(@PathVariable String name) {
        return "Hello, {name}!".replace("{name}", name);
    }
    
    /**
     * application.ymlのメッセージを表示する
     */
    @GetMapping("/hello/message")
    public String message() {
        return message;
    }
}

application.ymlの中身は下記

server:
  port: 8081

sample:
  message: Hello, Application Yaml!

ファイル作成

jbang init を使用して新規にファイルを作成します。

jbang init api/SpringBootSample.java

上記でファイルが作成されます。

パッケージ設定

SpringBootはルートパッケージですとDB設定などが動きだしてしまうので、テスト用のパッケージを指定します。

package api;

Javaバージョン

Java21以上で動作するように設定します。

//JAVA 21+

Javaがインストールされていない場合は下記のコマンドを入力してみてください。

// JBangを使用する場合
jbang jdk install 21

or 

// SDKMANを使用する場合
sdk install java 21.0.7-tem

依存関係

SpringBootをAPIサーバとして動かすのはこの1行だけで十分になります。

//DEPS org.springframework.boot:spring-boot-starter-web:3.5.0

なんかすごいですよね。この1行だけでSpringBootが起動できるなんて。

ファイル

//FILESで起動時に読み込むファイルを指定する。

//FILES application.yml

Javaコード

Javaコードの部分は、SpringBootのRestControllerのときに書くものを1ファイルにまとめたものです。いくつかのパターンで書いてあります。

@SpringBootApplication
@RestController
public class SpringBootSample {

    @Value("${sample.message}")
    private String message;

    public static void main(String[] args) {
        SpringApplication.run(SpringBootSample.class, args);
    }

    @GetMapping("/hello")
    public String hello() {
        return "Hello, World!";
    }

    @GetMapping("/hello/{name}")
    public String hello(@PathVariable String name) {
        return "Hello, {name}!".replace("{name}", name);
    }
    
    @GetMapping("/hello/message")
    public String message() {
        return message;
    }
}

実行

Webアプリケーションでも実行方法は変わりません。下記のコマンドで実行します。

$jbang api/SpringBootSample.java

[jbang] Building jar for SpringBootSample.java...

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/

 :: Spring Boot ::                (v3.5.0)

2025-06-14T10:11:49.229+09:00  INFO 60388 --- [           main] api.SpringBootSample                     : Starting SpringBootSample using Java 21.0.7 with PID 60388 (/Users/kohei.sasaki/.jbang/cache/jars/SpringBootSample.java.be468baacb1ab8033bb744d7e1c4d7073e1736db3075b9bb1af7e20d0c22a440/SpringBootSample.jar started by kohei.sasaki in /Users/kohei.sasaki/git/jbang-sample)
2025-06-14T10:11:49.230+09:00  INFO 60388 --- [           main] api.SpringBootSample                     : No active profile set, falling back to 1 default profile: "default"
2025-06-14T10:11:49.664+09:00  INFO 60388 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port 8081 (http)
2025-06-14T10:11:49.674+09:00  INFO 60388 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2025-06-14T10:11:49.674+09:00  INFO 60388 --- [           main] o.apache.catalina.core.StandardEngine    : Starting Servlet engine: [Apache Tomcat/10.1.41]
2025-06-14T10:11:49.706+09:00  INFO 60388 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2025-06-14T10:11:49.706+09:00  INFO 60388 --- [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 449 ms
2025-06-14T10:11:49.911+09:00  INFO 60388 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port 8081 (http) with context path '/'
2025-06-14T10:11:49.917+09:00  INFO 60388 --- [           main] api.SpringBootSample                     : Started SpringBootSample in 0.938 seconds (process running for 1.114)
2025-06-14T10:12:09.050+09:00  INFO 60388 --- [nio-8081-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring DispatcherServlet 'dispatcherServlet'
2025-06-14T10:12:09.050+09:00  INFO 60388 --- [nio-8081-exec-1] o.s.web.servlet.DispatcherServlet        : Initializing Servlet 'dispatcherServlet'
2025-06-14T10:12:09.050+09:00  INFO 60388 --- [nio-8081-exec-1] o.s.web.servlet.DispatcherServlet        : Completed initialization in 0 ms

これで起動完了です。

curlコマンドでテストしてみます。

$ curl localhost:8081/hello
Hello, World!

$ curl localhost:8081/hello/hogehoge
Hello, hogehoge!

$ curl localhost:8081/hello/message
Hello, Application Yaml!

ちゃんと期待したものが応答されました。

Gistを経由してのコード共有

上記のコードをGistにあげておきます。jbangコマンドにGistのURLを指定すると自動でダウンロードして、依存解決をして実行してくれます。とても便利です。

$ jbang https://gist.github.com/ko-sasaki/f38dfe9fc7146268520cf72f333565e5

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/

 :: Spring Boot ::                (v3.5.0)

2025-06-14T14:50:06.953+09:00  INFO 6158 --- [           main] api.SpringBootSample                     : Starting SpringBootSample using Java 21.0.7 with PID 6158 (/Users/kohei.sasaki/.jbang/cache/jars/SpringBootSample.java.6ed9516f48ab6c188d73ce5f1a848c811e4640775b1e8eea6cc1305d8c9a6c69/SpringBootSample.jar started by kohei.sasaki in /Users/kohei.sasaki/git/jbang-sample)
2025-06-14T14:50:06.954+09:00  INFO 6158 --- [           main] api.SpringBootSample                     : No active profile set, falling back to 1 default profile: "default"
2025-06-14T14:50:07.358+09:00  INFO 6158 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port 8081 (http)
2025-06-14T14:50:07.368+09:00  INFO 6158 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2025-06-14T14:50:07.368+09:00  INFO 6158 --- [           main] o.apache.catalina.core.StandardEngine    : Starting Servlet engine: [Apache Tomcat/10.1.41]
2025-06-14T14:50:07.395+09:00  INFO 6158 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2025-06-14T14:50:07.395+09:00  INFO 6158 --- [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 415 ms
2025-06-14T14:50:07.574+09:00  INFO 6158 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port 8081 (http) with context path '/'
2025-06-14T14:50:07.579+09:00  INFO 6158 --- [           main] api.SpringBootSample                     : Started SpringBootSample in 0.846 seconds (process running for 1.019)

とても簡単ですね。

プロジェクトに変換する

ここまでプロジェクトができたら、jbangではなく通常のプロジェクトに変換したくなってきます。JBangではexportコマンドがあり、それを実現できます。exportのコマンドはExecutableJar形式、Gradle形式、Maven形式など色々な形式で出力できます。今回はGradle形式で出力してみます。

$ jbang export gradle --output export_dir api/SpringBootSample.java

export_dirディレクトリの中身をみてみます。

$ tree export_dir
export_dir/
├── build.gradle
└── src
    └── main
        ├── java
        │   └── api
        │       └── SpringBootSample.java
        └── resources
            └── application.yml

jbangで書いた構成がそのまま出力されています。build.gradleも生成されており、実際にビルドや実行などができます。

まとめ

JBangでSpringBootのWebアプリケーションサーバの起動がすぐできました。Gist経由でコードの共有や実行まで確認できたり、exportコマンドでスムーズにプロジェクトに昇格できるようになっています。プロダクトエンジニアのことがよくわかっている、かなり気が利いているツールになっています。エコシステムとしても既存のものを最大限フル活用しており、独自仕様のようなものがコンパクトにまとまっています。チーム内でも広めたいですし、Javaエンジニアのみならず、Java初学者の方がもっと使ってくれたらと思います。

次回はAI系またはデバッグについて書こうと思います。

Javaをスクリプト言語っぽくするJBang その2(JAVA,DEPS,SOURCES,FILES, etc...)

JBangのその2です。依存関係や複数ファイルでの実行などその1より少し大きいコードを書いていこうとおもいます。

インストールや基本的な設定はその1をみてください。 k-sasaki.net

成果物

本日のコードは下記のようになります。内容は、PlayWright for Javaのライブラリを使用して、引数で入力したURLのスクリーンショットを取得するコードになります。これもサクっとできちゃうので、JBangのいいところです。

1つ1つ解説していきます。

Sample.Java

///usr/bin/env jbang "$0" "$@" ; exit $?

//SOURCES ScreenShotService.java

//FILES application.yml

//REPOS central,jitpack,google

import java.util.Scanner;

public class Sample {
    public static void main(String... args) {
        System.out.println("starting playwright. please input url.");
        Scanner scanner = new Scanner(System.in); // 標準入力待ち状態にする
        String inputUrl = scanner.next();  // 標準入力を取り出す
        new ScreenShotService().takeScreenShot(inputUrl);
    }
}

今回は、説明のためにもう1ファイル追加します。

ScreenShotService.java

//DEPS com.microsoft.playwright:playwright:1.52.0

import java.nio.file.Paths;

import com.microsoft.playwright.Page;
import com.microsoft.playwright.Playwright;
import com.microsoft.playwright.Response;

public class ScreenShotService {

    public void takeScreenShot(String url) {
        try (Playwright playwright = Playwright.create();) {
            Page page = playwright.chromium()
                    .launch(new com.microsoft.playwright.BrowserType.LaunchOptions().setHeadless(true))
                    .newPage();
            Response response = page.navigate(url);
            page.screenshot(new Page.ScreenshotOptions().setPath(Paths.get("example_com_screenshot.png")));
            response.headers().forEach((key, value) -> {
                System.out.println(key + ": " + value);
            });
        }
    }
}

ディレクティブ

//JAVA//DEPSのようなものをディレクティブと呼びます。こちらはJBang特有のディレクティブでJBang Directiveとドキュメントにも書いてあります。この記法を使い、依存関係やJavaのバージョンなどを解決していきます。

JAVA

Javaのバージョン指定をするディレクティブになります。

下記のようにJava21固定なものとJava21以上のと範囲を宣言できます。

//JAVA21

or 

//JAVA21+

PATHから該当するJavaのバージョンが見つからなかったら、実行時エラーとなります。

その場合は、jbangでもJDKのインストールコマンドがあるので、実行してみてください。

jbang jdk install 21

SDKMANでもインストールは可能です。

sdk install java 21.0.7-tem

DEPS

依存関係を解決するディレクティブです。Gradleの依存解決の記法を採用しています。 Javaソースコード内に必要な依存関係を書くだけで実行時にライブラリを取得してきてくれます。

//DEPS com.microsoft.playwright:playwright:1.52.0

REPOS

Maven Repositoryを指定するディレクティブです。central, jitpack, google などが指定でき、個別で立てているMavenRepositoryは、key=valueの形で記述します。

//REPOS central,jitpack,google,MYREPO=https://www.example.com/

SOURCES

実行時に指定したJavaファイル以外のJavaファイルを指定するディレクティブです。いくつ書いてもOKです。今回の例ではPlayWrightの処理は、別ファイルに書いてますので、実行時に指定するJavaファイルに//SOURCESでファイルを指定しないとコンパイルエラーになります。

//SOURCES ScreenShotService.java

FILES

//SOURCESJavaファイルでしたが、それ以外のファイルはFILESディレクティブで指定します。指定方法は//SOUECESのときと同じで引数にファイル名を書く感じです。いくつ書いてもOKです。

//FILES application.yml

実行

$ jbang Sample.java

[jbang] Resolving dependencies...
[jbang]    com.microsoft.playwright:playwright:1.52.0
[jbang] Dependencies resolved
[jbang] Building jar for Sample.java...

starting playwright. please input url.   // URLの入力の促し
https://www.example.com/

// ヘッダ情報の出力
accept-ranges: bytes  
alt-svc: h3=":443"; ma=93600,h3-29=":443"; ma=93600,quic=":443"; ma=93600; v="43"
cache-control: max-age=2328
content-encoding: gzip
content-length: 648
content-type: text/html
date: Fri, 13 Jun 2025 08:43:17 GMT
etag: "84238dfc8092e5d9c0dac8ef93371a07:1736799080.121134"
last-modified: Mon, 13 Jan 2025 20:11:20 GMT
vary: Accept-Encoding

スクリーンショットもとれています。

example_com_screenshot.png

まとめ

今回は、JBang Directiveの一部を紹介しました。このソースコードだけでPlayWrightのサンプルを作ることができます。GradleやMavenの設定とかは書く必要はなくて、コード上に依存関係を書くだけなので、モックやサンプル実行などに最適なんじゃないかと思います。次回は、実際のアプリケーションを書いてみます。SpringBootやAIのサンプル実装を書いてみます。

Javaをスクリプト言語っぽくするJBang その1(install, edit, run, build)

JBangはJavaでプロトタイピングやMVPやバグ検証などにサクッと試せるツールだということは知っていたのですが、JJUG CCC 2025のセッションでJBangの有効活用法の紹介があり、これは試してみないとということで、数回にわけてご紹介させていただきます。

感銘を受けたJJUGのセッション資料

https://github.com/tadayosi/jjug2025-jbang

インストール

公式のインストール手順はこちら

SDKMANでのインストール

ここでは、SDKMANでインストールしてみます。

sdk install jbang

JBangで処理開始

jbangを始めるにあたってjbang initを使うのが簡単です。

jbang init Sample.java

下記のファイルが生成されます。

///usr/bin/env jbang "$0" "$@" ; exit $?

import static java.lang.System.*;

public class Sample {
    public static void main(String... args) {
        out.println("Hello World");
    }
}

先頭行がハックらしくて、//Javaのインラインコメントで、そのあとにshebangが書いてありShellからでも実行ができるようになっています。

編集

jbangコマンド経由ですぐに編集ができます。

$ jbang edit Sample.java

[jbang] Assuming your editor have JBang support installed. See https://jbang.dev/ide
[jbang] If you prefer to open in a sandbox run with `jbang edit -b` instead.
[jbang] You requested to open default editor but no default editor configured.

jbang can download and configure a visual studio code (VSCodium) with Java support to use
See https://vscodium.com for details

Do you want to

(1) Download and run VSCodium
(2) Use 'code'
(3) Use 'idea'
(0) Cancel

使いたいIDE or エディタを選ぶのみになります。各環境で快適にコードを書くにはプラグインを入れる必要があるのでインストールをしてください。プラグイン一覧

実行

runコマンド

実行コマンドです。runがなくても第二引数にJavaファイルがあると動作します。Shellスクリプトとしても実行可能です。(Windowsの場合はWSL環境で実行できます)

$ jbang run Sample.java 
or
$ jbang Sample.java 
or
$ ./Sample.java
or
$ jbang https://gist.github.com/ko-sasaki/a655db96992709cd597908ae702aaad0  // Gistに上がっているものもローカルに落とさずに実行が可能です

Hello World

buildコマンド

buildコマンドもあり、すぐにexecutable jarにしてくれる。

$ jbang build --build-dir=jbang_build Sample.java
$ ls jbang_build
Sample.jar
$ java -jar jbang_build/Sample.jar
Hello World

GraalVMがインストールされていればネイティブコマンドにもビルドできます。

$ jbang build --build-dir=jbang_native_build --native Sample.java
... 時間がかかります ...

$ ls jbang_native_build/
Sample.bin      Sample.jar

$ ./jbang_native_build/Sample.bin 
Hello World

.jarファイルと.binファイルが生成されています。.binファイルの方は実行可能です。

まとめ

今回はJBangのインストールと実行とビルドについてでした。GistにアップしているソースもURL指定で実行できたり、Nativeビルドのコマンドが標準でついているあたり便利だと思います。Javaは実行するまでの設定が重い印象ですが随分ショートカットができていると思います。 次回は、依存関係や複数ファイルでの設定方法をまとめたいとおもいます。

JJUG CCC 2025 Springの参加レポート

ccc2025spring.java-users.jp に参加してきました。簡単にですが、感想とその後をまとめます。

セッション一覧はこちら。https://sessionize.com/api/v2/s2ztutnz/view/Sessions

クレディセゾンの内製開発事例:ユーザー部門と共にSMS送信システムを開発した話

スポンサーセッションで、セゾンの中の開発体制や組織体制の話が中心で、非エンジニアとのコミュニケーションの取り方や、環境構築の試行錯誤などの絶賛組織拡大中なお話でした。

docswell.com

シンプルは作れる! - 実験からも見えてきたイミュータブルデータモデルの効果

自分のここ10年くらいの設計は、実は名前がついていたということに気づいたセッションだった。この考え方はあんまり受け入れてもらえないときが続いたけど、同じようなことを考えていた人がいたという安心感とこの灯火を消してはいけないという思いも自分の中に芽生えたと思う。(自分はこの設計や考え方を変える気はさらさらないけど)

この設計を行うために、環境やツールなどを揃えているので今後広められると良いなと思う。

docs.google.com

scrapbox.io

単一Gitリポジトリから独立しました

LINEヤフーさんのセッション。モノレポに寄せたけど、組織的な課題や権限問題で分離したっていう話。個人的にマルチリポにする方法でも違うやり方しているからぜひLINEヤフーさんと色々ディスカッションして、理解を深めたいところ。

speakerdeck.com

Javaスクリプト言語だ ― JBangが変えるJava開発の未来

今回、一番業務に取り入れてみようと思った内容だった。以前、Groovyにも@Grabっていうのがあって、コード上で依存関係を解消できる機能があったんだけど、あんまりうまく動かせないのとIDEの支援がそんなになかったので断念してしまったが、JBangはCLIとして提供されており、そこが画期的だった。

コマンド 説明
init 雛型の作成
build コンパイルコマンド。jarファイルおよびGraalVMがあればネィティブコマンドにもなる
edit 編集コマンド(IDEVScodeなどが選べる)
export Mavan/Gradle/fatjar/などいろいろな出力プロジェクトに変換が可能
wrapper GradleWrapperのように、JBangがインストールされていなくても実行時にインストールする仕組みを提供
template テンプレートの作成や既存テンプレートの呼び出しが可能

検証やモック開発、MVPをサクっとつくったり、CLIなどをサクッとつくるのに便利なコマンド群がすでに揃っている。検証やモック開発、MVPを作り終わったら、ある程度見慣れたJavaの実用的なプロジェクトに変換してくれるというかゆいところに手が届きまくっているツールだった。

ちなみに、下記ソースコードでSpringBootがサクっと立ち上がります。

package rest;
///usr/bin/env jbang "$0" "$@" ; exit $?

//DEPS org.springframework.boot:spring-boot-dependencies:3.4.6@pom
//DEPS org.springframework.boot:spring-boot-starter-web

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

/**
 * Spring Boot版のREST実装。
 * Spring Bootはパッケージがないと
 * クラススキャンが動かない。
 */
@SpringBootApplication
@RestController

public class RestSampleApp {
    /**
     * Spring Bootではmainメソッドが必要。
     */
    public static void main(String... args) {
        SpringApplication.run(RestSampleApp.class, args);
    }

    @GetMapping("/hello")
    public String hello() {

        return "Hello, World!";
    }

    @GetMapping("/hello/{name}")
    public String helloArgs(@PathVariable String name) {
        return "Hello, " + name + "!";
    }
}

github.com

Feature Flag 開発を標準化し、加速させる Open Feature を導入する

OpenFeatureをSpringBootでの使い方と守った方がいいことの発表は参考になった。濫用はしない方がいいし、使われなくなったFeatureFlagはちゃんと回収して、削除するなり処理をした方がいい。ここまでは当たり前のことなんだけど、LLMでやると良い感じにうまくいくという話があって、良い使い方かもと思って、個人的にも深ぼってみる予定。なお、OpenFeatureはまだExperimentalが多いようで、FF4JやSpringConfigで自前実装している部分は、そのままでいいかなと思った。

speakerdeck.com

Java 30周年記念! Javaの30年をふりかえる

Java30年+Javaになる前5年の歴史を振り返りです。政治的な思惑や予算的な観点など、あまり語られない裏話的なものがたくさんあり、Java前史〜興隆期〜停滞期〜再興期と色々なものを乗り越えてきたJavaとそれを30年以上見守ってきた桜庭さんのとてもいい話でした。ロングバージョンでぜひ聞きたいです。

speakerdeck.com

まとめ

Javaが始まってから30年経つのに開発の勢いが衰えてないどころか増しておりついていくのが割と大変ではあるが、後方互換性もある程度ちゃんとしているので安心しては使えるかなと。(JDK9 jigsawは例外)次回はぜひ登壇したいので、色々ネタを探してストックしておくとともに発信もしていこうかなと。

@SpringBootTestのときに、@Value("{$spring.profiles.active}")が動作しなかったのでメモ

@SpringBootTestでテスト実行を行ったときに、@Values("${spring.profiles.active}"が取れないってエラーがでたのでメモ。

コード

アプリケーションコードは、単純なRestControllerになります。

@RestController
@RequestMapping("/sample")
public class LineController {

@Value("${spring.profiles.active}")
private String profile

// 省略

}

テストコードも、単純な@SpringBootTestを使ったものなります。

@SpringBootTest
class SampleControllerTest {

  // 省略
}

実行してみると、下記のようなエラーがでます。

Caused by: java.lang.IllegalArgumentException: Could not resolve placeholder 'spring.profiles.active' in value "${spring.profiles.active}"
    at org.springframework.util.PropertyPlaceholderHelper.parseStringValue(PropertyPlaceholderHelper.java:180) ~[spring-core-6.1.6.jar:6.1.6]
    at org.springframework.util.PropertyPlaceholderHelper.replacePlaceholders(PropertyPlaceholderHelper.java:126) ~[spring-core-6.1.6.jar:6.1.6]
    at org.springframework.core.env.AbstractPropertyResolver.doResolvePlaceholders(AbstractPropertyResolver.java:239) 
....

${spring.profiles.active}の解決ができていないとログがでています。@SpringBootTestに引数を渡してあげると解決します。

解決方法

@SpringBootTestに引数で、設定値を渡すと解決します。

@SpringBootTest(properties = "spring.profiles.active=unit-test")
class SampleControllerTest {

  // 省略
}

テストを実行すると、値も取得できておりテストも通ります。

割とハマりポイントかなと思いますので、メモになります。

@SpringBootTestのときに、@Value("{spring.profiles.active}")が動作しなかったのでメモ

@SpringBootTestでテスト実行を行ったときに、@Values("${spring.profiles.active}"が取れないってエラーがでたのでメモ。

コード

アプリケーションコードは、単純なRestControllerになります。

@RestController
@RequestMapping("/sample")
public class LineController {

@Value("${spring.profiles.active}")
private String profile

// 省略

}

テストコードも、単純な@SpringBootTestを使ったものなります。

@SpringBootTest
class SampleControllerTest {

  // 省略
}

実行してみると、下記のようなエラーがでます。

Caused by: java.lang.IllegalArgumentException: Could not resolve placeholder 'spring.profiles.active' in value "${spring.profiles.active}"
    at org.springframework.util.PropertyPlaceholderHelper.parseStringValue(PropertyPlaceholderHelper.java:180) ~[spring-core-6.1.6.jar:6.1.6]
    at org.springframework.util.PropertyPlaceholderHelper.replacePlaceholders(PropertyPlaceholderHelper.java:126) ~[spring-core-6.1.6.jar:6.1.6]
    at org.springframework.core.env.AbstractPropertyResolver.doResolvePlaceholders(AbstractPropertyResolver.java:239) 
....

${spring.profiles.active}の解決ができていないとログがでています。@SpringBootTestに引数を渡してあげると解決します。

解決方法

@SpringBootTestに引数で、設定値を渡すと解決します。

@SpringBootTest(properties = "spring.profiles.active=unit-test")
class SampleControllerTest {

  // 省略
}

テストを実行すると、値も取得できておりテストも通ります。

割とハマりポイントかなと思いますので、メモになります。

OpenRewriteでマルチプロジェクト構成のSpringBoot2のマイグレーションを行う

本エントリーはJava Advent Calendarの21日目です。昨日はHatanoさんの文鳥は家鴨の夢を見るか #Java - Qiitaでした。

OpenRewriteを使用して、MultiProject構成のSpringBoot2をSpringBoot3にマイグレーションを行います。所属している事業部ではよく用いられているSpringBoot x Gradle x マルチプロジェクト 構成に対して、マイグレーションを実施します。弊社でもOpenRewriteを使用してマイグレーションを行っています。

docs.openrewrite.org

ディレクト

appディレクトリとserviceディレクトリの2つのマルチプロジェクト構成になっています。

├── app
│   ├── build
│   ├── build.gradle
│   └── src
├── build.gradle
├── gradlew
├── gradlew.bat
├── service
│   ├── build
│   ├── build.gradle
│   └── src
└── settings.gradle

build.gradle

build.gradleは、下記にようになっており、SpringBoot2.7.4Java17となっています。

plugins {
    id 'java'
    id 'org.springframework.boot' version '2.7.4'  apply false
    id 'io.spring.dependency-management' version '1.0.11.RELEASE'  apply false
}

group = 'jp.co.excite'
version = '0.0.1-SNAPSHOT'

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

subprojects {

    apply plugin: 'java'
    apply plugin: 'io.spring.dependency-management'
    apply plugin: 'org.springframework.boot'

    configurations {
        compileOnly {
            extendsFrom annotationProcessor
        }
    }

    repositories {
        mavenCentral()
    }

    dependencies {
        implementation 'org.springframework.boot:spring-boot-starter'
        implementation 'org.springframework.boot:spring-boot-starter-validation'

        compileOnly 'org.projectlombok:lombok'
        developmentOnly 'org.springframework.boot:spring-boot-devtools'
        annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'
        annotationProcessor 'org.projectlombok:lombok'

        testImplementation 'org.springframework.boot:spring-boot-starter-test'
        testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
    }
    tasks.named('test') {
        useJUnitPlatform()
    }

}

project(":app") {
    dependencies {
        implementation project(":service")
        implementation 'org.springframework.boot:spring-boot-starter-web'
    }
}

project(":service") {
    repositories {
        mavenCentral()
    }
}

コード

入力文字を大文字にするAPIを用意します。

コントローラは下記のようになります。

package jp.co.excite.adventcalendar.controller;

import jp.co.excite.adventcalendar.SampleService;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.validation.constraints.NotBlank;


@RestController
@RequestMapping("/")
public class RootController {

    private final SampleService sampleService;

    public RootController(SampleService sampleService) {
        this.sampleService = sampleService;
    }

    @RequestMapping("upper")
    public String upper(@NotBlank String text) {
        return sampleService.upper(text);
    }
}

サービスは下記のようになります。

package jp.co.excite.adventcalendar;

import org.springframework.stereotype.Service;


public interface SampleService {
    String upper(String text);
}

@Service
class SampleServiceImpl implements SampleService {

    @Override
    public String upper(String text) {
        return String.format("input: %s", text.toUpperCase());
    }
}

上記のAPIにOpenRewriteでマイグレーションをかけていきます。

build.gradleにOpenRewriteの設定を追加

OpenRewriteの設定を追加します。 マルチプロジェクト構成でも、rootprojectsubprojectsのディレクティブの中に書くのではなく、トップ階層に書くのがポイントです。

plugins {
    id 'java'
    id 'org.springframework.boot' version '2.7.4'  apply false
    id 'io.spring.dependency-management' version '1.0.11.RELEASE'  apply false

    id 'org.openrewrite.rewrite' version '6.28.3' apply false    // <-- 追加
}

/* 下記を追加 */

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

apply plugin: 'org.openrewrite.rewrite'

rewrite {
    activeRecipe("org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_3")
    activeRecipe("org.openrewrite.java.migrate.UpgradeToJava21")
}

repositories {
    mavenCentral()
}

dependencies {
    rewrite("org.openrewrite.recipe:rewrite-spring:5.25.0")
    rewrite("org.openrewrite.recipe:rewrite-migrate-java:2.31.0")
}

/* ここまで追加 */

今回は、SpringBoot3.3にするのに追加して、Java21にもアップデートします。 設定は、 rewriteの中に適用するレシピを記述します。今回は、UpgradeSpringBoot_3_3UpgradeToJava21を適用します。dependenciesには、それぞれの依存関係を書いておきます。

今回使用したレシピ以外にも、他の言語のものもあったりするので、探してみてください。

docs.openrewrite.org

OpenRewriteを実行する

./gradlew rewriteDryRun   // <-- コードは変更されません
./gradlew rewriteRun  // <-- コードが変更されます

実行後の差分

キャプチャで見づらいかもですが、SpringBootのバージョン変更、java tool chain のバージョン変更、javax -> jakarta パッケージの変更や、String.format -> "text".formatted など変更されています。

SpringBoot3へのマイグレーション

まとめ

小さいサンプルコードなので変更は少ないですけど、実際は、2000ファイルや4000ファイルくらい変更がある場合もあります。そして、ほぼすぐ動作しますので単体テストを通して、ある程度負荷とテストとかして1週間もあればマイグレーションはできると思います。こういうツールが充実しているのはサービスを運営していく身としては大変助かります。