작심십삼일러의 스프링 시작하기(13)

서은경·2022년 9월 10일
0

Spring

목록 보기
23/43

커맨드 객체 Date 타입 프로퍼티 변환 처리 : @DateTimeFormat

회원 가입 일시를 기준으로 회원을 검색한다고 가정했을 때 입력한 문자열을 LocalDateTime 타입으로 변환해야 하는데, 스프링은 Long이나 int와 같은 기본 데이터 타입으로의 변환은 기본적으로 처리해주지만 LocalDateTime 타입으로의 변환은 추가 설정이 필요하다.

package controller;

import org.springframework.format.annotation.DateTimeFormat;

import java.time.LocalDateTime;

public class ListCommand {

    @DateTimeFormat(pattern = "yyyyMMddHH")
    private LocalDateTime from;
    @DateTimeFormat(pattern = "yyyyMMddHH")
    private LocalDateTime to;

    public LocalDateTime getFrom() {
        return from;
    }

    public void setFrom(LocalDateTime from) {
        this.from = from;
    }

    public LocalDateTime getTo() {
        return to;
    }

    public void setTo(LocalDateTime to) {
        this.to = to;
    }
}

이렇게 커맨드 객체에 @DateTimeFormat 어노테이션이 적용되어 있으면 지정한 형식을 이용해 문자열을 LocalDateTime 타입으로 변환해준다.

컨트롤러 클래스는 별도 설정없이 해당 클래스파일만 사용하면 끝이다.

package controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.Errors;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import spring.Member;
import spring.MemberDao;

import java.util.List;

@Controller
public class MemberListController {

    private MemberDao memberDao;

    public void setMemberDao(MemberDao memberDao) {
        this.memberDao = memberDao;
    }

    @RequestMapping("/members")
    public String list(@ModelAttribute("cmd") ListCommand listCommand, Errors errors, Model model) {
        if (errors.hasErrors()) {
            return "member/memberList";
        }
        if (listCommand.getFrom() != null && listCommand.getTo() != null) {
            List<Member> members = memberDao.selectByRegdate(listCommand.getFrom(), listCommand.getTo());
            model.addAttribute("members", members);
        }
        return "member/memberList";
    }
}

변환 처리에 대한 이해

@DateTimeFormat 어노테이션을 사용하면 지정한 형식의 문자열을 LocalDateTime 타입으로 변환해준다. 그런데 도대체 누가 ⁉️ 문자열을 LocalDateTime 타입으로 변환해주는 것일까?

답은 바로 💡 WebDataBinder에 있다.
(로컬 범위 Validator 할 때 나왔었음 ! initBinder 설정할 때 WebDataBinder 를 파라미터로 받아 컨트롤러 범위에 적용할 Validator를 설정한다.)

스프링 MVC는 요청 매핑 어노테이션 적용 메서드와 DispatcherServlet 사이를 연결하기 위해 RequestMappingHandlerAdapter 객체를 사용한다. 이 핸들러 어댑터 객체는 요청 파라미터와 커맨드 객체 사이의 변환 처리를 위해 WebDataBinder를 이용한다.

WebDataBinder는 커맨드 객체를 생성하고 커맨드 객체의 프로퍼티와 같은 이름을 갖는 요청 파라미터를 이용해 프로퍼티 값을 생성한다.

WebDataBinder는 직접 타입을 변환하지 않고 위 그림처럼 ConversionService에 그 역할을 위임한다. 스프링 MVC를 위한 설정인 @EnableWebMvc 어노테이션을 사용하면 DefaultFormattingConversionService를 ConversionService를 사용한다.

DefaultFormattingConversionService는 int, long과 같은 기본 데이터 타입뿐만 아니라 @DateTimeFormat 어노테이션을 사용한 시간 관련 타입 변환 기능을 제공한다.

@PathVariable

정보 조회를 할 때 url에 키값을 넘겨 조회하는 경우를 많이 봤을 것이다! 이렇게 경로의 일부가 고정되어 있지 않고 달라질 때 사용하는 것이 @PathVariable 어노테이션이다. get방식에 파라미터와 같은 것

 	@GetMapping("/members/{id}")
    public String detail(@PathVariable("id") Long memId, Model model) {
        Member member = memberDao.selectById(memId);
        if (member == null) {
            throw new MemberNotFoundException();
        }
        model.addAttribute("member", member);
        return "member/memberDetail";
    }

이렇게 중괄호로 둘러쌓인 경로변수를 @PathVariable 파라미터에 전달하고 이걸 memId에 전달한다. 타입값은 알맞게 변환되므로 걱정 노노

컨트롤러 익셉션 처리하기

컨트롤러에서 발생한 익셉션을 직접 처리하려면 어떻게 해야할까 ⁉️ 답은 바로 @ExceptionHandler ‼️
같은 컨트롤러에 @ExceptionHandler 어노테이션을 적용한 메서드가 존재하면 그 메서드가 익셉션을 처리한다.

    @ExceptionHandler(TypeMismatchException.class)
    public String handleTypeMismatchException() {
        return "member/invalidId";
    }

    @ExceptionHandler(MemberNotFoundException.class)
    public String handleNotFoundException() {
        return "member/noMember";
    }

TypeMismatchException이 발생하면 에러 응답 대신 handleTypeMismatchException() 메서드를 실행한다.

@ControllerAdvice를 이용한 공통 익셉션 처리

컨트롤러 클래스에 @ExceptionHandler 어노테이션을 적용하면 해당 컨트롤러에서 발생한 익셉션만을 처리한다. 여러 컨트롤러에서 동일하게 처리할 익셉션이 발생한다면 @ControllerAdvice 어노테이션을 이용해서 중복을 없앨 수 있다!

(이 때 AOP를 다시 한번 보고 또 공부해보면 아주 좋겠다!)

import org.springframework.web.bind.annotaion.ControllerAdvice;
import org.springframework.web.bind.annotaion.ExceptionHandler;

@ControllerAdvice("spring")
public class CommonExceptionHandler {
	@ExceptionHandler(RuntimeException.class)
    public String handleRuntimeException() {
    	return "error/commonException";
    }
}

@ControllerAdvice 어노테이션이 적용된 클래스는 지정한 범위의 컨트롤러에 공통으로 사용될 설정을 지정할 수 있다. 위 코드는 spring 캐피지와 그 하위 패키지에 속한 컨트롤러 클래스를 위한 공통 기능이 정의된 것이다.
‼️ @ControllerAdvice 적용 클래스가 동작하려면 해당 클래스를 스프링에 빈으로 등록해야 한다.

@ExceptionHandler 적용 메서드의 우선 순위

우선순위를 따지자면
@ControllerAdvice 클래스에 있는 @ExceptionHandler 메서드 < 컨트롤러 클래스에 있는 @ExceptionHandler 메서드
즉 컨트롤러의 메서드를 실행하는 과정에서 익셉션이 발생하면 다음의 순서로 익셉션을 처리할 @ExceptionHandler 메서드를 찾는다.

  1. 같은 컨트롤러에 위치한 @ExceptionHandler 메서드 중 해당 익셉션을 처리할 수 있는 메서드 검색
  2. 같은 클래스에 위치한 메서드가 익셉션을 처리할 수 없을 경우 @ControllerAdvice 클래스에 위치한 @ExceptionHandler 메서드를 검색

@ControllerAdvice 어노테이션은 공통 설정을 적용할 컨트롤러 대상을 지정하기 위해 다음과 같은 속성을 제공한다.

  • value/basePackages : String[] :
    공통 설정을 적용할 컨트롤러가 속하는 기준 패키지
  • annotations : Class<? extends Annotation>[] :
    특정 어노테이션이 적용된 컨트롤러 대상
  • assignableTypes : Class<?>[] :
    특정 타입 또는 그 하위 타입인 컨트롤러 대상

@ExceptionHandler 어노테이션 적용 메서드의 파라미터와 리턴 타입

@ExceptionHandler 어노테이션을 붙인 메서드는 다음 파라미터를 가질 수 있다.

  • HttpServletRequest, HttpServletResponse, HttpSession
  • Model
  • Exception

리턴 가능한 타입은 다음과 같다.

  • ModelAndView
  • String(뷰 이름)
  • (@ResponseBody 어노테이션을 붙인 경우) 임의 객체
  • ResponseEntity

🙋‍♀️사담,, 책이 점점 끝이 보인다 공부는 해도해도 끝이 없지만 여러번 정독해서 내걸로 만들어야지! 보는 사람이 있을지 모르겠지만 메리 추석 보내세요 ㅎ0ㅎ

0개의 댓글