POST나 PUT방식으로,
JSON형식 데이터를 요청데이터로 전송할 수 잇다.
name=이름&age=17을, {"name": "이름", "age":17}로.
@RequestBody를 붙이면 됨.
@PostMapping("/api/members")
public void newMember(@RequestBody @Valid RegisterRequest regReq,
HttpServletResponse response) throws IOException {
try {
Long newMemberId= registerService.regist(regReq);
response.setHeader("Location", "/api/members/"+ newMemberId);
response.setStatus(HttpServletResponse.SC_CREATED);
}
catch(DuplicateMemberException dupEx) {
response.sendError(HttpServletResponse.SC_CONFLICT);
}
}
이를 통해, JSON형식 문자열을 해당 자바객체(RegisterRequest)로 변환.
가령
{
"email": "asdf@naver.com,"
"password": "1234",
"comfirmPassword": "1234",
"name":"아무개"
}
를, RegisterRequest형식으로.
스프링MVC가 JSON형식으로 저농된 데이터를 올바르게 처리하려면, 요청컨텐츠타입이 application/json이여야 함.
보통 POST방식 폼 데이터는 쿼리문자열인 "P1=V1&P2=V2"로 전송되는데, 이때 컨텐츠타입은
application.x-www-form-urlencoded.
쿼리문자열대신 JSON을 이용하려면, applicaion.json타입으로 데이터를 전송할 수 있는 별도프로그램이 필요.
newMember()은 회원가입을 정상적으로 처리하면 응답코드로 201(created)를 전송.
또한 Location 헤더를 응답에 추가하며, 중복된 ID를 전송한 경우 409(CONFLICT)를 리턴.
////////////////////
JSON형식이 데이터를 날짜 형식으로 변환해보자.
별도설정이 없다면, yyyy-MM-ddTHH:mm:ss의 형식을 LocalDateTime이나 Date로 변환한다.
특정패턴을 가진 문자열을 LocalDateTime이나 Date로 변환하고자 한다면, @JsonFormat애노테이션의 pattern속성을 사용해 패턴을 지정한다.
@JsonFormat(pattern="yyyyMMddHHmmss")
private LocalDateTime birthDateTime;
@JsomFormat(patern="yyyyMMdd HHmmss")
private Date birthDate
마찬가지로, 특정속성이 아니라, 해당타입을 갖는 모든 속성에 적용할 수도 있다.
public class MvcConfig implements WebMvcConfigurer{
@Override
public void extendMessageConverters(
List<HttpMessageConverter<?>> converters) {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");
ObjectMapper objectMapper=Jackson2ObjectMapperBuilder.json().
featuresToEnable(SerializationFeature.INDENT_OUTPUT).
deserializerByType(LocalDateTime.class, new LocalDateTimeDeserializer(formatter))
.simpleDateFormat("yyyyMMdd HHmmss").build();
converters.add(0,
new MappingJackson2HttpMessageConverter(objectMapper));
}
}
deserializerByType()은 JSON데이터를 LocalDateTime을 변환시 사용할 패턴을 지정
simpleDateFormat()은 Date타입으로 변환시 사용할 패턴을 지정
이때, simpleDateFormat()은 Date타입을 JSON으로 변환할때도 쓰임.
//////////////////
위의 newMember()에는, regReq 패러미터에 @Valid가 있다.
JSON형식으로 전송한 데이터를 변환한 객체도, @Valid나 별도 Validator을 통해 검증이 가능하다.
검증실패시 400에러
Validator을 쓰면, 직접 상태처리 코드를 넣어야 한다.
@PostMapping("/api/members")
public void newMember(@RequestBody @Valid RegisterRequest regReq,
Errors errors,
HttpServletResponse response) throws IOException {
try {
new RegisterRequestValidator().validate(regReq, errors);
if(errors.hasErrors()) {
response.sendError(HttpServletResponse.SC_BAD_REQUEST);
return;
}
Long newMemberId= registerService.regist(regReq);
response.setHeader("Location", "/api/members/"+ newMemberId);
response.setStatus(HttpServletResponse.SC_CREATED);
}
catch(DuplicateMemberException dupEx) {
response.sendError(HttpServletResponse.SC_CONFLICT);
}
}
그러나, 위처럼 HttpServletResponse를 이용해 404에러응답을 하면, JSON형식이 아닌, 서버가 기본으로 제공하는
HTML을 응답결과로 제공한다.
즉,
@GetMapping("/api/members/{id}")
public Member member(@PathVariable Long id, HttpServletResponse response)
throws IOException {
Member member= memberDao.selectById(id);
if(member==null) {
response.sendError(HttpServletResponse.SC_NOT_FOUND);
return null;
}
return member;
}
는 id에 해당하는 멤버가 존재하면 해당 객체를 리턴, 안하면 NOT_FOUND 404에러를 리턴.
그러나 API호출하는 프로그램 입장에서, JSON에러와 HTML응답을 모두 처리하는 건 부담스러우므로,
처리를 실패한 경우 HTML응답데이터 대신 JSON식의 응답데이터를 호출시 API호출프로그램이 일관된 방법으로 응답을 처리할 수 있다.
1)에러상황에서 응답으로 쓸 ErrorResponse클래스 작성
2)ResponseEntity를 통해, member()메소드 구현
@GetMapping("/api/members/{id}")
public ResponseEntity<Object> member(@PathVariable Long id) {
Member member= memberDao.selectById(id);
if(member==null) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).
body(new ErrorResponse("no member"));
}
return ResponseEntity.status(HttpStatus.OK).body(member);
}
스프링MVC는 리턴타입이 ResponseEntity일경우, ResponseEntity의 body로 지정된 객체를 사용해 변환을 처리.
가령 위에선, member을 body로 지정(...body(member))했는데, 이러면 member객체를 JSON으로 변환.
또는 ErrorResponse를 body로 지정했다면, member가 null일경우 "no member" 메시지를 갖는 ErrorResponse를 JSON으로 변환.
ResponseEntity의 status로 지정한 값을 응답상태코드로 이용. null일 땐 NOT_FOUND를, DKSLAUS 200(OK)를 응답.
ResponseEntity 생성방법은, status와 body를 이용해 상태코드와 JSON으로 변환할 객체를 지정하는 것.
EX) ResponseEntity.status(상태코드).body(객체)
200(OK) 응답코드 & 몸체 데이터 생성시, ok()메서드 통해서도 생성가능
ResponseEntity.ok(member)
몸체내용이 없다면, body를 지정안하고 바로 build()로 생성.
ResponseEntity.status(HttpStatus.NOT_FOUND).build()
몸체 내용이 없다면, status()대신
ResponseEntity.notFound().build() 도 가능
이것이 가능한건, noContent() : 204 || badRequest() : 400 || notFound() : 404
newMember()에서는 201(Created)와 Location 헤더를 함꼐 보냈는데,
response.setHeader("Location", "/api/members/"+ newMemberId);
response.setStatus(HttpServletResponse.SC_CREATED);
이 경우를 ResponseEntity로 보낼 경우,
ResponseEntity.created() 메서드에 Location헤더로 전달할 URI를 전달하면 됨.
@PostMapping("/api/members")
public ResponseEntity<Object> newMember(@RequestBody @Valid RegisterRequest regReq,
Errors errors, HttpServletResponse response) {
try {
Long newMemberId=registerService.regist(regReq);
URI uri= URI.create("/api/members/"+newMemberId);
return ResponseEntity.created(uri).build();
} catch(DuplicateMemberException dupEx) {
return ResponseEntity.status(HttpStatus.CONFLICT).build();
}
}
/////
한 메서드에 정상응답&에러응답을 ResponseBody로 생성시, 코드가 중복됨(예. 위의 member)
만약 회원이 존재하지 않을 때, 404상태 코드를 응답해야 하는 기능이 많다면,
에러응답을 위해 ResponseEntity를 생성하는 코드가 여러곳에 중복됨.
이는 @ExceptionHandler을 통해 에러응답을 처리하게 하면 됨.
@GetMapping("/api/members/{id}")
public Member member(@PathVariable Long id) {
Member member= memberDao.selectById(id);
if(member==null) {
throw new MemberNotFoundException();
}
return member;
}
@ExceptionHandler(MemberNotFoundException.class)
public ResponseEntity<ErrorResponse> handleNoData() {
return ResponseEntity.status(HttpStatus.NOT_FOUND).
body(new ErrorResponse("no member");
}
member()메서드는 Member 자체를 리턴.
회원데이터가 존재시, Member객체를 리턴하므로, JSON으로 변환한 결과를 응답함.
회원데이터가 존재하지 않으면, MemberNotFoundException을 발생시킴.
그러면 @ExceptionHandler을 쓴 handleNoData()가 에러를 처리.
이는 404상태코드(NOT)FOUND)와 ErrorResponse객체를 몸체로 갖는 ResponseEntity를 리턴.
즉, MemberNotFoundException이 발생시, 상태코드가 404이고 몸체가 JSON인 응답을 전송.
또는 @RestControllerAdvice를 이용해 에러처리코드를 별도클래스로 분리하기도 가능.
이는 @ControllerAdvice와 동일하며, 응답을 JSON이나 xml형식으로 변환한다
이를 통해, 에러처리코드가 하나로 모여 효과적 에러응답이 가능하다.
package controller;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import spring.MemberNotFoundException;
@RestControllerAdvice("controller")
public class ApiExceptionAdvice {
@ExceptionHandler(MemberNotFoundException.class)
public ResponseEntity<ErrorResponse> handleNoData() {
return ResponseEntity.status(HttpStatus.NOT_FOUND).
body(new ErrorResponse("no matter"));
}
}
////valid에러 결과
@Valid 에노테이션을 붙인 커맨드객체가 값 검증에 실패시, 400상태코드를 응답한다.
예를 들어,
@PostMapping("/api/members")
public ResponseEntity<Object> newMember(@RequestBody @Valid RegisterRequest regReq,
Errors errors, HttpServletResponse response) {
try {
Long newMemberId=registerService.regist(regReq);
URI uri= URI.create("/api/members/"+newMemberId);
return ResponseEntity.created(uri).build();
} catch(DuplicateMemberException dupEx) {
return ResponseEntity.status(HttpStatus.CONFLICT).build();
}
}
는 HttpServletResponse를 이용해 상태코드를 응답했을때와 마찬가지로, HTML응답을 전송한다.
이것대신 JSON형식 응답을 제공하려 하면, Errors 타입 파라미터를 추가하면 됨.
@PostMapping("/api/members")
public ResponseEntity<Object> newMember(@RequestBody @Valid
RegisterRequest regReq, Errors errors) {
if(errors.hasErrors()) {
String errorCodes= errors.getAllErrors() //List<ObjectError>
.stream().map(error->error.getCodes()[0]) //error는 ObjectError
.collect(Collectors.joining("."));
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
.body(new ErrorResponse("errorCodes = "+errorCodes));
}
try{..}
catch{..}
hasErrors()를 통해 검증에러가 존재하는지 확인 & 존재하면 getAllErrors()로 모든 에러정보를 구하고(ErrorObject타입의 객체 목록), 각 에러의 코드값을 연결한 문자열을 생성해 errorCodes 문자열에 할당.
@RequestBody를 붙인 경우, @Valid를 붙인 객체의 검증에 실패시 Errors타입 파라미터가 존재하지 않으면
MethoArgumentNotValidException이 발생. 즉, @ExcepionHandler애노테이션 이용해서 검증실패시, 에러 응답을 생성해도 됨.
@RestControllerAdvice("controller")
public class ApiExceptionAdvice {
@ExceptionHandler(MemberNotFoundException.class)
public ResponseEntity<ErrorResponse> handleBindException
(MethodArgumentNotValidException ex) {
String errorCodes= ex.getBindingResult().getAllErrors().stream()
.map(error->error.getCodes()[0]).collect(Collectors.joining("."));
return ResponseEntity.status(HttpStatus.BAD_REQUEST).
body(new ErrorResponse("errorCodes = " + errorCodes));
}
}
'동방프로젝트' 카테고리의 다른 글
이더리움 2 (0) | 2021.07.08 |
---|---|
이더리움 1 (2) | 2021.07.07 |
JSON 예시 (0) | 2021.02.09 |
ControllerAdvice (2) | 2021.02.08 |
jdbcTemplate 중복코드 제거 (2) | 2021.02.08 |