Spring BootでSpringFox(Swagger)を試す
Spring BootでSpringFox(Swagger)を試したメモです。
目次
SpringFoxでAPIドキュメントを生成してみました。
SpringFoxのドキュメントのサンプルが分かりやすいですのでこちらを参考にしました。
Springfox Reference Documentation
SpringFox
dependency
Maven dependencyにはspringfox-swagger2とspringfox-swagger-uiを追加。
<dependencies> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>2.6.1</version> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>2.6.1</version> </dependency> </dependencies>
API生成の対象クラス
以前使ったコントローラを再使用。
GET, POST, UPDATE, DELETEのそれぞれのメソッドを用意しました。
PeopleController.java
@Controller public class PeopleController { @ResponseBody @RequestMapping(value = "/api/people/{country}", method = RequestMethod.GET) public People getPeople(@PathVariable String country) { People people = new People(); people.setCountry("Japan"); people.setYear(2001); people.setPopulation(1_000_000); return people; } @ResponseBody @RequestMapping(value = "/api/people", method = RequestMethod.GET) public List<People> getPeopleList() { People japan = new People(); japan.setCountry("Japan"); japan.setYear(2001); japan.setPopulation(1_000_000); People america = new People(); america.setCountry("America"); america.setYear(2001); america.setPopulation(2_000_000); return Arrays.asList(japan, america); } @ResponseBody @RequestMapping(value = "/api/people", method = RequestMethod.POST) public int postPeople(@RequestBody People people) { // 登録処理 // 登録件数を返す return 1; } @ResponseBody @RequestMapping(value = "/api/people", method = RequestMethod.PUT) public int putPeople(@RequestBody People people) { // 更新処理 // 更新件数を返す return 2; } @ResponseBody @RequestMapping(value = "/api/people/{country}", method = RequestMethod.DELETE) public int deletePeople(@PathVariable String country) throws Exception { // 削除処理 // 削除件数を返す return 3; } @Data @NoArgsConstructor @FieldDefaults(level = AccessLevel.PRIVATE) public static class People { String country; int year; int population; } }
SpringFox Configuration
SpringFoxの最小の設定でやってみます。
(1) SpringFoxを有効にするアノテーションです。
(2) DocketがSwaggerの設定を行うインターフェースになります。
(3) DocumentationTypeにSwagger2を指定します。
(4) select()でApiSelectorBuilderを生成
(5) apis()で対象とするRequestHandlerを選択します。RequestHandlerSelectors.any()ですべてのRequestHandlerを対象にしています。
(6) paths()で対象とするパスを選択します。PathSelectors.any()ですべてのパスを対象にしています。
(7) build()でDocketを生成します。
SpringFoxConfigMinimum.java
@Configuration @EnableSwagger2 // (1) public class SpringFoxConfigMinimum { @Bean public Docket springFoxExampleDoc() { // (2) return new Docket(DocumentationType.SWAGGER_2) // (3) .select() // (4) .apis(RequestHandlerSelectors.any()) // (5) .paths(PathSelectors.any()) // (6) .build(); // (7) } }
実行
SpringBootアプリケーションを起動して、
localhost:8080/swagger-ui.htmlがデフォルトのURLになっているのでアクセスしてみます。
するとAPIドキュメントが表示されました。
あれだけのConfigurationを設定するだけで出来てしまうのですごいです!!
見てみると、SpringBootのErrorControllerもAPIドキュメント対象になっています。
これは、対象をPathSelectors.any()ですべてを対象にしたためです。
ErrorControllerを外すのは後ほど設定してみます。
確認
PeopleControllerのそれぞれのメソッドを確認してみます。
GETメソッド
パスにcountryを取り、Peopleオブジェクトを返すGETメソッド。
@ResponseBody @RequestMapping(value = "/api/people/{country}", method = RequestMethod.GET) public People getPeople(@PathVariable String country) { People people = new People(); people.setCountry("Japan"); people.setYear(2001); people.setPopulation(1_000_000); return people; }
生成されたAPIを確認すると、
Parametersにparameter typeがpathでcountryになっています。
ResponseClassにはPeopleオブジェクトがjsonにシリアライズされて返っています。
リストでPeopleオブジェクトを返すGETメソッド。
@ResponseBody @RequestMapping(value = "/api/people", method = RequestMethod.GET) public List<People> getPeopleList() { People japan = new People(); japan.setCountry("Japan"); japan.setYear(2001); japan.setPopulation(1_000_000); People america = new People(); america.setCountry("America"); america.setYear(2001); america.setPopulation(2_000_000); return Arrays.asList(japan, america); }
生成されたAPIを確認すると、
ResponseClassにはPeopleオブジェクトがjsonの配列でシリアライズされて返っています。
POSTメソッド
リクエストボティにPeopleオブジェクトを取り、登録件数をintで返すPOSTメソッド。
@ResponseBody @RequestMapping(value = "/api/people", method = RequestMethod.POST) public int postPeople(@RequestBody People people) { // 登録処理 // 登録件数を返す return 1; }
生成されたAPIを確認すると、
Parametersにparameter typeがbodyでPeopleのjsonになっています。
ResponseClassにはint32が返っているのが分かります。
PUTメソッド
リクエストボティにPeopleオブジェクトを取り、更新件数をintで返すPUTメソッド。
@ResponseBody @RequestMapping(value = "/api/people", method = RequestMethod.PUT) public int putPeople(@RequestBody People people) { // 更新処理 // 更新件数を返す return 2; }
生成されたAPIを確認すると、
Parametersにparameter typeがbodyでPeopleのjsonになっています。
ResponseClassにはint32が返っているのが分かります。
DELETEメソッド
パスにcountryを取り、削除件数をintで返すDELETEメソッド。
@ResponseBody @RequestMapping(value = "/api/people/{country}", method = RequestMethod.DELETE) public int deletePeople(@PathVariable String country) throws Exception { // 削除処理 // 削除件数を返す return 3; }
生成されたAPIを確認すると、
Parametersにparameter typeがpathでcountryになっています。
ResponseClassにはint32が返っているのが分かります。
他の設定
他の設定を確認するために下記のクラスを追加。
CountryController.java
@Controller public class CountryController { @ResponseBody @RequestMapping(value = "/api/country/now", method = RequestMethod.GET) public Date getDate() { return new Date(); } @ResponseBody @RequestMapping(value = "/api/country/{countryName}/{cityName}", method = RequestMethod.GET) public MyResponseEntityWithStatus<City> getCity(@PathVariable String country, @PathVariable String city) { City yokohama = new City("yokohama", 100); HttpStatus status = HttpStatus.OK; return new MyResponseEntityWithStatus<>(yokohama, status); } @ResponseBody @RequestMapping(value = "/api/country/{countryName}", method = RequestMethod.GET) public MyResponseEntity<List<Country>> getCountry(@PathVariable String country) { Country america = new Country("America", "English", 1_000_000_000); Country japan = new Country("Japan", "japanese", 1_000_000); List<Country> countries = Arrays.asList(america, japan); return new MyResponseEntity<>(countries); } @ApiIgnore(value = "this is dummy") @ResponseBody @RequestMapping(value = "/api/country/dummy", method = RequestMethod.GET) public String dummy() { return "dummy country"; } @Data @AllArgsConstructor public static class Country { String name; String language; int population; } @Data @AllArgsConstructor public static class City { String name; int population; } @Data @AllArgsConstructor public static class MyResponseEntity<T> { T res; } @Data @AllArgsConstructor public static class MyResponseEntityWithStatus<T> { T res; HttpStatus httpStatus; } }
paths()
paths()で対象とするRequestHandlerを選択できます。
peopleControllerとcountryControllerだけ対象にする場合、こんな感じで設定できます。
@Configuration @EnableSwagger2 public class SpringFoxConfigPaths { @Bean public Docket springFoxExampleDoc() { return new Docket(DocumentationType.SWAGGER_2) .select() .apis(RequestHandlerSelectors.any()) .paths(paths()) .build(); } private Predicate<String> paths() { return or( regex("/api/people.*"), regex("/api/country.*") ); } }
確認すると、peopleControllerとcountryControllerだけ表示されており、
paths(PathSelectors.any())を指定した場合に表示されていたbasic error controllerが対象外になっています。
basic error controllerを対象外にしたいだけの場合は、下記のようにも設定できます。
private Predicate<String> paths() { return not( regex("/error") ); }
pathMapping()
pathMapping()でservletのパスマッピングがある場合に、パスマッピングを指定できます。
pathMapping()の引数で指定した文字列がパスのprefixとして付与されます。
@Configuration @EnableSwagger2 public class SpringFoxConfigPathMapping { @Bean public Docket springFoxExampleDoc() { return new Docket(DocumentationType.SWAGGER_2) .select() .apis(RequestHandlerSelectors.any()) .paths(PathSelectors.any()) .build() .pathMapping("/prefix_path"); } }
見てみるとパスのprefixとして指定した文字列が付与されています。
apiInfo()
apiInfo()でAPIのメタ情報を設定できます。
(1) APIのタイトル
(2) APIのdescription
(3) APIのバージョン
(4) APIのライセンス
(5) APIのライセンス情報のURL
@Configuration @EnableSwagger2 public class SpringFoxConfigApiInfo { @Bean public Docket springFoxExampleDoc() { return new Docket(DocumentationType.SWAGGER_2) .select() .apis(RequestHandlerSelectors.any()) .paths(PathSelectors.any()) .build() .apiInfo(apiInfo()); } private ApiInfo apiInfo() { return new ApiInfoBuilder() .title("My SpringFox Example Api") // (1) .description("My SpringFox description xxxxxx.") // (2) .version("1.0") // (3) .license("Apache License v2.0") // (4) .licenseUrl("https://www.apache.org/licenses/LICENSE-2.0") // (5) .build(); } }
確認してみると、ApiInfo()で設定した情報が表示されているのがわかります。
@ApiIgnore
@ApiIgnoreを指定すると、ドキュメント対象外にします。
下記のdummy()メソッドに@ApiIgnoreを指定。
@ApiIgnore(value = "this is dummy") @ResponseBody @RequestMapping(value = "/api/country/dummy", method = RequestMethod.GET) public String dummy() { return "dummy country"; }
確認すると、dummy()メソッドが表示されないことがわかります。
directModelSubstitute()
directModelSubstitute()で第1引数で指定したクラスを、第2引数で指定したクラスに置き換えます。
Date型を返す下記のようなメソッドの場合。
@ResponseBody @RequestMapping(value = "/api/country/now", method = RequestMethod.GET) public Date getDate() { return new Date(); }
directModelSubstitute()でDateをStringに置き換えるように指定。
@Configuration @EnableSwagger2 public class SpringFoxConfigDirectModelSubstitute { @Bean public Docket springFoxExampleDoc() { return new Docket(DocumentationType.SWAGGER_2) .select() .apis(RequestHandlerSelectors.any()) .paths(PathSelectors.any()) .build() .directModelSubstitute(Date.class, String.class); } }
確認してみると、Response ClassがStringに置き換わっています。
genericModelSubstitutes()
genericModelSubstitutes()は一つの型変数を持つクラスClazz<T>をTへ置き換えます。
下記のCityクラスを型変数に持つ、MyResponseEntityWithStatus<City>を返すメソッドの場合。
@ResponseBody @RequestMapping(value = "/api/country/{countryName}/{cityName}", method = RequestMethod.GET) public MyResponseEntityWithStatus<City> getCity(@PathVariable String country, @PathVariable String city) { City yokohama = new City("yokohama", 100); HttpStatus status = HttpStatus.OK; return new MyResponseEntityWithStatus<>(yokohama, status); } @Data @AllArgsConstructor public static class City { String name; int population; } @Data @AllArgsConstructor public static class MyResponseEntityWithStatus<T> { T res; HttpStatus httpStatus; }
下記の様にgenericModelSubstitutes(MyResponseEntityWithStatus.class)を指定すると、Cityで置き換えます。
@Configuration @EnableSwagger2 public class SpringFoxConfigGenericModelSubstitutes { @Bean public Docket springFoxExampleDoc() { return new Docket(DocumentationType.SWAGGER_2) .select() .apis(RequestHandlerSelectors.any()) .paths(PathSelectors.any()) .build() .genericModelSubstitutes(MyResponseEntityWithStatus.class); } }
何も指定しないと、下記の様にMyResponseEntityWithStatus<City>を返しますが、
確認してみると、Response ClassでCityが返っているのがわかります。
alternateTypeRules()
alternateTypeRules()は複雑な変換ルールを指定できます。
下記のようにネストした型変数MyResponseEntity<List<Country>>を返すメソッドの場合。
@ResponseBody @RequestMapping(value = "/api/country/{countryName}", method = RequestMethod.GET) public MyResponseEntity<List<Country>> getCountry(@PathVariable String country) { Country america = new Country("America", "English", 1_000_000_000); Country japan = new Country("Japan", "japanese", 1_000_000); List<Country> countries = Arrays.asList(america, japan); return new MyResponseEntity<>(countries); } @Data @AllArgsConstructor public static class Country { String name; String language; int population; } @Data @AllArgsConstructor public static class MyResponseEntity<T> { T res; }
下記の様にalternateTypeRulesを指定すると、Countryで置き換えます。
@Configuration @EnableSwagger2 public class SpringFoxConfigAlternateTypeRules { @Autowired private TypeResolver typeResolver; @Bean public Docket springFoxExampleDoc() { return new Docket(DocumentationType.SWAGGER_2) .select() .apis(RequestHandlerSelectors.any()) .paths(PathSelectors.any()) .build() .alternateTypeRules( newRule(typeResolver.resolve(MyResponseEntity.class, typeResolver.resolve(List.class, Country.class)), typeResolver.resolve(Country.class)) ); } }
何も指定しないと、下記の様にMyResponseEntity<List<Country>>を返しますが、
確認してみると、Response ClassでCountryが返っているのがわかります。
configクラス全体
configの全体です。
package com.example.springfox.config; import com.fasterxml.classmate.TypeResolver; import com.google.common.base.Predicate; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import springfox.documentation.builders.ApiInfoBuilder; import springfox.documentation.builders.RequestHandlerSelectors; import springfox.documentation.service.ApiInfo; import springfox.documentation.spi.DocumentationType; import springfox.documentation.spring.web.plugins.Docket; import springfox.documentation.swagger2.annotations.EnableSwagger2; import java.util.Date; import java.util.List; import static com.google.common.base.Predicates.or; import static springfox.documentation.builders.PathSelectors.regex; import static springfox.documentation.schema.AlternateTypeRules.newRule; import com.example.springfox.controller.CountryController.MyResponseEntity; import com.example.springfox.controller.CountryController.MyResponseEntityWithStatus; import com.example.springfox.controller.CountryController.Country; @Configuration @EnableSwagger2 public class SpringFoxConfig { @Autowired private TypeResolver typeResolver; @Bean public Docket springFoxExampleDoc() { return new Docket(DocumentationType.SWAGGER_2) .select() .apis(RequestHandlerSelectors.any()) .paths(paths()) .build() .pathMapping("/") .directModelSubstitute(Date.class, String.class) .genericModelSubstitutes(MyResponseEntityWithStatus.class) .alternateTypeRules( newRule(typeResolver.resolve(MyResponseEntity.class, typeResolver.resolve(List.class, Country.class)), typeResolver.resolve(Country.class)) ) .apiInfo(apiInfo()); } private ApiInfo apiInfo() { return new ApiInfoBuilder() .title("My SpringFox Example Api") .description("My SpringFox description xxxxxx.") .version("1.0") .license("Apache License v2.0") .licenseUrl("https://www.apache.org/licenses/LICENSE-2.0") .build(); } private Predicate<String> paths() { return or( regex("/api/people.*"), regex("/api/country.*") ); } }
試してみたのはこんなとこです。
ソースは一応あげときました。
終わり。