最終更新日:2020/03/23 原本2019-06-05

Spring BootでWeb APIを作ろう

Spring Bootで作るマイクロサービス 第4回

 前回、サンプルアプリケーションを通じて、Spring Bootで利用する基本的なアノテーションについて紹介しました。今回も前回利用したサンプルアプリケーションを通じて、Web APIを実装する際に利用するアノテーションや必要となるSpring Bootの設定方法などを中心に紹介します。

サンプルアプリケーションの概要

 サンプルアプリケーションは、前回と同様で図1に示すような簡単な住所録管理アプリケーションです。

図1:サンプルアプリケーションのイメージ
図1:サンプルアプリケーションのイメージ

 このアプリケーションでは、表1のAPIを提供します。

表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ファイル)例です。

[リスト1]Spring Bootを使わない場合のWebコンテナに対してのSpring MVCの設定方法
<?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を通じて実装されています。

図2:DispatcherServletクラスとSpring MVCとの関係
図2:DispatcherServletクラスとSpring MVCとの関係

Spring MVCのControllerを定義する

 Spring MVCのControllerを定義する場合には、@Controllerと@RequestMappingというアノテーションを使ってControllerを定義します。

 リスト2はサンプルアプリケーションでのControllerの定義例です。

[リスト2]Controllerの定義例(java/src/main/java/com/coltware/contacts/controller/ContactController.javaの抜粋)
@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のような属性が指定が可能です。

図3:RequestMappingでのクラスとメソッドの関係
図3:RequestMappingでのクラスとメソッドの関係
表2:RequestMappingで指定可能な属性
属性  概要
value(path) リクエストパス(pathというパラメータでも可能)
method  受け付けるHTTPメソッド(GET,POST,PUT,DELETEなど)
params 指定するリクエストパラメータの有無(Rest APIなどを実装する場合にはあまり利用しません)
headers  リクエスト時のヘッダの有無
consumes リクエストのContent-Typeの指定(Rest APIの場合には、application/jsonなどを指定する)
produces  リクエストのAcceptの値の指定

 リスト3はサンプルアプリケーションでの利用例です。

[リスト3]@RequestMappingの利用例(java/src/main/java/com/coltware/contacts/controller/ContactController.javaの抜粋)
// (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"という文字列をパラメータとして扱うためのサンプルコードです。

[リスト4]パスパラメータの指定と取得例(java/src/main/java/com/coltware/contacts/controller/ContactController.javaの抜粋)
@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が利用可能です。

表3:指定できるスコープ一覧
スコープ名 概要
singleton スコープを指定しない場合と同様。起動時に一度だけインスタンスを作成し、同じインスタンスを共有する。
prototype  取得時に毎回新しいインスタンスを作成する。
request  HTTPでのリクエストごとに生成する。Webアプリケーションの場合のみ有効です。
session HTTPでのセッション単位で生成します。Webアプリケーションの場合のみ有効です。
application  サーブレットコンテキスト単位で生成します。Webアプリケーションの場合のみ有効です。
websocket  WebSocketの接続単位で生成します。Webアプリケーションの場合のみ有効です。

 サービスなどではデフォルトのsingletonでの利用が一般的ではありますが、Web APIの場合にはリクエストごとにインスタンスを生成した方が良いケースが多いためrequestを指定します。

エラー処理を行うコントローラの定義

 コントローラ内でエラーが発生したときには、それらのエラーに応じたレスポンスの定義をする場合が一般的です。従って、各個別の実装でエラーが起きても共通したエラー処理を記述したくなります。

 そのような場合、図4のようにSpring MVCではエラー処理を行うためのコントローラを@ControllerAdviceアノテーションを使って作成します。

図4:@ControllerAdviceを用いたエラー処理の共通化
図4:@ControllerAdviceを用いたエラー処理の共通化

 リスト5は、サンプルアプリケーションでの@ControllerAdviceの利用例です。

[リスト5]@ControllerAdviceの利用例(java/src/main/java/com/coltware/contacts/controller/ErrorController.javaの抜粋
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があります。

表4:WebMvcConfigureで設定可能な主なメソッド
設定時に利用するメソッド  概要
addCorsMappings Cors(Cros:Cross-origin resource sharing)でのリクエストに対応するための設定
addFormatters リクエストデータのフォーマット変換処理の登録。例えば、文字列から日付形式など
addInterceptors リクエストに対する処理の前後に追加で行う処理を登録。例えば、リクエストに対するアクセス制限やヘッダ操作等など
addResourceHandlers 静的リソース(HTMLやJavaScript、画像、CSS等)の登録
configureMessageConverters クエスト・レスポンスデータ形式変換処理の登録。例えば、文字列からバイト形式など

 リスト6は、Web APIにおけるCorsリクエストに対応する場合のサンプルコードです。

[リスト6]WebMvcConfigurerの(java/src/main/java/com/coltware/contacts/configuration/WebConfiguration.javaの抜粋)
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でのリクエストを許可する場合のサンプルコードです。

[リスト7]@Autowiredの利用例(java/src/main/java/com/coltware/contacts/configuration/CorsFilter.javaの抜粋)
: (省略)
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のドキュメントと実際のサンプルコードなどを参照すると理解がより深まります。

 次回はデータベース関連のアノテーションなど機能部分について紹介します。

参考資料