
ํผ์ ๊ฐ๋ฐํ ๋์๋ ๊ทธ๋ฅ ๋ง๋ค๋ฉด ๋์ง๋ง, ํ์ ์์๋ API ๋ฌธ์๋ฅผ ์ ๋ฐ์ดํธํ๋ ๊ฒ์ด ๊ฝค๋ ์ ๊ฒฝ ์ฐ์ด๋ ์ผ์ด๋ค. ๊ณต์ ํด๋ผ์ฐ๋๋ ํ๋ซํผ(notion, confluence ๋ฑ)์ ํ์ฉํ๋ ๊ฒ๋ ์ข์ ๋ฐฉ๋ฒ์ด์ง๋ง ๋ ์ฝ๊ฒ ๋ง๋ค ์ ์๋ ๋ฐฉ๋ฒ์ด ์๋ค! ๋ฌผ๋ก ๋ ๊ณต์ ๋ฌธ์์ ์์ฑํด๋๊ณ , Swagger๋ ์ฌ์ฉํ๋ค. ๊ฐ๊ฐ์ ๋ฐฉ์์๋ ์ฅ๋จ์ ์ด ์๊ธฐ ๋๋ฌธ์ด๋ค.
Swagger?REST web service๋ฅผ ์ค๊ณ, ๋น๋, ๋ฌธ์ํ ๋ฑ์ ์ผ์ ๋์์ฃผ๋ ๋ํ ๋๊ตฌ ์ํ๊ณ์ ์ง์์ ๋ฐ๋ ์คํ์์ค ์ํํธ์จ์ด ํ๋ ์ ์ํฌ
springfox-boot-starter ์ฌ์ฉ ์ Spring Boot 2.5.x ๋ฒ์ ์ดํ๋ถํฐ๋ Spring๊ณผ Swagger ๊ฐ์ ๋ฒ์ ํธํ์ด ๋์ง ์์ ์ค๋ฅ ๋ฐ์ โ Spring Boot 2.4.x ๋ฒ์ ์ผ๋ก ๋ฎ์ถฐ์ ํด๊ฒฐ ํ์
์ฐธ๊ณ : Spring Boot 2.6.2์ Swagger ์ ์ฉ ์ ์ค๋ฅ, SpringBoot 2.6์์ swagger 3.0 ์ฌ์ฉ
์ด ๊ธ์์๋ springfox ๋์ springdoc์ ์ฌ์ฉํ๋ค.
dependencies {
...
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.3.0'
...
}
<dependencies>
...
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.3.0</version>
</dependency>
...
</dependencies>
springdoc:
api-docs:
path: /api-docs
group:
enable: true
swagger-ui:
path: /swagger-ui-custom.html
enabled: true
groups-order: ASC
tags-sorter: alpha
operations-sorter: alpha
display-request-duration: true
doc-expansion: none
cache:
disabled: true
model-and-view-allowed: true
import io.swagger.v3.oas.models.Components;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.security.SecurityRequirement;
import io.swagger.v3.oas.models.security.SecurityScheme;
import io.swagger.v3.oas.models.servers.Server;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class SwaggerConfig {
@Bean
public OperationCustomizer operationCustomizer() {
return (operation, handlerMethod) -> {
this.addResponseBodyWrapperSchemaExample(operation);
return operation;
};
}
private void addResponseBodyWrapperSchemaExample(Operation operation) {
final Content content = operation.getResponses().get("200").getContent();
if (content != null) {
content.forEach((mediaTypeKey, mediaType) -> {
Schema<?> originalSchema = mediaType.getSchema();
Schema<?> wrapperSchema = wrapSchema(originalSchema);
mediaType.setSchema(wrapperSchema);
});
}
}
private Schema<?> wrapSchema(Schema<?> originalSchema) {
Schema<?> wrapperSchema = new Schema<>();
wrapperSchema.addProperties("status", new Schema<>().type("integer").example(200));
wrapperSchema.addProperties("message", new Schema<>().type("string"));
wrapperSchema.addProperties("data", originalSchema);
return wrapperSchema;
}
@Bean
public OpenAPI openAPI() {
Info info = new Info()
.version("v1.0.0")
.title("API")
.description("");
SecurityScheme auth = new SecurityScheme()
.type(SecurityScheme.Type.APIKEY).in(SecurityScheme.In.COOKIE).name("JSESSIONID");
SecurityRequirement securityRequirement = new SecurityRequirement().addList("sessionAuth"); // ํค๋์ ํ ํฐ ํฌํจ
// When Using JWT, Activate Below Code
// String jwt = "JWT";
// SecurityRequirement securityRequirement = new SecurityRequirement().addList(jwt); // ํค๋์ ํ ํฐ ํฌํจ
// Components components = new Components().addSecuritySchemes(jwt, new SecurityScheme()
// .name(jwt)
// .type(SecurityScheme.Type.HTTP)
// .scheme("Bearer")
// .bearerFormat("JWT")
// );
return new OpenAPI()
.addServersItem(new Server().url("/"))
.info(info)
.addSecurityItem(securityRequirement)
.components(new Components().addSecuritySchemes("sessionAuth", auth))
;
}
}
@RestController
@RequiredArgsConstructor
@RequestMapping("example")
@Tag(name = "์์ ์ปจํธ๋กค๋ฌ")
public class ExampleController {
private final ExampleService exampleService;
@GetMapping("{exampleId}")
@Operation(summary = "์์ ์กฐํ", description = "์์๋ฅผ ์กฐํํฉ๋๋ค.")
@ApiResponse(responseCode = "200", description = "์ฑ๊ณต", content = @Content(schema = @Schema(implementation = ExampleResponseDto.class)))
public Response<?> exampleMethod(@RequestBody ExampleRequestDto exampleRequestDto, @PathVariable Long exampleId) {
return Response.ok(exampleService.exampleMethod(exampleRequestDto), "์ฑ๊ณต์ ์ผ๋ก ์์๋ฅผ ๋ฐํํ์์ต๋๋ค.");
}
}
DTO class์์๋ request๋ฅผ ์ํ example value๋ฅผ ์ค์ ํ ์ ์๋ค. ๋ฏธ๋ฆฌ ์์ฑํด์ ์ ๋ฌํด๋๋ฉด ๋์ ํ ์คํธ ์ ํธํ๊ธฐ ๋๋ฌธ์ ๊ถ์ฅํ๋ค.
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class ExampleRequestDto {
@Schema(description = "์์ ID", example = "1")
private Long exampleId;
@Schema(description = "์์ ์ ๋ชฉ", example = "title")
private String exampleTitle;
}
DTO ํด๋์ค๋ ๋ฌผ๋ก DTO ํด๋์ค์์ ์ฌ์ฉํ๋ ๋ค๋ฅธ ํด๋์ค ๋ด๋ถ์๋ @Schema annotation์ ํ์ฉํ๋ฉด ๋์ผํ๊ฒ ์ค์ ํ ์ ์๋ค.
| annotation | description |
|---|---|
@Api | ํด๋์ค๋ฅผ ์ค์จ๊ฑฐ์ ๋ฆฌ์์ค๋ก ํ์ โข ํด๋น ํด๋์ค๊ฐ Swagger ๋ฆฌ์์ค๋ผ๋ ๊ฒ์ ๋ช ์ โข ํด๋์ค ์์ ์ ์ธ |
@ApiOperation | ํน์ ๊ฒฝ๋ก์ operation HTTP method ์ค๋ช
โข ํ ๊ฐ์ Operation์ ์ ์ธ โข ๋ฉ์๋ ์์ ์ ์ธ |
@ApiParam | operation parameter์ ๋ฉํ ๋ฐ์ดํฐ ์ค๋ช
โข ํ๋ผ๋ฏธํฐ์ ๋ํ ์ ๋ณด ๋ช ์ โข method ์์, ํ๋ผ๋ฏธํฐ ์์ ์ ์ธ |
@ApiResponse | operation์ ์๋ต ์ง์ |
@ApiModelProperty | ๋ชจ๋ธ์ ์์ฑ ๋ฐ์ดํฐ๋ฅผ ์ค๋ช
โข Dto class ์์ property ์์ ์ ์ธ |
@ApiImplicitParam | method ๋จ์์ operation parameter ์ค๋ช
โข ํ๋ผ๋ฏธํฐ์ ๋ํ ์ ๋ณด ๋ช ์ โข method ์์ ์ ์ธ |
@ApiImplicitParams | method ๋จ์์ operation parameter ์ฌ๋ฌ ๊ฐ ์ค๋ช
โข ํ๋ผ๋ฏธํฐ์ ๋ํ ์ ๋ณด ๋ช ์ โข method ์์ ์ ์ธ |
| method | description |
|---|---|
useDefaultResponseMessages() | false : Swagger์์ ์ ๊ณตํ๋ ์๋ต ์ฝ๋ (200, 401, 403, 404)์ ๋ํ ๊ธฐ๋ณธ ๋ฉ์ธ์ง ์ ๊ฑฐ |
groupName() | Docket Bean์ด ํ ๊ฐ์ผ ๊ฒฝ์ฐ ์๋ต ๊ฐ๋ฅ, ๋ ์ด์์ผ ๊ฒฝ์ฐ ์ถฉ๋ ๋ฐฉ์ง๋ฅผ ์ํด ์ค์ |
select() | โข ApiSelectorBuilder๋ฅผ ์์ฑโข apis()์ paths()๋ฅผ ์ฌ์ฉํ ์ ์๊ฒ ํด์ค |
apis() | โข api spec์ด ์์ฑ๋์ด ์๋ ํจํค์ง ์ง์ โข ์ปจํธ๋กค๋ฌ๊ฐ ์กด์ฌํ๋ ํจํค์ง๋ฅผ base package๋ก ์ง์ -> ํด๋น ํจํค์ง์ ์กด์ฌํ๋ API ๋ฌธ์ํ โข RequestHandlerSelectors.any() : ๋ชจ๋ ํจํค์ง๋ฅผ base package๋ก ์ง์ ํด API ๋ฌธ์ํ |
paths() | โข apis()๋ก ์ ํ๋์ด์ง API ์ค ํน์ path ์กฐ๊ฑด์ ๋ง๋ API๋ค์ ๋ค์ ํํฐ๋ง -> ๋ฌธ์ํโข PathSelectors.any()๋ก ์ค์ ํ๋ฉด ํจํค์ง ์์ ๋ชจ๋ API๋ฅผ ํ ๋ฒ์ ๋ณผ ์ ์์ |
apiInfo() | ์ ๋ชฉ, ์ค๋ช ๋ฑ ๋ฌธ์์ ๋ํ ์ ๋ณด๋ค์ ์ค์ ํ๊ธฐ ์ํด ํธ์ถ |

Reference : Swagger ๊ณต์ ๋ฌธ์, springdoc-openapi repository, springdoc.org, Baeldung OpenAPI 3.0 ๋ฌธ์