NHN Cloud Meetup 編集部
spring-boot-starterを作成する
2018.05.28
3,576
はじめに
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を試してみてほしいです。