순수한 데이터만 제공하는 서버 사이드 프로그래밍
사용 기술
- REST
- Ajax
- JSON
- Swagger
- RESTful 웹 서비스 (Representational State Transfer)
- 특정한 URL과 HTTP 메소드를 결합해 '특정한 자원에 특정한 작업'을 지정하는 방식
- 기존 방식 : URL이 '행위나 작업'을 의미하고, GET/POST가 '데이터를 전송하는 위치'를 의미
- Ajax를 이용해 브라우저의 주소 이동없이도 서버와 데이터를 교환하므로,
REST 방식 : URL이 '원하는 대상'을 의미, PUT/DELETE가 '행위나 작업'을 의미한다 - 쿼리 스트링을 이용하지 않고, 직접 주소의 일부로 사용해
하나의 자원을 하나의 주소로 유일무이 표현함으로써, 하나의 URL이 하나의 자원을 식별하는 고유값이 된다. - URI이라는 자원을 이용해 '원하는 행위/작업'을 나타내는 GET/POST/PUT/DELETE를 수행하는 방식
- 비동기로 서버와 상호작용하기 때문에 언제, 어디서 에러가 발생하는지 파악하기 위해 단계별로 검증 작업을 수행해야한다.
: @Valid, @RestControllerAdvice
- URL (Uniform Resource Locator) : URI의 하위 개념으로 원하는 정보의 위치를 가리키는 값
- URI (Uniform Resource Identifier) : 좀더 구체적인 의미의 정보의 주소를 나타내는 자원식별자로, 기본키와 유사한 의미의 값
- RESTful의 메서드
- GET : 조회
- POST : 등록
- PUT : 수정
- DELETE : 삭제
- Ajax
- 자바스크립트과 XML을 이용한 비동기 처리
- 모든 작업이 브라우저 내부에서 이루어지는 방식으로, 브라우저 화면의 변화 없이 서버와 통신할 수 있다.
- 자동 완성, 지도 서비스등에서 사용
- 최근에는 XML보다는 JSON을 이용하는 방식을 선호한다.
- JSON이 각광받는 이유는 순수한 데이터만 전송하기 문에 클라이언트 구현이 웹/앱에 관계없이 데이터의 재사용이 가능하다.
- 또한, 대규모 웹 애플리케이션 개발을 지원하는 라이브러리나 프레임워크가 등장하면서 클라이언트와 서버의 역할 분배가 대두됨
- JSON 문자열 - DTO <-> JSON (객체를 JSON으로 표현, JSON을 객체로 표현)
- '문자열'은 종속된 데이터가 아니라는 장점이 있지만, 복잡한 구조의 데이터를 전송할 경우 문제가 발생
- 따라서 등장한 개념이 XML과 JSON이라는 문자열 포맷
- 자바스크립트가 키와 값 형태로 객체를 표현하는 방식의 문법을 지원하는 문자열로 데이터를 표현한다.
- 클라이언트에서 어떤 기술을 이용하든 공통적으로 인식할 수 있다.
구현 순서
- Swagger 연동
- RestController 작성
- 검증작업 수행
Swagger 연동
1. Swagger 의존성 추가
//swagger
implementation 'io.springfox:springfox-boot-starter:3.0.0'
implementation 'io.springfox:springfox-swagger-ui:3.0.0'
2. Swagger를 위한 SwaggerConfig 클래스 작성
package org.zerock.b01.config;
import lombok.Builder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.RequestHandler;
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;
@Configuration
public class SwaggerConfig {
@Bean
public Docket api(){
return new Docket(DocumentationType.OAS_30)
.useDefaultResponseMessages(false)
.select()
.apis(RequestHandlerSelectors.basePackage("org.zerock.b01.controller"))
.paths(PathSelectors.any())
.build()
.apiInfo(apiInfo());
}
private ApiInfo apiInfo(){
return new ApiInfoBuilder()
.title("Boot 01 Proejct Swagger")
.build();
}
}
3. Spring Web 관련 설정을 위한 CustomServletConfig 클래스 작성
package org.zerock.b01.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
@Configuration
@EnableWebMvc
public class CustomServletConfig {
}
4. 'http://localhost:8080/swagger-ui/index.html' 호출 확인
5. 정적 파일 경로 설정
-Swagger UI 적용시 정적 파일의 경로가 변경되는 문제
-CustomServletConfig에서 Web-MvcConfigurer 인터페이스를 구현해 addResourceHandler 오버라이딩
@Configuration
@EnableWebMvc
public class CustomServletConfig implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry){
registry.addResourceHandler("/js/**")
.addResourceLocations("classpath:/static/js/");
registry.addResourceHandler("/fonts/**")
.addResourceLocations("classpath:/static/fonts/");
registry.addResourceHandler("/css/**")
.addResourceLocations("classpath:/static/css/");
registry.addResourceHandler("/assets/**")
.addResourceLocations("classpath:/static/assets/");
}
}
REST 방식의 댓글 처리
HTTP 요청/응답 방식이 아닌 REST 방식을 사용하면 새로고침 없이 댓글 처리가 가능하다
REST 방식의 댓글처리 단계
- URL의 설계와 데이터 포맷 결정
- 컨트롤러의 JSON/XML 처리
- 동작 확인
- 자바스크립트를 통한 화면 처리
URL의 설계와 DTO 설계
XML이나 JSON 형태의 문자열을 전송해 컨트롤러에서 처리하는 방식
DTO형식에 맞는 JSON 전송-> -> 스프링에서 데이터 처리 -> DTO로 처리
Method | 역할 | 반환 | |
POST | /replies | 게시물에 댓글 추가 | 생성된 댓글 번호: {'rno' :11} |
GET | /replies/list:bno | 게시물의 댓글 목록 '?' + '페이지 번호'로 댓글 페이징 처리 |
JSON으로 PageResponseDTO 처리 |
/replies/:rno | 특정 댓글 조회 | 댓글 객체를 JSON으로 변환한 문자열 | |
PUT | /replires/:rno | 특정 댓글 수정 | 수정된 댓글 번호: {'rno' :11} |
DELETE | /replires/:rno | 특정 댓글 삭제 | 삭제된 댓글 번호: {'rno' :11} |
컨트롤러의 JSON/XML 처리
1. JSON 데이터를 받아 객체로 변환하는 RestController 작성
(1) ReplyController 작성
-@RestController : Ajax와 JSON을 이용해 비동기처리를 수행하는 어노테이션으로 메소드의 리턴 값이 JSON/XML으로 처리된다.
-@RequestMapping : JSON 데이터를 이용해 DTO로 변환하는 어노테이션
@RestController
@Log4j2
//JSON -> DTO로 변환하는 어노테이션
@RequestMapping("/replies")
public class ReplyController {
}
(2) ReplyController에 댓글 작성 메서드 register() 추가
-@ApiOpertaion : Swagger UI에서 출력하는 어노테이션으로 value 속성과, notes 속성이 있다.
-consume : REST에서의 @PostMapping 어노테이션을 이용해 데이터를 전송할 때 어떤 데이터를 사용하는지 명시하는 속성
-@RequestBody : 입력받은 JSON 문자열을 DTO로 변환하는 어노테이션
-ResponseEntity : 리턴값으로 JSON을 사용하는데, HTTP 상태 코드를 함께 전송하는 어노테이션
//Swagger UI에서 출력하는 메시지
@ApiOperation(value = "Replies POST", notes = "POST 방식으로 댓글등록")
//consumes : 메서드를 받아서 사용하는 데이터가 어떤 데이터인지 명시하는 속성
@PostMapping(value = "/", consumes = MediaType.APPLICATION_JSON_VALUE)
//댓글 등록 메서드로, ReplyDTO를 전달받아 키,값 객체인 ResponseEntity로 반환한다.
public ResponseEntity<Map<String, Long>> register(@RequestBody ReplyDTO replyDTO){
log.info(replyDTO);
Map<String,Long> resultMap=Map.of("rno", 111L);
return ResponseEntity.ok(resultMap);
}
2. 검증 작업 수행
-@Valid : 해당 객체를 검증하는 어노테이션
-@RestControllerAdvice : 검증 과정에서 문제가 발생했을 때 처리하기위한 어노테이션으로, 예외처리를 수행한다.
(1) ReplyDTO에 검증 관련된 어노테이션을 추가
javax.validation.constraints 어노테이션
- @NotNull, @Null, @NotEmpty, @NotBlank
- @Size(min=, max=), @Pattern(regex=)
- @Max(num), Min(num)
- @Future @Past
- @Positive @PositiveOrZero @Negative @NegativeOrZero
package org.zerock.b01.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.time.LocalDateTime;
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class ReplyDTO {
private Long rno;
//특정 게시글의 번호를 지정하기 위한 멤버변수
@NotNull
private Long bno;
@NotEmpty
private String replyText;
@NotEmpty
private String replyer;
private LocalDateTime regDate, modDate;
}
(2) 검증을 수행했을 때 에러처리하는 CustomRestAdvice클래스 작성
메서드 명 | handleBindException | handleFKException | handleSuchException |
예외처리 | BindException | DataIntegrityViolationExcetpion | NoSuchElementException |
파라미터 | BindException e | Exception e | Exception e |
리턴타입 | ResponseEntity <Map<String, String>> |
ResponseEntity <Map<String, String>> |
ResponseEntity <Map<String, String>> |
특징 | JSON과 DTO 불일치 | 참조하는 FK값이 존재하지 않음 | 원하는 요소가 존재하지 않음 |
메시지 | 에러 발생 시간, 에러 메시지 | 에러 발생 시간, 에러 메시지 | 에러 발생 시간, 에러 메시지 |
리턴값 | ResponseEntity.badRequest() .body(errorMap); |
ResponseEntity.badRequest() .body(errorMap); |
ResponseEntity.badRequest() .body(errorMap); |
- CustomRestAdvice에 BindException 예외처리를 위해 handleBindException() 메서드 생성
import org.springframework.validation.BindException;
-handleBindException() : 검증 과정에서 문제 발생해서 BindingResult에 에러가 존재하면 JSON 메시지와 400에러 전송하는 메서드
-@RestControllerAdvice : 검증 과정의 예외처리를 수행하는 클래스를 나타내는 어노테이션
@RestControllerAdvice
@Log4j2
public class CustomRestAdvice {
@ExceptionHandler(BindException.class)
@ResponseStatus(HttpStatus.EXPECTATION_FAILED)
public ResponseEntity<Map<String, String>> handleBindException(BindException e) {
log.error(e);
Map<String, String> errorMap = new HashMap<>();
if(e.hasErrors()){
BindingResult bindingResult = e.getBindingResult();
bindingResult.getFieldErrors().forEach(fieldError -> {
errorMap.put(fieldError.getField(), fieldError.getCode());
});
}
return ResponseEntity.badRequest().body(errorMap);
}
동작 확인
RestController의 register()를 수정해 동작 여부를 확인
- @Valid 사용해 ReplyDTO 수집
- BingResult를 파라미터로 추가하고 예외처리를 위해 BindException 추가
- 메소드 선언부에 예외처리를 위해 BindException 추가
- 메소드 리턴값의 문제 발생시 @RestControllerAdvice가 처리하므로, 해당 컨트롤러에서는 항상 정상처리된 값만 리턴
변경 전, register()
//Swagger UI에서 출력하는 메시지
@ApiOperation(value = "Replies POST", notes = "POST 방식으로 댓글등록")
//consumes : 메서드를 받아서 사용하는 데이터가 어떤 데이터인지 명시하는 속성
@PostMapping(value = "/", consumes = MediaType.APPLICATION_JSON_VALUE)
//댓글 등록 메서드로, ReplyDTO를 전달받아 키,값 객체인 ResponseEntity로 반환한다.
public ResponseEntity<Map<String, Long>> register(@RequestBody ReplyDTO replyDTO){
log.info(replyDTO);
Map<String,Long> resultMap=Map.of("rno", 111L);
return ResponseEntity.ok(resultMap);
}
변경 후, register()
//Swagger UI에서 출력하는 메시지
@ApiOperation(value = "Replies POST", notes = "POST 방식으로 댓글등록")
//consumes : 메서드를 받아서 사용하는 데이터가 어떤 데이터인지 명시하는 속성
@PostMapping(value = "/", consumes = MediaType.APPLICATION_JSON_VALUE)
//댓글 등록 메서드로, ReplyDTO를 전달받아 키,값 객체인 ResponseEntity로 반환한다.
public Map<String, Long> register(@Valid @RequestBody ReplyDTO replyDTO, BindingResult bindingResult)
throws BindException {
log.info(replyDTO);
if(bindingResult.hasErrors()){
throw new BindException(bindingResult);
}
Map<String,Long> resultMap=new HashMap<>();
resultMap.put("rno", 111L);
return resultMap;
}