본문 바로가기
공부/프로그래밍

[springboot] swagger 설정 & 사용법

by demonic_ 2021. 2. 4.
반응형

문서 자동화에 쓰이는 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 클래스 구성을 보여주라는 뜻이다. 문서내용은 이전과 같다.

 

 

끝.

 

반응형

댓글