NHN Cloud Meetup 編集部
spring-boot-starterを作成する
2018.05.28
3,916
はじめに
spring-bootのstarterとは、依存関係や設定を自動化するモジュールを意味します。
たとえば、spring-boot-starter-jpaに依存性を追加した際、以下のことを行います。
spring-aop,spring-jdbcなどの依存性をかけるclasspathを検索して、どのDBを使用するか把握し、自動でentityManagerを構成する- 当該モジュール設定に必要なproperties設定を提供する(Configuration Processorを使用すると効果UP)
プロジェクトを進める上で、一般的に使用されるspring設定をモジュールに据え置き、使用することができます。
また、必要に応じて上位プロジェクトで何回も設定を上書きすることができます。
今回は、spring-boot-starterを作成して動作する方法を共有します。
spring-bootバージョンは2.0.0.RELEASEを使用します。
実装内容
以下の要件を満たしている場合に、reaquest parameterをloggingするspring-boot-starterを作成します。
- application.yml
でspring.mvc.custom-uri-logging-filter.enabled: trueであること - application.yml
でspring.mvc.custom-uri-logging-filter.level: info等、指定されたレベルでとること
実装
説明
sample-boot-starter 内部に3つのモジュールを生成します。
- sample-spring-boot-autoconfigure
: @Configurationで、特定の条件に合わせて設定を実行 - sample-spring-boot-starter-request-parameter-logging-filter
: autoconfigureと必要な依存関係を持つ - sample-spring-boot-starter-web
: starterを注入してもらう
autoconfigureとstarterを分けず、starter内にautoconfigureを定義して配布する場合もあります。
Module Naming
starterを作成する際、設定を担当するautoconfigureと、依存関係を担うstarterモジュールを作成する必要があります。
spring referenceでは、次のようにモジュールの命名規則を定義しています。
- spring-boot
で始まらないこと - acme
に対するstarterを作成する場合、- autoconfigure
: acme-spring-boot-autoconfigure - starter
: acme-spring-boot-starter
- autoconfigure
springの場合、spring-boot-autoconfigureで、すべてのspring-boot-starter-XXXの自動設定事項を保持しています。
これを基盤としてspring-boot-starter-XXX(例: spring-boot-starter-jpa)モジュールでは、依存性だけを管理しています。
このようなルールを応用して{project}-spring-boot-configure, {project}-spring-boot-starter-{module}と命名しても問題ないと思われます。
application property key
referenceでは可能な限り、固有のkeyを使うことを推奨しています。server,management,springなど、springがすでに定義したproperty keyを使用した場合、今後、springの修正内容がどのような影響を及ぼすか分からないためです。
autoconfigureモジュール
autoconfigureモジュールは、自動設定に必要なすべての要素(@ConfigurationPropertiesなど)とlibraryを有しています。autoconfigureで参照した依存性にはoptionalをかけた方が良いでしょう。この場合、autoconfigureを参照するモジュールで必要な依存性がないとき、Spring Bootは自動設定をしません。
実装
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
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.parfait.study</groupId>
<artifactId>sample-spring-boot-autoconfigure</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>sample-spring-boot-autoconfigure</name>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<spring-boot.version>2.0.0.RELEASE</spring-boot.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies> <!-- The parent should provide all that -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>
- slf4j-api
: logを使用するために依存 - javax.servlet-api
: Filterを使用するために依存 - spring-boot-configuration-processor
: IDEがapplication.ymlの内容をガイドできるようにする。追って説明する。
application.yml
default設定を定義できます。
spring.mvc.request-parameter-logging-filter:
enabled: true
level: DEBUG
logging.level.com.parfait.study.autoconfigure.logging.filter.RequestParameterLoggingFilter: ${spring.mvc.request-parameter-logging-filter.level}
src/main/resoucres/META-INF/additional-spring-configuration-metadata.json
application.ymlで設定したkeyの情報を定義できます。
これをConfiguration Metadataと呼び、このファイルを定義した場合、IDEから対象keyのガイドを表示できます。
Configuration Metadataについて
{
"properties": [
{
"name": "spring.mvc.request-parameter-logging-filter.enabled",
"type": "org.slf4j.event.Level",
"description": "true 또는 false"
},
{
"name": "spring.mvc.request-parameter-logging-filter.level",
"type": "java.lang.String",
"description": "ロギングのレベル設定"
}
]
}
RequestParameterLoggingFilterProperties
application.ymlで作成したkeyのJava classを定義できます。
@ConfigurationProperties(prefix = "spring.mvc.request-parameter-logging-filter")
public class RequestParameterLoggingFilterProperties {
private boolean enabled;
private Level level;
// getters and setters
}
RequestParameterLoggingFilter
実際のロジックを担当するフィルタを定義します。
@Slf4j
public class RequestParameterLoggingFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
String params = request.getParameterMap()
.entrySet()
.stream()
.map(entry -> entry.getKey() + "=" + String.join(",", entry.getValue()))
.flatMap(Stream::of)
.collect(Collectors.joining("&"));
log(params);
chain.doFilter(request, response);
}
// private void log(String params) { ... }
}
SampleAutoConfiguration
材料(property,logic)がすべて集まったので、これを自動設定できるようにしてみよう。
@Configuration
@AutoConfigureAfter(WebMvcAutoConfiguration.class) // 1
@EnableConfigurationProperties(RequestParameterLoggingFilterProperties.class) // 2
public class SampleAutoConfiguration {
@Autowired
private RequestParameterLoggingFilterProperties requestParameterLoggingFilterProperties;
@Bean
@ConditionalOnProperty(name = "spring.mvc.request-parameter-logging-filter.enabled", havingValue = "true") // 3
public Filter customUriLoggingFilter() {
return new RequestParameterLoggingFilter(requestParameterLoggingFilterProperties.getLevel());
}
}
- Filter設定(webmvc-specific)をするもので、webmvc設定が完了した後、設定が動作する。
- RequestParameterLoggingFilterPropertiesをbeanに生成して@Autowiredできるようにする。
- @ConditionalOnXXX
を使って@Bean作成条件、@Configuration動作条件を作成できる。
@Conditional(Condition condition)にConditionインターフェイスを実装して、詳細な条件を指定することもできる。
src/main/resource/META-INF/spring.factories
.jarファイルに含まれ、当該bootモジュールが設定すべき情報を持っています。
org.springframework.boot.autoconfigure.EnableAutoConfigurationkeyに作成した@Configurationclassをカンマ(,)区切りにしておきます。
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.parfait.study.autoconfigure.SampleAutoConfiguration
spring-boot-autoconfigureで使用されるspring.factories
starterモジュール
必要な設定情報は、autoconfigureですべて完了しました。starterでは依存性だけをかけておけばよいでしょう。autoconfigureでoptionalを削除するだけです。
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">
<modelVersion>4.0.0</modelVersion>
<groupId>com.parfait.study</groupId>
<artifactId>sample-spring-boot-starter-request-parameter-logging-filter</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>sample-spring-boot-starter-request-parameter-logging-filter</name>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<spring-boot.version>2.0.0.RELEASE</spring-boot.version>
</properties>
<dependencies>
<dependency>
<groupId>com.parfait.study</groupId>
<artifactId>sample-spring-boot-autoconfigure</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies> <!-- The parent should provide all that -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>
webモジュール
これから設定したstarterを使ってみよう。
sample-spring-boot-starter-webという名前ですが、単なるWeb API Applicationです。
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">
<modelVersion>4.0.0</modelVersion>
<groupId>com.parfait.study</groupId>
<artifactId>sample-spring-boot-starter-web</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>sample-spring-boot-starter-web</name>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.0.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- starter 依存性設定 -->
<dependency>
<groupId>com.parfait.study</groupId>
<artifactId>sample-spring-boot-starter-request-parameter-logging-filter</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
UserController
@RestController
@RequestMapping("/users")
public class UserController {
@GetMapping("/{id}")
public String get(@PathVariable Long id) {
return new RestTemplate().getForObject("https://jsonplaceholder.typicode.com/users/{id}", String.class, id);
}
}
application.yml
src/main/resoucres/META-INF/additional-spring-configuration-metadata.jsonで述べたIDEのガイドとは、このようなものです。


値型を解析して候補まで表示してくれます。
spring.mvc.request-parameter-logging-filter.enabled=true spring.mvc.request-parameter-logging-filter.level=info
autoconfigure
で作成したappliaction.yml値がdefaultです。
autoconfigureにおいてもdefault値を指定せずに、Configuration Metaだけ渡すこともできます。
実行結果
サーバーに接続してAPIを実行してみよう。
使用するソースでは、application.yml以外は何も設定していませんが、フィルタが動作することを確認できます。
GET /users/1?name=hello
2018-03-15 21:24:20.656 INFO 16048 --- [nio-8080-exec-1] .p.s.a.l.f.RequestParameterLoggingFilter : uri : name=hello
このほかにできること
spring-boot-starterは自動設定に対応できるという点で、強く推奨します。bootを使用するチーム間のサポートを非常に簡単に行えるようにしてくれます。
たとえば、ビッグデータ分析のために情報収集が必要なサービスでは、bigdata-spring-boot-starter-logを提供して、下記のような設定だけでログ収集ロジックを動作させたり、
bigdata.log: enable: true server: http://bigdata.server.com service.name: ABC Portal
会員の暗号化トークンをCookieやヘッダで受け取った後、会員サーバーと通信し、その結果をrequestのattributeに注入する場合、以下のような設定だけで行えるようになります。
member-server:
enabled: true
request:
token:
# or HEADER
type: COOKIE
name: MEMBER_TOKEN
read-timeout: 2000
connect-timeout: 1000
result.attribute-name: RESOLVER_MEMBER_INFO
フィルタ以外に他の例を挙げてみよう。
springのCache Abstractionを使って@CacheableのNear Cacheとして構成することもできます。
chained-cache-namager:
bean-name: cacheManager
local:
type: EHCACHE
config: classpath:/ehcache.xml
global:
type: REDIS
cluster.hosts:
- 10.0.0.1:6379
- 10.0.0.2:6379
まとめ
spring-bootによって、設定が簡潔化され、開発者はさらに重要なロジックに注力できるようになりました。かつては上位pom.xmlから依存性を管理するか、開発プロジェクトでそれぞれ設定するか、プロジェクトを作成するたびに、同じような検討を繰り返すことが多かったですが、現在は非常に簡単に設定を簡素化できています。bootを使用されている方は、ぜひstarterを試してみてほしいです。