일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- 맥
- Oracle
- 6월 공모주 청약 일정
- 7월 공모주 청약 일정
- 자바스크립트
- 주식
- java
- IPO
- Eclipse
- html
- Stock ipo
- php
- Stock
- 주식 청약
- 제이쿼리
- 공모주
- codeigniter
- 오라클
- 주식 청약 일정
- MYSQL
- 코드이그나이터
- 리눅스
- jquery
- linux
- 공모주 청약 일정
- 자바
- css
- SQL
- JavaScript
- 공모주 청약
- Today
- Total
개발자의 끄적끄적
[java/spring] RESTful 서비스 설계와 개발 - 메시지와 예외 처리 [펌] 본문
[java/spring] RESTful 서비스 설계와 개발 - 메시지와 예외 처리 [펌]
결과 메시지 설계
Response Message 구성
fieldtypeDescription
code | int |
결과 코드, 정상인 경우 200 |
status |
boolean |
정상, 성공인 경우 true |
timestamp |
Date |
시간 |
data |
Map | 처리 결과 데이터들 |
message |
String |
결과 메시지 |
error | Set | 에러 발생시 에러 내용 |
error : Set
fieldtypeDescription
code | int |
결과 코드, 정상인 경우 200 |
errorMessage | String |
상세 에러 메시지 |
referedUrl |
String |
요청 url |
HTTP 응답상태코드 (rfc2616 참고)
성공
200 : OK, 요청 정상 처리
201 : Created, 생성 요청 성공
202 : Accepted, 비동기 요청 성공
204 : No Content, 요청 정상 처리, 응답 데이터 없음.
실패
400 : Bad Request, 요청이 부적절 할 때, 유효성 검증 실패, 필수 값 누락 등.
401 : Unauthorized, 인증 실패, 로그인하지 않은 사용자 또는 권한 없는 사용자 처리
402 : Payment Required
403 : Forbidden, 인증 성공 그러나 자원에 대한 권한 없음. 삭제, 수정시 권한 없음.
404 : Not Found, 요청한 URI에 대한 리소스 없을 때 사용.
405 : Method Not Allowed, 사용 불가능한 Method를 이용한 경우.
406 : Not Acceptable, 요청된 리소스의 미디어 타입을 제공하지 못할 때 사용.
408 : Request Timeout
409 : Conflict, 리소스 상태에 위반되는 행위 시 사용.
413 : Payload Too Large
423 : Locked
428 : Precondition Required
429 : Too Many Requests
500 : 서버 에러
* 응답 상태 코드 중 사용할 만한 것만 선별한 것입니다.
참고 소스 (스프링 기반)
ResponseMessage.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
package eblo.common.domain;
import java.util.Date; import java.util.HashMap; import java.util.Map;
import org.springframework.http.HttpStatus;
import eblo.common.exception.AbstractEbloBaseException; import lombok.Getter;
@Getter public class ResponseMessage extends BaseObject {
private static final long serialVersionUID = 1L;
private static final String DEFAULT_KEY = "result"; private int code; private boolean status; private String message; private Date timestamp; private Map<String, Object> data; private ErrorMessage error;
public ResponseMessage() { this(HttpStatus.OK); }
public ResponseMessage(HttpStatus httpStatus) { this.data = new HashMap<>(); this.code = httpStatus.value(); this.status = (httpStatus.isError())? false:true; this.message = httpStatus.getReasonPhrase(); this.timestamp = new Date(); }
public ResponseMessage(AbstractEbloBaseException ex, String referedUrl) { HttpStatus httpStatus = ex.getHttpStatus(); this.data = new HashMap<>(); this.code = httpStatus.value(); this.status = (httpStatus.isError())? false:true; this.message = httpStatus.getReasonPhrase(); this.error = new ErrorMessage(code, ex.getMessage(), referedUrl); this.timestamp = new Date(); }
public ResponseMessage(HttpStatus status, Object result) { this(status); this.data.put(DEFAULT_KEY, result); }
public void add(String key, Object result) { this.data.put(key, result); }
public void remove(String key) { this.data.remove(key); } }
|
cs |
ErrorMessage.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
@Getter @Setter public class ErrorMessage extends BaseObject{
private static final long serialVersionUID = 1L;
private int code; private String errorMessage; private String referedUrl; public ErrorMessage(int code, String errorMessage, String referedUrl) { super(); this.code = code; this.errorMessage = errorMessage; this.referedUrl = referedUrl; } }
|
cs |
Test
1 2 3 4 5 6 7 8 9 10 11 |
@GetMapping("/api/users") public ResponseMessage users(User user) { List<User> users = new ArrayList<>(); for(int i=0; i<10;i++) { users.add(new User("user_"+i, "사용자_"+i, "facebook")); }
ResponseMessage message = new ResponseMessage(HttpStatus.OK); message.add("users", users); return message; } |
cs |
결과
{
- "code":200,
- "status":true,
- "message":"OK",
- "timestamp":1550117593498,
- "data":{
- "users":[
- {
- "userId":"user_0",
- "userPwd":null,
- "name":"사용자_0",
- "authType":"facebook"
- {
- "userId":"user_1",
- "userPwd":null,
- "name":"사용자_1",
- "authType":"facebook"
- {
- "userId":"user_2",
- "userPwd":null,
- "name":"사용자_2",
- "authType":"facebook"
- {
- "userId":"user_3",
- "userPwd":null,
- "name":"사용자_3",
- "authType":"facebook"
- {
- "userId":"user_4",
- "userPwd":null,
- "name":"사용자_4",
- "authType":"facebook"
- {
- "userId":"user_5",
- "userPwd":null,
- "name":"사용자_5",
- "authType":"facebook"
- {
- "userId":"user_6",
- "userPwd":null,
- "name":"사용자_6",
- "authType":"facebook"
- {
- "userId":"user_7",
- "userPwd":null,
- "name":"사용자_7",
- "authType":"facebook"
- {
- "userId":"user_8",
- "userPwd":null,
- "name":"사용자_8",
- "authType":"facebook"
- {
- "userId":"user_9",
- "userPwd":null,
- "name":"사용자_9",
- "authType":"facebook"
- {
- "users":[
- "error":null
}
예외 생성(스프링 기반)
에러를 어떻게 처리할지를 먼저 결정하고 그에 따라 Exception을 설계합니다.
Exception 발생시 Respons status는 Exception에 정의한 HttpStatus를 활용할거며 에러 메시지와 HttpStatus의 value를 반환할 것입니다.
AbstractEbloBaseException.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
import org.springframework.http.HttpStatus;
public abstract class AbstractEbloBaseException extends RuntimeException {
private static final long serialVersionUID = 1L;
public AbstractEbloBaseException() { super(); }
public AbstractEbloBaseException(String msg) { super(msg); }
public AbstractEbloBaseException(Throwable e) { super(e); }
public AbstractEbloBaseException(String errorMessge, Throwable e) { super(errorMessge, e); }
public abstract HttpStatus getHttpStatus();
}
|
cs |
EbloNotFoundException.java
not found exception
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
import org.springframework.http.HttpStatus;
public class EbloNotFoundException extends AbstractEbloBaseException {
private static final long serialVersionUID = 1L;
public EbloNotFoundException() { super(); }
public EbloNotFoundException(Throwable e) { super(e); }
public EbloNotFoundException(String errorMessge) { super(errorMessge); }
public EbloNotFoundException(String errorMessge, Throwable e) { super(errorMessge, e); }
public HttpStatus getHttpStatus() { return HttpStatus.NOT_FOUND; } } |
cs |
EbloInvalidRequestException.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
import org.springframework.http.HttpStatus;
public class EbloInvalidRequestException extends AbstractEbloBaseException {
private static final long serialVersionUID = 1L;
public EbloInvalidRequestException() { super(); }
public EbloInvalidRequestException(Throwable e) { super(e); }
public EbloInvalidRequestException(String errorMessge) { super(errorMessge); }
public EbloInvalidRequestException(String errorMessge, Throwable e) { super(errorMessge, e); }
public HttpStatus getHttpStatus() { return HttpStatus.BAD_REQUEST; } } |
cs |
이외 EbloForbiddenException, EbloUnauthorizedException, EbloUnknownException, EbloSystemException 등 공통 Exception 생성.
에러 코드는 스프링의 HttpStatus 이용합니다.
Exception 처리
스프링에서는 Controller based Exception Handling과 Global Exception Handling 두가지 형태로 처리 할 수 있습니다.
참고
Exception Handling in Spring MVC : https://spring.io/blog/2013/11/01/exception-handling-in-spring-mvc
저는 서비스에 따라 각각 처리할 때 공통 컨트롤러를 만들고 그것을 상속 받아서 처리하게 만들었습니다.
AbstractBaseRestController. java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
@Slf4j @RestController public class AbstractBaseRestController extends AbstractBaseController{
@ExceptionHandler(AbstractEbloBaseException.class) public ResponseMessage abstractBaseException(HttpServletRequest req, HttpServletResponse res, final AbstractEbloBaseException exception){ log.error("AbstractEbloBaseException : "+exception.getMessage()); res.setStatus(exception.getHttpStatus().value()); return new ResponseMessage(exception, req.getRequestURL().toString()); }
@ExceptionHandler(IllegalArgumentException.class) @ResponseStatus(value=HttpStatus.BAD_REQUEST) public ResponseMessage illegalArgumentException(HttpServletRequest req, final IllegalArgumentException exception){ log.error("IllegalArgumentException : "+exception.getMessage()); return new ResponseMessage(new EbloInvalidRequestException(exception.getMessage(), exception), req.getRequestURL().toString()); }
@ExceptionHandler(Exception.class) @ResponseStatus(value=HttpStatus.INTERNAL_SERVER_ERROR, reason="Internal server error") public ResponseMessage exception(HttpServletRequest req, final Exception exception){ log.error("Exception : "+exception.getMessage()); return new ResponseMessage(new EbloUnknownException(exception.getMessage(), exception), req.getRequestURL().toString()); }
@ExceptionHandler(BindException.class) @ResponseStatus(value=HttpStatus.BAD_REQUEST) public ResponseMessage bindingErrors(final HttpServletRequest req, final BindException exception) { log.error("BindException : "+exception.getMessage()); List<FormError> formErrors = bindingResultHandler.getErrors(exception.getBindingResult()); StringBuilder msg = new StringBuilder(); for(FormError err : formErrors) { msg.append(String.format(bindingErrorMsgFormat, err.getInputName(), err.getMessage())); } return new ResponseMessage(new BindingException(exception.getMessage(), exception), req.getRequestURL().toString()); }
} |
cs |
Rest 서비스에 대한 컨트롤러입니다.
각 서비스, 컨트롤러에서 예외를 던지면 @ExceptionHandler를 통해 예외를 자동 처리 해주게 됩니다.
예외에 따라 Response status를 결정하고 에러 코드와 메시지를 생성합니다.
이렇게 예외를 처리하게 되면 불필요한 코드를 다 제거 할 수 있게 되고 소스의 가독성 역시 엄청나게 향샹 됩니다.
* 이외 일반 컨트롤러에서 에러 발생시 에러 페이지로 연결하는 공통 컨트롤러를 만들어서 사용하게 되면 코딩이 한결 수월해 집니다.
컨트롤러 예제
UserAuthRestController.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
@RestController @Slf4j public class UserAuthRestController extends AbstractBaseRestController{
@GetMapping("/api/users") public ResponseMessage users(User user) { Assert.notNull(user.getUserId(), "요청 사용자 id는 필수 입니다."); List<User> users = new ArrayList<>(); for(int i=0; i<10;i++) { users.add(new User("user_"+i, "사용자_"+i, "facebook")); }
ResponseMessage message = new ResponseMessage(HttpStatus.OK); message.add("users", users); return message; } } |
cs |
결과
{
- "code":400,
- "message":"Bad Request",
- "error":{
- "code":400,
- "errorMessage":"요청 사용자 id는 필수 입니다.",
- "referedUrl":"https://localhost:8080/api/users"
- "status":false
}
마무리
REST 서비스에서 사용하는 공통 MESSAGE와 EXCEPTION에 대한 예제를 만들어 보았습니다.
이중에 Exception은 매우 중요하다고 생각합니다. Rest 서비스가 아니더라도 exception 만큼은 잘 알고 코딩을 했으면 좋겠습니다.
고급 개발자 또는 잘하는 사람의 코딩을 보면 굉장히 간결하고 짧게 코딩이 되어 있는 것을 볼 수 있습니다.
코드를 간결하고 쉽게 만드는 비결 중 하나는 얼마만큼 exception을 다룰 수 있는가 하는 능력이라고 봅니다.
코드의 반은 try-catch이고 나머지 반은 조건문인 경우가 많습니다. 이런 부분만 해소하면 코드 품질이 엄청나게 향상될거라 생각합니다.
출처 : eblo.tistory.com/48
'개발 > java & jsp' 카테고리의 다른 글
[java] spring 환경에서 Ajax - queryString / html 방식 [펌] (0) | 2020.10.03 |
---|---|
[ibatis] 비교문 지원 태그 (0) | 2020.10.02 |
[java/spring] 이클립스 힙 메모리 상태 보기 (0) | 2020.09.27 |
[java] maven dependency 추가시 유용한 사이트 (방법) [펌] (0) | 2020.09.23 |
[Java] ScheduledExecutorService를 이용해서 스케줄러를 만들어보자 [펌] (0) | 2020.09.22 |