🔷 프로젝트 개발 시 일반적으로 FrontEnd 개발자와 BackEnd 개발자가 분리된다. FrontEnd 개발자의 경우 화면에 집중하고 BackEnd 개발자가 만든 문서 API를 보며 데이터 처리를 하게 된다. 이 때 개발 상황의 변화에 따른 API의 추가 또는 변경할 때 마다 문서에 적용하는 불편함이 발생한다.
🔷 Swagger
🖥 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 등록
<?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
Annotation | Description |
---|---|
@Api | Controller 가 REST 방식을 처리하기 위한 것임을 명시 |
@ApiIgnore | Class, method에 선언이 가능하며 클라이언트에 노출하고 싶지 않은 경우 사용 |
@ApiOperation | 제공되는 API에 대한 간단한 설명 |
@ApiModel | URL 경로에 있는 값을 파라미터로 추출. |
@ApiModelProperty | 결과로 응답되는 데이터 필드에 대한 설명 |
@ApiImplicitParam | API 요청시 설정하는 파라미터에 대한 설명 |
@ApiImplicitParams | API 요청 시 설정하는 파라미터가 여러개일 경우 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 테스트 또한 가능한데
나오는 결과를 볼 수 있다.
🔷 추가 HTTP 헤더를 사용하여, 한 출처에서 실행 중인 웹 애플리케이션이 다른 출처의 선택한 자원에 접근할 수 있는 권한을 부여하도록 브라우저에 알려주는 체제, 웹 애플리케이션은 리소스가 자신의 출처(도메인, 프로토콜, 포트)와 다를 때 교차 출처 HTTP 요청을 실행한다.
🔷 해결 방법
@CrossOrigin
활용방금 전의 실습 코드를 통해 게시글을 불러오려 하면 콘솔에 이런 식으로 CORS 정책을 어겼다는 경고가 뜬다.
🖥 BoardRestController 클래스에 @CrossOrigin을 부착
*
로 전체를 지정할 수도 있다.@RestController
@RequestMapping("/api")
@Api(tags="게시판 컨트롤러")
@CrossOrigin("*")
public class BoardRestController {
// ...
}
단 한 줄의 애너테이션 만으로 CORS 위반을 허용했다.
개념이 어렵고, 설정이 어렵다 싶으면 이해가 될 때까지 반복해도 봐도 좋겠지만, 필요할 때마다 다시 검색해보는 것도 좋을듯 하다.