技術ネタはQiitaに移りました。壁もどこぞに。

Spring Boot のビルド方法あれこれ

Spring Boot の起動には基本的に、Maven (または、Gradle) を使用するが、2つのゴールが用意されているので、その使い方などを紹介。

spring-boot:run

Spring Boot アプリケーションを起動するためのゴール。Spring Boot の設定は、Maven--define オプションで渡す。

mvn spring-boot:run --define server.port=8011 --define logging.level.org.springframework.web=DEBUG

spring-boot:repackage

Spring Boot アプリケーション パッケージを作成するためのゴール。pom.xmlpackaging 要素での指定により、jar、または war のいずれかの成果物を選択できる。packaging 要素を省略した場合は、jar になる。

<?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">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.yo1000</groupId>
    <artifactId>bluefairy</artifactId>
    <version>0.1.0</version>
    <packaging>jar</packaging>

    ...
</project>

また、Maven から spring-boot:repackage を実行する場合には、package ゴールといっしょに使用する。

mvn package spring-boot:repackage

成果物は {プロジェクト ディレクトリ}/target に配置される。これを起動する場合は、java コマンドを使用する。

java -jar bluefairy-0.1.0.jar --server.port=8011 --logging.level.org.springframework.web=DEBUG

成果物まで用意できると、java のみで起動できるようになるので、サーバーのセットアップなども省力化できて、とても良いですね。

Spring Framework で @RequestMapping にパターンを指定する

Spring Framework (とくに Spring MVC) を使用して、コントローラーを作成する場合、クラスに @Contoroller アノテーションを指定し、各アクションメソッド@RequestMapping アノテーションを指定し、引数に @PathVariable を指定することで、URL 内の変数を受け取ることができる、というのはよく知られているところ。

念のため、この例を挙げておくと、以下のようなコードになる。指定された ID を持つユーザーの詳細ページを返すようなアクション。

@Controller
@RequestMapping("user")
public class UserController {
    @RequestMapping("{id}")
    public String item(@PathVariable String id) {
        return "item";
    }
}

ところがここで、URL から取得した値に特定の文字制約を設けたい場合に、どのように記述するかというのはあまり知られていないように思う。

この文字制約は、先の例では、URL に ID を示す文字列が含まれているが、この ID には小英数しか許可しない、といった場合に使用することができる。

書き方としては、@RequestMapping アノテーションの URL 内で変数表現されている箇所に、{変数:正規表現} の形式で記述する。

これを先ほどのコード例に盛りこむと、以下のようになる。

@Controller
@RequestMapping("user")
public class UserController {
    @RequestMapping("{id:^[a-z0-9]+$}")
    public String item(@PathVariable String id) {
        return "item";
    }
}

この条件にマッチしない URL へのリクエストの場合は 404 が返却されることになる。また、正規表現で記述するため、否定の先読みなども使用することができる。これは類似の URL パターンが存在する場合に、これを除外したいときなどに利用する。書き方としては、(?!除外キーワード) のように記述する。

たとえば、/user/{id} という URL パターンと、/user/register という URL パターンが混在するような場合が考えられる。

これも先ほどのコードに盛り込んでみると、以下のようになる。

@Controller
@RequestMapping("user")
public class UserController {
    @RequestMapping("{id:(?!^register$)^[a-z0-9]+$}")
    public String item(@PathVariable String id) {
        return "item";
    }

    @RequestMapping("register")
    public String register() {
        return "register";
    }
}

さらに、否定の先読みを使用して、複数のキーワードを URL から除外したい場合には、(?!(?:除外キーワードA|除外キーワードB)) のように記述する。?: を先頭に使用するのは正規表現にマッチした文字列がキャプチャされないようにするため。

さらにこれを盛り込んでみると、以下のようになる。

@Controller
@RequestMapping("user")
public class UserController {
    @RequestMapping("{id:(?!^(?:register|update)$)^[a-z0-9]+$}")
    public String item(@PathVariable String id) {
        return "item";
    }

    @RequestMapping("register")
    public String register() {
        return "register";
    }

    @RequestMapping("update")
    public String update() {
        return "update";
    }
}

今回は @RequestMapping を使用した URL 上の変数組み立てを、基本形から少しずつ変化を加えて紹介してみた。ここまでを知っておくと、Spring MVC を使用した URL 設計では、ほとんど困ることがないように思う。

Spring Boot で設定可能なプロパティを追加する

Spring Boot で、アノテーションベースの設定を書いていると、これら設定を外部化できないかと思うことが度々ある。これまでの xml ベースの設定であれば、必要な設定ファイルだけアプリケーション起動時にクラスパスに追加すれば外部化できていた。

Spring Boot で、アノテーションベースの設定に対し、このようなアプリケーション内部の設定を起動時に上書きするには、以前の記事でも触れたように application.properties や、アプリケーション起動時のコマンドライン引数を使用する。

これを可能にするためには、Spring Boot がデフォルトで持っている設定可能なプロパティを拡張し、独自のアプリケーション設定を application.properties で設定できるようにしてやる必要がある。

具体的には @ConfigurationProperties を使用して、以下のように実装する。アノテーションの引数に prefix を指定すると、プロパティのグルーピングができる。

@SpringBootApplication
public class Application {
    public static void main(String[] args) throws Exception {
        SpringApplication.run(Application.class, args);
    }

    @Bean
    public MongoConfiguration mongoConfiguration() {
        return new MongoConfiguration();
    }

    @ConfigurationProperties(prefix = "app.mongo")
    public static class MongoConfiguration {
        private String uri = "mongodb://localhost:27017/";
        private String database = "db";
        private String username = "user";
        private String password = "password";

        // getter/setter
    }
}

これに対して application.properties で設定する場合、以下のようになる。

app.mongo.url=mongodb://192.168.0.11:27017/
app.mongo.database=userapp
app.mongo.username=admin
app.mongo.password=pwd1234

Spring Boot の設定変更

Spring Boot が気になって調べていたり、始めてみたけどよく分からず調べたりしてここに辿り着いた方であれば、既にご存知だとは思うのだが、Spring Boot は、DropWizard などと同様に Web アプリケーションコンテナが組み込まれており、Java さえ実行できれば起動できる Web アプリケーションの作成を行うことができる。

Spring Boot では、組み込みの Tomcat をコンテナに使用することが多いかと思うが、これをそのまま起動すると、Tomcat のデフォルト設定に従い、8080 ポートでサービスが公開される。ただ、多くの場合、他のアプリケーションで既に 8080 ポートを使用していたりして、なるべくであれば 8080 ポートを使用したくない場合のほうが多いのではないかと思う。

で、このような場合どうするかというと、コンテキストルートに application.properties ファイルを作成して、対応するプロパティを上書きしていく。先のようなサービスポートの変更であれば、server.port プロパティに値を設定する。なお、properties でなく、yaml で書いても同様の設定が可能。

また、application.properties 以外にもこの設定を上書きできる箇所は用意されており、 以下のような優先順位で設定が適用される。(番号の小さいものほど設定優先度が高い)

  1. コマンドライン引数
  2. JNDI の java:comp/env 属性
  3. Javaシステムプロパティ
  4. OS の環境変数
  5. random.* プロパティのみ、RandomValuePropertySource の設定
  6. アプリケーション jar パッケージ外の application-プロファイル名.properties、および application-プロファイル名.yml の設定 (クラスパスの通された設定ファイル)
  7. アプリケーション jar パッケージ内の application-プロファイル名.properties、および application-プロファイル名.yml の設定
  8. アプリケーション jar パッケージ外の application.properties、および application.yml の設定 (クラスパスの通された設定ファイル)
  9. アプリケーション jar パッケージ内の application.properties、および application.yml の設定
  10. @Configuration アノテーションの付与されたクラス内の、@PropertySource アノテーションで定義された設定
  11. Spring Boot のデフォルト設定

Spring Boot Reference Guide

この中でも、とくによく使用するのが、コマンドライン引数による設定値の上書きと、application.properties の配置による設定値の上書きの2つ。なお、コマンドライン引数による設定値の上書きは、jar を起動する場合と、mvn コマンドからの起動の場合で、引渡し方が少々異なるようなので注意。

jar 起動時には、以下のようにする。

java -jar bluefairy-0.1.0.jar --server.port=41000 --server.contextPath=/app

Maven 起動時は先ほどとは少し異なり、以下のように --defive オプションで渡す。

mvn spring-boot:run --define server.port=41000 --define server.contextPath=/app

デフォルトで設定可能なすべてのプロパティ一覧は以下。

Appendix A. Common application properties

Spring Boot での Bean 定義

Spring Boot でアプリケーションを作成する際に、プレーンな Spring Framework を使用した場合との違いでまず大きいのが、Bean の定義ではないかと思う。XML ベースの定義が基本的にはアノテーションベースに置き換わる。(もちろん XML ベースでの記述も可能ではあるが、多くのサンプルがアノテーションベースなので、これを理解したほうが良い。)

さて、これまで Bean 定義というと、以下のように書いていたかと思う。

<?xml version="1.0" encoding="UTF-8"?>
<beans ...>
    <context:component-scan/>

    <bean id="mongoTemplate" name="mongoTemplate" class="org.springframework.data.mongodb.core.MongoTemplate">
        <constructor-arg name="mongo" ref="mongo"/>
        <constructor-arg name="databaseName" value="example_db"/>
    </bean>
    <bean id="mongo" class="org.springframework.data.mongodb.core.MongoFactoryBean">
        <constructor-arg name="host" value="127.0.0.1"/>
        <constructor-arg name="port" value="27017"/>
</beans>

これが、以下のように変わる。

@SpringBootApplication // <context:component-scan/> に対応 
public class Application {
    /**
     * アプリケーションの起動ポイント
     */
    public static void main(String[] args) throws Exception {
        SpringApplication.run(Application.class, args);
    }

    @Bean
    public MongoTemplate mongoTemplate() throws Exception {
        return new MongoTemplate(
                this.mongo(),  // <constructor-arg name="mongo" ref="mongo"/> に対応
                "example_db"); // <constructor-arg name="databaseName" value="example_db"/> に対応
    }

    @Bean
    public Mongo mongo() throws Exception {
        return new Mongo(
                "127.0.0.1", // <constructor-arg name="host" value="127.0.0.1"/> に対応
                "27017"); // <constructor-arg name="port" value="27017"/> に対応
    }
}

タグは @Bean に対応し、作成した @Bean アノテーションのついたメソッドを他のメソッドから呼び出すと、XML での ref 属性相当になるという点さえおさえておけば、これまでとそう変わりなく使用できるはず。

コントローラや、サービスレイヤからはこれまでどおり、@Resource や、@Autowired アノテーションからインジェクションをかけることができる。

Spring Boot ことはじめ

このところ、記事をなにも書いていない期間が長かったのだけど、Docker のインストールまわりが落ち着いたため、クライアント Web アプリケーションを書いていた。

Docker のクライアント Web アプリケーションは既に、Panamax、Shipyard など優れたものが多く公開されているのだが、あえて新たに書いたのは、お勉強のためがひとつと、既存のものには、ユーザー管理機能がない、ロール制御機能が存在していない、など、いくつかの小さな要件差分があったためです。

ある程度形になってきたので、いったんコーディングの手を休めて、色々とはじめてだったことなどを書き残そうと思う。

今回作成したアプリケーションの主な依存モジュールは以下のとおり。ほとんどが Spring Boot でカバーされている。

  • Spring Boot 1.2.2.RELEASE
    • spring-boot-starter-web
    • spring-boot-starter-thymeleaf
    • spring-boot-starter-security (Spring Security 3)
    • spring-boot-starter-data-mongodb
    • spring-boot-starter-tomcat
  • Thymeleaf Extra SpringSecurity3 2.1.2.RELEASE
  • Apache HttpClient 4.3.6

Spring Boot は、それを使用するだけでこれまで、個別に依存モジュールを探したりしながらやっと構築していた Spring Framework を使用したアプリケーションの立ち上がりを、ぐっと加速させてくれる。

ただ、これまでプレーンな Spring Framewoek のみしか触れてこなかった場合には、やや取っ付きについ部分などもあるので、そのあたりをピックして紹介していこうと思う。内容が散らかるので、具体的には次の記事から。

つくったものはこちら。
Yoichi-KIKUCHI/bluefairy · GitHub

Github を Maven リポジトリとして使う

調べてみると、いろいろとハマりどころがあったのでメモ。

事前に用意するもの

まず、適当な Java ファイル。今回はこれをモジュールとして扱うものとする。

public class Hoge {
    public static void main(String[] args) {
        System.out.println("Hoge module");
    }
}

そして、Maven は 3.x を使用する。今回使用したのは以下。

$ mvn --version
Apache Maven 3.2.5 (12a6b3acb947671f09b81f49094c53f426d8cea1; 2014-12-15T02:29:23+09:00)
Maven home: /Applications/Developments/apache-maven-3.2.5
Java version: 1.6.0_65, vendor: Apple Inc.
Java home: /System/Library/Java/JavaVirtualMachines/1.6.0.jdk/Contents/Home
Default locale: ja_JP, platform encoding: SJIS
OS name: "mac os x", version: "10.10.2", arch: "x86_64", family: "mac"

Github リポジトリの作成

Maven リポジトリとして使用する、Github リポジトリを作成しておく。なお、ブランクリポジトリだと site-maven-plugin の実行に失敗する。事前に README.md などをコミットしておく。

POM ファイルを用意

ネットで見つかるサンプルだと、言っていることがまちまちだったりして、けっこう設定にクセがあるように感じたので、いくらか紹介しつつ最後に使用した pom.xml を載せておく。

  • github.global.host プロパティ
    • Github API のホスト名、または URL を設定する。
  • github.global.userName プロパティ
    • Github ユーザー名(または、メールアドレス)。
  • github.global.password プロパティ
  • github.global.repositoryName プロパティ
  • github.global.repositoryOwner プロパティ
    • Github ユーザー名。
    • または Organization 名。
  • distributionManagement
    • モジュールの出力ディレクトリを設定する。
  • maven-deploy-plugin/altDeploymentRepository
    • distributionManagement にでてきた id と、url を使用する。
  • site-maven-plugin/repositoryName
    • github.global.repositoryName プロパティと同じものを設定する。省略不可。
    • ${github.global.repositoryOwner} を設定する。
  • site-maven-plugin/repositoryOwner
    • github.global.repositoryOwner プロパティと同じものを設定する。省略不可。
    • ${github.global.repositoryOwner} を設定する。
<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/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.yo1000</groupId>
    <artifactId>hoge-module</artifactId>
    <packaging>jar</packaging>
    <version>1.0-SNAPSHOT</version>
    <name>Maven Quick Start Archetype</name>
    <url>http://maven.apache.org</url>
    
    <properties>
        <!-- global.host、他、プロパティを設定する場合は不要 -->
        <!-- <github.global.server>github</github.global.server> -->
        
        <!-- Github の場合に使用 -->
        <github.global.host>api.github.com</github.global.host>
        <!-- GithubEnterprise の場合に使用、{{hostname}} を適宜書き換える -->
        <!-- <github.global.host>https://{{hostname}}/api/v3/</github.global.host> -->
        
        <!-- Github リポジトリユーザー名 -->
        <github.global.userName>{{ユーザー名}}</github.global.userName>
        
        <!-- Github リポジトリユーザーのパスワード -->
        <github.global.password>{{パスワード}}</github.global.password>
        
        <!-- Github リポジトリ名 -->
        <github.global.repositoryName>mvn-repos</github.global.repositoryName>
        
        <!-- Github リポジトリユーザー名、または Organization 名 -->
        <github.global.repositoryOwner>{{ユーザー名 or Organization 名}}</github.global.repositoryOwner>
    </properties>
  
    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>3.8.1</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <distributionManagement>
        <repository>
            <id>internal.repos</id>
            <name>Temporary Staging Repository</name>
            <url>file://${project.build.directory}/hoge-mod</url>
        </repository>
    </distributionManagement>

    <build>
        <plugins>
            <plugin>
                <artifactId>maven-deploy-plugin</artifactId>
                <version>2.8.1</version>
                <configuration>
                    <altDeploymentRepository>internal.repos::default::file://${project.build.directory}/hoge-mod</altDeploymentRepository>
                </configuration>
            </plugin>
            <plugin>
                <groupId>com.github.github</groupId>
                <artifactId>site-maven-plugin</artifactId>
                <version>0.11</version>
                <configuration>
                    <!-- Git コミットメッセージ -->
                    <message>Maven artifacts for ${project.version}</message>
                    <noJekyll>true</noJekyll>
                    <!-- distributionManagement の url と一致させる -->
                    <outputDirectory>${project.build.directory}/hoge-mod</outputDirectory>
                    <!-- リモートブランチ名 -->
                    <branch>refs/heads/hoge-mod-branch</branch>
                    <includes><include>**/*</include></includes>
                    <!-- Github リポジトリ名 -->
                    <repositoryName>${github.global.repositoryName}</repositoryName>
                    <!-- Github リポジトリユーザー名 -->
                    <repositoryOwner>${github.global.repositoryOwner}</repositoryOwner>
                </configuration>
                <executions>
                    <!-- run site-maven-plugin's 'site' target as part of the build's normal 'deploy' phase -->
                    <execution>
                        <goals>
                            <goal>site</goal>
                        </goals>
                        <phase>site</phase>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

Github でユーザープロファイルを確認

ここが一番のクセモノ。使用するユーザー名のプロファイル情報に Name が設定されているかどうかを確認する。ここに Name が設定されていないと site-maven-plugin は正常に動作しない。

設定は、https://github.com/settings/profile から。

f:id:Yoichi-KIKUCHI:20150318021944p:plain

Maven 実行

ここまで準備ができれば、Maven を実行できる。

$ mvn clean deploy site
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building Maven Quick Start Archetype 1.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO]

...

[INFO]
[INFO] --- site-maven-plugin:0.11:site (default) @ hoge-module ---
[INFO] Creating 12 blobs
[INFO] Creating tree with 13 blob entries
[INFO] Creating commit with SHA-1: aa31ccc2cf0d701420ddf4dea23299fb9fb3278a
[INFO] Creating reference refs/heads/hoge-mod-branch starting at commit aa31ccc2cf0d701420ddf4dea23299fb9fb3278a
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 14.949 s
[INFO] Finished at: 2015-03-18T01:30:01+09:00
[INFO] Final Memory: 19M/81M
[INFO] ------------------------------------------------------------------------

リポジトリの利用確認

適当なプロジェクトで先ほどの成果物を、Github 経由で参照するように依存関係を設定したら、また Maven コマンドを実行してみる。なお、リポジトリ URL は https://github.com/{username}/{repository-name}/raw/{branch-name}/ のようになる。

最後に利用側の POM サンプルと、実行結果を載せておく。

<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/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.yo1000</groupId>
    <artifactId>hoge-module-use</artifactId>
    <packaging>jar</packaging>
    <version>1.0-SNAPSHOT</version>
    <name>Maven Quick Start Archetype</name>
    <url>http://maven.apache.org</url>
    
    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>3.8.1</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>com.yo1000</groupId>
            <artifactId>hoge-module</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
    </dependencies>

    <repositories>
        <repository>
            <id>github-maven</id>
            <url>https://github.com/{{username}}/{{repository-name}}/raw/{{branch-name}}/</url>
        </repository>
    </repositories>
</project>
$ mvn compile
[INFO] Error stacktraces are turned on.
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building Maven Quick Start Archetype 1.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------
Downloading: https://github.com/

...

[INFO]
[INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ hoge-module-use ---
[INFO] No sources to compile
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 7.310 s
[INFO] Finished at: 2015-03-18T03:01:03+09:00
[INFO] Final Memory: 7M/81M
[INFO] ------------------------------------------------------------------------

動いた!けっこう便利に使えそう。なお、POM 内に直接パスワードを記述するのが嫌な場合は、もちろん API トークンなども使用できる模様。

参考:https://github.com/github/maven-plugins