HTTP Method 와 Exception Handling
이번에는 HTTP 메서드와 전역 예외처리를 예제를 통해 공부해보자
URL이 같아도 메서드 요청 방식에따라 각각 처리할 수 있다
ex) GET("users"), POST("users") 각각 달리 동작한다
@Data
@AllArgsConstructor
public class User {
private Integer id;
private String name;
private Date joinDate;
}
@Service
public class UserDaoService {
private static List<User> users = new ArrayList<>();
private static int usersCount =3;
static {
users.add(new User(1, "Kevin", new Date()));
users.add(new User(2, "Alice", new Date()));
users.add(new User(3, "Elena", new Date()));
}
public List<User> findAll(){
return users;
}
public User save(User user){
if(user.getId()==null){
user.setId(++usersCount);
}
users.add(user);
return user;
}
public User findOne(int id) {
for(User user : users){
if(user.getId()==id){
return user;
}
}
return null;
}
}
@RestController
@RequiredArgsConstructor
public class UserController {
private final UserDaoService service;
@GetMapping("/users")
public List<User> retrieveAllUser(){
return service.findAll();
}
@GetMapping("/users/{id}")
public User retrieveUser(@PathVariable int id){
return service.findOne(id);
}
}
간단한 예제이다. User 도메인을 생성해주었고, Service 클래스의 경우 회원을 생성하고 전체조회화고, 개별조회 하는 메서드를 만들어주었고, Controller에서 클라이언트의 요청을 받아 진행하는 순서로 구현을 해놓았다.(Repository의 경우 DB를 연결하지않았으므로 Servcie에서 같이 처리하였으며, 메모리에 저장하게끔 설정해놓았다)
아래 이미지는 GET 방식에 대한 결과이다
이제 POST 방식에 대해서 알아보자
@PostMapping("/users")
public void createUser(@RequestBody User user){
User saveUser = service.save(user);
}
위의 로직에서 알아야할점은 POST메서드와 PUT메서드의 경우 클라이언트로부터 FORM형식으로 받는 것이 아닌, JSON형태의 타입으로 받는 경우에는 @RequestBody를 사용해야한다.
포스트 맨을 통해 JSON 형식의 POST 타입의 요청을 보냈고, 상태코드도 200OK로 정상확인되었다.
전체 회원조회를 했을때 정상 추가되는것을 확인할 수 있다.
추가적으로 상태 코드를 구분하는 방법에 대해 알아보자
@PostMapping("/users")
public ResponseEntity<User> createUser(@RequestBody User user){
User saveUser = service.save(user);
URI location = ServletUriComponentsBuilder.fromCurrentRequest()
.path("/{id}")
.buildAndExpand(saveUser.getId())
.toUri();
return ResponseEntity.created(location).build();
}
ServletUriComponentsBuilder을 사용하였는데, 자세히 알아보자 ServletUriComponentsBuilder 클래스는 현재 요청의 URI를 가져올 수 있다.
fromCurrentRequest()를 사용하면 현재 요청된 Request 값을 가죠오고
path로 반환시킬 위치,
buildAndExpand에 새롭게 설정한 id값을 path에 지정시켜준다.
마지막으로 toUri()로 URI로 반환시켜준다.
이전의 상태코드는 200번이였는데, 현재 201번으로 상태코드를 변경한것을 확인할 수 있다.
가장 안좋은 설계는 모든 요청을 GET, POST로만 처리하고 상태코드를 200번으로 처리하는 것이다.
Exception Handling - 예외처리
이미지 처럼 없는 회원(100번)을 조회했는데 값이 없는것이 정상적으로 출력되는 것을 알 수있다. 그리고 상태코드도 200번OK로 확인이 된다. 현재 상황의 경우 DB의 값이 없는데 서버측에서는 로직상 정상 구현되었으니, 200 OK를 출력한 것인데, 클라이언트의 의도와는 다르므로 적절하지 않다. 그러므로 상태코드를 제어하여 예외처리를 해보자
@GetMapping("/users/{id}")
public User retrieveUser(@PathVariable int id){
User user = service.findOne(id);
if(user==null){
throw new UserNotFoundException(String.format("ID[%s]가 없습니다",id));
}
return user;
}
개별회원을 조회하는 요청을 처리하는 Controller의 메서드인데, 가변형 데이터의 id가 없으면 예외를 발생시키도록 코드를 구현하였다.
public class UserNotFoundException extends RuntimeException {
public UserNotFoundException(String message) {
super(message);
}
}
이제 의도했던 대로 에러가 발생된것을 확인하였는데, 현재 의도한내용은 클라이언트의 요청이 잘못되었으므로 500번 서버 에러가 아닌 400번대 클라이언트 에러로 변경을 할 것이다
@ResponseStatus(HttpStatus.NOT_FOUND)
public class UserNotFoundException extends RuntimeException {
public UserNotFoundException(String message) {
super(message);
}
}
@ResponseStauts로 응답되는 상태코드를 변경해주었다.
404 NotFound로 발생되는 것을 확인할 수 있다.
이제 일반화된 예외 클래스를 만들어보자
AOP(Aspect Oriented Programming) : 관점 지향 프로그래밍. 비즈니스 로직에서 관점별로 나누어보고, 그 기준으로 각각을 모듈화하는 프로그래밍 방식이다. 간단히 말하면, 애플리케이션 전체에 걸쳐 사용되는 기능을 모듈화하여 재사용 하는 것이다.
예외를 처리 해줄 자바 클래스를 생성한다
//예외처리를 위한 자바객체
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ExceptionResponse {
private Date timestamp; //예외가 발생한 시간
private String message; //예외 메시지
private String details; //예외 상세 정보
}
@RestController
@ControllerAdvice
public class CustomizedResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {
@ExceptionHandler(Exception.class)
public final ResponseEntity<Object> handleAllException(Exception ex, WebRequest request){
ExceptionResponse response = new ExceptionResponse(new Date(), ex.getMessage(), request.getDescription(false));
return new ResponseEntity(response, HttpStatus.INTERNAL_SERVER_ERROR);
}
@ExceptionHandler(UserNotFoundException.class)
public final ResponseEntity<Object> handleUserNotFoundException(Exception ex, WebRequest request){
ExceptionResponse response = new ExceptionResponse(new Date(), ex.getMessage(), request.getDescription(false));
return new ResponseEntity(response, HttpStatus.NOT_FOUND);
}
}
내가 정의한 CustomizedResponseEntityExceptionHandler 클래스를 만들어준다.
@ContrpllerAdvice는 전역에서 발생할 예외를 처리할수 있게 한다. 모든 컨트롤러가 실행될 때 이 클래스가 실행 될 것이다.
@ExceptionHandler 애노테이션은 @Controller와 @RestController이 적용된 Bean 내에서 발생하는예외를 처리하는 메소드 기능을 하게 해준다.(매개변수로 예외를 처리할 클래스를 지정해준다)
handleAllExceptions의 매개변수로 에러난 객체를 ex, 에러 리퀘스트를 request로 받아오도록 선언하였다.
그리고 각 에러에 대한 객체를 만들고 날짜, 에러난 객체, 요청에 대한 상세정보를 리턴하도록 구성하였다.
해당 GET 요청은 USERNOTFOUNDEXCEPTION에 해당되는 예외이므로 404 NOT FOUND가 발생하는 것을 확인할 수 있다.
HTTP 메서드 중 DELETE를 사용해보자
서비스
public User deleteById(int id){
Iterator<User> iterator = users.iterator();
while (iterator.hasNext()){
User user = iterator.next();
if(user.getId()==id){
iterator.remove();
return user;
}
}
return null;
}
컨트롤러
@DeleteMapping("/users/{id}")
public void deleteUser(@PathVariable int id) {
User user = service.deleteById(id);
if(user == null){
throw new UserNotFoundException("회원이 없어 삭제할 수 없습니다");
}
}
1번 이미지는 없는 회원일때 예외가 발생되는 것을 확인할 수 있고, 2번이미지에서는 기존 회원을 삭제할때 정상적으로 삭제되는것을 확인할 수 있다.
참고
https://www.inflearn.com/course/spring-boot-restful-web-services/dashboard
Spring Boot를 이용한 RESTful Web Services 개발 - 인프런 | 강의
이 강의는 Spring Boot를 이용해서 RESTful Web Services 애플리케이션을 개발하는 과정에 대해 학습하는 강의으로써, REST API 설계에 필요한 기본 지식에 대해 학습할 수 있습니다., - 강의 소개 | 인프런..
www.inflearn.com