[Spring] 토비의 스프링 Vol.2 3장 스프링 웹 기술과 스프링 MVC

Shiba·2023년 11월 3일
0

🍀 스프링 정리

목록 보기
15/21
post-thumbnail

📗 스프링 웹 기술과 스프링 MVC

3장에서는 먼저 스프링의 웹 계층 설계와 기술의 선정에 관한 기본 원칙을 알아보고, 스프링 웹 기술의 다양한 전략을 살펴볼 것이다.

📖 스프링의 웹 프레젠테이션 계층 기술

📝 스프링에서 사용되는 웹 프레임워크의 종류

스프링 엔터프라이즈 애플리케이션애 사용할 수 있는 웹 프레임워크의 종류를 살펴보자

◼ 스프링 웹 프레임워크

스프링이 직접 제공하는 웹 프레임워크를 살펴보자

스프링 서블릿/스프링 MVC

스프링이 직접 제공하는 서블릿 기반의 MVC 프레임워크
- 프론트 컨트롤러 역할을 하는 DispatcherServlet을 핵심 엔진으로 사용
- 스프링 서블릿의 모든 컴포넌트는 스프링의 서블릿 애플리케이션 컨텍스트의 빈으로 등록되어 동작

스프링 포틀릿

스프링이 제공하는 포틀릿 MVC 프레임워크
- 서블릿과 유사한 포틀릿 컨테이너에서 동작
(스프링 서블릿 MVC와 거의 동일한 기능을 제공)
- 포틀릿이라는, 자유로운 조합이 가능한 작은 단위의 프레젠테이션 컴포넌트를 포틀릿을 지원하는 포탈 서버에 배치하여 사용

◼ 스프링 포트폴리오 웹 프레임워크

스프링 서블릿을 기반으로하는 고급 웹 프레임워크의 종류를 알아보자

Spring Web Flow

SWF라고 불리기도 함.
스프링 서블릿을 기반으로 해서 상태유지 스타일의 웹 애플리케이션을 작성하게 해주는 프레임워크
- 컨트롤러를 직접 코드로 작성하지 않아도, DSL을 이용해 웹 페이지의 흐름과 규칙을 지정해주면 상태정보를 유지하는 고급 웹 애플리케이션도 손쉽게 개발 가능

Spring JavaScript

자바스크립트 툴킷인 Dojo를 추상화한 것
- 스플링 서블릿과 스프링 웹 플로우에 연동하여 손쉽게 Ajax기능을 구축할 수 있도록 만들어져 있다.

Spring Faces

JSF스프링 MVC와 스프링 SWF의 뷰로 손쉽게 사용할 수 있게 해주는 프레임워크

Spring Web Service

스프링 MVC와 유사한 방식으로 SOAP 기반웹 서비스 개발을 가능하게 해주는 프레임워크.
- 강력한 오브젝트 매핑 기능XML 마샬링 기능을 제공
- 스프링 보안을 비롯한 각종 스프링의 기능 사용 가능

Spring BlazeDS Integration

어도비 플렉스의 BlazeDS와 스프링을 통합해서 빠르고 쉽게 플렉스를 지원하는 스프링 애플리케이션을 개발할 수 있도록 해주는 연동 프레임워크
- BlazeDS의 오브젝트를 스프링 빈 오브젝트로 직접 등록해 사용가능하도록 해줌

◼ 스프링을 기반으로 두지 않는 웹 프레임워크

JSP/Servlet

기존에 만들어둔 모델 1 방식의 JSP나 서블릿을 스프링 애플리케이션의 웹 프레젠테이션 계층으로 사용 가능

📝 스프링 MVC와 DispatcherServlet 전략

프레임워크 기술은 두 가지 방향으로 발전

  • 스프링과 같이 유연성과 확장성에 중점을 두고 어떤 종류의 시스템 개발이나 환경, 요구조건에도 잘 들어맞도록 재구성할 수 있는 범용적 프레임워크
    - 각 계층과 기술 사이의 독립성을 중요하게 생각
  • 일체형 고속개발 프레임워크
    - 기술에 대한 자기 주장이 강하고 기술 선호도가 분명함
    - 제한적인 기술만을 사용하도록 강제
    - 느슨한 연결을 유지해야하는 부담이 없고 강하게 결합된 최적화된 코드를 만들 수 있음
    => 스프링의 유연한 확장성을 최대한 활용해서 두 번째 스타일의 프레임워크를 지향하는 것이 목표

◼ DispatcherServlet MVC 아키텍처

MVC 아키텍처프론트 컨트롤러 패턴과 함께 사용됨
- 프론트 컨트롤러 패턴중앙집중형 컨트롤러를 프레젠테이션 계층의 제일 앞에 두어 서버로 들어오는 모든 요청을 먼저 받아 처리하게 만듦
- 예외가 발생했을 때 프론트 컨트롤러가 이를 일관된 방식으로 처리
=> 스프링이 제공하는 스프링 서블릿/MVC의 핵심 - DispatcherServlet이라는 프론트 컨트롤러

📌 서버가 브라우저나 여타 HTTP 클라이언트로부터 HTTP 요청을 받기 시작해서 다시 HTTP로 결과를 응답해주기까지의 과정을 살펴보자

(1) DispatcherServlet의 HTTP 요청 접수

자바 서버의 서블릿 컨테이너는 HTTP 프로토콜을 통해 들어오는 요청이 스프링의 DispatcherServlet에 할당된 것이라면 HTTP 요청정보를 DispatcherServlet에 전달

(2) DispatcherServlet에서 컨트롤러로 HTTP 요청 위임

DispatcherServlet은 URL이나 파라미터 정보, HTTP 명령 등을 참고로 하여 어떤 컨트롤러에게 작업을 위임할지 결정
- DispatcherServlet의 핸들러 매핑 전략을 이용
-- 사용자 요청을 기준으로 어떤 핸들러에게 작업을 위임할지를 결정해주는 것
- 작업을 위임하기 위해 어떤 컨트롤러든 사용이 가능함
-- 어댑터 패턴을 사용하여 특정 컨트롤러를 호출할 때 중간이 끼여서 호출

(3) 컨트롤러의 모델 생성과 정보 등록

컨트롤러가 보통 맵의 담긴 정보인 '모델'을 생성하고 모델에 정보를 등록한다.
- MVC 패턴의 장점이 모델과 뷰가 분리된다는 점이다!
- 모델은 이름과 오브젝트 값의 쌍으로 만들어진다

(4) 컨트롤러의 결과 리턴: 모델과 뷰

컨트롤러가 뷰의 논리적인 이름을 리턴해주면 DispatcherServlet의 전략인 뷰 리졸버가 이를 이용해 뷰 오브젝트를 생성
- 뷰 오브젝트와 뷰 템플릿이 결합해서 최종적으로 사용자가 보게 될 HTML 생성

(5) DispatcherServlet의 뷰 호출과 (6) 모델 참조

DispatcherServlet이 컨트롤러로부터 모델과 뷰를 받은 뒤뷰 오브젝트에게 모델을 전달해주고 클라이언트에게 돌려줄 최종 결과물을 생성해달라고 요청

(7) HTTP 응답 돌려주기

DispatcherServlet이 등록된 후처리기가 있는지 확인하고, 있다면 후처리기에서 후속 작업을 진행한 뒤에 뷰가 만들어준 HttpServletResponse에 담긴 최종 결과를 서블릿 컨테이너에게 돌려줌. 서블릿 컨테이너가 HttpSerlvetResponse에 담긴 정보를 HTTP응답으로 만들어 사용자의 브라우저나 클라이언트에게 전송하고 작업을 종료

위 과정이 익숙해질 때까지 보도록 하자. 그렇지 않으면 스프링 MVC 코드와 설정을 이해하기가 힘들다.

◼ DispatcherServlet의 DI 가능한 전략

DispatcherServlet에는 다양한 방식으로 동작방식과 기능을 확장, 변경할 수 있도록 준비된 전략들이 존재
- DispatcherServlet이 직접 사용하는 것만 알아보자

HandlerMapping

URL과 요청 정보를 기준으로 어떤 컨트롤러를 사용할 것인지를 결정하는 로직을 담당
- HandlerMapping 인터페이스를 구현해서 만들 수 있음
- DispatcherServlet은 하나 이상의 핸들러 매핑을 가질 수 있음
(디폴트로는 BeanNameUrlHandlerMapping과 DefaultAnnotationHandlerMapping 두 가지가 등록된다.

HandlerAdapter

핸들러 매핑으로 선택한 컨트롤러를 DispatcherServlet이 호출할 때 사용하는 어댑터
- 컨트롤러 호출 방법은 타입에 따라 다르기 때문에 컨트롤러 타입을 지원하는 어댑터가 필요
- 디폴트로 HttpRequestHandlerAdapter, SimpleControllerHandlerAdapter, AnnotationMethodHandlerAdapter 세 가지가 등록된다.

HandlerExceptionResolver

예외가 발생했을 때 이를 처리하는 로직을 가짐
- DispatcherServlet은 HandlerExceptionResolver중에 발생한 예외에 적합한 것을 찾아 예외처리를 위임
- 디폴트로 AnnotationMethodHandlerExceptionResolver, ResponseStatusExceptionResolver, DefaultHandlerExceptionResolver 세 가지가 등록된다.

ViewResolver

컨트롤러가 리턴한 뷰 이름을 참고해서 적절한 뷰 오브젝트를 찾아주는 로직을 가진 전략 오브젝트
- 디폴트로 등록되는 InternalResourceViewResolver는 JSP나 서블릿 같이 RequestDispatcher에 의해 포워딩될 수 있는 리소스를 뷰로 사용하게 해줌

LocaleResolver

지역 정보를 결정해주는 전략
- 디폴트인 AcceptHeaderLocaleResolverHTTP 헤더의 정보를 보고 지역정보를 설정. 이렇게 결정된 지역정보는 애플리케이션에서 활용 가능
- HTTP 헤더 대신 세션이나 URL 파라미터, 쿠키 또는 XML 설정에 직접 지정한 값다양한 방식으로 결정 가능

ThemeResolver

테마를 가지고 이를 변경해서 사이트를 구성할 경우 쓸 수 있는 테마 정보를 결정해주는 전략. 자주 사용되지는 않지만 테마를 적용하는 경우 유용.

RequestToViewNameTranslator

컨트롤러에서 뷰 이름이나 뷰 오브젝트를 제공해주지 않았을 경우 URL과 같은 요청정보를 참고해서 자동으로 뷰 이름을 생성해주는 전략
- 디폴트로 DefaultRequestToViewNameTranslator가 등록됨

📖 컨트롤러

컨트롤러는 MVC의 세 가지 컴포넌트 중에 가장 많은 책임을 지고 있음
이 절에서는 컨트롤러의 종류와 핸들러 어댑터를 알아보고, 컨트롤러를 설계하고 만드는 방법도 살펴볼 것이다

📝 컨트롤러의 종류와 핸들러 어댑터

스프링 MVC가 지원하는 컨트롤러의 종류는 네 가지이다. 각 컨트롤러를 DispatcherServlet에 연결해주는 핸들러 어댑터가 하나씩 있어야 하므로, 핸들러 어댑터도 네 개이다.

◼ Servlet과 SimpleServletHandlerAdapter

표준 서블릿. 표준 서블릿 인터페이스인 javax.servlet.Servlet을 구현한 서블릿 클래스

  • 서블릿 클래스 코드를 그대로 유지하면서 스프링 빈으로 등록됨
    - 서블릿 코드를 점진적으로 스프링 애플리케이션에 맞게 포팅할 때 유용
    - 컨트롤러 빈으로 등록된 경우 자동으로 init(),destory()와 같은 생명주기 메소드가 호출되지 않는다.
package springbook.learningtest.spring.web.controllers;
...

public class ServletControllerTest extends AbstractDispatcherServletTest {
	@Test
    public void helloServletController() throws ServletException, IOException {
    	//핸들러 어댑터와 컨트롤러를 빈으로 등록
    	setClasses(SimpleServletHandlerAdapter.class, HelloServlet.class);
        initRequest("/hello").addParameter("name", "Spring");
        
        assertThat(runService().getContentAsString(), is("Hello Spring"));
    }
    
    @Component("/hello")
    static class HelloServlet extends HttpServlet {
    	protected void doGet(HttpServletRequest req, HttpServletResponse resp)
        		throws ServletException, IOException {
        	String name = req.getParameter("name");//get메소드로 name 파라미터 받기
            resp.getWriter().print("Hello " + name);//메세지를 만들어 HttpRequest에 넣기
        }
    }
}

◼ HttpRequestHandler와 HttpRequestHandlerAdapter

HttpRequestHandler는 인터페이스로 정의된 컨트롤러 타입

  • 서블릿처럼 동작하는 컨트롤러를 만들기 위해 사용
    - 서블릿 스펙을 준수할 필요 없이 HTTP 프로토콜을 기반으로 한 전용 서비스를 만드려고 할 때 사용
  • 자바의 RMI(Remote Method Invocation(원격 메소드 호출))를 대체할 수 있는 HTTP 기반의 가벼운 원격 호출 서비스HTTP Invoker를 제공

◼ Controller와 SimpleControllerHandlerAdapter

Controller 인터페이스를 구현해서 만드는 Controller 타입 컨트롤러

  • 스프링 MVC의 가장 대표적인 컨트롤러 타입
    - Controller 컨트롤러는 DispatcherServlet이 컨트롤러와 주고받는 정보를 그대로 메소드의 파라미터와 리턴 값을 갖고 있음.
  • 특정 클래스를 상속할 필요없이 Controller 인터페이스를 구현하기만 하면 됨
    - 여타 MVC 프레임워크의 컨트롤러보다 유연하게 컨트롤러 클래스 설계 가능
    실제로는 필수 기능이 구현되어 있는 AbstractController를 상속해서 컨트롤러를 만드는 것이 편리하여 잘 사용하지 않는 방법
  • 스프링 MVC를 확장해서 애플리케이션에 최적화된 전용 컨트롤러를 설계할 때 가장 유용

⁕ AbstractController가 제공하는 프로퍼티

synchronizeOnSession

HTTP 세션에 대한 동기화 여부를 결정하는 프로퍼티.
사용자가 자신의 HTTP 세션에 동시에 접근하는 것을 막아줌

supportedMethods

컨트롤러가 허용하는 HTTP 메소드(GET, POST 등)를 지정.
디폴트는 모든 종류의 HTTP 메소드를 허용
허용하지 않는 메소드로 접근할 때는 자동으로 예외를 발생시킴.

useExpiresHeader, useCacheControlHeader, useCacheControlNoStore, cacheSeconds

HTTP 1.0/1.1의 Expires, Cache-Control HTTP 헤더를 이용해서 브라우저의 캐시 설정정보를 보내줄 것인지를 결정
디폴트는 캐시를 사용하지 않도록 모두 "no-cache"값을 보내주도록 되어있음.

◼ AnnotationMethodHandlerAdapter

  • 지원하는 컨트롤러의 타입이 정해져 있지 않음
    - 컨트롤러 타입에 제한이 없음
    - 클래스와 메소드에 붙은 몇 가지 애노테이션의 정보와 메소드 이름, 파라미터, 리턴 타입에 대한 규칙 등을 종합적으로 분석해서 컨트롤러를 선별하고 호출 방식을 결정
  • 컨트롤러 하나가 하나 이상의 URL에 매핑될 수 있다
    - URL의 매핑을 컨트롤러의 단위가 아닌 메소드 단위로 가능하도록 만듦
  • 여타 핸들러 어댑터와 다르게 DefaultAnnotationHandlerMapping 핸들러 매핑을 함께 사용해야 한다.

📝 핸들러 매핑

HTTP 요청정보를 이용해서 이를 처리할 컨트롤러(핸들러 오브젝트)를 찾아주는 기능을 가진 DispatcherServlet의 전략
컨트롤러의 타입과는 상관이 없음 - 여러 가지 타입의 컨트롤러를 선택할 수 있다
스프링은 기본적으로 5가지의 핸들러 매핑을 제공

◼ BeanNameUrlHandlerMapping

디폴트 핸들러 매핑중 하나.
빈의 이름에 들어 있는 URLHTTP의 요청의 URL과 비교해서 일치하는 빈을 찾아줌
- URL에는 ANT 패턴이라고 불리는, * 나 ** , ?와 같은 와일드카드를 사용하는 패턴을 넣을 수 있음. (패턴에 일치하는 모든 URL을 가진 요청이 해당 컨트롤러 빈으로 매핑)
- 컨트롤러 갯수가 많아지면 URL 정보가 XML 빈 선언이나 클래스의 애노테이션 등에 분산되어 나타나므로 전체적인 매핑구조를 한눈에 파악하고 관리하기가 불편

◼ ControllerBeanNameHandlerMapping

빈의 아이디나 빈 이름을 이용해 매핑해주는 핸들러 매핑 전략
- 빈 이름 앞뒤에 붙일 수 있는 prefix, suffix를 지정할 수 있음
- 디폴트 핸들러 매핑이 아니므로 전략 빈으로 등록해주어야 한다.
(❗ 특정 전략 클래스를 빈으로 등록하면 디폴트 전략은 모두 무시되는 점에 주의)
(AnnotationMethodHandlerAdapter와 DefaultAnnotationHandlerMapping 적용X)

◼ ControllerClassNameHandlerMapping

빈 이름 대신 클래스 이름을 URL에 매핑해주는 핸들러 매핑 클래스
- Controller로 끝날 때는 Controller를 뺀 나머지 이름을 URL에 매핑
- 디폴트 전략이 아니므로 빈으로 등록해주어야 적용된다.

◼ SimpleUrlHandlerMapping

URL과 컨트롤러의 매핑정보를 한 곳에 모아놓을 수 있는 핸들러 매핑 전략
- 매핑정보는 SimpleUrlHandlerMapping 빈의 프로퍼티에 넣어준다.
- 디폴트 핸들러 매핑 전략이 아니기도 하고 프로퍼티에 매핑 정보를 직접 넣어줘야 하므로 빈으로 등록해야 사용 가능

  • 한곳에 매핑정보가 모여 있기 때문에 URL을 관리하기가 편리함
    - 컨트롤러의 개수가 많은 대규모 프로젝트에서 선호
  • 매핑할 컨트롤러 빈의 이름을 직접 적어주어야 하기 때문에 오타 등의 오류가 발생할 가능성이 있음

◼ DefaultAnnotationHandlerMapping

@RequestMapping이라는 애노테이션컨트롤러 클래스나 메소드에 직접 부여하고 이를 이용해 매핑하는 전략
- 메소드 단위로 URL을 매핑해줄수 있어서 컨트롤러 개수를 획기적으로 줄일 수 있음
- GET/POST와 같은 HTTP 메소드, 심지어는 파라미터와 HTTP 헤더정보까지 매핑에 활용 가능
- 매핑 애노테이션의 사용 정책과 작성 기준을 잘 만들어두지 않으면, 개발자마다 제멋대로 매핑 방식을 적용해서 매핑정보가 지저분해지고 관리하기 힘들어질 수 있음

◼ 기타 설정정보

핸들러 매핑 설정에서 공통적으로 사용되는 주요 프로퍼티만 간단히 살펴보자

order

핸들러 매핑을 두 개이상 사용할 때 우선순위를 지정해주기 위해 사용

defaultHandler

핸들러 매핑 빈의 defaultHandler 프로퍼티를 지정해두면 URL을 매핑할 대상을 찾지 못했을 경우 자동으로 디폴트 핸들러를 선택해줌

alwaysUseFullPath

URL 매핑은 기본적으로 웹 애플리케이션의 컨텍스트 패스서블릿 패스 두가지를 제외한 나머지만 가지고 비교
❗ 특별한 이유가 있어서 URL 전체를 사용해서 컨트롤러를 매핑하기 원한다면 핸들러 매핑 빈의 alwaysUseFullPath 프로퍼티를 true로 선언해주면 됨.

detectHandlersInAncestorContexts

일반적으로 서블릿 컨텍스트의 부모 컨텍스트는 루트 컨텍스트.
- 자유롭게 루트 컨텍스트에 정의한 DAO나 서비스 계층 빈 참조 가능
❗ 하지만 핸들러 매핑의 경우는 다름. 핸들러 매핑 클래스는 기본적으로 현재 컨텍스트인 서블릿 컨텍스트 안에서만 매핑할 컨트롤러를 찾음.
detectHandlersInAncestorContexts가 false로 되어있기 때문.
강제로 true로 바꾸어주면 부모 컨텍스트까지 뒤져서 컨트롤러를 찾을 수 있음.


❗❗ 이 프로퍼티는 절대 사용하지 말자! 동작 방식을 이해하기 위해 설명한 것

📝 핸들러 인터셉터

핸들러 인터셉터는 DispatcherServlet이 컨트롤러를 호출하기 전과 후에 요청과 응답을 참조하거나 가공할 수 있는 일종의 필터.

핸들러 매핑이 DispatcherServlet으로부터 매핑 작업을 요청받으면 그 결과로 핸들러 실행 체인을 돌려줌.핸들러 실행 체인은 하나 이상의 핸들러 인터셉터를 거쳐서 컨트롤러가 실행될 수 있도록 구성

핸들러 인터셉터는 서블릿 필터와 쓰임새가 유사함. 하지만 핸들러 인터셉터는 HttpServletRequest, HttpServletResponse뿐 아니라, 실행될 컨트롤러 빈 오브젝트, 컨트롤러가 돌려주는 ModelAndView, 발생할 예외 등을 제공받을 수 있음. 또한 스프링의 빈이기 때문에 DI로 다른 빈들을 활용할 수도 있음

◼ HandlerInterceptor

핸들러 인터셉터는 HandlerInterceptor 인터페이스를 구현해서 만듦.
인터페이스에는 다음 세 개의 메소드가 포함

boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)throws Exception

컨트롤러가 호출되기 전에 실행. handler 파라미터핸들러 매핑이 찾아준 컨트롤러 빈 오브젝트.
컨트롤러 실행 이전처리해야 할 작업이 있다거나, 요청정보를 가공하거나 추가하는 경우에 사용 가능. 또는 요청에 대한 로그를 남기기 위해 사용하기도 함.
리턴 값true면 다음 단계 진행, false면 작업을 중단하고 리턴함.

void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)throws Exception

컨트롤러를 실행하고 난 후에 호출됨. 일종의 후처리 작업 진행 가능
컨트롤러가 돌려준 ModelAndView 타입의 정보가 제공돼서 컨트롤러 작업 결과를 참조하거나 조작할 수 있음
preHandle()에서 false 리턴시 실행되지 않음.

void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)throws Exception

이름 그대로 모든 뷰에서 최종 결과를 생성하는 일을 포함한 모든 작업이 완료된 후에 실행. 요청 처리 중에 사용한 리소스를 반환해주기에 적당한 메소드

◼ 핸들러 인터셉터 적용

핸들러 인터셉터를 사용하려면 먼저 핸들러 매핑 클래스를 빈으로 등록해야 함.
핸들러 매핑 빈의 interceptors 프로퍼티를 이용핸들러 인터셉터 빈의 레퍼런스를 넣어주면 된다.

<bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping">
	<property name="interceptors">
      	<list>
          	<ref bean="simpleInterceptor" />
          	<ref bean="eventInterceptor" />
      	</list>
    </property>
</bean>

<bean id="simpleIntegerceptor" class="..." />
<bean id="eventIntegerceptor" class="..." />

핸들러 인터셉터는 기본적으로 핸들러 매핑 단위로 등록됨
하나 이상의 핸들러 매핑에 하나의 핸들러 인터셉터를 적용하려면 핸들러 매핑 빈마다 반복적으로 등록해주어야 한다.
(스프링 3.0에서 일괄 적용 기능이 추가되었음)

서블릿 필터와 핸들러 인터셉터

  • 서블릿 필터
    • web.xml에 별도로 등록해주어야 하고 필터 자체는 스프링의 빈이 아님.
    • 웹 애플리케이션으로 들어오는 모든 요청에 적용된다
  • 핸들러 인터셉터
    • 적용 대상DispatcherServlet특정 핸들러 매핑으로 제한됨
    • 스프링의 빈으로 등록 가능
    • 컨트롤러 오브젝트에 접근 가능
    • ModelAndView와 같은 컨트롤러가 리턴하는 정보를 활용할 수 있음

📝 컨트롤러 확장

◼ 커스텀 컨트롤러 인터페이스와 핸들러 어댑터 개발

핸들러 어댑터를 이용해 호출 가능한 인터페이스로 정의해서 독자적인 컨트롤러 인터페이스로 만들어보자.

컨트롤러 인터페이스 정의

public interface SimpleController {
	void control(Map<String, String> params, Map<String, Object> model);
}

뷰 이름을 지정하는 애노테이션을 정의

@Retention(RetentionPolicy.RUNTiME)
@Inherited
public @interface Viewname {
	String value();
}

필수 파라미터 이름을 정의해주는 애노테이션을 정의

@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface RequiredParams {
	String[] value();
}

SimpleController 타입 컨트롤러 생성

public class HelloController implements SimpleController {
	@ViewName("/WEB-INF/view/hello.jsp")
    @RequiredParams({"name"})
    public void control(Map<String, String> params, Map<String, Object> model) {
    	model.put("message", "Hello " + params.get("name"));
    }
}

이제 핸들러 어댑터를 만들어보자. 핸들러 어댑터는 HandlerAdapter 인터페이스의 세 개의 메소드를 구현하기만 하면 된다.

public class SimpleHandlerAdapter implements HandlerAdater {
	public boolean supports(Object handler) { //핸들러 어댑터가 지원하는 타입을 확인
    	return (handler instanceof SimpleController);
    }
    
    // 컨트롤러의 getLastModified()를 다시 호출해서 컨트롤러가 결정하도록 만든다.
    public long getLastModified(HttpServletRequest req, Object handler) {
    	return -1;
    }
    
    public ModelAndView handle(HttpServletRequest req, HttpServletResponse res, 
    		Object handler) throws Exception {
    	Method m = ReflectionUtils.findMethod(handler.getClass(), "control", 
        	Map.class, Map.class);
        
        // 컨트롤러 메소드의 애노테이션에서 필요한 정보를 가져옴.
        // 스프링 유틸리티 클래스를 이용해 간단히 애노테이션을 가져옴
        ViewName viewName = AnnotationUtils.getAnnotation(m, ViewName.class);
        RequiredParams requiredParams = AnnotationUtils.getAnnotation(m,
        	RequiredParams.class);
        
        Map<String, String> params = new HashMap<String, String>();
        for(String param : requiredParams.value()) {
        	String value = req.getParameter(param);
            if(value == null) throw new IllegalStateException();
            params.put(param, value);
        }
        
        Map<String, Object> model = new HashMap<String, Object>();
        
        // 컨트롤러 타입을 모르기 때문에 컨트롤러를 Object 타입으로 넘겨줌
        ((SimpleController)handler).control(params, model);
    }
}

SimpleHandlerAdapter를 적용하는 방법은 서블릿 컨텍스트의 빈으로 등록해주는 것이다.

📖 뷰

MVC 아키텍처에서 모델이 가진 정보를 어떻게 표현해야 하는지에 대한 로직을 갖고 있는 컴포넌트
웹 환경에서 뷰가 생성하는 결과물은 일반적으로 브라우저에 나타낼 수 있는 HTML

뷰 리졸버

뷰 이름으로부터 실제 사용할 뷰를 결정
- 이러한 뷰 이름을 "논리적인 뷰 이름"이라고 부르기도 한다.

📝 뷰

DispatcherServlet이 사용하는 뷰 오브젝트는 스프링의 View 인터페이스를 구현해야 한다.

package.org.springframework.web.servlet;
...
public interface view {
	String getContentType();
    
    void render(Map<String, ?> model, HttpServletRequest request,
    	HttpServletResponse response) throws Exception;
}

◼ InternalResourceView와 JstlView

InternalResourceViewRequestDispatcher의 forward()나 include()를 이용하는 뷰
forward()나 include()다른 서블릿을 실행해서 그 결과를 현재 서블릿의 결과로 사용하거나 추가하는 방식
- 서블릿을 forward() 용도로 사용하는 일은 드물다. 주로 JSP 뷰 적용에 사용

//RequestDispatcher를 이용한 JSP 뷰 생성
req.setAttribute("message", message);
req.getRequestDispatcher("/WEB-INF/view/hello.jsp").forward(req, res);

InternalResourceView가 동작하는 방식도 위와 동일하다고 생각하면 된다.

public class HelloController implements Controller {
	...
    public ModelAndView handleRequest(HttpServletRequest req, HttpServletResponse 
    		res) throws Exception {
    	Map<String, Object> model = new HashMap<String, Object>();
        model.put("message", message);
        
        View view = new InternalResourceView("/WEB-INF/view/hello.jsp"); //뷰 생성
        
        return new ModelAndView(view, model);
  	}
}

◼ RedirectView

HttpServletResponse의 sendReidirect()를 호출해주는 기능을 가진 뷰
- 실제 뷰가 생성되는 것이 아니라, URL만 만들어져 다른 페이지로 리다이렉트. 모델정보가 있다면 URL 뒤에 파라미터로 추가
- 컨트롤러가 RedirectView를 직접 만들어 리턴해도 되지만, 그보다는 뷰 리졸버가 인식할 수 있도록 redirect:로 시작하는 뷰 이름을 사용하면 편리
- 리다이렉트에서 쓰는 URL은 http://로 시작할 수도 있고, /로 시작할 수도 있음
/로 시작하는 경우는 서버의 루트 URL로부터 시작되어야 한다.
/가 아니라면 생성자나 프로퍼티를 통해 contextRelative를 true로 바꿔주는 것이 편함

//RedirectView() 오브젝트를 리턴
return new ModelAndView(new RedirectView("/main"));

//redirect: 접두어를 사용해 뷰 이름만 리턴
return new ModelAndView("redirect:/main");

◼ VelocityView, FreeMarkerView

Velocity와 FreeMarker라는 두 개의 대표적인 자바 템플릿 엔진을 뷰로 사용하게 해줌.
- 컨트롤러에서 직접 뷰 오브젝트를 만드는 대신 뷰 리졸버를 통해 자동으로 뷰가 만들어져 사용되게 하는 편이 낫다.
- JSP에 비해 문법이 훨씬 강력하고 속도가 빠른 템플릿 엔진을 사용할 수 있다
- 템플릿 언어를 지원해주어 매크로 같은 확장 기능을 만들기가 쉬움
- 템플릿 기술과 마크업 언어를 학습하지 않으면 사용불가
- IDE나 툴의 에디터 지원도 JSP보다 상대적으로 떨어지는 편

◼ MarshallingView

스프링 3.0에서 새롭게 등장한 OXM 추상화 기능을 활용해서 application/xml 타입의 XML 콘텐트를 작성하게 해주는 편리한 뷰
- 미리 준비해둔 마샬러 빈을 지정하고 모델에서 변환에 사용할 오브젝트를 지정해주면, OXM 마샬러를 통해 모델 오브젝트를 XML로 변환해서 뷰의 결과로 사용가능

◼ AbstractExcelView, AbstractJExcelView, AbstractPdfView

엑셀과 pdf를 만들어주는 뷰. 상속을 하여 코드를 구현해야하는 뷰
- AbstractExcelView는 아파치 POI 라이브러리를 이용해 엑셀 뷰를 만들어줌.
- AbstractJExcelView는 JExcelAPI를 사용해 엑셀 문서를 만들어줌.
- AbstractPdfView는 iText 프레임워크 API로 PDF 문서를 생성

◼ AbstractAtomFeedView, AbstractRssFeedView

각각 application/atom+xml과 application/rss+xml 타입의 피드(feed) 문서를 생성해주는 뷰.
- 상속을 통해 피드정보를 생성하는 메소드를 직접 구현해주어야 함

◼ XsltView, TilesView, AbstractJasperReportsView

- XsltView는 XSLT 변환을 통해 뷰를 생성해줌. javax.xml.transform.TransformerFactory 타입의 팩토리 클래스와 적용할 모델 오브젝트를 지정해주면 된다.
- TilesView는 Tiles 1, 2를 이용해 뷰 생성 가능
- AbstractJasperReportsView는 리포트 작성용 프레임워크JasperReports를 이용CSV, HTML, PDF, Excel 형태의 리포트를 작성 해줌.

◼ MappingJacksonJsonView

AJAX에서 많이 사용되는 JSON 타입의 콘텐트를 작성해주는 뷰.
기본적으로 모델의 모든 오브젝트를 JSON으로 변환해줌
- Set<String> 타입인 renderedAttributes 프로퍼티를 지정해서 일부 모델만 JSON변환에 이용 가능

📝 뷰 리졸버

뷰 이름으로부터 사용할 뷰 오브젝트를 찾아줌.
- ViewResolver 인터페이스를 구현해서 만들어짐
- 뷰 리졸버를 빈으로 등록하지 않으면 DispatcherServlet의 디폴트 뷰 리졸버인 InternalResourceViewResolver가 사용됨

◼ InternalResourceViewResolver

디폴트 뷰 리졸버. 주로 JSP를 뷰로 사용하고자 할 때 사용됨
테스트용으로 단순한 예제를 만드는 경우가 아니라면 디폴트로 등록된 기본 상태를 그대로 사용하는 일은 피해야 한다.
- 디폴트로 사용 시, 뷰의 전체 경로를 다 적어주어야 한다.
- prefix, suffix 프로퍼티를 이용해 앞뒤에 붙는 내용을 생략하도록 하자
(프로퍼티 설정을 위해서는 InternalResourceViewResolver를 빈으로 등록해줘야 함.)

◼ VelocityViewResolver, FreeMarkerViewResolver

템플릿 엔진 기반의 뷰VelocityView와 FreeMarkerView를 사용하게 해주는 뷰 리졸버.
- 컨트롤러가 돌려준 뷰 이름에 prefix와 suffix를 붙여 실제 템플릿 파일 이름을 생성
- JSP와 달리 템플릿의 경로를 만들 때 사용할 루트 패스를 미리 VelocityConfigure나 FreeMarkerConfigurer로 지정해주어야 함. - 그래서 prefix는 잘 사용 안함.

◼ ResourceBundleViewResolver, XmlViewResolver, BeanNameViewResolver

  • ResourceBundleViewResolver와 XmlViewResolver
    - 컨트롤러마다 뷰의 종류가 달라지는 경우 사용
    - 외부 리소스 파일에 각 뷰 이름에 해당하는 뷰 클래스와 설정을 담아두고, 이를 참조하도록 함.
    - 독립적인 파일을 이용해 뷰를 자유롭게 매핑 가능
    하지만, 모든 뷰를 일일이 파일에 정의해야하는 불편함도 존재
  • BeanNameViewResolver
    - 뷰 이름과 동일한 이름을 가진 빈을 찾아 뷰로 사용하게 해줌
    - XmlViewResolver와 달리 서블릿 컨텍스트의 빈을 사용함

◼ ContentNegotiatingViewResolver

직접 뷰 이름으로부터 뷰 오브젝트를 찾아주지 않음
대신 미디어 타입 정보를 활용해서 다른 뷰 리졸버에게 뷰를 찾도록 위임한 후 가장 적절한 뷰를 선정하여 돌려줌
- 뷰 리졸버를 결정해주는 리졸버

📖 기타 전략

📝 핸들러 예외 리졸버

컨트롤러의 작업 중발생한 예외를 어떻게 처리할지 결정하는 전략
- 예외를 처리할 수 있는 핸들러 예외 리졸버가 있으면 해당 핸들러 예외 리졸버가 예외를 처리함
스프링은 총 네 개의 HandlerExceptionResolver 구현 전략을 제공하고 있음
(그중 세 개는 디폴트로 등록되도록 설정)

◼ AnnotationMethodHandlerExceptionResolver

예외가 발생한 컨트롤러 내의 메소드 중에서 @ExceptionHandler 애노테이션이 붙은 메소드를 찾아 예외처리를 맡겨주는 핸들러 예외 리졸버. - 디폴트로 등록됨

◼ ResponseStatusExceptionResolver

특정 HTTP 응답 상태 코드로 예외를 전환해주는 것 - 디폴트로 등록된 리졸버
- HTTP 500 에러 대신 의미있는 HTTP 응답상태를 돌려주는 방법
예외 클래스에 @ResponseStatus를 붙이고, HttpStatus에 정의되어 있는 HTTP 응답 상태 값을 value 엘리먼트에 지정

◼ DefaultHandlerExceptionResolver

디폴트로 등록되는 핸들러 예외 리졸버 중 위 두가지에서 처리못한 예외를 처리
- 스프링에서 내부적으로 발생하는 주요 예외를 처리해주는 표준 예외처리 로직을 담고 있음

◼ SimpleMappingExceptionResolver

web.xml의 <error-page>와 비슷하게 예외를 처리할 뷰를 지정할 수 있게 해줌
- 디폴트 전략이 아니므로 직접 빈으로 등록해주어야 함.
- 실제로 활용하기에 가장 편리한 핸들러 예외 리졸버
- 모든 컨트롤러에서 발생하는 예외에 일괄적용됨

<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
  	<property name="mappedHandlers">
      //예외와 그에 대응되는 뷰 이름을 프로퍼티로 등록!
      	<props>
          	<prop key="DataAccessException">error/dao</prop>
          	<prop key="BusinessLogicException">error/login</prop>
        </props>
    </property>
  	<property name="defaultErrorView">error/default</property>
</bean>

📝 지역정보 리졸버

애플리케이션에서 사용하는 지역정보를 결정하는 전략

  • 디폴트로 사용되는 AcceptHeaderLocaleResolver는 HTTP 헤더의 지역정보를 그대로 사용
  • 브라우저의 설정을 따르지 않고 사용자가 직접 변경하도록 만드려면 SessionLocaleResolver나 CookieLocaleResolver를 사용하는 것이 편리
    - HTTP세션이나 쿠키에 들어 있는 값을 확인해서 애플리케이션의 지역정보를 결정

📝 멀티파트 리졸버

파일 업로드와 같이 멀티파트 포맷의 요청정보를 처리하는 전략 설정 가능
- 현재는 아파치 Commons의 FileUpload 라이브러리를 사용하는 CommonsMultipartResolver 한 가지만 지원됨.
- 디폴트로 등록되는 것이 없어 적용하려면 빈을 등록해줘야 한다
- 과도한 크기의 파일 업로드를 막기 위해 maxUploadsize 프로퍼티 설정 권장

◼ RequestToViewNameTranslator

컨트롤러에서 뷰 이름이나 뷰 오브젝트를 돌려주지 않았을 경우 HTTP 요청정보를 참고해서 뷰 이름을 생성해주는 로직을 담음
- 디폴트로 DefaultRequestToViewNameTranslator 전략이 등록되어 있음.

📖 스프링 3.1의 MVC

📝 플래시 맵 매니저 전략

스프링 3.1에서는 플래시 맵을 관리하는 플래시 맵 매니저 전략이 추가됨

◼ 플래시 맵

플래시 애트리뷰트를 저장하는 맵

플래시 애트리뷰트

하나의 요청에서 생성되어 다음 요청으로 전달되는 정보
- 다음 요청에서 한 번 사용되고 바로 제거됨
- Post/Redirect/Get 패턴을 적용하는 경우에 POST 단계의 작업 결과 메시지를 리다이렉트된 페이지로 전달할 때 주로 사용됨

◼ 플래시 맵 매니저

플래시 맵을 저장, 유지, 조회, 제거하는 등의 작업을 담당하는 오브젝트
- FlashMapManager 인터페이스를 구현해서 만듦
- DispatcherServlet이 미리 준비해둔 것을 사용하면 됨
RequestContextUtils.getFlashMapManager()를 사용해서 가져올 수 있음

// 플래시 맵 매니저를 이용해 플래시 맵 오브젝트를 저장!
RequestContextUtils.getFlashMapManager(request).saveOutputFlashMapManager(fm,
 request, response);
profile
모르는 것 정리하기

0개의 댓글