문서 자동화에 쓰이는 swagger를 설정 및 사용하는 방법에 대한 포스팅이다. 그럼 시작.
build.gradle 에 다음 의존성을 추가한다
dependencies {
...
// swagger
compile group: 'io.springfox', name: 'springfox-swagger2', version: '2.9.2'
compile group: 'io.springfox', name: 'springfox-swagger-ui', version: '2.9.2'
compile group: 'io.swagger', name: 'swagger-annotations', version: '1.6.2'
compile group: 'io.swagger', name: 'swagger-models', version: '1.6.2'
...
위에서 swagger-annotations 와 swagger-models는 버전이 다른데, 이유는 다음과 같은 에러가 발생할때가 있기 때문.
java.lang.NumberFormatException: For input string: ""
at java.base/java.lang.NumberFormatException.forInputString(NumberFormatException.java:65) ~[na:na]
at java.base/java.lang.Long.parseLong(Long.java:702) ~[na:na]
at java.base/java.lang.Long.valueOf(Long.java:1144) ~[na:na]
at io.swagger.models.parameters.AbstractSerializableParameter.getExample(AbstractSerializableParameter.java:412) ~[swagger-models-1.5.20.jar!/:1.5.20]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:na]
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
at java.base/java.lang.reflect.Method.invoke(Method.java:566) ~[na:na]
swagger2 에서 발생하는 에러로 보이는데, 이전 버전의 annotations와 models 를 추가하면 해당 에러가 사라진다.
Config파일을 추가한다
package kr.sample.swagger.config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.*;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.service.contexts.SecurityContext;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
@Configuration
@EnableSwagger2
public class SwaggerConfig {
@Value("${oauth2.server.url}")
private String oauth2ServerUrl;
private final String apiBasePackage = "kr.sample.swagger.controller";
@Bean
public Docket api() {
return new Docket(DocumentationType.SWAGGER_2)
.select()
.apis(RequestHandlerSelectors.basePackage(apiBasePackage))
.paths(PathSelectors.any())
.build()
//.securityContexts(Collections.singletonList(securityContext()))
//.securitySchemes(Arrays.asList(securitySchema()))
;
}
private SecurityContext securityContext() {
return SecurityContext.builder()
.securityReferences(defaultAuth())
.build();
}
private List<SecurityReference> defaultAuth() {
final AuthorizationScope[] authorizationScopes = new AuthorizationScope[] {
new AuthorizationScope("read", "read all"),
new AuthorizationScope("write", "write all")
};
return Collections.singletonList(new SecurityReference("oauth2", authorizationScopes));
}
private OAuth securitySchema() {
final List<AuthorizationScope> authorizationScopeList = new ArrayList<>(2);
authorizationScopeList.add(new AuthorizationScope("read", "read all"));
authorizationScopeList.add(new AuthorizationScope("write", "access all"));
final List<GrantType> grantTypes = new ArrayList<>(1);
// password 기반으로 설정된 것 사용
// 토큰 end point (http://localhost:3000/oauth/token)
grantTypes.add(new ResourceOwnerPasswordCredentialsGrant(oauth2ServerUrl+"/oauth/token"));
return new OAuth("oauth2", authorizationScopeList, grantTypes);
}
}
위 설정에는 SpringSecurity 도 포함되어 있는데, 해당옵션을 빼려면 securityContexts와 securitySchemes 를 제거하면 된다. 요즘 개발에는 oauth2를 발급받아서 하는경우가 많아 추가한 거다.
있을때와 없을때 화면에서 다음 차이가 있다.
authorization 사용안할 때
사용할 때
만약 security로 인해 api 필터를 걸고 있다면 authorize를 이용해 접근이 가능하다.(그러지 않으면 401에러 발생) 다만 여기서는 사용하지 안하도 되기때문에 주석으로 처리한다.
서버를 띄운 후 접속하는 URL은 다음과 같다
http://localhost:8080/swagger-ui.html
뒤에 swagger-ui.html 을 입력하면 메인페이지로 이동한다.
운영서버에 올릴경우 swagger를 막아야 할텐데 ConditionalOnExpression을 이용해 가능하다.
application.properties에 다음을 추가한다.
true때는 사용, false때는 사용하지 않는다는 뜻이다.
...
swagger.enable=false
...
@ConditionalOnExpression을 추가한다.
:false 는 해당 옵션이 없을경우 기본값이 false로 설정된다는 의미다.
@ConditionalOnExpression(value = "${swagger.enable:false}")
@Configuration
@EnableSwagger2
public class SwaggerConfig {
...
웹을 띄워보면 다음처럼 접근이 안된다.
다시 true로 변경한다
swagger 로 설명 표기
우선 별다른 설정을 하지않고 컨트롤러를 만들어 수행해보자.
다음의 2개 컨트롤러를 만든다.
@RestController
public class MemberController {
@GetMapping("/swagger/member")
public ResponseSwaggerPage1 member() {
return new ResponseSwaggerPage1("success", "성공");
}
@PostMapping("/swagger/signup")
public ResponseSwaggerPage1 signup() {
return new ResponseSwaggerPage1("success", "성공");
}
}
@RestController
public class OrderController {
@GetMapping("/swagger/order/me")
public ResponseSwaggerPage1 orderMe() {
return new ResponseSwaggerPage1("success", "성공");
}
}
다음과 같이 2개의 컨트롤러에 대한 설명이 추가되었다
그럼이제부터 하나씩 옵션을 넣어 문서를 꾸며보자.
@ApiOperation
메서드나 타입에 붙인다. value 에 설명을 쓸 수 있고, 숨기고 싶으면 hidden 을 입력한다.
다음과 같이 설정해보자
...
@ApiOperation(value = "회원조회")
@GetMapping("/swagger/member")
public ResponseSwaggerPage1 member() {
return new ResponseSwaggerPage1("success", "성공");
}
@ApiOperation(value = "회원가입", hidden = true)
@PostMapping("/swagger/signup")
public ResponseSwaggerPage1 signup() {
return new ResponseSwaggerPage1("success", "성공");
}
...
hidden 을 설정한 회원가입은 API문서에 표시되지 않는다.
value에 입력한 것들이 설명에 나온것을 확인할 수 있다.
여기에 설명을 더 넣고 싶다면 note에 넣으면 된다. 다음과 같다.
...
@ApiOperation(value = "회원조회", notes = "회원조회에 사용된다.\n일반유저만 조회가능하며 휴면회원은 조회되지 않는다")
@GetMapping("/swagger/member")
public ResponseSwaggerPage1 member() {
return new ResponseSwaggerPage1("success", "성공");
}
...
이번엔 파라미터와 설명을 달아보자.
다음 Request객체를 생성한다. lombok을 사용했다.
@Getter
@ToString
public class RequestMemberSignup {
private String name;
private String age;
}
회원가입쪽을 다음과 같이 수정하자
...
@ApiOperation(value = "회원가입", hidden = true)
@PostMapping("/swagger/signup")
public ResponseSwaggerPage1 signup(@RequestBody RequestMemberSignup request) {
System.out.println("request = " + request);
return new ResponseSwaggerPage1("success", "성공");
}
...
문서에 다음과 같이 설정되었다.
지금이야 이름이 직관적이어서 문제가 없지만 복잡할수록 파라미터 이름만으로 추측하기 쉽지 않다. 이럴때 @ApiModelProperty를 이용하면 상세한 설명을 붙일 수 있다. RequestMemberSignup 파일을 열어 추가해준다.
@Getter
@ToString
public class RequestMemberSignup {
@ApiModelProperty(value = "이름", required = true)
private String name;
@ApiModelProperty(value = "나이")
private String age;
}
swagger ui로 가서 해당 API에서 model을 클릭하면 다음과 같이 설명추가한 것을 확인할 수 있다.
만약 model이 아닌 외부에 표기하고 싶다면 컨트롤러에 추가해야한다.
...
@ApiOperation(value = "회원가입")
@ApiImplicitParams({
@ApiImplicitParam(name = "name", value = "이름입력", required = true, dataType = "string", paramType = "query", defaultValue = ""),
})
@PostMapping("/swagger/signup")
public ResponseSwaggerPage1 signup(@RequestBody RequestMemberSignup request) {
System.out.println("request = " + request);
return new ResponseSwaggerPage1("success", "성공");
}
...
name이란 이름이 밖으로 나왔으며 설명이 추가되었다.
각각 다른 파일에 있는 API를 하나로 묶을 수 있다. 바로 Tag 옵션이다.
이번엔 태그를 이용해 하나의 카테고리에 넣어보도록 하자.
각 컨트롤러에 있는 메서드에 다음 태그를 추가한다.
@RestController
public class MemberController {
@ApiOperation(tags = "1. 회원", value = "회원조회", notes = "회원조회에 사용된다.\n일반유저만 조회가능하며 휴면회원은 조회되지 않는다")
@GetMapping("/swagger/member")
public ResponseSwaggerPage1 member() {
return new ResponseSwaggerPage1("success", "성공");
}
@ApiOperation(tags = "1. 회원", value = "회원가입")
@PostMapping("/swagger/signup")
public ResponseSwaggerPage1 signup(@RequestBody RequestMemberSignup request) {
System.out.println("request = " + request);
return new ResponseSwaggerPage1("success", "성공");
}
}
@RestController
public class OrderController {
@ApiOperation(tags = "1. 회원", value = "내 주문 API")
@GetMapping("/swagger/order/me")
public ResponseSwaggerPage1 orderMe() {
return new ResponseSwaggerPage1("success", "성공");
}
}
셋다 같은 "1. 회원" 이라는 이름으로 만들었다. 이제 swagger ui를 확인해보자
"1. 회원" 이라는 이름으로 묶여 있음을 확인할 수 있다. 흥미로운 점은 MemberController 에 있던 API들이 모두 "1.회원"으로 옮겨졌다는 점이다. 문제는 MemberController 를 없앨 수 없다는 점이다.
한가지 편법이 있다면 컨트롤러 단위는 모두 하나의 카테고리에 담아갈 수 있도록 설정했다. 다음과 같다.
@Api(tags = "0. ALL", value="controller")
@RestController
public class MemberController {
...
@Api(tags = "0. ALL", value="controller")
@RestController
public class OrderController {
...
다음처럼 화면이 구성된다.
마지막으로 Response에 대한 문서방법이다.
앞 코드에서 다음의 객체를 등록해서 리턴하는 것을 확인할 수 있다.
ResponseSwaggerPage1
@Getter
public class ResponseSwaggerPage1 {
@ApiModelProperty(value = "코드", required = true)
private String code;
@ApiModelProperty(value = "메세지")
private String message;
public ResponseSwaggerPage1(String code, String message) {
this.code = code;
this.message = message;
}
}
여기서도 @ApiModelProperty 를 등록할 수 있다. 문서를 보면 다음처럼 등록되어 있다.
혹여 보여줄 객체를 직접 등록하고 싶다면 @ApiResponse를 이용해 등록가능하다.
컨트롤러에 다음 옵션을 추가한다.
@ApiOperation(tags = "1. 회원", value = "회원가입")
@ApiResponses(value={
@ApiResponse(code = 200, message = "success", response = ResponseSwaggerPage1.class)
})
@PostMapping("/swagger/signup")
public ResponseSwaggerPage1 signup(@RequestBody RequestMemberSignup request) {
System.out.println("request = " + request);
return new ResponseSwaggerPage1("success", "성공");
}
200일 경우 ResponseSwaggerPage1 클래스 구성을 보여주라는 뜻이다. 문서내용은 이전과 같다.
끝.
'공부 > 프로그래밍' 카테고리의 다른 글
[springboot] 엑셀 다운로드 시 이름지정 및 한글 깨짐 방지 (1) | 2021.02.10 |
---|---|
[aws] lambda@edge 설정 중 파라미터(query string)이 넘어오지 않는 경우(이미지 리사이징) (0) | 2021.02.08 |
[spring] 객체 내 객체에 @Valid 점검하기 (0) | 2021.01.25 |
[java] lambda 를 이용한 GroupBy mulitiple field 사용하기 (1) | 2021.01.20 |
[retrofit, gson] Expected BEGIN_OBJECT but was STRING 에러 해결(String -> LocalDate) (1) | 2021.01.18 |
댓글