본문 바로가기
프로그래밍/Springboot-토이프로젝트

【게시판-번외06】예외처리

by 코이킹 2022. 9. 16.
반응형

안녕하세요 코이킹입니다.
이번 포스트는 스프링 부트에서 예외처리 구현에 대한 내용입니다. 

 


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

 

GitHub - leeyoungseung/template-springboot

Contribute to leeyoungseung/template-springboot development by creating an account on GitHub.

github.com


예외처리 구현에 대한 포스트는 이상으로 마치겠습니다. 

다음 포스트는 페이징 처리가 되겠습니다. 

반응형

댓글