Let's check the feel of Spring Boot + Swagger 2.0

Select only the web from https://start.spring.io/ and create it quickly I didn't have a Swagger, so I brought it from the Maven Repository Please refer to build.gradle for version etc.

Sample code https://github.com/ririkku/swagger-demo

Minimum configuration to try

Total amount of code

build.gradle


plugins {
	id 'org.springframework.boot' version '2.2.1.RELEASE'
	id 'io.spring.dependency-management' version '1.0.8.RELEASE'
	id 'java'
}

group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '1.8'

repositories {
	mavenCentral()
}

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-web'

	//Minimum required for Swagger
	implementation "io.springfox:springfox-swagger2:2.9.2"
	implementation "io.springfox:springfox-swagger-ui:2.9.2"
	
	testImplementation('org.springframework.boot:spring-boot-starter-test') {
		exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
	}
}

test {
	useJUnitPlatform()
}

SwaggerDemoApplication.java


import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class SwaggerDemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(SwaggerDemoApplication.class, args);
    }
}

SwaggerDemoConfiguration.java


import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

@Configuration
@EnableSwagger2 // swagger2.Use 0
public class SwaggerDemoConfiguration {

    @Bean
    public Docket petApi() {
        return new Docket(DocumentationType.SWAGGER_2) // Swagger2.Declaration to use 0
                .select()
                .paths(PathSelectors.ant("/apis/**"))
                .build();
    }
}

SwaggerDemoRestController.java


import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("apis")
public class SwaggerDemoRestController {

    @GetMapping
    public String get() {
        return "get";
    }
}

Verification

I usually start it from Intellij IDEA, but some people are not in that environment, so I start it with Gradle ./gradlew bootRun Access on localhost http://localhost:8080/swagger-ui.html (Since it did not fit, it was divided into two) スクリーンショット 2019-12-03 0.04.52.png スクリーンショット 2019-12-03 0.05.09.png

Miscellaneous feelings

UI is good! !! !! I didn't write much code and it might be practical But it feels a lot of humor, let's customize it a little

Customize Header-like parts

Code (change file)

SwaggerDemoConfiguration.java


import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

@Configuration
@EnableSwagger2 // swagger2.Use 0
public class SwaggerDemoConfiguration {

    @Bean
    public Docket petApi() {
        return new Docket(DocumentationType.SWAGGER_2) // Swagger2.Declaration to use 0
                .select()
                .paths(PathSelectors.ant("/apis/**"))
                .build()
                .apiInfo(new ApiInfoBuilder()
                        .title("Customise Title Swagger Demo Application")
                        .description("I customized it to my liking")
                        .contact(new Contact("customise-name", "http://customise-contact", "customise-email"))
                        .version("1.0")
                        .termsOfServiceUrl("http://customise.com")
                        .license("Customise License").licenseUrl("http://customise-license-url") //Text if only license, link if licenseUrl is set
                        .build());
    }
}

Verification

スクリーンショット 2019-12-03 0.27.24.png

Miscellaneous feelings

The place where the suffix of Customise was rewritten Can I change the parts of Base URL and http // localhost: 8080 / v2 / api-docs? Impressions Also, ʻextensions can be set in the ʻApiInfoBuilder class Do you use it when you want to create your own plug-in? (https://swagger.io/docs/specification/2-0/swagger-extensions/) No, what does the Http method look like?

HTTP method UI confirmation

Code (change file)

SwaggerDemoRestController.java


import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("apis")
public class SwaggerDemoRestController {

    @GetMapping
    public String get() {
        return "get";
    }

    @PostMapping
    public void post() {
    }

    @DeleteMapping
    public void delete() {
    }

    @PutMapping
    public void put() {
    }

    @PatchMapping
    public void patch() {
    }
}

Verification

I saw GET a while ago, so next time I feel like POST スクリーンショット 2019-12-03 0.38.16.png スクリーンショット 2019-12-03 0.41.35.png

Miscellaneous feelings

Colorful and easy to see (PATCH or something) It seems that the details of the API will be taken into consideration in various ways (status code, etc.) Next, let's customize the details

Endpoint customization

I changed my mind and created a new class On purpose, @PathVariable is bound to a class, and @RequestParam is bound to a String!

code

Identifier.java


public class Identifier {

    private String value;

    public Identifier(String value) {
        this.value = value;
    }

    public String value() {
        if (value == null) return "";
        return value;
    }
}

SwaggerDemoCustomiseRestController.java


import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("apis/customise")
public class SwaggerDemoCustomiseRestController {

    @GetMapping("{identifier}")
    public String detail(@RequestHeader("X-Customise-Header") String customiseHeader,
                         @PathVariable("identifier") Identifier identifier,
                         @RequestParam(value = "name", required = false) String name,
                         @RequestParam("limit") int limit) {
        return identifier.value();
    }
}

Verification

I tried putting @RequestHeader, @PathVariable, @RequestParam Mandatory things are now marked with required, which is convenient.

スクリーンショット 2019-12-03 0.56.52.png

By the way, when you press the Try it out button, the following display is displayed and you can enter the value and verify it!

スクリーンショット 2019-12-03 1.00.22.png

Press ʻExecute and you'll get curl, Request URL, ResponseCode, ResponseBody, and ResponseHeaders`!

スクリーンショット 2019-12-03 1.04.11.png スクリーンショット 2019-12-03 1.05.13.png

Miscellaneous feelings

Looks pretty convenient Next, let's limit the response status that is generated arbitrarily to only what you want

Specifying the response status

By setting ʻuseDefaultResponseMessages, only 200` will be set as the default.

Code (change file)

SwaggerDemoConfiguration.java


import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

@Configuration
@EnableSwagger2 // swagger2.Use 0
public class SwaggerDemoConfiguration {

    @Bean
    public Docket petApi() {
        return new Docket(DocumentationType.SWAGGER_2) // Swagger2.Declaration to use 0
                .select()
                .paths(PathSelectors.ant("/apis/**"))
                .build()
                .useDefaultResponseMessages(false) // <-add to
                .apiInfo(new ApiInfoBuilder()
                        .title("Customise Title Swagger Demo Application")
                        .description("I customized it to my liking")
                        .contact(new Contact("customise-name", "http://customise-contact", "customise-email"))
                        .version("1.0")
                        .termsOfServiceUrl("http://customise.com")
                        .license("Customise License").licenseUrl("http://customise-license-url") //Text if only license, link if licenseUrl is set
                        .build());
    }
}

Verification

スクリーンショット 2019-12-03 1.11.07.png

Miscellaneous feelings

Oshisuke disappeared Next, let's define all the APIs of the cases that are likely to be actually used.

Practical endpoint definition

I just want to use Swagger, so the processing content is appropriate of appropriate

code

LessonIdentifier.java


class LessonIdentifier {

    private Integer value;

    LessonIdentifier(String value) {
        this.value = Integer.valueOf(value);
    }

    LessonIdentifier(int value) {
        this.value = value;
    }

    Integer value() {
        if (value == null) return 0;
        return value;
    }
}

LessonRequest.java


public class LessonRequest {

    private String studentName;
    private String tutorName;

    public LessonRequest(String studentName, String tutorName) {
        this.studentName = studentName;
        this.tutorName = tutorName;
    }

    public String getStudentName() {
        return studentName;
    }

    public String getTutorName() {
        return tutorName;
    }
}

LessonResponse.java


public class LessonResponse {

    private int id;
    private String studentName;
    private String tutorName;

    LessonResponse(int id, String studentName, String tutorName) {
        this.id = id;
        this.studentName = studentName;
        this.tutorName = tutorName;
    }

    public int getId() {
        return id;
    }

    public String getStudentName() {
        return studentName;
    }

    public String getTutorName() {
        return tutorName;
    }
}

LessonIdentifierResponse.java


public class LessonIdentifierResponse {

    private int value;

    LessonIdentifierResponse(LessonIdentifier lessonIdentifier) {
        this.value = lessonIdentifier.value();
    }

    public int getValue() {
        return value;
    }
}

ErrorResponse.java


public class ErrorResponse {

    private String message;

    public ErrorResponse(String message) {
        this.message = message;
    }

    public String getMessage() {
        return message;
    }
}

LessonController.java


import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;

import java.util.Arrays;
import java.util.List;

@RestController
@RequestMapping("apis/lessons")
public class LessonController {

    @ApiResponse(code = 400, message = "Bad Request", response = ErrorResponse.class)
    @GetMapping
    @ResponseStatus(HttpStatus.OK)
    public List<LessonResponse> list() {
        //Acquisition process
        return Arrays.asList(
                new LessonResponse(1, "studentName1", "tutorName1"),
                new LessonResponse(2, "studentName2", "tutorName2"),
                new LessonResponse(3, "studentName3", "tutorName3"));
    }

    @ApiResponses(value = {
            @ApiResponse(code = 400, message = "Bad Request", response = ErrorResponse.class),
            @ApiResponse(code = 404, message = "Not Found", response = ErrorResponse.class)})
    @GetMapping("{lessonIdentifier}")
    @ResponseStatus(HttpStatus.OK)
    public LessonResponse detail(@PathVariable("lessonIdentifier") LessonIdentifier lessonIdentifier) {
        //Acquisition process
        return new LessonResponse(1, "studentName1", "tutorName1");
    }

    @ApiResponses(value = {
            @ApiResponse(code = 400, message = "Bad Request", response = ErrorResponse.class),
            @ApiResponse(code = 404, message = "Not Found", response = ErrorResponse.class)})
    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    public LessonIdentifierResponse add(@RequestBody LessonRequest lessonRequest) {
        //Additional processing
        return new LessonIdentifierResponse(new LessonIdentifier(4));
    }

    @ApiResponses(value = {
            @ApiResponse(code = 400, message = "Bad Request", response = ErrorResponse.class),
            @ApiResponse(code = 404, message = "Not Found", response = ErrorResponse.class)})
    @DeleteMapping("{lessonIdentifier}")
    @ResponseStatus(HttpStatus.NO_CONTENT)
    public void delete(@PathVariable("lessonIdentifier") LessonIdentifier lessonIdentifier) {
        //Delete process
    }

    @ApiResponses(value = {
            @ApiResponse(code = 400, message = "Bad Request", response = ErrorResponse.class),
            @ApiResponse(code = 404, message = "Not Found", response = ErrorResponse.class)})
    @PutMapping("{lessonIdentifier}")
    @ResponseStatus(HttpStatus.OK)
    public LessonResponse edit(@PathVariable("lessonIdentifier") LessonIdentifier lessonIdentifier,
                               @RequestBody LessonRequest lessonRequest) {
        //Editing process
        return new LessonResponse(1, "EditStudentName1", "EditTutorName1");
    }
}

Verification

There are many, so only some ʻExample Valuecame out nicely Also, only those specified byResponseStatus`!

スクリーンショット 2019-12-03 1.42.28.png スクリーンショット 2019-12-03 1.43.52.png スクリーンショット 2019-12-03 1.44.06.png

Miscellaneous feelings

I've come to a point where I can use it nicely Want to write an API description? So I will write it

Refined endpoint description

I tried adding @ApiOperation

Code (change file)

LessonController.java


import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;

import java.util.Arrays;
import java.util.List;

@RestController
@RequestMapping("apis/lessons")
public class LessonController {

    @ApiOperation(value = "Get a list of lessons", notes = "Take all records without search criteria to stop the time in your browser.")
    @ApiResponse(code = 400, message = "Bad Request", response = ErrorResponse.class)
    @GetMapping
    @ResponseStatus(HttpStatus.OK)
    public List<LessonResponse> list() {
        //Acquisition process
        return Arrays.asList(
                new LessonResponse(1, "studentName1", "tutorName1"),
                new LessonResponse(2, "studentName2", "tutorName2"),
                new LessonResponse(3, "studentName3", "tutorName3"));
    }

    @ApiOperation(value = "Get a lesson", notes = "ID指定したGet a lesson")
    @ApiResponses(value = {
            @ApiResponse(code = 400, message = "Bad Request", response = ErrorResponse.class),
            @ApiResponse(code = 404, message = "Not Found", response = ErrorResponse.class)})
    @GetMapping("{lessonIdentifier}")
    @ResponseStatus(HttpStatus.OK)
    public LessonResponse detail(@PathVariable("lessonIdentifier") LessonIdentifier lessonIdentifier) {
        //Acquisition process
        return new LessonResponse(1, "studentName1", "tutorName1");
    }

    @ApiOperation(value = "Create a lesson", notes = "After creating it, it will return the ID!")
    @ApiResponses(value = {
            @ApiResponse(code = 400, message = "Bad Request", response = ErrorResponse.class),
            @ApiResponse(code = 404, message = "Not Found", response = ErrorResponse.class)})
    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    public LessonIdentifierResponse add(@RequestBody LessonRequest lessonRequest) {
        //Additional processing
        return new LessonIdentifierResponse(new LessonIdentifier(4));
    }

    @ApiOperation(value = "Delete lesson", notes = "I won't return anything")
    @ApiResponses(value = {
            @ApiResponse(code = 400, message = "Bad Request", response = ErrorResponse.class),
            @ApiResponse(code = 404, message = "Not Found", response = ErrorResponse.class)})
    @DeleteMapping("{lessonIdentifier}")
    @ResponseStatus(HttpStatus.NO_CONTENT)
    public void delete(@PathVariable("lessonIdentifier") LessonIdentifier lessonIdentifier) {
        //Delete process
    }

    @ApiOperation(value = "Edit lesson", notes = "Returns the edited lesson!")
    @ApiResponses(value = {
            @ApiResponse(code = 400, message = "Bad Request", response = ErrorResponse.class),
            @ApiResponse(code = 404, message = "Not Found", response = ErrorResponse.class)})
    @PutMapping("{lessonIdentifier}")
    @ResponseStatus(HttpStatus.OK)
    public LessonResponse edit(@PathVariable("lessonIdentifier") LessonIdentifier lessonIdentifier,
                               @RequestBody LessonRequest lessonRequest) {
        //Editing process
        return new LessonResponse(1, "EditStudentName1", "EditTutorName1");
    }
}

Verification

Details are now available!

スクリーンショット 2019-12-03 1.52.23.png スクリーンショット 2019-12-03 1.52.35.png

Miscellaneous feelings

However, I don't like the messages written in @ApiOperation or @ApiResponse becoming too long. .. .. I will try it by wondering if it can be made into a separate file

Define the message in a separate file

code

application.properties


LessonController.list.value=Get a list of lessons
LessonController.list.notes=Stop time in your browser by taking all records without search criteria

LessonController.detail.value=Get a lesson
LessonController.detail.notes=Get the lesson with the specified ID

LessonController.add.value=Create a lesson
LessonController.add.notes=After creating it, it will return the ID!

LessonController.delete.value=Delete lesson
LessonController.delete.notes=I won't return anything

LessonController.edit.value=Edit lesson
LessonController.edit.notes=Returns the edited lesson!

LessonController.java


import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;

import java.util.Arrays;
import java.util.List;

@RestController
@RequestMapping("apis/lessons")
public class LessonController {

    @ApiOperation(value = "${LessonController.list.value}", notes = "${LessonController.list.notes}")
    @ApiResponse(code = 400, message = "Bad Request", response = ErrorResponse.class)
    @GetMapping
    @ResponseStatus(HttpStatus.OK)
    public List<LessonResponse> list() {
        //Acquisition process
        return Arrays.asList(
                new LessonResponse(1, "studentName1", "tutorName1"),
                new LessonResponse(2, "studentName2", "tutorName2"),
                new LessonResponse(3, "studentName3", "tutorName3"));
    }

    @ApiOperation(value = "${LessonController.detail.value}", notes = "${LessonController.detail.notes}")
    @ApiResponses(value = {
            @ApiResponse(code = 400, message = "Bad Request", response = ErrorResponse.class),
            @ApiResponse(code = 404, message = "Not Found", response = ErrorResponse.class)})
    @GetMapping("{lessonIdentifier}")
    @ResponseStatus(HttpStatus.OK)
    public LessonResponse detail(@PathVariable("lessonIdentifier") LessonIdentifier lessonIdentifier) {
        //Acquisition process
        return new LessonResponse(1, "studentName1", "tutorName1");
    }

    @ApiOperation(value = "${LessonController.add.value}", notes = "${LessonController.add.notes}")
    @ApiResponses(value = {
            @ApiResponse(code = 400, message = "Bad Request", response = ErrorResponse.class),
            @ApiResponse(code = 404, message = "Not Found", response = ErrorResponse.class)})
    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    public LessonIdentifierResponse add(@RequestBody LessonRequest lessonRequest) {
        //Additional processing
        return new LessonIdentifierResponse(new LessonIdentifier(4));
    }

    @ApiOperation(value = "${LessonController.delelte.value}", notes = "${LessonController.delete.notes}")
    @ApiResponses(value = {
            @ApiResponse(code = 400, message = "Bad Request", response = ErrorResponse.class),
            @ApiResponse(code = 404, message = "Not Found", response = ErrorResponse.class)})
    @DeleteMapping("{lessonIdentifier}")
    @ResponseStatus(HttpStatus.NO_CONTENT)
    public void delete(@PathVariable("lessonIdentifier") LessonIdentifier lessonIdentifier) {
        //Delete process
    }

    @ApiOperation(value = "${LessonController.edit.value}", notes = "${LessonController.edit.notes}")
    @ApiResponses(value = {
            @ApiResponse(code = 400, message = "Bad Request", response = ErrorResponse.class),
            @ApiResponse(code = 404, message = "Not Found", response = ErrorResponse.class)})
    @PutMapping("{lessonIdentifier}")
    @ResponseStatus(HttpStatus.OK)
    public LessonResponse edit(@PathVariable("lessonIdentifier") LessonIdentifier lessonIdentifier,
                               @RequestBody LessonRequest lessonRequest) {
        //Editing process
        return new LessonResponse(1, "EditStudentName1", "EditTutorName1");
    }
}

Verification

Let's get out without any problems!

スクリーンショット 2019-12-03 2.16.51.png

Miscellaneous feelings

It's very convenient when you want to combine messages into one file However, if you do not overdo it, it tends to be a complicated message file, so be careful when using it.

Summary

I understood that it seems to be convenient for the time being However, there are some parts that I haven't been able to investigate, such as after Build or when I don't want the production environment to access swagger-ui, so I'll investigate later.

reference

https://springfox.github.io/springfox/docs/current/

Recommended Posts

Let's check the feel of Spring Boot + Swagger 2.0
The story of raising Spring Boot 1.5 series to 2.1 series
Specify the encoding of static resources in Spring Boot
Access the built-in h2db of spring boot with jdbcTemplate
05. I tried to stub the source of Spring Boot
I tried to reduce the capacity of Spring Boot
Check the behavior of include, exclude, ExhaustedRetryException of Spring Retry
Check the behavior of getOne, findById, and query methods in Spring Boot + Spring Data JPA
The story of raising Spring Boot from 1.5 series to 2.1 series part2
About the function of Spring Boot due to different versions
A story packed with the basics of Spring Boot (solved)
Let's grasp the operation image (atmosphere) of the DI container of Spring
Spring Boot for the first time
[Rails] Check the contents of the object
Going out of message (Spring boot)
Check the migration status of rails
[Spring Boot] Role of each class
Check date correlation with Spring Boot
Filter the result of BindingResult [Spring]
Get a proxy instance of the component itself in Spring Boot
See the behavior of entity update with Spring Boot + Spring Data JPA
I want to control the default error message of Spring Boot
[Beginner] Let's write REST API of Todo application with Spring Boot
The story of encountering Spring custom annotation
Check the contents of the Java certificate store
Check the contents of params with pry
Memo: [Java] Check the contents of the directory
Procedure to make the value of the property file visible in Spring Boot
HTTPS with Spring Boot and Let's Encrypt
About the initial display of Spring Framework
WebMvcConfigurer Memorandum of Understanding for Spring Boot 2.0 (Spring 5)
Check the version of the standard Web software.
[Java] Check the number of occurrences of characters
[Java] [Spring] Test the behavior of the logger
Check the operation of the interface through threads
Introducing the Spring Boot Actuator, a function that makes the operation of Spring Boot applications easier.
How to check the latest version of io.spring.platform to describe in pom.xml of Spring (STS)
Organize the differences in behavior of @NotBlank, @NotEmpty, @NotNull with Spring Boot + Thymeleaf
Get the class name and method name of Controller executed by HandlerInterceptor of Spring Boot
Get the path defined in Controller class of Spring boot as a list
Resource handler settings when delivering SPA with the static resource function of Spring Boot
How to set environment variables in the properties file of Spring boot application
What is JSP? ~ Let's know the basics of JSP !! ~
Check the version of the JDK installed and the version of the JDK enabled
About the official start guide of Spring Framework
[FCM] Implementation of message transmission using FCM + Spring boot
Feel the passage of time even in Java
Various correspondence table of Spring Framework and Spring Boot
When @Transactional of Spring Boot does not work
[Java / Spring Boot] Spring security ④ --Implementation of login process
[Java / Spring Boot] Spring security ⑤ --Implementation of logout processing
The official name of Spring MVC is Spring Web MVC
[Spring Boot] How to refer to the property file
View the Gradle task in the Spring Boot project
[Verification] Comparison of Spring Boot vs Micronaut boot speed
[Comparison verification] How different is the development productivity of Spring Boot apps from the past?
Challenge Spring Boot
Let's make a circuit breaker for back-end service using Actuator of Spring Boot (Part 1)
[Spring Boot] The story that the bean of the class with ConfigurationProperties annotation was not found
Spring Boot Form
Spring Boot Memorandum