今回は、Spring Framework 4.3の変更点紹介シリーズの第5回で、Web関連の変更点を紹介します。
(リリース前の投稿を更新しました!! 差分は、「★6/11追加」でマークしてあります)
シリーズ
- 第1回:Spring 4.3 DIコンテナ関連の主な変更点
- 第2回:Spring 4.3 データアクセス関連の主な変更点
- 第3回:Spring 4.3 キャッシュ関連の主な変更点
- 第4回:Spring 4.3 JMS関連の主な変更点
- 第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時点)
Web Improvements
今回は、Web関連の主な変更点をみていきます。
| No | Web関連の主な変更点 |
|---|---|
| 1 | HEADとOPTIONSに対するリクエストハンドラーをSpring MVCが暗黙的に用意してくれるため、これらのHTTPメソッドをハンドリングするための実装が不要になります。 |
| 2 | リクエストマッピング用のアノテーションとして、@RequestMappingの合成アノテーション(@GetMapping, @PostMapping, @PutMapping, @DeleteMapping, @PatchMapping)が追加されます。 |
| 3 | Beanのスコープ(Web専用のスコープ)を指定するアノテーションとして、@Scopeの合成アノテーション(@RequestScope, @SessionScope, @ApplicationScope)が追加されます。 |
| 4 | RESTful Web Services(REST API)向けのControllerAdviceクラスの作成をサポートするアノテーションとして、@RestControllerAdvice(@ControllerAdviceと@ResponseBodyを合成したアノテーション)が追加されます。 |
| 5 |
@ResponseStatusをクラスレベルに付与することができるようになります。 |
| 6 |
HttpSessionで管理しているオブジェクトにアクセスするためのアノテーションとして、@SessionAttributeが追加されます。 |
| 7 |
HttpServletRequestで管理しているオブジェクトにアクセスするためのアノテーションとして、@RequestAttributeが追加されます。 |
| 8 |
Modelから取得したオブジェクトに対して、リクエストパラメータのバインディング有無を制御できるようになります。 |
| 9 | Spring MVCの例外ハンドラ(HandlerExceptionResolver)でErrorや自作のThrowableをハンドリングできるようになります。 (★6/11 追加) |
| 10 | マルチパートのパート部分をコンバートするHttpMessageConverterのデフォルトエンコーディングがUTF-8になります。 (★6/11 追加) |
| 11 | 静的リソースのContent-Type(Media Type)を決定する際に、ContentNegotiationManagerの設定を利用できるようになります。 |
| 12 |
RestTemplateとAsyncRestTemplateにて、DefaultUriTemplateHandlerを介してURI変数値に厳格なURLエンコーディングを適用するか否かを指定できるようになります。 |
| 13 |
AsyncRestTemplateにインタセプター(RestTemplateでいうところのClientHttpRequestInterceptor相当)の仕組みが追加されます。 |
HEADとOPTIONSに対するリクエストハンドラーが暗黙的に用意される
Spring 4.3から、HEADとOPTIONSのHandlerメソッドを実装する必要がなくなります!!
HTTPの仕様に準拠したWebアプリケーション(主にRESTful Web Services)を作成する場合は、GETメソッドを提供する際にはHEADも提供する必要があります。また、指定したリソースがサポートしている操作(HTTPメソッド)をOPTIONSメソッド経由で知ることができるようにしておく必要があります。(とはいえ、実際にHEADやOPTIONSメソッドを使うのか???という話はここでは触れないことにします
)
HEADメソッド
Spring 4.3からは、GETメソッド用のHandlerメソッドを実装すると、暗黙的にHEADメソッドも実装したことになります。
@RequestMapping("/todos")
@RestController
public class TodoRestController {
private final ConcurrentMap<String, Resource> resources = new ConcurrentHashMap<>();
@RequestMapping(method = RequestMethod.GET) // GETメソッドのみマッピング
List<Resource> getAll() {
return resources.values().stream()
.sorted((a, b) -> a.createdAt.compareTo(b.createdAt))
.collect(Collectors.toList());
}
// ...
}
起動直後にGETメソッドでアクセスすると、空のリスト(JSON)が返却されます。
$ curl -D - http://localhost:8080/todos
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Sun, 29 May 2016 14:32:33 GMT
[]
HEADメソッドでアクセスすると、リソースのメタ情報(Content-Typeなど)のみ返却されます ![]()
$ curl -I http://localhost:8080/todos
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Content-Type: application/json;charset=UTF-8
Content-Length: 2
Date: Sun, 29 May 2016 14:33:36 GMT
Spring 4.2で同じ実装をすると・・・405 Method Not Allowedになってしまいます ![]()
HTTP/1.1 405 Method Not Allowed
Server: Apache-Coyote/1.1
Allow: GET
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Sun, 29 May 2016 14:37:42 GMT
Spring 4.2で4.3と同じ挙動にするためには、HEADメソッドを明示的にHandlerメソッドにマッピングする必要があります。
@RequestMapping(method = {RequestMethod.GET, RequestMethod.HEAD}) // HEADも加える
List<Resource> getAll() {
// ...
}
OPTIONSメソッド
Spring 4.3からは、暗黙的にOPTIONSメソッドを実装したことになります。
OPTIONSメソッドでアクセスすると、明示的に実装したGETとSpringが暗黙実装してくれるHEADの2つがAllowヘッダに設定されます ![]()
$ curl -D - -X OPTIONS http://localhost:8080/todos
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Allow: GET,HEAD
Content-Length: 0
Date: Sun, 29 May 2016 14:46:12 GMT
Spring 4.2だと・・・DispatcherServletが扱えるメソッドが全てAllowヘッダに設定されてしまいます。このレスポンスだと、HTTPの仕様に準拠しているとはいえません・・・ ![]()
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Allow: GET, HEAD, POST, PUT, DELETE, TRACE, OPTIONS, PATCH
Content-Length: 0
Date: Sun, 29 May 2016 14:52:10 GMT
Spring 4.2でHTTPの仕様に準拠するためには、DispatcherServletのオプションを変更(dispatchOptionsRequestプロパティをtrueに)した上で、OPTIONSメソッドをハンドリングするためのHandlerメソッドを明示的に実装する必要があります。ここではSpring Boot上での変更方法を紹介します。
spring.mvc.dispatch-options-request=true
@RequestMapping(method = RequestMethod.OPTIONS) // OPTIONSを明示的にハンドリングする
HttpHeaders options() {
HttpHeaders headers = new HttpHeaders();
Set<HttpMethod> allows = new LinkedHashSet<>();
allows.add(HttpMethod.GET);
allows.add(HttpMethod.HEAD);
headers.setAllow(allows);
return headers;
}
@RequestMappingの合成アノテーションが追加される
Spring 4.3から、リクエストマッピング用のアノテーションとして、@RequestMappingの合成アノテーション(@GetMapping, @PostMapping, @PutMapping, @DeleteMapping, @PatchMapping)が追加されます。ここでは、GETメソッドのマッピング例を紹介します。
@RequestMapping(method = RequestMethod.GET)
List<Resource> getAll() {
return resources.values().stream()
.sorted((a, b) -> a.createdAt.compareTo(b.createdAt))
.collect(Collectors.toList());
}
@GetMapping // 合成アノテーションを利用
List<Resource> getAll() {
return resources.values().stream()
.sorted((a, b) -> a.createdAt.compareTo(b.createdAt))
.collect(Collectors.toList());
}
イメージ的には、JAX-RSの@GET, @POSTと同じような切り口ですね。だた、SpringはHTTPメソッド以外の属性(リクエストパラメータ、リクエストヘッダなど)を使ってマッピングルールを表現できるので、JAX-RSのアノテーションとは別物です。
Web専用のBeanスコープを指定する合成アノテーションが追加される
Spring 4.3から、Web環境専用のBeanスコープ(request, session, application)であることを指定する合成アノテーションが追加されます。ここでは、session(セッションスコープ)のアノテーションの使用例を紹介します。
@Scope(WebApplicationContext.SCOPE_SESSION)
@Component
public class Cart implements Serializable {
private static final long serialVersionUID = 9202491228533102493L;
// ...
}
@SessionScope // 合成アノテーションを利用
@Component
public class Cart implements Serializable {
private static final long serialVersionUID = 9202491228533102493L;
// ...
}
@RestControllerAdviceが追加される
Spring 4.3から、RESTful Web Services(REST API)向けのControllerAdviceクラスの作成をサポートするアノテーションとして、@RestControllerAdviceが追加されます。@RestControllerAdviceは、@ControllerAdviceと@ResponseBodyを合成したアノテーションで、基本的には@ControllerAdviceと同じ機能を提供しており、@ExceptionHandlerメソッドで@ResponseBodyの指定を省くことができます。
@ControllerAdvice
public class ApiGlobalExceptionHandler {
private static final Logger logger = LoggerFactory.getLogger(ApiGlobalExceptionHandler.class);
@ExceptionHandler
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
@ResponseBody // 明示的に指定する必要がある
ApiError handleException(Exception e) {
logger.error("System error occurred.", e);
ApiError error = new ApiError();
error.setCode("SYSTEM_ERROR");
return error;
}
}
@RestControllerAdvice // @ControllerAdviceの代わりに@RestControllerAdviceを指定
public class ApiGlobalExceptionHandler {
@ExceptionHandler
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
// @ResponseBody 省略できる
ApiError handleException(Exception e) {
// ...
}
}
@ResponseStatusをクラスレベルに付与できる
@ResponseStatusをクラスレベルに付与することで、応答するレスポンスコード(デフォルトのレスポンスコード)を各メソッドで共有することができます。ControllerクラスとControllerAdviceクラスで利用できますが、@ExceptionHandlerを集約したControllerAdviceクラスで利用するのが有効的だと思います。
以下は、@ExceptionHandlerメソッドで@ResponseStatusの指定を省略した時のデフォルト値を「500(Internal Server Error)」にしています。なお、Spring MVCのデフォルトは「200(OK)」です。例外が発生しているのに「200(OK)」というのは適切ではないので、デフォルト値を「500(Internal Server Error)」にしておき、必要に応じて各メソッドで上書きするのがよいでしょう。
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) // デフォルトを「500(Internal Server Error)」に設定する
@RestControllerAdvice
public class ApiGlobalExceptionHandler {
@ExceptionHandler
ApiError handleException(Exception e) {
// ...
}
}
@SessionAttributeを使用してセッションスコープのオブジェクトにアクセスできる
HttpSessionで管理しているオブジェクトにアクセスしたい場合は、HttpSessionのAPIを直接使わずに@SessionAttributeを使うことができます。
@RequestMapping("/users")
@Controller
public class UserController {
// ...
@PostMapping(path = "create")
String create(@Validated Form form, BindingResult result,
HttpSession session) {
ClientInfo clientInfo = ClientInfo.class.cast(session.getAttribute("clientInfo")); // 引数で受け取ったHttpSessionのメソッドを直接利用する
// ...
return "redirect:/users/create?complete";
}
// ...
}
Spring 4.2までは、Servlet APIに依存する実装になってしまいます。HandlerMethodArgumentResolverの実装クラスを作成すればServlet APIに依存しない実装にすることもできますが、やや大げさな作りになってしまいます。Spring 4.3だと、上記コードを以下のように書き換えることができます。
@RequestMapping("/users")
@Controller
public class UserController {
// ...
@PostMapping(path = "create")
String create(@Validated Form form, BindingResult result,
@SessionAttribute ClientInfo clientInfo) { // 引数として直接受け取れる
// ...
return "redirect:/users/create?complete";
}
// ...
}
デフォルトの動作では、HttpSessionに指定したオブジェクトが格納されていない場合は、ServletRequestBindingExceptionが発生して400(Bad Request)になります。セッションに該当するオブジェクトが格納されていないことがアプリケーションの作り上あり得る場合は、required属性をfalseにするか、java.util.Optionalを利用してください。
@PostMapping(path = "create")
String create(@Validated Form form, BindingResult result,
@SessionAttribute(required = false) ClientInfo clientInfo) { // required = falseを指定
// ...
return "redirect:/users/create?complete";
}
@PostMapping(path = "create")
String create(@Validated Form form, BindingResult result,
@SessionAttribute Optional<ClientInfo> clientInfo) { // Optionalとして受け取る
// ...
return "redirect:/users/create?complete";
}
なお、HttpSessionからオブジェクトを取得する際の属性名は、デフォルトでは引数の名前になりますが、これはデバッグ情報(JDKの「-g」オプション)またはパラメータのメタ情報(JDK 8から追加された「-parameters」オプション)がコンパイル後のクラスに含まれている必要があります。これらの情報をクラスに含めない場合は、name属性(or value属性)に属性名を明示的に指定してください。
@PostMapping(path = "create")
String create(@Validated Form form, BindingResult result,
@SessionAttribute("clientInfo") Optional<ClientInfo> clientInfo) { // 明示的に属性名を指定する
// ...
return "redirect:/users/create?complete";
}
@RequestAttributeを使用してリクエストスコープのオブジェクトにアクセスできる
HttpServletRequestで管理しているオブジェクトにアクセスしたい場合は、HttpServletRequestのAPIを直接使わずに@RequestAttributeを使うことができます。具体的な使い方は、@SessionAttributeと同じなので、本投稿では割愛します。
リクエストパラメータのバインディング有無を制御できる
Spring 4.3から、Modelから取得したオブジェクトに対して、リクエストパラメータのバインディング有無を制御できるようになります。Spring MVCでは、Handlerメソッドの引数にフォームクラスなどのJavaBeanを指定すると、JavaBeanのプロパティにリクエストパラメータ値がバインドされる仕組みになっています。この仕組みは非常に便利なのですが、セッションスコープやフラッシュスコープで管理しているオブジェクトに対しては、この仕組みを適用したくないケースがあります。たとえば、入力フォームをセッションスコープで管理していて、確認画面から送信処理を行う際にはリクエストパラメータをバインドさせたくない!!というケースが考えられます。
Spring 4.3では、@ModelAttributeのbinding属性にfalseを指定することで、リクエストパラメータのバインディングを抑止できます。
@RequestMapping(path = "create", method = RequestMethod.POST)
String create(ModelMap model) {
Form form = Form.class.cast(model.get("form")); // 引数でうけとったMapから取得する
// ...
return "redirect:/users/create?complete";
}
@PostMapping(path = "create")
String create(@ModelAttribute(binding = false) Form form) { // binding=falseを指定して引数として直接受け取る
// ...
return "redirect:/users/create?complete";
}
Spring MVCの例外ハンドラでErrorやThrowableをハンドリングできる
Spring 4.3から、Spring MVCの例外ハンドラ(HandlerExceptionResolver)でErrorや自作のThrowableをハンドリングできるようになります。Spring 4.2までは、例外ハンドラは呼び出されず、NestedServletExceptionにラップしてサーブレットコンテナへスローされていました。
@ExceptionHandler
public String handleStackOverflowError(StackOverflowError e) {
// ...
}
基本的には、ErrorやThrowableはむやみにハンドリングすべきではないと思いますが、アプリケーションの要件によっては利用を検討してもよいかもしれません。
この改善に伴い1点注意が必要なことがあります。それは、すでにjava.lang.Exceptionやjavax.servlet.ServletExceptionをハンドリングしていると、Spring 4.3を使うと意図せずErrorやThrowableもハンドリングされてしまいます・・・ ![]()
これを回避するには・・・(どうするのがいいのかな・・・
) とりあえず以下のようなバイパス的なハンドリング処理を実装すればSpring 4.2と同じ動きに(サーブレットコンテナに通知)することはできるけど・・・・(なんか違う気がしてならない・・・
)
@ExceptionHandler
public void handleNestedServletException(NestedServletException e) throws NestedServletException {
throw e;
}
マルチパートのパート部分をコンバートするHttpMessageConverterのデフォルトエンコーディングがUTF-8になる
Spring 4.3から、マルチパートのパート部分をコンバートするHttpMessageConverterのデフォルトエンコーディングがUTF-8になります。これは、RestTemplateを使ってサーバーへマルチパートデータを送信する場合や、ResponseEntityや@ResponseBodyを使ってマルチパートデータをクライアントへ応答する際に適用されます。新規に作るアプリケーションの場合は、UTF-8を利用するケースが多いだろうから、この対応は地味にうれしいですね ![]()
ここでは、マルチパートを使ってテキストファイルとフォームデータを送る例を見てみましょう。
@RequestMapping("/upload")
@RestController
public class UploadRestController {
@RequestMapping(method = RequestMethod.POST)
String upload(MultipartFile file, String text) throws IOException {
System.out.println(StreamUtils.copyToString(file.getInputStream(), StandardCharsets.UTF_8));
System.out.println(text);
return file.getOriginalFilename();
}
}
@Test
public void upload() {
RestTemplate restTemplate = new RestTemplate();
// デバッグ用にリクエストボディをコンソールに出力するインタセプタを差し込む
restTemplate.setInterceptors(Collections.singletonList((request, body, execution) -> {
System.out.println(new String(body));
return execution.execute(request, body);
}));
// ファイルとテキストデータをマルチパートデータとしてセットアップ
MultiValueMap<String, Object> form = new LinkedMultiValueMap<>();
form.add("file", new FileSystemResource("./a.txt"));
form.add("text", "あああ");
// マルチパートリクエストを作成
RequestEntity<MultiValueMap<String, Object>> requestEntity =
RequestEntity.post(URI.create("http://localhost:8080/upload"))
.contentType(MediaType.MULTIPART_FORM_DATA)
.body(form);
// マルチパートリクエストを送信
ResponseEntity<String> responseEntity = restTemplate.exchange(requestEntity, String.class);
System.out.println(responseEntity.getBody());
}
JUnitを実行すること、コンソールに以下のような結果が出力されます。
--_U91C0cWNkMO5HsYdnhF29Vz1Tj7dh0e0nQrAzM0
Content-Disposition: form-data; name="file"; filename="a.txt"
Content-Type: text/plain
Content-Length: 12
ああああ
--_U91C0cWNkMO5HsYdnhF29Vz1Tj7dh0e0nQrAzM0
Content-Disposition: form-data; name="text"
Content-Type: text/plain;charset=UTF-8
Content-Length: 9
あああ
--_U91C0cWNkMO5HsYdnhF29Vz1Tj7dh0e0nQrAzM0--
a.txt
ここでポイントになるのが、フォームデータのtextのContent-TypeのcharsetがUTF-8になっている点です。
--_U91C0cWNkMO5HsYdnhF29Vz1Tj7dh0e0nQrAzM0
Content-Disposition: form-data; name="text"
Content-Type: text/plain;charset=UTF-8
Content-Length: 9
あああ
Spring 4.2だと、この部分がISO-8859-1になるため、日本語などのマルチバイト文字がデフォルトのまま使うと文字化けしてしまいます。もちろん任意のエンコーディングを指定できるようになっていますが、コンフィギュレーションが面倒なんだよね・・・ ![]()
--JPpi8b-TOpRe3PyEB7vcjpjID072o6wxXAZy7
Content-Disposition: form-data; name="text"
Content-Type: text/plain;charset=ISO-8859-1
Content-Length: 3
???
では、UTF-8以外のエンコーディングにする必要がある場合はどうすればよいのでしょうか? ここでは、日本のシステムだとまだまだ現役!?の「Windows-31J」に変更してみます。
RestTemplate restTemplate = new RestTemplate();
restTemplate.getMessageConverters().stream()
.filter(c -> c instanceof FormHttpMessageConverter)
.forEach(c -> ((FormHttpMessageConverter) c).setCharset(Charset.forName("Windows-31J")));
--oym_khz0wPMOlL5rD3WZ70mNapWvjiaMXs
Content-Disposition: form-data; name="text"
Content-Type: text/plain;charset=windows-31j
Content-Length: 6
あああ
Content-Lengthも6バイトなので、Window-31Jになりましたね ![]()
静的リソースのContent-typeをContentNegotiationManager経由で指定できる
Spring 4.3から、静的リソースのContent-Type(Media Type)を決定する際に、ContentNegotiationManagerの設定を利用できるようになります。まず、デフォルト設定のままで静的リソースにアクセスしています。
<test>
<code>TEST</code>
</test>
$ curl -D - http://localhost:8080/test.xml
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Last-Modified: Sun, 29 May 2016 18:42:38 GMT
Accept-Ranges: bytes
Content-Type: application/xml
Content-Length: 36
Date: Sun, 29 May 2016 18:42:54 GMT
<test>
<code>TEST</code>
</test>
次にContentNegotiationManagerの設定を変更(拡張子がxmlのメディアタイプをtext/xmlに設定)してから静的リソースにアクセスします。
@Configuration
public class WebMvcConfig extends WebMvcConfigurerAdapter {
@Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer.mediaType("xml", MediaType.TEXT_XML); // XMLのメディアタイプを明示的に指定
}
}
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Last-Modified: Sun, 29 May 2016 18:42:38 GMT
Accept-Ranges: bytes
Content-Type: text/xml
Content-Length: 36
Date: Sun, 29 May 2016 18:45:21 GMT
<test>
<code>TEST</code>
</test>
RestTemplateとAsyncRestTemplateにてURI変数値に厳格なURLエンコーディングを適用できる
Spring 4.3から、RestTemplateとAsyncRestTemplateに設定するDefaultUriTemplateHandlerを介して、URI変数値に厳格なURLエンコーディングを適用することができます。デフォルトは、厳格なエンコーディングは行いません。
厳格なエンコーディングを行うようにすると、「RFC 3986のセクション2」で定義されている「unreserved」以外の文字もURLエンコーディングされます。
ここでは、「;」を例に、厳格なURLエンコーディングの有無でどのような違いがでるのか説明します。
@GetMapping(path = "/users/{username}")
@ResponseBody
String get(@PathVariable String username) {
return username; // パス変数からユーザー名を取得し、取得したユーザー名を返却する
}
まず、厳格なURLエンコーディングを行わないでアクセスしてみます。
RestTemplate restTemplate = new RestTemplate();
String body = restTemplate.getForObject("http://localhost:8080/users/{username}"
, String.class, "Kazuki;Shimizu");
System.out.println(body);
Kazuki
残念なことに、「;」以降の文字が欠落してしまいました。厳格なURLエンコーディングを行わないと、「http://localhost:8080/users/Kazuki;Shimizu」というURLでサーバー側にアクセスするため、「;」以降が欠落しました。
次に、厳格なURLエンコーディングを行ってアクセスしてみます。
RestTemplate restTemplate = new RestTemplate();
DefaultUriTemplateHandler handler = new DefaultUriTemplateHandler();
handler.setStrictEncoding(true); // 厳格なURLエンコーディングを有効化する
restTemplate.setUriTemplateHandler(handler);
String body = restTemplate.getForObject("http://localhost:8080/users/{username}"
, String.class, "Kazuki;Shimizu");
System.out.println(body);
Kazuki;Shimizu
厳格なURLエンコーディングを行うと、「http://localhost:8080/users/Kazuki%3BShimizu」というURLでサーバー側にアクセスするため、「;」以降の文字は欠落しません。
AsyncRestTemplateにインタセプターを組み込める
Spring 4.3から、AsyncRestTemplateにインタセプター(RestTemplateでいうところのClientHttpRequestInterceptor相当)の仕組みが追加されます。個人的には・・・待望の・・・といった感じがあります ![]()
ここでは、リクエストとレスポンスをログ出力するインタセプターを作成し、AsyncRestTemplateに適用してみます。
public class LoggingInterceptor implements AsyncClientHttpRequestInterceptor {
@Override
public ListenableFuture<ClientHttpResponse> intercept(HttpRequest request, byte[] body, AsyncClientHttpRequestExecution execution) throws IOException {
// リクエスト前に行う処理を実装する
if (logger.isDebugEnabled()) {
logger.debug("Request Header : {}", request.getHeaders());
logger.debug("Request Body : {}", new String(body, StandardCharsets.UTF_8));
}
// 後続処理(別のAsyncClientHttpRequestInterceptorまたは非同期通信処理)を呼び出す
ListenableFuture<ClientHttpResponse> future = execution.executeAsync(request, body);
// レスポンス後に行う処理をコールバック関数としてListenableFutureに追加する
if (logger.isDebugEnabled()) {
future.addCallback(
response -> {
try {
logger.debug("Response Status : {}", response.getStatusCode());
logger.debug("Response Header : {}", response.getHeaders());
} catch (IOException e) {
throw new UncheckedIOException(e);
}
},
e -> logger.debug("Error!!", e)
);
}
return future;
}
}
AsyncRestTemplate asyncRestTemplate = new AsyncRestTemplate();
asyncRestTemplate.setInterceptors(Collections.singletonList(new LoggingInterceptor())); // インタセプターを適用する
ListenableFuture<ResponseEntity<String>> future = asyncRestTemplate.getForEntity("http://localhost:8080/users/{username}"
, String.class, "kazuki43zoo");
future.addCallback(
entity -> System.out.println(entity.getBody()),
Throwable::printStackTrace);
06:37:56.706 [main] DEBUG org.springframework.web.client.AsyncRestTemplate - Created asynchronous GET request for "http://localhost:8080/users/kazuki43zoo"
06:37:56.713 [main] DEBUG org.springframework.web.client.RestTemplate - Setting request Accept header to [text/plain, application/json, application/*+json, */*]
06:37:56.715 [main] DEBUG com.example.RestTemplateTests - Request Header : {Accept=[text/plain, application/json, application/*+json, */*], Content-Length=[0]}
06:37:56.717 [main] DEBUG com.example.RestTemplateTests - Request Body :
06:37:59.755 [SimpleAsyncTaskExecutor-1] DEBUG com.example.RestTemplateTests - Response Status : 200
06:37:59.755 [SimpleAsyncTaskExecutor-1] DEBUG com.example.RestTemplateTests - Response Header : {Server=[Apache-Coyote/1.1], Content-Type=[text/plain;charset=UTF-8], Content-Length=[11], Date=[Sun, 29 May 2016 21:37:59 GMT]}
06:37:59.756 [SimpleAsyncTaskExecutor-1] DEBUG org.springframework.web.client.AsyncRestTemplate - Async GET request for "http://localhost:8080/users/kazuki43zoo" resulted in 200 (OK)
06:37:59.757 [SimpleAsyncTaskExecutor-1] DEBUG org.springframework.web.client.RestTemplate - Reading [java.lang.String] as "text/plain;charset=UTF-8" using [org.springframework.http.converter.StringHttpMessageConverter@245d5499]
kazuki43zoo
まとめ
今回は、Web関連の主な変更点を紹介しました。インパクトのある新機能はないものの、新しいアノテーションの追加や既存アノテーションの挙動の改善などが行われており、アノテーション駆動開発を手助けする機能が強化されています。また、個人的にはAsyncRestTemplateにインタセプターが導入されたのはGood Newsでした!!
次回は、「Web Socket関連の主な変更点」を紹介する予定です。
参考サイト
- 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追加」でマークしてあります)