Controller를 작성할 때 예외 상황을 고려하면 처리해야 하는 작업이 엄청 늘어날 수 밖에 없습니다.

이럴 때 스프링 MVC에서는 이러한 작업을 다음과 같은 방식으로 처리할 수 가 있는데요?

 

- @ExceptionHandler와 @ControllerAdvice를 이용한 처리
- @ResponseEntity를 이용하는 예외 메시지 구성

 

 

@ContollerAdvice

 

@ContollerAdvice는 뒤에서 배우게 되는 AOP(Aspect-Oriented-Programming)를 이용하는 방식입니다.

AOP에 대해 간단히 언급하자면 핵심적인 로직은 아니지만 프로그램에서 필요한 '공통적인 관심사'(cross-concern)는

분리하자는 개념입니다.

Controller를 작성할 때는 메서드의 모든 예외사항을 전부 핸들링해야 한다면 중복적이고 많은양의 코드를 작성해야 하지만, AOP 방식을 이용하면 공통적인 예외사항에 대해서는 별도로 @ControllerAdvice를 이용해서 분리하는 방식입니다.

 

 

package org.zerock.exception;

import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

import lombok.extern.log4j.Log4j;


@ControllerAdvice
@Log4j
public class CommonExceptionAdvice {

	@ExceptionHandler(Exception.class)
	public String exceppt(Exception ex, Model model) {
		log.error("Exception ......." + ex.getMessage());
		model.addAttribute("exception", ex);
		log.error(model);
		return "error_page";
	}
}

 

CommonExceptionAdvice 클래스에는 @ControllerAdvice는 어노테이션과 @ExceptionHandler라는 어노테이션을

사용하고 있습니다.

@ControllerAdvice는 해당 객체가 스프링의 컨트롤러에서 발생하는 예외를 처리하는 존재임을 명시하는 용도이며,

@ExceptionHandler는 해당 메서드의 예외 타입을 처리한다는 것을 의미합니다.

@ExceptionHandler의 속성으로는 Exception 클래스 타입을 지정할 수 있습니다.

위의 코드의 경우는 Exception.class를 지정하였으므로 모든 예외에 대한 처리가 exceppt()만을 이용해 처리합니다.

 

 

만약 특정한 타입의 예외를 다루고 싶다면 Exception.class 대신에 구체적인 예외의 클래스를 지정해야 합니다.

JSP 화면에서도 구체적인 메시지를 보고 싶다면 Model을 이용해서 전달하는 것이 좋습니다.

 

그리고 org.zerock.exception 패키지는 servlet-context.xml서 인식하지 않기 때문에 

<component-scan>을 이용해서 해당 패키지의 내용을 조사하도록 해야 합니다.

 

<!-- servlet-context.xml>

<context:component-scan base-package="org.zerock.controller" />
<context:component-scan base-package="org.zerock.exception" />

 

CommonExceptionAdvice의 exceppt()의 리턴값은 문자열이므로 jsp 파일의 경로가 됩니다.

jsp는 error_page.jsp 이므로 /WEB-INF/views 폴더내에 작성해야 합니다.

 

<!-- error_page.jsp -->

<%@ page language="java" contentType="text/html; charset=EUC-KR"
    pageEncoding="EUC-KR"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="EUC-KR">
<title>Insert title here</title>
</head>
<body>
	<h4><c:out value="${exception.getMessage()}"></c:out></h4>

	<ul>
		<c:forEach items="${exception.getStackTrace() }" var="stack">
			<li><c:out value="${stack }"></c:out></li>
		</c:forEach>
	</ul>
</body>
</html>

 

예외의 메시지가 정삭적으로 출력되는지 확인해보려면 고의로 파리미터 값의 변환에 문제가 있게 만들어서

호출해보는 방법이 있습니다.

 

package org.zerock.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.zerock.domain.SampleDTO;

import lombok.extern.log4j.Log4j;

@Controller
@RequestMapping("/sample/*")
@Log4j
public class SampleController {
	
	@GetMapping("/ex04")
	public String ex04(SampleDTO dto, @ModelAttribute("page")int page) {
		log.info("dto : " + dto);
		log.info("'page : " + page);
		
		return "/sample/ex04";
	}
}

 

ex04메서드의 파라미터 값은 dto이며, dto 안에는 namer과 page가 있습니다.

 

이제 서버를 구동시켜 확인해보겠습니다.

page의 반환값은 int인데 문자열을 주었기 때문에 다음과 같은 에러가 발생합니다.

 

package org.zerock.sample;

import static org.junit.Assert.assertNotNull;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import lombok.Setter;
import lombok.extern.log4j.Log4j;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("file:src/main/webapp/WEB-INF/spring/root-context.xml")
@Log4j
public class SampleTests {
	
	@Setter(onMethod_=@Autowired)
	private Restaurant restaurant;
	
	@Test
	public void testExist() {
		assertNotNull(restaurant);
		
		log.info(restaurant);
		log.info("----------------------");
		log.info(restaurant.getChef());
	}
}

 

@Runwith

- 테스트 코드는 우선 현재 테스트 코드가 스프링을 실행하는 역할을 할 것이다 라는 것을 나타냅니다

 

@ContextConfiguration

- 해당 어노테이션은 지정된 클래스나 문자열을 이용해서 필요한 객체들을 스프링 내에 객체로 등록합니다. (빈 등록)

- 이클립스에서 자동으로 생성된 root-context.xml의 경로를 지정할 수 있습니다.

 

@Log4j

- Lombok을 이용해 로그를 기록하는 logger를 변수로 생성합니다.

- 별도의 객체 생성 없이 사용가능 합니다.

 

@Autowired

- 해당 인스턴스 변수가 스프링으로부터 자동으로 주입해 달라는 표시입니다.

- 스프링은 정상적으로 주입이 가능하다면 obj 변수에 Restaurant 타입의 객체를 주입하게 됩니다.

 

@Test

- JUnit에서 테스트 대상을 표시하는 어노테이션입니다.

- 해당 메서드를 선택하고 JUnit Test 기능을 실행합니다.

 

assertNotNull()

- 해당 코드에서 restaurant 변수가 null이 아니어야만 테스트가 성공한다는 것을 의미합니다

POJO 기반의 구성

 

스프링은 다른 프레워크들과 달리 이 관계를 구성할 때 별도의 API등을 사용하지 않는 POJO(Plain Old Java Object)의

구성만으로 가능하도록 제작되어 있습니다.

이것이 중요한 이유는 코드를 개발할 때 개발자가 특정한 라이브러리나 컨테이너의 기술에 종속적이지 않다는 것을 의미합니다.

개발자는 가장 일반적인 형태로 코드를 작성하고 실행할 수 있기 때문에 생산성에서도 유리하고, 코드에 대한 테스트 작업 역시 좀 더 유연하게 할 수 있다는 장점이 생깁니다.

 

의존성 주입(DI)과 스프링

 

의존성(Dependency)라는 것은 하나의 객체가 다른 객체 없이 제대로 된 역할을 할 수 없다는 것을 의미합니다.

음식점을 예로 들자면 서빙을 담당하는 직원이 갑자기 하루 못나오는 상황이 있어도 장사는 할 수 있지만,

주방장에게 문제가 생겨서 못나오면 장사를 할 수 없는 일이 발생합니다.

의존성은 이처럼 하나의 객체가 다른 객체의 상태에 따라 영향을 받는것을 의마하며

흔히 A 객체가 B 객체 없이 동작이 불가능한 상황을 'A가 B에 의존적이다'라고 표현합니다.

 

주입(Injection)은 말 그대로 외부에서 '밀어 넣는 것'을 의미합니다.

예를들어 일반 음식점과 프랜차이즈 음식점이 있다고 한다면

일반 음식점은 재료를 직접사고 오고 하지만

프랜차이즈 음식점은 장사에만 집중할 수 있도록 먼저 파악해서 재료들을 보내줍니다.

 

 

AOP의 지원

 

좋은 개발환경의 중요 원칙은 '개발자가 비즈니스 로직에만 집중할 수 있게 한다.'입니다.

이 목표를 이루기 위해서는 몇 가지 중요한 원칙이 있지만, 가장 쉽게 생각할 수 있는것이 반복적인 코드의 제거입니다.

대부분의 시스템이 공통으로 가지고 있는 보안이나 로그, 트랜잭션과 같이 비즈니스 로직은 아니지만, 

반드시 처리가 필요한 부분을 스프링에서는 '횡단 관심사(cross-concern)'라고 합니다.

스프링은 이러한 횡단 관심사를 분리해서 제작하는 것이 가능합니다.

AOP(Aspect Oriented Programming)는 이러한 횡단 관심사를 모듈로 분리하는 프로그래밍의 패러다임 입니다.

 

스프링은 AOP를 AspectJ의 문법을 통해 작성할 수 있는데, 이를 통해서 개발자는

 

1. 핵심 비즈니스 로직에만 집주애서 코드 개발

2. 각 프로젝트마다 다른 관심사를 적용할 때 코드의 수정을 최소화

3. 원하는 관심사의 유지보수가 수월한 코드 구성

을 할 수 있습니다.

 

 

트랜잭션의 지원

 

데이터베이스를 이용할 때 반드시 신경 써야 하는 부분은 하나의 업무가 여러 작업으로 이루어지는 경우의 트랜잭션 처리입니다.

이 트랜잭션 처리는 상황에 따라서 복잡하게 구성될 수도 있고, 아닐 수도 있는데, 그때마다 코드를 이용해서 처리하는 작업은 개발자에게는 상당히 피곤한 일입니다.

스프링은 이런 트랜잭션의 관리를 어노테이션이나 xml로 설정할 수 있기 때문에 개발자가 매번 상황에 맞는 코드를

작성할 필요가 없도록 설계되었습니다.

+ Recent posts