사랑해 마니마니

Spring Boot로 RESTful Service 만들기 본문

카테고리 없음

Spring Boot로 RESTful Service 만들기

분리불안증후군 2018. 10. 6. 09:35

 

Engin Yöyen 의 Building Microservices with Spring Boot 따라 해보기: 책 참 좋다. 

0. Spring Boot 프로젝트 만들어 보자

start.spring.io

  • grade project
  • java: 1.8
  • Spring boot: 2.0.5
  • group: com.example
  • artifact: http-programming
  • dependencies: Web, Lombok

Lombok 설치하기

  • IntelliJ plugin
  • IntelliJ IDEA > Preferences > Build, Execution, Deployment > Compile > Annotation Processes > Enable Annotation Processing

Git 설정하기

  • root에서 git init 실행

.gitignore 파일 만들기

  • gitignore.io 사이트에서
  • java, gradle, git, intellij를 항목으로 .gitignore 파일 생성하기
  • .gitignore 파일 만들고 git init

Project 실행하기

  • Terminal에서 gradle bootRun
  • Control + Shift + R

프로젝트 Packaging

  • gradle build
  • java -jar build/libs/java -jar build/libs/http-programming-0.0.1-SNAPSHOT.jar

@SpringBootApplication

  • HttpProgrammingApplication.java 파일을 ApplicationStarter.java로 rename
package com.example;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class ApplicationStarter {

   public static void main(String[] args) {
      SpringApplication.run(ApplicationStarter.class, args);
   }
}
package com.example.controller;
import org.springframework.web.bind.annotation.PathVariable; 
import org.springframework.web.bind.annotation.RequestMapping; 
import org.springframework.web.bind.annotation.RestController;

@RestController 
public class HelloController {
  @RequestMapping(value = "/hello/{name}") 
  String hello(@PathVariable String name) {
	return String.format("Hello %s", name);
  }
}

브라우져에서 http://localhost:8080/hello/min 로 접근하면 화면상에 min 출력됨

1. Http Request와 Http Response 메시지 to Spring Annotation

http://localhost:8080/compute/function/sumhttp://localhost:8080/compute/function/sum?limit=5http://localhost:8080/compute/function/max?limit=8http://localhost:8080/compute/function/min?limit=6

 

2. Controller

Postman을 설치한 후에 Chrome에서 주소창에 chrome://apps 을 실행하고, postman 실행

NOT_IMPLEMENTED

public static final HttpStatus NOT_IMPLEMENTED

501 Not Implemented

See Also:

HTTP/1.1: Semantics and Content, section 6.6.2

결과 - Status: 501 Not implemented가 출력됨

User model 추가 및 /user에 대한 Get Request 구현

package com.example.model;

import lombok.*;

@NoArgsConstructor
@AllArgsConstructor
@Getter
@Setter
@ToString
public class User {
    private Long id;
    private String username;
}
package com.example.controller;

import com.example.model.User;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

import java.util.Arrays;
import java.util.List;


@Controller
public class Users {

    @ResponseBody
    @GetMapping("/users")
    List<User> getAll() {
        User jane = new User(1L, "Jane");
        User john = new User(2L, "John");
        return Arrays.asList(jane, john);
    }

    @ResponseStatus(HttpStatus.NOT_IMPLEMENTED)
    @RequestMapping(value = "/users/jane", method = RequestMethod.GET)
    void get() {
    }

    @ResponseStatus(HttpStatus.NOT_IMPLEMENTED)
    @RequestMapping(value = "/users", method = RequestMethod.PUT)
    void put() {
    }

    @ResponseStatus(HttpStatus.NOT_IMPLEMENTED)
    @RequestMapping(value = "/users/jane", method = RequestMethod.POST)
    void post() {
    }

    @ResponseStatus(HttpStatus.NOT_IMPLEMENTED)
    @RequestMapping(value = "/users/jane", method = RequestMethod.DELETE)
    void delete() {
    }

}

Terminal에서 curl로 확인해 보면

~: $ curl -X GET http://localhost:8080/users -i

 

HTTP/1.1 200
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Sun, 07 Oct 2018 11:53:46 GMT

[{"id":1,"username":"Jane"},{"id":2,"username":"John"}]

@RestController 사용하기

@RestController 어노테이션은 Spring 4.0에 도입됨

@RestController = @Controller + @ResponseBody

3. Request Body 관련 기능 만들기

package com.example.controller;

import com.example.model.User;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

import java.util.Arrays;
import java.util.List;


@RestController
@RequestMapping("/users")
public class Users {

    @GetMapping
    List<User> getAll() {
        User jane = new User(1L, "Jane");
        User john = new User(2L, "John");
        return Arrays.asList(jane, john);
    }

    @ResponseStatus(HttpStatus.NOT_IMPLEMENTED)
    @GetMapping(value = "/jane")
    void get() {
    }

    @ResponseStatus(HttpStatus.NOT_IMPLEMENTED)
    @PutMapping
    void put() {
    }

    @PostMapping(value = "/jane")
    User post(@RequestBody User user) {
        return user;
    }

    @ResponseStatus(HttpStatus.NOT_IMPLEMENTED)
    @DeleteMapping("/jane")
    void delete() {
    }

}

Postman에서 Request를 

{
"id": 198555,
"username": "Jane"
}

보내면

Response가 Status : 200 OK로 해서 아래와 같이 Return 됨.

{
"id": 198555,
"username": "Jane"
}

여기서 { "_id_": 198555, "_username_": "Jane" }와 같이 잘못된 파라메터로 보내면

Response가 Status : 200 OK, { "id": null, "username": null } 로 리턴됨

Status : 200 OK가 아닌 에러가 출력되도록 하기 위하서는

application.properties

spring.jackson.deserialization.fail_on_unknown_properties=true

를 셋팅해야 한다

다시 { "_id_": 198555, "_username_": "Jane" } 로 Reqeust를 보내면  

{
"timestamp": "2018-10-07T12:17:27.746+0000",
"status": 400,
"error": "Bad Request",
"message": "JSON parse error: Unrecognized field \"-id\" (class com.example.model.User), not marked as ignorable; nested exception is com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field \"-id\" (class com.example.model.User), not marked as ignorable (2 known properties: \"id\", \"username\"])\n at [Source: (PushbackInputStream); line: 2, column: 16] (through reference chain: com.example.model.User[\"-id\"])",
"path": "/users/jane"
}

를 출력함.

4. URI Template

scheme:[//[user:password@]host[:port]][/]path[?query][#fragment]

GET /users/:username/repos?page=2&size=10

http://example.com/users/jane/repos?page=2&size=10

Path Variable 처리하기

http://example.com/users/jane/repos?page=2&size=10과 같은 request를 이제 처리해 보자

@PathVariable 어노테이션을 이용하여 path variable을 URI 매핑하는 데 이용해 보자

  • http://example.com/users/jane/repos?page=2&size=10에서 jane을 Path Variable로 받을 수 있도록
package com.example.controller;

import com.example.model.User;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;

import java.util.Arrays;
import java.util.List;


@RestController
@RequestMapping("/users")
public class Users {

    @GetMapping
    List<User> getAll() {
        User jane = new User(1L, "Jane");
        User john = new User(2L, "John");
        return Arrays.asList(jane, john);
    }

    @GetMapping("/{username}")
    User get(@PathVariable String username) {
        return new User(1L, username);
    }

    @ResponseStatus(HttpStatus.NOT_IMPLEMENTED)
    @PutMapping
    void put() {
    }

    @PostMapping(value = "/jane")
    User post(@RequestBody User user) {
        return user;
    }

    @ResponseStatus(HttpStatus.NOT_IMPLEMENTED)
    @DeleteMapping("/jane")
    void delete() {
    }

}
min: ~$ curl -X GET http://localhost:8080/users/anyone -i
HTTP/1.1 200
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Mon, 08 Oct 2018 15:49:06 GMT

{"id":1,"username":"anyone"}

Query String 처리하기

  • http://example.com/users/jane/repos?page=2&size=10에서 page, size 값을 Variable로 받을 수 있도록
package com.example.controller;

import com.example.model.User;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;

import java.util.Arrays;
import java.util.List;


@RestController
@RequestMapping("/users")
@Slf4j
public class Users {

    @GetMapping
    List<User> getAll(@RequestParam(value = "page", required = false) Integer page,
                      @RequestParam(value = "size", required = false) Integer size) {

        log.info(String.format("Requested all users with page %d and size %d ", page, size));

        User jane = new User(1L, "Jane");
        User john = new User(2L, "John");
        return Arrays.asList(jane, john);
    }

    @GetMapping("/{username}")
    User get(@PathVariable String username) {
        return new User(1L, username);
    }

    @ResponseStatus(HttpStatus.NOT_IMPLEMENTED)
    @PutMapping
    void put() {
    }

    @PostMapping(value = "/jane")
    User post(@RequestBody User user) {
        return user;
    }

    @ResponseStatus(HttpStatus.NOT_IMPLEMENTED)
    @DeleteMapping("/jane")
    void delete() {
    }

}

@RequestParam으로 "page"값과 "size" 변수를 각각 Integer pageInteger size값으로 받는다.

로그를 찍기 위해서 Lombok의 @Slf4j 어노테이션을 사용하고 log.info(...)로 로그 출력

Postman에서 GET 방식으로 http://localhost:8080/users?page=2&size=10을 던지면

Console(Log)에 INFO 17003 --- [nio-8080-exec-2] com.example.controller.Users : Requested all users with page 2 and size 10  이 출력됨

request = false 대신 Java 1.8 의 java.util.Optinal을 사용하면

List<User> getAll(@RequestParam(value = "page") Optional<Integer> page,
                  @RequestParam(value = "size") Optional<Integer> size) {

    log.info(String.format("Requested all users with page %d and size %d ", page.orElse(null), size.orElse(null)));

    User jane = new User(1L, "Jane");
    User john = new User(2L, "John");
    return Arrays.asList(jane, john);
}

5. HTTP Response 만들기

RequestEntity를 사용한다면

  • 도메인 객체를 return하지 않고
  • 좀더 flexisble한 프로그램을 원하거나
  • 다른 형태의 response body를 원한다면
  • 또는 error를 throw하기를 원한다면
package com.example.controller;

import com.example.model.User;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.Arrays;
import java.util.List;
import java.util.Optional;


@RestController
@RequestMapping("/users")
@Slf4j
public class Users {

    @GetMapping
    List<User> getAll(@RequestParam(value = "page") Optional<Integer> page,
                           @RequestParam(value = "size") Optional<Integer> size) {

        log.info(String.format("Requested all users with page %d and size %d ", page.orElse(null), size.orElse(null)));

        User jane = new User(1L, "Jane");
        User john = new User(2L, "John");
        return Arrays.asList(jane, john);
    }

    @GetMapping("/{username}")
    User get(@PathVariable String username) {
        return new User(1L, username);
    }

    @PutMapping
    ResponseEntity<?> put(@RequestBody User user) {
        if (user.getUsername() != null && user.getUsername().length() < 5) {
            return new ResponseEntity(HttpStatus.UNPROCESSABLE_ENTITY);
        } 
        
        //create user first than
        return new ResponseEntity(user, HttpStatus.CREATED); 
    }

    @PostMapping(value = "/jane")
    User post(@RequestBody User user) {
        return user;
    }

    @ResponseStatus(HttpStatus.NOT_IMPLEMENTED)
    @DeleteMapping("/jane")
    void delete() {
    }

}

Postman으로

  • PUT method
  • URI http://localhost:8080/users
  • Request Body { "id" : 198555, "username" : "Jane" }

요청하면 Status: 422 Unprocessable Entity (WebDAV) (RFC 4918) 에러 상태임

만약 Request Body에서 "username" : "Jane.hi"로 user.getUsername.length()가 5보다 크면 Status: 201 Created 상태가 출력됨

다른 형태의 Response Body

오류가 발생하면 Http Status code (422) 정보만으로는 부족하다. 그래서 error message 을 ResponseEntity에 실어 보내는 방법을 사용한다.

먼저 Error Message를 담을 모델을 만들자

package com.example.model;

import lombok.AllArgsConstructor;
import lombok.Getter;

@AllArgsConstructor
@Getter
public class ErrorMessage {
    private String message;
    private String error;
}
package com.example.controller;

import com.example.model.ErrorMessage;
import com.example.model.User;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.Arrays;
import java.util.List;
import java.util.Optional;


@RestController
@RequestMapping("/users")
@Slf4j
public class Users {

    @GetMapping
    List<User> getAll(@RequestParam(value = "page") Optional<Integer> page,
                      @RequestParam(value = "size") Optional<Integer> size) {

        log.info(String.format("Requested all users with page %d and size %d ", page.orElse(null), size.orElse(null)));

        User jane = new User(1L, "Jane");
        User john = new User(2L, "John");
        return Arrays.asList(jane, john);
    }

    @GetMapping("/{username}")
    User get(@PathVariable String username) {
        return new User(1L, username);
    }

    @PutMapping
    ResponseEntity<?> put(@RequestBody User user) {
        if (user.getUsername() != null && user.getUsername().length() < 5) {
            ErrorMessage error = new ErrorMessage("Validation Failed", "User name is less than 5 character");
            return new ResponseEntity(error, HttpStatus.UNPROCESSABLE_ENTITY);
        }

        //create user first than
        return new ResponseEntity(user, HttpStatus.CREATED);
    }

    @PostMapping(value = "/jane")
    User post(@RequestBody User user) {
        return user;
    }

    @ResponseStatus(HttpStatus.NOT_IMPLEMENTED)
    @DeleteMapping("/jane")
    void delete() {
    }

}

  앞에서와 같이 Postman으로

  • PUT method
  • URI http://localhost:8080/users
  • Request Body { "id" : 198555, "username" : "Jane" }

요청하면 Status: 422 Unprocessable Entity (WebDAV) (RFC 4918) 에러 상태와 함께 response body에 

{
    "message": "Valication Failed",
    "error": "User name is less than 5 character"
}

Response Header에 정보 보내기

HTTP status와 body와 함께 response header도 ResponseEntity에 실어 보낼수 있음.

(e.g. Rate-Limiting 정보)

package com.example.controller;

import com.example.model.ErrorMessage;
import com.example.model.User;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.Arrays;
import java.util.List;
import java.util.Optional;


@RestController
@RequestMapping("/users")
@Slf4j
public class Users {

    @GetMapping
    List<User> getAll(@RequestParam(value = "page") Optional<Integer> page,
                      @RequestParam(value = "size") Optional<Integer> size) {

        log.info(String.format("Requested all users with page %d and size %d ", page.orElse(null), size.orElse(null)));

        User jane = new User(1L, "Jane");
        User john = new User(2L, "John");
        return Arrays.asList(jane, john);
    }

    @GetMapping("/{username}")
    ResponseEntity<?> get(@PathVariable String username) {
        User user = new User(1L, username);
        HttpHeaders responseHeaders = new HttpHeaders();
        responseHeaders.set("X-RateLimit-Limit", "1000");
        responseHeaders.set("X-RateLimit-Remaining", "500");
        responseHeaders.set("X-RateLimit-Reset", "1457020923");
        return new ResponseEntity(user, responseHeaders, HttpStatus.OK);
    }

    @PutMapping
    ResponseEntity<?> put(@RequestBody User user) {
        if (user.getUsername() != null && user.getUsername().length() < 5) {
            ErrorMessage error = new ErrorMessage("Validation Failed", "User name is less than 5 character");
            return new ResponseEntity(error, HttpStatus.UNPROCESSABLE_ENTITY);
        }

        //create user first than
        return new ResponseEntity(user, HttpStatus.CREATED);
    }

    @PostMapping(value = "/jane")
    User post(@RequestBody User user) {
        return user;
    }

    @ResponseStatus(HttpStatus.NOT_IMPLEMENTED)
    @DeleteMapping("/jane")
    void delete() {
    }

}

Postman으로 GET, http://localhost:8080/users/jane을 보내면

  • content-type →application/json;charset=UTF-8
  • date →Tue, 09 Oct 2018 00:36:31 GMT
  • transfer-encoding →chunked
  • x-ratelimit-limit →1000
  • x-ratelimit-remaining →500
  • x-ratelimit-reset →1457020923
  • ResponseEntity

    public ResponseEntity(@Nullable
                          T body,
                          @Nullable
                          MultiValueMap<java.lang.String,java.lang.String> headers,
                          HttpStatus status)
    Create a new HttpEntity with the given body, headers, and status code.
    Parameters:
    body - the entity body
    headers - the entity headers
    status - the status code

6. Request Header 사용

Corelation ID등을 HTTP Request등을 Header에 실어 보낼 때 사용 

  • mendetory 나 optional로 header를 받을 것인지를 선택할 수 있음
  • mendetory로 설정했는 데 HTTP Request에 필요로 하는 값이 넘어 오지 않을 경우  status code 400(Bad Request) return
package com.example.controller;

import com.example.model.ErrorMessage;
import com.example.model.User;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.*;

import java.util.Arrays;
import java.util.List;
import java.util.Optional;


@RestController
@RequestMapping("/users")
@Slf4j
public class Users {

    @GetMapping
    List<User> getAll(@RequestParam(value = "page") Optional<Integer> page,
                      @RequestParam(value = "size") Optional<Integer> size) {

        log.info(String.format("Requested all users with page %d and size %d ", page.orElse(null), size.orElse(null)));

        User jane = new User(1L, "Jane");
        User john = new User(2L, "John");
        return Arrays.asList(jane, john);
    }

    @GetMapping("/{username}")
    ResponseEntity<?> get(@PathVariable String username) {
        User user = new User(1L, username);
        HttpHeaders responseHeaders = new HttpHeaders();
        responseHeaders.set("X-RateLimit-Limit", "1000");
        responseHeaders.set("X-RateLimit-Remaining", "500");
        responseHeaders.set("X-RateLimit-Reset", "1457020923");
        return new ResponseEntity(user, responseHeaders, HttpStatus.OK);
    }

    @PutMapping
    ResponseEntity<?> put(@RequestBody User user) {
        if (user.getUsername() != null && user.getUsername().length() < 5) {
            ErrorMessage error = new ErrorMessage("Validation Failed", "User name is less than 5 character");
            return new ResponseEntity(error, HttpStatus.UNPROCESSABLE_ENTITY);
        }

        //create user first than
        return new ResponseEntity(user, HttpStatus.CREATED);
    }

    @PostMapping(value = "/jane")
    User post(@RequestBody User user) {
        return user;
    }

    @ResponseStatus(HttpStatus.NO_CONTENT)
    @DeleteMapping("/jane")
    void delete(@PathVariable String username,
                @RequestHeader MultiValueMap<String, String> headers) {
        headers.forEach((k, v) -> log.info(String.format("%s : %s ", k, v)));
        //here is where the user will be deleted
    }

}

curl -i -X DELETE http://localhost:8080/users/jane을 보내면

HTTP/1.1 500
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Tue, 09 Oct 2018 00:51:18 GMT
Connection: close

{
  "timestamp":"2018-10-09T00:51:18.401+0000",
  "status":500,"error":"Internal Server Error",
  "message":"Missing URI template variable 'username' for method parameter of type String",
  "path":"/users/jane"
}

? Engin yoyen 책에서는 400 Bad Request를 return 한다고 했는 데 해보니 Status code가 500으로 return된다. 

7. Exception 처리하기

앞에서 validaion Exception이 발생했을 때 Status code 422 Unprocessable Entity를 return하도록 처리했는 데 .. 좀더 개선해 보자

Spring에서는 Controller에서 Exception이 발생했을 때 2가지 방식으로 처리함

  1. ExceptionHandler: exception이 발생(throw)했을 때 항상 call이 됨
  2. @ControllerAdvice: @RequestMapping에 대한 exception 처리
package com.example.exception;

public class ValidationException extends Exception {
    public ValidationException(String message) {
        super(message);
    }
}
package com.example.controller;

import com.example.exception.ValidationException;
import com.example.model.ErrorMessage;
import com.example.model.User;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.*;

import java.util.Arrays;
import java.util.List;
import java.util.Optional;


@RestController
@RequestMapping("/users")
@Slf4j
public class Users {

    @GetMapping
    List<User> getAll(@RequestParam(value = "page") Optional<Integer> page,
                      @RequestParam(value = "size") Optional<Integer> size) {

        log.info(String.format("Requested all users with page %d and size %d ", page.orElse(null), size.orElse(null)));

        User jane = new User(1L, "Jane");
        User john = new User(2L, "John");
        return Arrays.asList(jane, john);
    }

    @GetMapping("/{username}")
    ResponseEntity<?> get(@PathVariable String username) {
        User user = new User(1L, username);
        HttpHeaders responseHeaders = new HttpHeaders();
        responseHeaders.set("X-RateLimit-Limit", "1000");
        responseHeaders.set("X-RateLimit-Remaining", "500");
        responseHeaders.set("X-RateLimit-Reset", "1457020923");
        return new ResponseEntity(user, responseHeaders, HttpStatus.OK);
    }

    @PutMapping
    ResponseEntity<?> put(@RequestBody User user) throws Exception{
        if (user.getUsername() != null && user.getUsername().length() < 5) {
            //ErrorMessage error = new ErrorMessage("Validation Failed", "User name is less than 5 character");
            //return new ResponseEntity(error, HttpStatus.UNPROCESSABLE_ENTITY);
            throw new ValidationException("User name is less than 5 character");
        }

        //create user first than
        return new ResponseEntity(user, HttpStatus.CREATED);
    }

    @PostMapping(value = "/jane")
    User post(@RequestBody User user) {
        return user;
    }

    @ResponseStatus(HttpStatus.NO_CONTENT)
    @DeleteMapping("/jane")
    void delete(@PathVariable String username,
                @RequestHeader MultiValueMap<String, String> headers) {
        headers.forEach((k, v) -> log.info(String.format("%s : %s ", k, v)));
        //here is where the user will be deleted
    }

}

Postman으로 PUT, http://localhost:8080/users를 던지면

connection →close
content-type →application/json;charset=UTF-8
date →Tue, 09 Oct 2018 01:13:56 GMT
transfer-encoding →chunked

Status: 500 Internal Server Error

{
    "timestamp": "2018-10-09T01:13:56.258+0000",
    "status": 500,
    "error": "Internal Server Error",
    "message": "User name is less than 5 character",
    "path": "/users"
}
2018-10-09 10:32:58.252 ERROR 19394 --- [nio-8080-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is com.example.exception.ValidationException: User name is less than 5 character] with root cause

com.example.exception.ValidationException: User name is less than 5 character
	at com.example.controller.Users.put(Users.java:49) ~[classes/:na]
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_121]
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_121]
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_121]
	at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_121]
	at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:209) ~[spring-web-5.0.9.RELEASE.jar:5.0.9.RELEASE]
	at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:136) ~[spring-web-5.0.9.RELEASE.jar:5.0.9.RELEASE]
    ....

Status Code가 우리가 원하는 코드가 아님. 그래서 재대로 동작하기 위해 @ControllerAdvice를 사용하여 Exception 처리 Class를 만들어 

package com.example.controller;

import com.example.exception.ValidationException;
import com.example.model.ErrorMessage;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

@ControllerAdvice
class ExceptionHandlerAdvice {
    @ExceptionHandler(value = ValidationException.class)
    public ResponseEntity validationExceptionHandler(Exception e) {
        ErrorMessage error = new ErrorMessage("Validation Failed", e.getMessage());
        return new ResponseEntity(error, HttpStatus.UNPROCESSABLE_ENTITY);
    }
}
content-type →application/json;charset=UTF-8
date →Tue, 09 Oct 2018 01:30:48 GMT
transfer-encoding →chunked

Status: 422 Unprocessable Entity (WebDAV) (RFC 4918)

{
    "message": "Validation Failed",
    "error": "User name is less than 5 character"
}

log에 Exception은 찍히지 않음(깨끗해 졌다.)

8. HTTP Caching (Comming soon)

Time-based Cache Headers

Conditional Cache Headers

 

 

 

 

 

 

 

Comments