[Spring] Swagger

young-gue Park·2023년 10월 23일
0

Spring

목록 보기
11/14
post-thumbnail

⚡ Swagger


📌 Swagger를 이용한 REST API 문서화

🔷 프로젝트 개발 시 일반적으로 FrontEnd 개발자와 BackEnd 개발자가 분리된다. FrontEnd 개발자의 경우 화면에 집중하고 BackEnd 개발자가 만든 문서 API를 보며 데이터 처리를 하게 된다. 이 때 개발 상황의 변화에 따른 API의 추가 또는 변경할 때 마다 문서에 적용하는 불편함이 발생한다.

  • 이 문제를 해결하기 위해 Swagger를 사용한다.

🔷 Swagger

  • 간단한 설정으로 프로젝트의 API 목록을 웹에서 확인 및 테스트 할 수 있게 해주는 Library
  • Swagger를 사용하면 Controller에 정의되어 있는 모든 URL을 바로 확인할 수 있다.
  • API 목록 뿐 아니라 API의 명세 및 설명도 볼 수 있으며, 또한 API를 직접 테스트해 볼 수도 있다.

🖥 pom.xml에 dependency 추가

<!-- Swagger -->
<!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger2 -->
<dependency>
  <groupId>io.springfox</groupId>
  <artifactId>springfox-swagger2</artifactId>
  <version>3.0.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger-ui -->
<dependency>
  <groupId>io.springfox</groupId>
  <artifactId>springfox-swagger-ui</artifactId>
  <version>3.0.0</version>
</dependency>


<!-- https://mvnrepository.com/artifact/io.springfox/springfox-boot-starter -->
<!-- springBoot 사용 시 Swagger 적용 -->
<!-- <dependency>
      <groupId>io.springfox</groupId>
      <artifactId>springfox-boot-starter</artifactId>
      <version>3.0.0</version>
  </dependency> -->

🖥 servlet-context.xml에 resources 추가 및 bean 등록

  • swagger-ui로 UI를 확인하기 위해 해당 URL이 넘어올 경우 그에 맞는 페이지로 이동하게끔 설정한다.
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/mvc"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:beans="http://www.springframework.org/schema/beans"
	xmlns:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd
		http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">

	<!-- DispatcherServlet Context: defines this servlet's request-processing infrastructure -->
	
	<!-- Enables the Spring MVC @Controller programming model -->
	<annotation-driven />

	<!-- Handles HTTP GET requests for /resources/** by efficiently serving up static resources in the ${webappRoot}/resources directory -->
	<resources mapping="/resources/**" location="/resources/" />
	<resources mapping="/swagger-ui/**" location="classpath:/META-INF/resources/webjars/springfox-swagger-ui/"></resources>
	
	<context:component-scan base-package="com.bzeromo.board.controller" />
	<beans:bean id="SwaggerConfig" class="com.bzeromo.board.config.SwaggerConfig"></beans:bean>
	
	
</beans:beans>

💡 이제 View를 사용하지 않기 때문에 ViewResolver와 관련된 servlet-context의 태그는 지워도 좋다.

🖥 config 패키지에 SwaggerConfig 클래스를 추가한다.

package com.bzeromo.board.config;

import org.springframework.context.annotation.Bean;

import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
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;

@EnableSwagger2
public class SwaggerConfig {
	
	@Bean
	public Docket api() {
		return new Docket(DocumentationType.SWAGGER_2)
				.select()
				.apis(RequestHandlerSelectors.basePackage("com.bzeromo.board.controller"))
				.paths(PathSelectors.ant("/*/api/**"))
				.build().apiInfo(apiInfo());
	}
	
	private ApiInfo apiInfo() {
		return new ApiInfoBuilder()
				.title("Spring Swagger Test")
				.description("스웨에엑 어")
				.version("1.0")
				.build();
	}
}

🔷 Swagger 관련 Annotation

AnnotationDescription
@ApiController 가 REST 방식을 처리하기 위한 것임을 명시
@ApiIgnoreClass, method에 선언이 가능하며 클라이언트에 노출하고 싶지 않은 경우 사용
@ApiOperation제공되는 API에 대한 간단한 설명
@ApiModelURL 경로에 있는 값을 파라미터로 추출.
@ApiModelProperty결과로 응답되는 데이터 필드에 대한 설명
@ApiImplicitParamAPI 요청시 설정하는 파라미터에 대한 설명
@ApiImplicitParamsAPI 요청 시 설정하는 파라미터가 여러개일 경우 ApiImplicitParam과 함께 사용

🖥 BoardController를 BoardRestController로 바꾸고 Swagger로 확인하기

package com.bzeromo.board.controller;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.bzeromo.board.model.dto.Board;
import com.bzeromo.board.model.dto.SearchCondition;
import com.bzeromo.board.model.service.BoardService;

import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import springfox.documentation.annotations.ApiIgnore;

//목록 GET + ___ : 8080/context-root/api/board
//상세보기 GET +           ""            /board/{id}
//등록 POST +             ""            /board
//수정 PUT +              ""            /board or /board/{id}
//삭제 DELETE +           ""            /board/{id}

@RestController
@RequestMapping("/api")
@Api(tags="게시판 컨트롤러")
public class BoardRestController {

	@Autowired
	private BoardService boardService;
	
	//1. 목록(검색조건이 있을 수도 있고 없을 수도 있다)
	@GetMapping("/board")
	@ApiOperation(value="게시글 조회", notes="검색조건도 같이 넘기면 넘어온다")
	public ResponseEntity<?> list(SearchCondition condition) {
//		List<Board> list = boardService.getList(); //전체조회
		List<Board> list = boardService.search(condition);
		
		if(list == null || list.size() == 0) 
			return new ResponseEntity<Void>(HttpStatus.NO_CONTENT);
		
		return new ResponseEntity<List<Board>> (list, HttpStatus.OK);
	}
	
	//2. 상세보기
	@GetMapping("/board/{id}")
	public ResponseEntity<Board> detail(@PathVariable int id) {
		Board board = boardService.getBoard(id);
		//정석대로면 게시글 제목을 클릭해서 상세보기로 들어가니 여기서 마무리
		//주소창을 통해 접근하려하는 사람이 있을 수도 있으니 없는 값을 넣을 때의 처리 필요
		
		return new ResponseEntity<Board>(board, HttpStatus.OK);
	}
	
	//3. 등록
	@PostMapping("/board")
	public ResponseEntity<Board> write(Board board) {
		boardService.writeBoard(board);
		
		//ID는 어차피 중복이 안되기 때문에 게시글 등록이 무조건 된다.
		//문제가 발생하여 게시글 등록이 안될 경우를 위해 조건을 새로 달 필요가 있다. (행의 변환 개수 등을 이용)
		return new ResponseEntity<Board> (board, HttpStatus.CREATED);
	}
	
	//4. 삭제
	@DeleteMapping("/board/{id}")
	public ResponseEntity<Void> delete(@PathVariable int id) {
		boardService.removeBoard(id);
		
		return new ResponseEntity<Void>(HttpStatus.OK);
	}
	
	//5. 수정
	@ApiIgnore
	@PutMapping("/board")
	public ResponseEntity<Void> update(@RequestBody Board board) {
		boardService.modifyBoard(board);
		
		return new ResponseEntity<Void>(HttpStatus.OK);
	}
}

http://localhost:8080/(context-root)/swagger-ui/index.html
접속이 안되면 서버를 껐다 켜보자.

정상 실행된 모습

수정 메서드에 해당하는 Update가 뜨지 않은 이유는 @ApiIgnore처리 때문이다.

파라미터를 입력하면 api 테스트 또한 가능한데

나오는 결과를 볼 수 있다.


📌 CORS(교차 출처 리소스 공유)

🔷 추가 HTTP 헤더를 사용하여, 한 출처에서 실행 중인 웹 애플리케이션이 다른 출처의 선택한 자원에 접근할 수 있는 권한을 부여하도록 브라우저에 알려주는 체제, 웹 애플리케이션은 리소스가 자신의 출처(도메인, 프로토콜, 포트)와 다를 때 교차 출처 HTTP 요청을 실행한다.

출처: CORS(mdn web docs)

🔷 해결 방법

  • 프록시 서버 활용
  • 헤더 추가
  • @CrossOrigin 활용

방금 전의 실습 코드를 통해 게시글을 불러오려 하면 콘솔에 이런 식으로 CORS 정책을 어겼다는 경고가 뜬다.

🖥 BoardRestController 클래스에 @CrossOrigin을 부착

  • () 안에 허용할 메서드만 지정할 수도 있고, *로 전체를 지정할 수도 있다.
@RestController
@RequestMapping("/api")
@Api(tags="게시판 컨트롤러")
@CrossOrigin("*")
public class BoardRestController {
	// ...
}

단 한 줄의 애너테이션 만으로 CORS 위반을 허용했다.


개념이 어렵고, 설정이 어렵다 싶으면 이해가 될 때까지 반복해도 봐도 좋겠지만, 필요할 때마다 다시 검색해보는 것도 좋을듯 하다.

profile
Hodie mihi, Cras tibi

0개의 댓글