관리 메뉴

거니의 velog

(9) 스프링에서 지원하는 여러 가지 기능 3 본문

Java_Spring Framework part2

(9) 스프링에서 지원하는 여러 가지 기능 3

Unlimited00 2023. 11. 16. 12:04

5. 스프링 인터셉터 사용하기

* 이번에는 스프링에서 제공하는 기능인 인터셉터(Interceptor)에 대해 알아보자. 인터셉터를 사용하면 브라우저 요청이 있을 때 요청 메서드 호출 전후에 개발자가 원하는 기능을 수행할 수 있다.

* 무슨 말인지 다음 그림을 보자.

* 위 그림과 같이 브라우저의 요청을 해당 컨트롤러의 메서드가 처리하기 전후에 인터셉터를 두어 특정 작업을 수행한다. 인터셉터는 필터와 비슷한 기능을 하지만 필터는 웹 애플리케이션의 특정한 위치에서만 동작하는 데 반해 인터셉터는 좀 더 자유롭게 위치를 변경해서 기능을 수행할 수 있다. 즉, 인터셉터는 애플리케이션 안에서 적용 범위를 설정할 수 있다. 주로 쿠키(cookie) 제어, 파일 업로드 작업 등에 사용한다.

* 다음 표는 스프링에서 인터셉터 기능을 구현하는 데 사용되는 HandlerInterceptor 클래스의 메서드들이다.

< 스프링 HandlerInterceptor 클래스의 여러 가지 메서드 >

메서드 기능
preHandle() 컨트롤러 실행 전 호출한다.
postHandle() 컨트롤러 실행 후 DIspatcherServlet이 뷰로 보내기 전에 호출한다.
afterCompletion() 뷰까지 수행하고 나서 호출된다.

(1) 인터셉터 사용해 다국어 기능 구현하기

* 이번에는 인터셉터를 사용해 다국어 기능을 구현해 보자.

1. 다국어 기능 관련 설정을 하기 위해 XML 파일을 준비한다.

2. servlet-context.xml에서 브라우저의 요청에 대해 수행할 인터셉터를 빈으로 설정한다.

<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/mvc" 
		xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
		xmlns:beans="http://www.springframework.org/schema/beans" 
		xmlns:mvc="http://www.springframework.org/schema/mvc" 
		xmlns:context="http://www.springframework.org/schema/context" 
		xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
		http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

	<!--  
		xmlns:mvc="http://www.springframework.org/schema/mvc" : <mvc:~> 태그를 사용하기 위해 추가한다.
	-->

    <!-- DispatcherServlet Context: defines this servlet's request-processing infrastructure -->

    <!-- Enables the Spring MVC @Controller programming model -->
    <annotation-driven />

    <!-- Handles HTTP GET requests for /resources/** by efficiently serving up static resources in the ${webappRoot}/resources directory -->
    <resources mapping="/resources/**" location="/resources/" />

    <!-- Resolves views selected for rendering by @Controllers to .jsp resources in the /WEB-INF/views directory -->
    <beans:bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <beans:property name="prefix" value="/WEB-INF/views/" />
        <beans:property name="suffix" value=".jsp" />
    </beans:bean>

    <context:component-scan base-package="com.myspring.pro28" />

    <!-- 멀티파트 리졸버 -->
    <beans:bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
        <beans:property name="maxUploadSize" value="52428800" />
        <beans:property name="maxInMemorySize" value="1000000" />
        <beans:property name="defaultEncoding" value="utf-8" />
    </beans:bean>

	<!--  
		<mvc:interceptors> : 인터셉터 기능을 사용하도록 설정한다.
		<mvc:mapping path="/test/*.do" /> : 특정 요청, 즉 /test/*.do에 대해서만 인터셉터 빈을 수행한다.
		<beans:bean class="com.myspring.pro28.ex05.LocaleInterceptor" /> : 인터셉터 기능을 수행할 빈을 설정한다.
	-->
    <mvc:interceptors>
        <mvc:interceptor>
            <mvc:mapping path="/test/*.do" />
            <mvc:mapping path="/*/*.do" />
            <beans:bean class="com.myspring.pro28.ex05.LocaleInterceptor" />
        </mvc:interceptor>
    </mvc:interceptors>
    
</beans:beans>

3. 그런 다음 다국어 기능과 관련된 빈과 메시지 파일을 읽어 들이는 message-context.xml을 작성한다. ReloadableResourceBundleMessageSource 클래스를 사용해 message 프로퍼티 파일을 읽어 들이면 다국어 기능을 사용할 수 있다.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" 
		xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
		xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    
    <!-- 스프링의  -->
    <bean id="localeResolver" class="org.springframework.web.servlet.i18n.SessionLocaleResolver" />
    <bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
        <property name="basenames">
            <list>
                <value>classpath:locale/messages</value> <!-- 패키지 locale에서 massages.properties를 읽어 들인다. -->
            </list>
        </property>
        <!-- 파일의 기본 인코딩을 지정합니다. -->
        <property name="defaultEncoding" value="UTF-8" /> <!-- 파일의 기본 인코딩을 지정한다. -->
        <property name="cacheSeconds" value="60" /> <!-- properties 파일이 변경되었는지 확인하는 주기를 지정한다. 60초 간격으로 지정한다. -->
    </bean>
    
</beans>

4. 다국어 메시지들을 저장한 프로퍼티 파일을 생성한다. 프로젝트의 /src/main/resource 패키지 하위에 locale 패키지를 만든 후 프로퍼티 파일들을 작성해 넣는다.

[messages_en.properties]

site.title=Member Info
site.name=name
site.job=job
name=hong kil-dong
job=student
btn.send=send
btn.cancel=cancel
btn.finish=finish

[messages_ko.properties]

site.title=\ud68c\uc6d0\uc815\ubcf4
site.name=\uc774\ub984
site.job=\uc9c1\uc5c5
name=\ud64d\uae38\ub3d9
job=\ud504\ub85c\uadf8\ub798\uba38
btn.send=\uc804\uc1a1
btn.cancel=\ucde8\uc18c
btn.finish=\uc885\ub8cc

[messages.properties]

site.title=\ud68c\uc6d0\uc815\ubcf4
site.name=\uc774\ub984
site.job=\uc9c1\uc5c5
name=\ud64d\uae38\ub3d9
job=\ud504\ub85c\uadf8\ub798\uba38
btn.send=\uc804\uc1a1
btn.cancel=\ucde8\uc18c
btn.finish=\uc885\ub8cc

5. 사용자가 직접 만든 인터셉터를 구현하는 데 필요한 자바 클래스 파일들을 준비한다.

6. 스프링의 인터셉터 기능은 스프링에서 제공하는 HandlerInterceptorAdapter 클래스를 상속받거나 인터페이스인 HandlerInterceptor를 구현해서 사용한다. preHandle() 메서드는 컨트롤러를 실행하기 전에 호출되어 수행한다.

package com.myspring.pro28.ex05;

import java.util.Locale;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

public class LocaleInterceptor extends HandlerInterceptorAdapter { // 사용자 정의 인터셉터는 반드시 HandlerInterceptorAdapter를 상속받아야 한다.
	
	@Override
	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { // 컨트롤러 실행 전 호출된다.
		HttpSession session = request.getSession();
		String locale = request.getParameter("locale"); // 브라우저에서 전달한 locale 정보를 가져온다.
		if (locale == null) locale = "ko"; // 최초 요청시 locale을 한국어로 설정한다.
		session.setAttribute("org.springframework.web.servlet.i18n.SessionLocaleResolver.LOCALE", new Locale(locale)); // LOCALE 속성 값을 세션에 저장해 SessionLocaleResolver가 사용할 수 있게 한다.
		return true;
	}

	@Override
	public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
			ModelAndView modelAndView) throws Exception { // 컨트롤러 실행 후 호출된다.
	}

	@Override
	public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
			throws Exception { // 뷰(JSP)를 수행한 후 호출된다.
	}
	
}

7. 컨트롤러는 요청에 대해 JSP 이름만 뷰리졸버로 반환한다.

package com.myspring.pro28.ex05;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;

@Controller("localeController")
public class LocaleController {
	@RequestMapping(value = "/test/locale.do", method = { RequestMethod.GET })
	public String locale(HttpServletRequest request, HttpServletResponse response) throws Exception {
		System.out.println("localeController입니다.");
		return "locale"; // 컨트롤러는 뷰이름만 반환한다.
	}
}

8. locale 값에 따라 다국어로 표시되는 JSP 페이지를 구현한다.

9. JSP에서 다국어들을 표시하기 위해 스프링의 <spring:message> 태그를 이용한다. code에 properties 파일의 키를 입력하면 키에 대한 값이 표시되고, code에 해당하는 값이 없으면 기본값으로 표시된다.

<%@ page language="java" contentType="text/html; charset=UTF-8"
     import="java.io.*"
    pageEncoding="UTF-8"
    isELIgnored="false" %>
<%-- spring:message 태그를 이용할 수 있도록 설정한다. --%>
<%@ taglib uri="http://www.springframework.org/tags" prefix="spring" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%
  request.setCharacterEncoding("UTF-8");
%>

<!DOCTYPE html>
<html>

    <head>
        <meta charset="UTF-8">
        <title>
        	<%-- spring:message 태그를 이용해 code 속성에 프로퍼티 파일의 site.title 값을 표시한다. --%>
            <spring:message code="site.title" text="Member Info" />
        </title>
    </head>

    <body>
    
    	<%-- 한국어를 요청한다. --%>
        <a href="${pageContext.request.contextPath }/test/locale.do?locale=ko">한국어</a>
        <%-- 영어를 요청한다. --%>
        <a href="${pageContext.request.contextPath }/test/locale.do?locale=en">ENGLISH</a>
        <h1>
            <spring:message code="site.title" text="Member Info" />
        </h1>
        
        <p>
        	<%-- 프로퍼티 site.name에 해당하는 값을 표시한다. --%>
            <spring:message code="site.name" text="no name" /> :
            <%-- 프로퍼티 name에 해당하는 값을 표시한다. --%>
            <spring:message code="name" text="no name" />
        </p>
        <p>
            <spring:message code="site.job" text="no job" /> :
            <spring:message code="job" text="no job" />
        </p>
        
        <%-- spring:message 태그를 이용해 프로퍼티 btn.send를 버튼 이름으로 설정한다. --%>
        <input type="button" value="<spring:message code='btn.send' />" />
        <input type="button" value="<spring:message code='btn.cancel' />" />
        <input type="button" value="<spring:message code='btn.finish' />" />
        
    </body>

</html>

10. 다음의 주소로 최초 요청 시 한글로 표시된다.

- http://localhost/pro28/test/locale.do

11. ENGLISH를 클릭하면 페이지의 텍스트가 영어로 표시된다.

12. 한국어를 클릭하면 다시 한국어로 표시된다.

* 이상으로 인터셉터 기능에 대해 알아봤다.


6. 인터셉터 사용해 요청명에서 뷰이름 가져오기

* 이번에는 인터셉터를 사용해 요청 URL에서 뷰이름을 가져오는 기능을 구현해 보자. 이전에 실습한 회원 관리 기능에 인터셉터 기능을 추가하고 수정하는 작업이다.

1. 실습에 필요한 파일은 다음과 같다.

2. servlet-context.xml에 인터셉터를 설정한다.

<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/mvc" 
		xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
		xmlns:beans="http://www.springframework.org/schema/beans" 
		xmlns:mvc="http://www.springframework.org/schema/mvc" 
		xmlns:context="http://www.springframework.org/schema/context" 
		xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
		http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

	<!--  
		xmlns:mvc="http://www.springframework.org/schema/mvc" : <mvc:~> 태그를 사용하기 위해 추가한다.
	-->

    <!-- DispatcherServlet Context: defines this servlet's request-processing infrastructure -->

    <!-- Enables the Spring MVC @Controller programming model -->
    <annotation-driven />

    <!-- Handles HTTP GET requests for /resources/** by efficiently serving up static resources in the ${webappRoot}/resources directory -->
    <resources mapping="/resources/**" location="/resources/" />

    <!-- Resolves views selected for rendering by @Controllers to .jsp resources in the /WEB-INF/views directory -->
    <beans:bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <beans:property name="prefix" value="/WEB-INF/views/" />
        <beans:property name="suffix" value=".jsp" />
    </beans:bean>

    <context:component-scan base-package="com.myspring.pro28" />

    <!-- 멀티파트 리졸버 -->
    <beans:bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
        <beans:property name="maxUploadSize" value="52428800" />
        <beans:property name="maxInMemorySize" value="1000000" />
        <beans:property name="defaultEncoding" value="utf-8" />
    </beans:bean>

	<!--  
		<mvc:mapping path="/*/*.do" /> : 모든 요청에 대해 인터셉터를 수행한다.
	-->
    <mvc:interceptors>
        <mvc:interceptor>
            <!-- <mvc:mapping path="/test/*.do" /> -->
            <mvc:mapping path="/*/*.do" />
            <beans:bean class="com.myspring.pro28.ex05.LocaleInterceptor" />
        </mvc:interceptor>
    </mvc:interceptors>
    
</beans:beans>

3. member 패키지 하위에 interceptor 패키지를 만든 후 ViewNameInterceptor 클래스를 작성한다.

4. 인터셉터 수행 시 preHandle() 메서드로 전달된 request에서 추가한 getViewName() 메서드를 이용해 뷰이름을 가져온 후 request에 바인딩한다.

package com.myspring.pro27.member.interceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

public class ViewNameInterceptor extends HandlerInterceptorAdapter {
	@Override
	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
		try {
			String viewName = getViewName(request); // getViewName() 메서드를 이용해 브라우저의 요청명에서 뷰이름을 가져온다.
			request.setAttribute("viewName", viewName); // 뷰이름을 request에 바인딩한다.
		} catch (Exception e) {
			e.printStackTrace();
		}
		return true;
	}

	@Override
	public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
			ModelAndView modelAndView) throws Exception {
	}

	@Override
	public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
			throws Exception {
	}

	private String getViewName(HttpServletRequest request) throws Exception { // 요청명에서 뷰 이름을 반환한다.
		String contextPath = request.getContextPath();
		String uri = (String) request.getAttribute("javax.servlet.include.request_uri");
		if (uri == null || uri.trim().equals("")) {
			uri = request.getRequestURI();
		}

		int begin = 0;
		if (!((contextPath == null) || ("".equals(contextPath)))) {
			begin = contextPath.length();
		}

		int end;
		if (uri.indexOf(";") != -1) {
			end = uri.indexOf(";");
		} else if (uri.indexOf("?") != -1) {
			end = uri.indexOf("?");
		} else {
			end = uri.length();
		}

		String fileName = uri.substring(begin, end);
		if (fileName.indexOf(".") != -1) {
			fileName = fileName.substring(0, fileName.lastIndexOf("."));
		}
		if (fileName.lastIndexOf("/") != -1) {
			fileName = fileName.substring(fileName.lastIndexOf("/", 1), fileName.length());
		}
		return fileName;
	}
}

5. 컨트롤러에서는 request에서 바인딩된 뷰이름을 가져와 뷰리졸버로 반환한다.

@Controller("memberController")
public class MemberControllerImpl   implements MemberController {
	
	...
	
	@Override
	@RequestMapping(value="/member/listMembers.do" ,method = RequestMethod.GET)
	public ModelAndView listMembers(HttpServletRequest request, HttpServletResponse response) throws Exception {
		//String viewName = getViewName(request);
		String viewName = (String) request.getAttribute("viewName"); // 인터셉터에서 바인딩한 뷰이름을 가져온다.
		List membersList = memberService.listMembers();
		ModelAndView mav = new ModelAndView(viewName);
		mav.addObject("membersList", membersList);
		return mav;
	}
    
    ...
    
    @RequestMapping(value = "/member/*Form.do", method =  RequestMethod.GET)
	private ModelAndView form(@RequestParam(value= "result", required=false) String result,
						       HttpServletRequest request, 
						       HttpServletResponse response) throws Exception {
		//String viewName = getViewName(request);
		String viewName = (String)request.getAttribute("viewName"); // 인터셉터에서 바인딩된 뷰이름을 가져온다.
		ModelAndView mav = new ModelAndView();
		mav.addObject("result",result);
		mav.setViewName(viewName);
		return mav;
	}

6. 다음의 주소로 로그인창을 요청하여 뷰이름을 인터셉터에서 가져온다. 그리고 이를 컨트롤러에 전달한다.

- http://localhost/pro27/member/loginForm.do

* 지금까지 스프링에서 제공하는 여러 가지 오픈 소스 라이브러리에 대해 알아봤다. 이 외에도 제공하는 기능이 많으므로 스프링 관련 사이트를 참고하기 바란다.