サンプルアプリケーションの概要
サンプルアプリケーションは、前回と同様で図1に示すような簡単な住所録管理アプリケーションです。
このアプリケーションでは、表1のAPIを提供します。
| エンドポイント | HTTPメソッド | 概要 |
|---|---|---|
| /contact/add | POST | 新規に連絡先データを登録する |
| /contact/list | GET | 登録してあるデータ一覧を取得する |
| /contact/item/{id} | GET | 指定したidの連絡先データ情報を取得する |
| /contact/item/{id} | POST | 指定したidの連絡先データ情報を更新する |
| /contact/item/{id} | DELETE | 指定したidの連絡先データを削除する |
Web APIを実装する(1)
Spring BootでWeb(REST)システムを構築する場合には、Spring MVCを通常利用します。
Spring Bootを使わないSpring MVCの設定方法
最初にSpring Bootを使わない場合のSpring MVCの設定方法を紹介します。
Spring BootからSpring MVCを利用している場合には、Spring MVCがServletを利用したフレームワークであることをあまり意識せずに開発が可能です。
最近のWebシステムでは、URLとそれに対応する処理を定義するスタイルで開発を進めるのが一般的であり、言語が異なってもおおよそ似た流れで開発が行えます。
ただし、Servlet APIの知識があると開発していて何か悩んだ場合に、簡単に解決できることもあります。そのため、Spring MVCが通常のServlet APIとしてどのように設定されているのか、知識だけでも知っておくと良いでしょう。
リスト1はSpring Bootを使わずに、Spring MVCを利用する場合の設定(web.xmlファイル)例です。
<?xml version="1.0" encoding="ISO-8859-1"?> // (省略) <servlet> <servlet-name>dispatcher</servlet-name> <servlet-class> org.springframework.web.servlet.DispatcherServlet // (1) 利用するサーブレットクラス </servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/spring/dispatcher-config.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> // (2) URLと動作するサーブレットの関係 <servlet-mapping> <servlet-name>dispatcher</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> // (省略)
(1)では、Spring MVCのサーブレットであるDispatcherServletクラスを登録します。次に、(2)ですべてのパス("/")のリクエストを(1)で登録したサーブレットが処理をするように指定します。
つまり、図2の関係でSpring MVCはサーブレットAPIを通じて実装されています。
Spring MVCのControllerを定義する
Spring MVCのControllerを定義する場合には、@Controllerと@RequestMappingというアノテーションを使ってControllerを定義します。
リスト2はサンプルアプリケーションでのControllerの定義例です。
@RestController // (1) WEB Controllerとして機能するためのBean登録 @RequestMapping("/contact") // (2) リクエストURLの指定 @Scope("request") // (3) オブジェクトのスコープ public class ContactController { // (省略) }
(1)では、@RestControllerというアノテーションを使っていますが、こちらはRest APIとして動作させるための@Controllerと@ResponseBodyを、1つのアノテーションとして指定しています。
続いて、(2)ではリクエストURLの指定をします。最低これら2つの指定をしますが、(3)のようにControllerオブジェクトのライフタイムスコープについて指定することもあります。
リクエストURLを定義するアノテーション
Spring MVCのリクエストとのマッピングを定義するには、@RequestMappingアノテーションを利用します。
@RequestMappingアノテーションは、クラスとメソッドに指定が可能であり、クラスに指定する場合には図3のように各メソッドで指定するパスの親パスとして扱われます。また、表2のような属性が指定が可能です。
| 属性 | 概要 |
|---|---|
| value(path) | リクエストパス(pathというパラメータでも可能) |
| method | 受け付けるHTTPメソッド(GET,POST,PUT,DELETEなど) |
| params | 指定するリクエストパラメータの有無(Rest APIなどを実装する場合にはあまり利用しません) |
| headers | リクエスト時のヘッダの有無 |
| consumes | リクエストのContent-Typeの指定(Rest APIの場合には、application/jsonなどを指定する) |
| produces | リクエストのAcceptの値の指定 |
リスト3はサンプルアプリケーションでの利用例です。
// (1) 基本的な利用例 @RequestMapping(value = "/add", method = RequestMethod.POST, consumes = "application/json")
// (2) PostMappingの利用例 // @PostMapping(value = "/add",consumes = "application/json")
// (3) パス指定だけのPostMappingの利用例 // @PostMapping("/add") public Response addAction(@RequestBody(required = false) ContactModel item){ // (省略) }
(1)は、/contact/addというURLに対してPOSTメソッドでJSON形式のリクエストに一致します。また、POSTメソッドに対応する場合にはmethodを(2)のように、@PostMappingというアノテーションが利用可能です。
同様に、GETメソッドでは@GetMapping、PUTメソッドでは@PutMapping、DELETEメソッドでは@DeleteMappingが利用可能です。
また、パスだけの指定の場合には、(3)のように記述可能です。
Web APIを実装する(2)
リクエストURL内のパスパラメータを取得する
一昔前であれば、リクエストのパラメータをプログラム側で取得する際にはリクエストパラメータを利用するのが当たり前でしたが、ここ最近、Rest APIなどの場合にはパス内にパラメータを含めるようになりました。
例えば、リスト4は/item/36のようなリクエストがあった場合に"36"という文字列をパラメータとして扱うためのサンプルコードです。
@GetMapping("/item/{id}") // (1) パスパラメータの指定 public Response itemAction(@PathVariable("id") String id){ // (2) パスパラメータからの値の取得 }
(1)では、{id}という記述を使ってパスパラメータを指定します。また、パスパラメータとして指定したパラメータは(2)のように@PathVariableを使って取得できます。
idが必ず数字という制限をかけたい場合には、{id:^[0-9]+$}のように正規表現を使って記述することもできます。
ただし、筆者はあまりURLレベルでこういったチェックは行わないようにしています。もし、idにアルファベットなどを指定した場合には、このパスには一致しなくなるので対象のパスが見つからないことになるからです。
しかし、実際には、入力値エラーとして処理したい場合が多くあります。この場合には、パスに一致してほしいために個別の理由がない場合には正規表現などはあまり利用していません。
Beanのライフタイムを指定するアノテーション(@Scope)
Beanとして登録されるクラスはデフォルトの動作として起動時に一度だけインスタンスが作成され、同じインスタンスが利用されます。
このデフォルトの定義を変更するときには、@Scopeアノテーションを利用します。利用できるスコープには、以下の表3が利用可能です。
| スコープ名 | 概要 |
|---|---|
| singleton | スコープを指定しない場合と同様。起動時に一度だけインスタンスを作成し、同じインスタンスを共有する。 |
| prototype | 取得時に毎回新しいインスタンスを作成する。 |
| request | HTTPでのリクエストごとに生成する。Webアプリケーションの場合のみ有効です。 |
| session | HTTPでのセッション単位で生成します。Webアプリケーションの場合のみ有効です。 |
| application | サーブレットコンテキスト単位で生成します。Webアプリケーションの場合のみ有効です。 |
| websocket | WebSocketの接続単位で生成します。Webアプリケーションの場合のみ有効です。 |
サービスなどではデフォルトのsingletonでの利用が一般的ではありますが、Web APIの場合にはリクエストごとにインスタンスを生成した方が良いケースが多いためrequestを指定します。
エラー処理を行うコントローラの定義
コントローラ内でエラーが発生したときには、それらのエラーに応じたレスポンスの定義をする場合が一般的です。従って、各個別の実装でエラーが起きても共通したエラー処理を記述したくなります。
そのような場合、図4のようにSpring MVCではエラー処理を行うためのコントローラを@ControllerAdviceアノテーションを使って作成します。
リスト5は、サンプルアプリケーションでの@ControllerAdviceの利用例です。
import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestControllerAdvice; @RestControllerAdvice // (1) @ControllerAdviceの指定 public class ErrorController { // (省略) @ExceptionHandler(ValidationException.class) // (2) 処理する例外クラスの指定 @ResponseStatus(HttpStatus.BAD_REQUEST) // (3) レスポンスステータス public Response handleValidationException(HttpServletRequest req, ValidationException ex){ return Response.createErrorResponse(ex); } // (4) 他の例外の指定 @ExceptionHandler(Exception.class) @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) public Response handleException(HttpServletRequest request, Exception ex){ return Response.createErrorResponse(ex); } }
(1)では@ControllerAdviceと@ResponseBodyの指定が定義された@RestControllerAdviceを指定します。
次に、(2)でコントローラ内で発生した例外に応じたクラスを@ExceptionHandlerで指定します。(3)でレスポンスのHTTPステータスコードを指定します。
同様に(4)のように他の例外クラスについても指定します。
Web APIを実装する(3)
Spring MVCの動作を変更するコンフィギュレーションクラス
Spring MVCで共通のアクセスコントロールや、共通のリクエストに対する処理、静的ファイルの指定などを行う場合には、WebMvcConfigurerインターフェースを実装したクラスをBean定義しておくと自動的にその設定が有効になります。
WebMvcConfigureで設定できる項目は、JavaDocに詳細がありますが、よく利用する設定として表4があります。
| 設定時に利用するメソッド | 概要 |
|---|---|
| addCorsMappings | Cors(Cros:Cross-origin resource sharing)でのリクエストに対応するための設定 |
| addFormatters | リクエストデータのフォーマット変換処理の登録。例えば、文字列から日付形式など |
| addInterceptors | リクエストに対する処理の前後に追加で行う処理を登録。例えば、リクエストに対するアクセス制限やヘッダ操作等など |
| addResourceHandlers | 静的リソース(HTMLやJavaScript、画像、CSS等)の登録 |
| configureMessageConverters | クエスト・レスポンスデータ形式変換処理の登録。例えば、文字列からバイト形式など |
リスト6は、Web APIにおけるCorsリクエストに対応する場合のサンプルコードです。
import org.springframework.web.servlet.config.annotation.EnableWebMvc; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; // (省略) @Configuration // (1) WebMvcConfigurerインターフェースを実装したクラスを作成 public class WebConfiguration implements WebMvcConfigurer { @Override // (2) addCorsMappingsの指定例 public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/contact/**") .allowedOrigins("*") .allowedMethods("GET","POST","DELETE") .allowedHeaders("Content-Type","accept","Origin") .allowCredentials(false).maxAge(3600); } // (省略) }
(1)のようにWebMvcConfigurerインターフェースを実装したクラスを作成し、@Configurationを使ってBean登録します。
(2)でaddCorsMappingsメソッドを使ってCORSリクエストを/contact以下のすべてのアクセスに受け入れるように指定します。
こういった流れで、Spring MVCで設定を変更していきます。その他の設定方法についてはSpring MVCのドキュメントに記されているので参考にしてください。
サーブレットフィルタを使う
サーブレットフィルタでも同様の実装が可能な場合があります。WebMvcConfigureよりもサーブレットAPI側で処理を行った方がわかりやすい方もいると思います。
Spring Bootでフィルタを実装する場合には、org.springframework.web.filter.GenericFilterBeanクラスを継承したクラスを作成し、そのオブジェクトをBean登録すると自動的にフィルタが有効になります。
リスト7は、サーブレットAPIのフィルタ機能を使って、Corsでのリクエストを許可する場合のサンプルコードです。
: (省略) import org.springframework.web.filter.OncePerRequestFilter; @Component public class CorsFilter extends OncePerRequestFilter { // (1) 利用するフィルタクラス // (2) フィルタの実装 @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { response.setHeader("Access-Control-Allow-Origin", "*"); if ("OPTIONS".equals(request.getMethod())) { response.setHeader("Access-Control-Allow-Methods", request.getHeader("Access-Control-Request-Method")); response.setHeader("Access-Control-Max-Age", "3600"); response.setHeader("Access-Control-Allow-Headers", "Content-Type"); response.setStatus(HttpServletResponse.SC_OK); } else { filterChain.doFilter(request, response); } } }
(1)では、GenericFilterBeanを継承したOncePerRequestFilterクラスを使っています。このクラスは1つのリクエスト内で必ず1回しか実行されないフィルタクラスです。
フィルタは同一リクエストであっても場合によっては複数回実行されますが、アクセス制御など、1回しか動かない方が都合が良い場合があります。そのようなケースのために、SpringではOncePerRequestFilterクラスが用意されています。
また、GenericFilterBeanクラスを利用した場合には、doFilterメソッドを使って処理を実装しますが、OncePerRequestFilterクラスを使った場合には、(2)のようにdoFilterInternalを利用します。
ただし、このフィルタを有効にすると先ほど紹介したWebMvcConfigurerでのCorsの動作を無効にしてしまうので、通常はWebMvcConfigurerを利用することをおすすめしますが、Spring MVCでもサーブレットレベルでの設定が可能です。
最後に
Spring MVCについて今回紹介した内容だけでは紹介しきれない内容がまだまだたくさんあり、Spring MVCの全体について本記事だけで詳細を紹介することは難しいです。
しかし、全体の流れについてを把握した上でSpring MVCのドキュメントと実際のサンプルコードなどを参照すると理解がより深まります。
次回はデータベース関連のアノテーションなど機能部分について紹介します。
