안녕하세요 코이킹입니다.
이번 포스트는 스프링 부트에서 예외처리 구현에 대한 내용입니다.
1. 목표
- 스프링 부트에서 제공하는 예외처리 기능을 사용하여 예외처리를 구현할 수 있다.
2. 예외 처리란?
예외처리란 어플리케이션 실행 중 예상하지 못한 에러를 대비한 코드를 작성해두는 것으로, 특히나 웹 어플리케이션은 실행 중에 에러가 발생할 경우 보안적으로 민감한 정보가 그대로 보일 수 있으므로 반드시 예외처리를 구현해 두어야 합니다.
3. 예외처리 구현
이 포스트에서는 Java의 기본문법의 try catch를 사용한 예외처리가 아닌 스프링 부트가 제공하는 @ExceptionHandler와 @ControllerAdvice어노테이션을 사용하여 예외처리를 구현하겠습니다.
※ @ExceptionHandler를 사용한 예외처리
1) 컨트롤러 : /template-springboot/src/main/java/com/sb/template/controller/BoardController.java
독립적인 예외처리가 필요할 경우 @ExceptionHandler를 사용합니다.
package com.sb.template.controller;
import java.util.List;
import java.util.stream.Collectors;
import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.ModelAndView;
import com.sb.template.entity.Board;
import com.sb.template.enums.BoardType;
import com.sb.template.exception.ProcFailureException;
import com.sb.template.exception.UnvalidParamException;
import com.sb.template.forms.BoardForm;
import com.sb.template.service.BoardService;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Controller
@RequestMapping(path = "/board")
public class BoardController {
@Autowired
private BoardService boardService;
@RequestMapping(method = RequestMethod.GET, path = "list")
public String viewBoardList(Model model) {
List<Board> boardList = boardService.getAllBoard();
model.addAttribute("boardList", boardList);
return "board/list";
}
@RequestMapping(method = RequestMethod.GET, path = "write")
public String writeBoard(Model model) {
model.addAttribute("boardTypes", BoardType.getBoardTypes());
return "board/write";
}
@RequestMapping(method = RequestMethod.POST, path = "write")
public String writeCompleteBoard(@Validated BoardForm form,
BindingResult bindingResult, Model model) {
validation(bindingResult);
Integer boardNo = null;
boardNo = boardService.createBoard(form.toEntity()).getBoardNo();
if (boardNo == null) {
throw new ProcFailureException("Failure Board write process");
}
return "redirect:/board/list";
}
@RequestMapping(method = RequestMethod.GET, path = "read/{boardNo}")
public String viewBoardOne(@PathVariable(required = true) int boardNo, Model model) {
model.addAttribute("board", boardService.getBoardOne(boardNo));
return "board/read";
}
@RequestMapping(method = RequestMethod.GET, path = "update/{boardNo}")
public String updateBoard(@PathVariable(required = true) int boardNo, Model model) {
boardService.updateBoardForm(boardNo, model);
return "board/update";
}
@RequestMapping(method = RequestMethod.POST, path = "update/{boardNo}")
public String updateCompleteBoard(
@PathVariable(required = true) int boardNo,
@Validated BoardForm form,
BindingResult bindingResult, Model model) {
validation(bindingResult);
if (boardService.updateBoard(boardNo, form.toEntity()) == null) {
throw new ProcFailureException("Failure Board update process");
}
model.addAttribute("message", "Update Success");
return "redirect:/board/read/"+boardNo;
}
@RequestMapping(method = RequestMethod.GET, path = "delete/{boardNo}")
public String deleteBoard(@PathVariable int boardNo, Model model) {
model.addAttribute("board", boardService.getBoardOne(boardNo));
return "board/delete";
}
@RequestMapping(method = RequestMethod.POST, path = "delete")
public String deleteCompleteBoard(
@RequestParam(name = "boardNo", required = true) int boardNo,
Model model) {
boardService.deleteBoard(boardNo);
model.addAttribute("message", "Delete Success");
return "redirect:/board/list";
}
/**
* If validation check result is OK, return false.
* In the opposite case return true.
*
* @param bindingResult
* @return
*/
private void validation(BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
List<String> errorStrList = bindingResult.getAllErrors()
.stream()
.map(e -> e.getDefaultMessage())
.collect(Collectors.toList());
log.error("Validation-NG : {} ", errorStrList);
throw new UnvalidParamException(errorStrList);
}
log.info("Validation-OK");
}
@ExceptionHandler
public ModelAndView procFailureErrorHandler(HttpServletRequest req, ProcFailureException e) {
ModelAndView mav = new ModelAndView();
String message = e.getMessage();
String redirectUrl = "http://localhost:8080/";
String view = "/common/error";
mav.addObject("message", message);
mav.addObject("redirectUrl", redirectUrl);
mav.setViewName(view);
log.error("Message : {}, RedirectUrl : {}, View : {}", message, redirectUrl, view);
return mav;
}
}
153~ 167행 : ProcFailureException예외가 발생할 경우. 이 메서드가 실행됩니다.
※ @ControllerAdvice를 사용한 예외처리
2) 예외 처리 핸들러 클래스 : /template-springboot/src/main/java/com/sb/template/handler/BaseWebExceptionHandler.java
어플리케이션 전체 또는 특정 컴포넌트를 지정하여 예외처리를 구현할 경우 @ControllerAdvice를 사용합니다.
package com.sb.template.handler;
import java.util.NoSuchElementException;
import javax.servlet.http.HttpServletRequest;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.ModelAndView;
import com.sb.template.aop.CommonAopLog;
import com.sb.template.controller.BoardController;
import com.sb.template.exception.UnvalidParamException;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@ControllerAdvice(assignableTypes = {DispatcherServlet.class, BoardController.class, CommonAopLog.class})
public class BaseWebExceptionHandler {
protected ModelAndView makeResult(String handlerType, String msg, String url, String view) {
ModelAndView mav = new ModelAndView();
String message = (msg == null || msg == "") ? "Server Error" : msg;
String redirectUrl = (url == null || url == "") ? "http://localhost:8080/" : url;
String viewPath = (view == null || view == "") ? "/common/error" : view;
mav.addObject("message", message);
mav.addObject("redirectUrl", redirectUrl);
mav.setViewName(viewPath);
log.error("handlerType : {}, Message : {}, RedirectUrl : {}, View : {}",handlerType, message, redirectUrl, viewPath);
return mav;
}
@ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
@ExceptionHandler
public ModelAndView processErrorHandler(HttpServletRequest req, Exception e) {
String handlerType = Thread.currentThread().getStackTrace()[1].getMethodName();
String message = e.getStackTrace().toString();
return makeResult(handlerType, message, null, null);
}
@ResponseStatus(value = HttpStatus.NOT_FOUND)
@ExceptionHandler
public ModelAndView notFoundErrorHandler(HttpServletRequest req, NoSuchElementException e) {
req.setAttribute("requestDispatcherPath", "");
String handlerType = Thread.currentThread().getStackTrace()[1].getMethodName();
String message = e.getMessage();
String redirectUrl = (req.getHeader("referer") == null ||req.getHeader("referer").isBlank()) ? "http://localhost:8080/board/list" : req.getHeader("referer");
String view = "/common/error";
return makeResult(handlerType, message, redirectUrl, view);
}
@ResponseStatus(value = HttpStatus.BAD_REQUEST)
@ExceptionHandler
public ModelAndView badRequestErrorHandler(HttpServletRequest req, UnvalidParamException e) {
String handlerType = Thread.currentThread().getStackTrace()[1].getMethodName();
String message = e.getMessage();
String redirectUrl = (req.getHeader("referer") == null ||req.getHeader("referer").isBlank()) ? "http://localhost:8080/board/list" : req.getHeader("referer");
String view = "/common/error";
return makeResult(handlerType, message, redirectUrl, view);
}
}
- 21행 : 예외처리 핸들러 클래스임을 선언합니다. assignableTypes에 이 핸들러 클래스에서 예외처리를 실시할 클래스를 지정합니다.
- 41, 52, 66 : 응답 시의 Http스테이터스를 지정합니다.
- 42, 53, 67 : 예외처리를 실시할 메서드로 선언합니다. 각각의 메서드가 처리하는 예외는 메서드의 매개변수로 설정하면 됩니다.
4. 동작확인
- 글 작성시 게시글 본문 데이터를 Null 값으로 넘겨 유효성 검사에서 예외처리 발생 : @ExceptionHandler사용
- 없는 게시글 번호로 글 상세보기 요청을 보내서 예외처리 발생 : @ControllerAdvice사용
5. 전체 소스코드
https://github.com/leeyoungseung/template-springboot/tree/feature/exception
예외처리 구현에 대한 포스트는 이상으로 마치겠습니다.
다음 포스트는 페이징 처리가 되겠습니다.
'프로그래밍 > Springboot-토이프로젝트' 카테고리의 다른 글
【게시판-08】타임리프 템플릿 결합과 부트스트랩 적용 (0) | 2022.09.17 |
---|---|
【게시판-07】페이징 처리 (1) | 2022.09.16 |
【게시판-번외05】유효성 검사 (0) | 2022.09.14 |
【게시판-번외04】AOP를 적용한 로그출력 (0) | 2022.09.14 |
【게시판-번외03】필수기능 설정 & Log4j설정 (0) | 2022.09.14 |
댓글