@DateTimeFormat
Spring에서 문자열 형태의 날짜/시간을 LocalDate, LocalDateTime, Date 등으로 변환할 때 사용하는 어노테이션
주로 Controller parameter, @ModelAttribute, Dto 등에 사용
GET /members?from=2025-07-01T00:00:00&to=2025-07-09T23:59:59
@GetMapping("/members")
public List<Member> getMembersByDateRange(
@RequestParam("from")
@DateTimeFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss") LocalDateTime from,
@RequestParam("to")
@DateTimeFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss") LocalDateTime to) {
return memberService.getMembersBetween(from, to);
}
** 타입 ISO 기본 포맷 예시
LocalDate | yyyy-MM-dd | 2025-07-09 |
LocalTime | HH:mm:ss | 14:30:00 |
LocalDateTime | yyyy-MM-dd'T'HH:mm:ss | 2025-07-09T14:30:00 |
Dto에서의 활용
public class SearchRequest {
@DateTimeFormat(pattern = "yyyy-MM-dd")
private LocalDate startDate;
@DateTimeFormat(pattern = "yyyy-MM-dd")
private LocalDate endDate;
// getters/setters
}
변환 에러처리
@RestControllerAdvice
public class GlobalExceptionHandler {
// 날짜 파라미터 형식이 잘못된 경우 처리
@ExceptionHandler({ MethodArgumentTypeMismatchException.class, BindException.class })
public ResponseEntity<ErrorResponse> handleDateTimeFormatException(Exception ex) {
return ResponseEntity
.badRequest()
.body(new ErrorResponse("INVALID_DATE_FORMAT", "날짜 형식이 잘못되었습니다. 예: yyyy-MM-dd 또는 yyyy-MM-dd'T'HH:mm:ss"));
}
// 기타 예외 처리
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handleGenericException(Exception ex) {
return ResponseEntity
.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(new ErrorResponse("INTERNAL_ERROR", "서버 오류가 발생했습니다."));
}
}
예외 클래스 발생 상황
MethodArgumentTypeMismatchException | @RequestParam 변환 실패 (LocalDate, LocalDateTime 등) |
BindException | @ModelAttribute 또는 폼 바인딩 실패 |
HttpMessageNotReadableException | @RequestBody 변환 실패 (JSON 날짜 오류 등) |
@PathVariable을 이용한 경로 변수 처리
spring MVC에서 URL 경로에 포함된 값을 메서드의 파라미터로 바인딩할 때 사용하는 어노테이션
REST API 설계에서 자주 사용되며, 리소스의 식별자(ID, 이름 등) 를 전달할 때 적합
예시
@GetMapping("/members/{id}")
public Member getMemberById(@PathVariable("id") Long id) {
return memberService.findById(id);
}
요청 예시
GET localhost:8080/members/123
위와 같이 members/ 뒤의 숫자를 통해 가변경로 처리 가능
Controller Exception 처리
Spring MVC에서 Exception을 처리하는 방식에는 크게 세 가지 방식 존재
범위 | 방식 | 특징 | 권장 여부 |
전역 처리 | @RestControllerAdvice / @ControllerAdvice | 모든 컨트롤러에서 공통 처리 | 표준방식 |
전역 저수준 | HandlerExceptionResolver | DispatcherServlet 수준 예외 가로채기 | 특수목적용 |
필터/서블릿 | Filter, HttpServlet | Spring 컨텍스트 외부에서 처리 | 보안/로깅 용도 |
@ExceptionHandler
@RestController
@RequestMapping("/members")
public class MemberController {
@GetMapping("/{id}")
public Member get(@PathVariable Long id) {
return memberService.findById(id); // 예외 가능성
}
@ExceptionHandler(MemberNotFoundException.class)
public ResponseEntity<ErrorResponse> handleNotFound(MemberNotFoundException ex) {
return ResponseEntity.status(HttpStatus.NOT_FOUND)
.body(new ErrorResponse("NOT_FOUND", ex.getMessage()));
}
}
개별 컨트롤러에 에러 처리 메서드 선언
@RestControllerAdvice, @ControllerAdvice
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(MemberNotFoundException.class)
public ResponseEntity<ErrorResponse> handleMemberNotFound(MemberNotFoundException ex) {
return ResponseEntity.status(HttpStatus.NOT_FOUND)
.body(new ErrorResponse("MEMBER_NOT_FOUND", ex.getMessage()));
}
@ExceptionHandler(MethodArgumentTypeMismatchException.class)
public ResponseEntity<ErrorResponse> handleTypeMismatch(MethodArgumentTypeMismatchException ex) {
return ResponseEntity.badRequest()
.body(new ErrorResponse("INVALID_PARAMETER", "요청 파라미터 형식이 잘못되었습니다."));
}
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handleGeneric(Exception ex) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(new ErrorResponse("INTERNAL_ERROR", "서버 내부 오류"));
}
}
위처럼 @RestControllerAdvice 어노테이션을 사용한 클래스에서
ExcptionHandler 메서드를 모아 전역으로 처리 가능
** RestControllerAdvice, ControllerAdvice 차이
RestController와 Controller의 차이와 같음
Response 의 형식에 대한 차이
항목 | @ControllerAdvice | @RestControllerAdvice |
응답 처리 방식 | View 이름 또는 Model 반환 | JSON, XML 등 객체 직렬화 반환 |
@ResponseBody 포함 여부 | ❌ 포함 안 됨 (수동 설정 필요) | ✅ 자동 포함 (@ResponseBody 내장) |
주 사용 대상 | JSP, Thymeleaf 기반의 웹 MVC | REST API, JSON 기반 서비스 |
반환 타입 | ModelAndView, String, Model 등 | POJO 객체 (ErrorResponse, Map 등) |
도입 버전 | Spring 3.2 이상 | Spring 4.3 이상 (Spring Boot 1.4+) |
쉽게 말해서 View를 반환하냐, json 응답 반환하냐 차이
대부분의 경우 이 방식 사용 권장
HandlerExceptionResolver
Spring MVC에서 제공하는 저수준 예외 처리 인터페이스
@ControllerAdvice나 @ExceptionHandler보다 먼저 실행되는 전역 예외 처리 수단
언제사용?
로깅 시스템 또는 알림 시스템과 연계 | 모든 예외를 공통 처리하거나 Slack/Email 전송 |
Spring이 처리하지 못하는 예외에 대한 대응 | DispatcherServlet 수준에서 직접 처리 |
View 기반 프로젝트에서 특정 예외 → 특정 페이지 리턴 | 예: 404.html, 500.jsp 등 매핑 처리 |
Exception 흐름 완전 차단 (기존 핸들러 무시) | 필터처럼 동작시킴 |
예시
@Component
public class CustomExceptionResolver implements HandlerExceptionResolver {
@Override
public ModelAndView resolveException(HttpServletRequest request,
HttpServletResponse response,
Object handler,
Exception ex) {
// 예외 로깅
System.err.println("예외 발생: " + ex.getClass().getSimpleName());
// 응답 상태 코드 설정
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
// REST API의 경우 직접 JSON 응답 작성 (비권장 방식)
// 또는 아래처럼 View를 지정
ModelAndView mav = new ModelAndView("error/globalError");
mav.addObject("errorMessage", ex.getMessage());
return mav;
}
}
+ Java config 등록
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {
resolvers.add(new CustomExceptionResolver());
}
}
비교
항목 | HandlerExceptionResolver | @RestControllerAdvice |
동작 시점 | DispatcherServlet 수준 | HandlerAdapter 이후 |
예외 흐름 차단 | 가능 (더 이상 예외 던지지 않음) | 예외는 전달되며 순서 중요 |
REST 응답 지원 | 비직관적 (직접 응답 작성 필요) | 자동 JSON 직렬화 |
사용 목적 | 로깅, 비표준 처리 | 일반적인 예외 처리 |
Filter, HttpServlet 방식
Spring MVC 바깥의 계층에서 예외를 잡는 저수준 방식
목적 | 설명 |
Spring MVC 진입 전 예외 처리 | 예: 인증 필터, 로깅 필터, WAS 레벨 예외 |
Spring이 잡지 못하는 500 에러 대응 | DispatcherServlet 밖에서 발생하는 예외 감지 |
커스텀 응답(JSON) 강제 처리 | 스프링이 처리하지 않는 응답 직접 구성 |
보안 필터 체인 / API Gateway 대응 | 인증, 권한 필터 등과 연계 |
예시
@Component
public class ExceptionHandlingFilter implements Filter {
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletResponse response = (HttpServletResponse) res;
try {
chain.doFilter(req, res);
} catch (Exception ex) {
// 예외 로깅
ex.printStackTrace();
// 커스텀 JSON 응답
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
response.setContentType("application/json");
response.getWriter().write("{\"error\": \"" + ex.getMessage() + "\"}");
}
}
}
- DispatcherServlet 이전 단계에서 예외 처리 가능
- JSON 응답 수동 처리 필요
- SecurityFilterChain 앞뒤 어디든 배치 가능
Filter, SecurityFilter
클라이언트 요청
↓
[서블릿 필터 (Filter)]
↓
[Spring Security FilterChainProxy]
↓
[DispatcherServlet (Spring MVC)]
↓
@Controller 등
- 일반 Filter는 Spring Security보다 먼저 실행됨
- Spring Security는 자체 필터 체인을 갖고 있음 (FilterChainProxy로 래핑됨)
'복습 > Spring' 카테고리의 다른 글
[Spring 5 프로그래밍 입문] MVC 3: 세션, 인터셉터, 쿠키 (1) | 2025.07.06 |
---|---|
[Spring 5 프로그래밍 입문] MVC 1: 요청 매핑, 커맨트 객체, 리다이렉트, 폼 태그, 모델 (1) | 2025.07.06 |
[Spring 5 프로그래밍 입문] 스프링 MVC 동작 방식 (1) | 2025.07.02 |
[Spring 5 프로그래밍 입문] AOP 프로그래밍 (0) | 2025.07.02 |
[Spring 5 프로그래밍 입문] 빈 라이프 사이클과 범위 (1) | 2025.07.01 |