2016/6/10リリースされたSpring Framework 4.3の主な新機能と改善点を紹介します。
(リリース前の投稿を更新しました!! 差分は、「★6/11追加」でマークしてあります)
- Core Container Improvements
- Data Access Improvements
- Caching Improvements
- JMS Improvements
- Web Improvements
- WebSocket Messaging Improvements
- Testing Improvements
- Support for new library and server generations (★6/11 追加)
本投稿は、「New Features and Enhancements in Spring Framework 4.3」で紹介されている内容を、サンプルも交えて具体的に説明したものです。(逆にいうと、「New Features and Enhancements in Spring Framework 4.3」にのっていない変更点は紹介しないので、あしからず・・・ )
なお、2016/6/23リリース予定のSpring Boot 1.4も、Spring 4.3ベースになります!!
シリーズ
- 第2回:Spring 4.3 データアクセス関連の主な変更点
- 第3回:Spring 4.3 キャッシュ関連の主な変更点
- 第4回:Spring 4.3 JMS関連の主な変更点
- 第5回:Spring 4.3 Web関連の主な変更点
- 第6回:Spring 4.3 WebSocket関連の主な変更点
- 第7回(最終回):Spring 4.3 テスト関連の主な変更点
動作検証環境
- Spring Framework 4.3.0.RELEASE
- Spring Boot 1.4.0.BUILD-SNAPSHOT (2016/6/11時点)
Core Container Improvements
今回は、DIコンテナ関連の主な変更点と新しいライブラリやAPサーバーのサポートの変更点をみていきます。
DIコンテナ関連の主な変更点
No | DIコンテナ関連の主な変更点 |
---|---|
1 | DI時に発生した例外から、エラー箇所を特定するための情報を取得できるようになります。 (★6/11追加) |
2 | Java SE 8でサポートされたインタフェースのデフォルトメソッドをBeanのプロパティとして扱うことができるようになります。 (★6/11追加) |
3 | オートワイヤリングによるコンストラクタインジェクションを行う際に、@Autowired の指定が省略できるようになります。 |
4 | Java Configクラス(@Configuration を付与したクラス)でコンストラクタインジェクションが利用できるようになります。 |
5 |
@EventListener のcondition 属性に指定するSpEL中で、DIコンテナに登録されているBeanを参照できるようになります。 |
6 | 合成アノテーションの非配列属性を使用して、メタアノテーション内の配列属性をオーバーライドできるようになります。 |
7 |
@Scheduled と@Schedules をメタアノテーションとして利用できるようになります。 |
8 |
@Scheduled を任意のスコープのBeanで利用できるようになります。 |
ライブラリバージョンとAPサーバーのサポートの変更点
No | ライブラリバージョンとAPサーバーバージョンのサポートの変更点 |
---|---|
9 | 新たに「Hibernate ORM 5.2」「Jackson 2.8」「OkHttp 3.x」「Netty 4.1」 がライブラリバージョンとしてサポートされます。(★6/11追加) |
10 | 新たに「Undertow 1.4」「Tomcat 8.5.2」「Tomcat 9.0 M6」がAPサーバーバージョンとしてサポートされます。 (★6/11追加) |
DIエラー時のエラー箇所を特定できる情報が取得できる
Spring 4.3から、DI時に失敗した場合に、エラー箇所(DI箇所)を特定するためのメタ情報(org.springframework.beans.factory.InjectionPoint
)が取得できるようになります。具体的には、UnsatisfiedDependencyException
をキャッチしてgetInjectionPoint
メソッドを呼び出してください。が、しかし・・・、あまり使うことはない気がします・・・
public static void main(String[] args) {
try {
SpringApplication.run(Spr43DemoApplication.class, args);
} catch (UnsatisfiedDependencyException e) {
InjectionPoint injectionPoint = e.getInjectionPoint();
// 必要に応じてメタ情報へアクセス
}
}
この対応により、Spring 4.2まではBeanCreationException
が発生していたところが、UnsatisfiedDependencyException
になります。ただ、UnsatisfiedDependencyException
はBeanCreationException
のサブクラスなので、アプリケーションに与える影響は基本的にはないでしょう。(特殊な例外ハンドリング処理を実装していると、ひょっとしたら影響があるかもしれませんが・・・ )
デフォルトメソッドをBeanのプロパティとして扱うことができる
Spring 4.3から、Java SE 8でサポートされたインタフェースのデフォルトメソッドをBeanのプロパティとして扱うことができるようになります。いい感じの使い方がパッと浮かんできませんが、例えば以下のような感じでしょうか!?
public interface TargetDateConsumer {
default void setTargetDateString(String dateString) { // デフォルトメソッドでsetterを用意
setTargetDate(LocalDate.parse(dateString));
}
void setTargetDate(LocalDate date);
}
public class UnusedFileRemover implements TargetDateConsumer {
private LocalDate targetDate;
@Override
public void setTargetDate(LocalDate targetDate) {
this.targetDate = targetDate.plusDays(7);
}
// ...
}
<bean class="com.example.UnusedFileRemover">
<property name="targetDateString" value="2016-06-10"/> <!-- デフォルトメソッド(setterメソッド)に値を指定 -->
</bean>
こうすると、インタフェースのデフォルトメソッド(setTargetDateString
)を介してsetTargetDate
メソッドが呼び出されます。上記の例だと、UnusedFileRemover
のtargetDate
フィールドは、「2016-06-17」になります。なお、Spring 4.2で同じことを行うと、以下のエラーが発生します。
Caused by: org.springframework.beans.NotWritablePropertyException: Invalid property 'targetDateString' of bean class [com.example.UnusedFileRemover]: Bean property 'targetDateString' is not writable or has an invalid setter method. Does the parameter type of the setter match the return type of the getter?
コンストラクタインジェクションで@Autowired
を省略できる
Spring 4.2では、オートワイヤリングによるコンストラクタインジェクションを行う際は@Autowired
の指定が必要でしたが、Spring 4.3から省略できるようになります。ただし、この仕組みは、引数をとるコンストラクタが複数ある場合は利用できません。
@Autowired
public MessageService(MessageSource messageSource) {
this.messageSource = messageSource;
}
// @Autowired ← 省略できる!!
public MessageService(MessageSource messageSource) {
this.messageSource = messageSource;
}
ぱっとみ大した改善ではないように見えるかもしれませんが、この改善により、Lombokで生成したコンストラクタを使用してふつうにインジェクションができるようになります。4.2でもLombokで生成したコンストラクタを使用してインジェクションすることはできますが、Lombokのアノテーションにやや特殊な指定が必要でした。
// 生成するコンストラクタに@Autowiredが付与するためにonConstructor属性の指定が必要
@lombok.RequiredArgsConstructor(onConstructor = @__(@Autowired))
@Service
public class MessageService {
private final MessageSource messageSource;
// ...
}
@Configuration
を付与したクラスでコンストラクタインジェクションが利用できる
Spring 4.3から、Java Configクラス(@Configuration
を付与したクラス)でコンストラクタインジェクションが利用できるようになります。
package com.example;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
import javax.sql.DataSource;
@Configuration
public class JdbcConfig {
private final DataSource dataSource;
public JdbcConfig(DataSource dataSource) { // コンストラクタインジェクションが可能
this.dataSource = dataSource;
}
@Bean
public JdbcTemplate jdbcTemplate() {
return new JdbcTemplate(dataSource);
}
}
@EventListener
のcondition
属性でBeanを参照ができる
Spring 4.3から、@EventListener
のcondition
属性に指定するSpEL中で、DIコンテナに登録されているBeanを参照できるようになります。 ここでは、外部リソースとの接続状態を管理するBeanのメソッドを呼び出して、接続先の外部リソースがアクティブな状態の時だけイベントを受け取るようにしてみます。
まず、外部リソースとの接続状態を管理するBeanを作ります。サンプルでは説明を簡略化するために固定値を返却しています。
@Component
public class ExternalResourceStatus {
// 状態を表現する列挙型
public enum Status {
ACTIVE, INACTIVE;
public boolean active() {
return ACTIVE == this;
}
}
// データソース(DB)との接続状態を返却するメソッド
public Status dataSource() {
return Status.ACTIVE;
}
// MQとの接続状態を返却するメソッド
public Status mq() {
return Status.INACTIVE;
}
}
イベントリスナーは以下のような実装にします。
@Service
public class MessageService {
// ...
@EventListener(condition = "@externalResourceStatus.dataSource().active()")
public void onMessageStoreToDb(Message message) {
System.out.println("onMessageStoreToDb:" + message.getText());
}
@EventListener(condition = "@externalResourceStatus.mq().active()")
public void onMessageSendingToMq(Message message){
System.out.println("onMessageSendingToMq:" + message.getText());
}
}
イベントを発行します。
@Rule
public OutputCapture capture = new OutputCapture();
@Autowired
ApplicationEventPublisher eventPublisher;
// ...
@Test
public void publishEvent() {
Message message = new Message();
message.setText("test");
eventPublisher.publishEvent(message); // イベントの発行
assertThat(capture.toString()).contains("onMessageStoreToDb:test");
assertThat(capture.toString()).doesNotContain("onMessageSendingToMq:test");
}
...
10:05:49.603 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Returning cached instance of singleton bean 'externalResourceStatus'
10:05:49.613 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Returning cached instance of singleton bean 'messageService'
onMessageStoreToDb:test
10:05:49.614 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Returning cached instance of singleton bean 'externalResourceStatus'
1
...
このケースだと、MessageService#onMessageStoreToDb
メソッドだけが呼び出されます。この改善により、イベントを受け取る条件をダイナミックに切り替えることができます。
メタアノテーション内の配列属性を合成アノテーションの非配列属性で上書きできる
Spring 4.3から、合成アノテーションの非配列属性を使用して、メタアノテーション内の配列属性をオーバーライドできるようになります。いまいち言葉だと伝えずらいので、サンプルコードを使って説明しましょう。
ここでは、フォーム画面を表示する際に、「GET /xxx?form」という形式のURLでアクセスするという共通的なルールがある前提で話をすすめます。このようなルールがある場合は、「GETメソッドを利用する」「リクエストパラメータにformを含める」という部分が共通ルールになります。この共通ルール部分をメタアノテーションで表現する合成アノテーションを作ってみましょう。
public @interface RequestMapping {
// ...
@AliasFor("value")
String[] path() default {}; // 配列属性
// ...
}
package com.example;
import org.springframework.core.annotation.AliasFor;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@RequestMapping(method = RequestMethod.GET, params = "form") // 共通ルールはメタアノテーション側に指定
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface GetForm {
@AliasFor(annotation = RequestMapping.class)
String path(); // 非配列属性で上書き
}
作成した合成アノテーションを利用して、リクエストマッピングを定義してみます。
@Controller
public class AccountController {
// GET /account/create?formをハンドリングする
@GetForm(path = "/account/create")
public String createForm() {
return "account/createForm";
}
// ...
// GET /account/update?formをハンドリングする
@GetForm(path = "/account/update")
public String updateForm() {
return "account/updateForm";
}
// ...
}
この改善と関係ありませんが、Spring 4.3から@RequestMapping
をメタアノテーションに指定した合成アノテーション(@GetMapping
, @PostMapping
など)がいくつか追加されています。追加された合成アノテーションについては、後日Web関連の変更点を紹介する時に説明します。
@Scheduled
と@Schedules
をメタアノテーションとして使用できる
Spring 4.3から、@Scheduled
と@Schedules
をメタアノテーションとして利用できるようになります。ここでは、@Scheduled
からみてきます。
@Scheduled(fixedRate = 1000)
public void deleteOldMessages() {
System.out.println("deleteOldMessages:" + LocalDateTime.now());
}
4.3以降では、@Scheduled
をメタアノテーションにしていした合成アノテーションを作成し、合成アノテーションを使用して定期実行するメソッドを指定することができます。
package com.example;
import org.springframework.scheduling.annotation.Scheduled;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Scheduled(fixedRate = 1000) // @Scheduledをメタアノテーションとして使用
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface PurgeScheduled {
}
@PurgeScheduled // 作成した合成アノテーションを指定 (@Scheduled(fixedRate = 1000)と同義)
public void deleteOldMessages() {
System.out.println("deleteOldMessages:" + LocalDateTime.now() + " on " + Thread.currentThread().getName());
}
@Rule
public OutputCapture capture = new OutputCapture();
// ...
@Test
public void scheduled() throws InterruptedException {
TimeUnit.MILLISECONDS.sleep(1500);
assertThat(capture.toString()).contains("deleteOldMessages:");
}
...
10:46:13.863 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Returning cached instance of singleton bean 'spring.http.multipart-org.springframework.boot.autoconfigure.web.MultipartProperties'
deleteOldMessages:2016-05-27T10:46:14.743 on pool-1-thread-1
10:46:15.367 [main] DEBUG org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate - Retrieved ApplicationContext from cache with key [[MergedContextConfiguration@27a8c74e testClass = CoreContainerTests, locations = '{}', classes = '{class com.example.Spr43DemoApplication}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{}', contextCustomizers = set[org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@343f4d3d, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer@0, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer@0], contextLoader = 'org.springframework.test.context.support.DelegatingSmartContextLoader', parent = [null]]]
...
なお、@Scheduled
を利用する場合は、Java Config(@Configuration
が付与されたクラス)に@org.springframework.scheduling.annotation.EnableScheduling
を付与する必要があります。このアノテーションがないと、スケジューリング機能が有効にならず、deleteOldMessages
は定期実行されません。
package com.example;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
@EnableScheduling // 追加
@SpringBootApplication
public class Spr43DemoApplication {
public static void main(String[] args) {
SpringApplication.run(Spr43DemoApplication.class, args);
}
}
最後に@Schedules
について簡単に説明しておきましょう。
@Schedules
は、@Scheduled
を複数指定できるようにするためのアノテーションで、Java SE 8+であれば@Scheduled
をメソッドやアノテーションに列挙することができるため、このアノテーションを直接使う必要はありません。
@Scheduled(...)
@Scheduled(...) // Java SE 8+なら@Scheduledを列挙できる
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface PurgeScheduled {
}
Java SE 7以前の場合は、@Schedules
を使用して複数の@Scheduled
を指定する必要があります。
@Schedules({
@Scheduled(...),
@Scheduled(...)
}) // Java SE 7以前なら@Schedulesを使って複数指定
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface PurgeScheduled {
}
@Scheduled
を任意のスコープのBeanに付与できる
Spring 4.3から、@Scheduled
を任意のスコープのBeanで利用できるようになります。Spring 4.3までは、非LazyなシングルトンのBeanにしか付与できませんでした。(付与しても無視されていました)
とはいえ、シングルトン以外のBeanで@Scheduled
を付与するケースって・・・、ほとんどない気もしています
ちなみに・・・任意のスコープのBeanで利用できると書きましたが・・・Web環境用のリクエストスコープ(@RequestScope
)やセッションスコープ(@SessionScope
)のBeanに付与すると実行時にエラーになるので、事実上付与できません。
そもそもこの対応が行われたきっかけは、「LazyなシングルトンのBeanで@Scheduledが有効にならないぜ!」というIssueから派生しています。たしかにLazyなシングルトンのBeanについては、@Scheduled
をつけたいと思うことはある気がします。
新たなライブラリバージョンのサポート
Spring 4.3から、以下のライブラリバージョンがサポートされます。なお、旧バージョンのサポート状況は以下のとおりです。
ライブラリ | サポートバージョン | 旧バージョンのサポート状況 |
---|---|---|
Hibernate ORM | 5.2 | 4系(4.2, 4.3)と5系(5.0, 5.1)の旧バージョンは引き続きサポートされますが、3系(3.6)は非推奨扱いになります。 |
Jackson | 2.8 | 2.6以上がサポート対象です。 |
OkHttp | 3.x | 2.x系も引き続きサポート対象です。 |
Netty | 4.1 | リファレンスに明記はありませんが、4.0系も引き続きサポート対象のようです。 |
Springに内蔵されているASMとObjenesisのバージョンも更新され、それぞれASM 5.1、Objenesis 2.4が内蔵されます。
新たなAPサーバーバージョンのサポート
Spring 4.3から、以下のAPサーバーバージョンがサポートされます。なお、旧バージョンのサポート状況は以下のとおりです。
APサーバー | サポートバージョン | 旧バージョンのサポート状況 |
---|---|---|
Undertow | 1.4 | リファレンスに明記はありませんが、Spring 4.2とサポート状況は同じ模様です。 |
Tomcat | 8.5.2, 9.0 M6 | リファレンスに明記はありませんが、Spring 4.2とサポート状況は同じ模様です。 |
まとめ
今回は、DIコンテナ関連の主な変更点を紹介しました。Spring 4.3のリリースにより、地味ではありますが、確実に使いやすくなります。次回は、「データアクセス関連の主な変更点」を紹介する予定です。
参考サイト
- http://docs.spring.io/spring/docs/4.3.0.RELEASE/spring-framework-reference/htmlsingle/
- http://www.slideshare.net/makingx/jjugccc-cccgh5-whats-new-in-spring-framework-43-boot-14-pivotals-cloud-native-approach
補足
Spring 4.3 GAに伴い変更点を追加 (2016/6/11)
ついに4.3がGAになり、そのタイミングで主な変更点に追加されたトピックスを反映しました。(「★6/11追加」でマークしてあります)