@MVC 는 웹 애플리케이션 개발에 필요한 유용한 기능을 대부분 제공한다. 또 다양한 MVC 기능을 전략 패턴을 통해 사용하게 해주므로 스프링이 제공하는 전략 중에서 원하는 것을 선택해 적용할 수 있다. 여기에 더해서 스프링이 제공하는 세부적인 기능을 확장하고 싶다면 스프링이 제공하는 확장 포인트를 이용할 수 있다. 여기서는 애노테이션 방식의 컨트롤러를 만들 때 적용되는 기능을 확장할 수 있도록 준비된 확장 포인트 중에서 몇 유용한 몇 가지를 살펴보자. 그 외에도 기본 동작 방식을 변경하거나 확장할 수 있는 많은 확장포인트가 있으므로 관심이 있다면 각 클래스의 API 문서를 자세히 살펴보기 바란다.

AnnotationMethodHandlerAdapter

확장 포인트가 가장 많은 전략은 다양한 관례와 규칙을 이용해 컨트롤러 메소드를 호출해 주는 AnnotationMethodHandlerAdapter 다. 이미 WebBindingInitializer 와 HttpMessageConverter 를 활용하는 방법은 설명했다. 그 외에도 다음과 같은 인터페이스를 구현한 확장 기능을 적용할 수 있다.

SessionAttributeStore

@SessionAttribute 에 의해 지정된 모델은 자동으로 HTTP 세션에 저장됐다가 다음 요청에서 사용할 수 있다. 정확히 말하자면 SesionAttributeStore 인터페이스의 구현 클래스에 의해 저장됐다가 다시 참조할 수 있는 것이다. 디폴트로 등록된 SessionAttributeStore 의 구현 클래스가 HTTP 세션을 이용하는 DefaultSessionAttributeStore 이기 때문에 기본적으로 HTTP 세션에 저장된다. HTTP 세션은 잘 다루지 않으면 웹 애플리케이션 메모리 누수의 원인이 될 수 있다. 적절하게 SessionStatus 의 setComplete() 을 호출해주지 않으면, 필요 없는 모델 오브젝트가 메모리에 계속 남게 된다. 세션 타임아웃에 결려서 해당 사용자의 모든 세션 정보가 제거되기 전까지 계속 메모리를 잠식하게 된다. 문제는 아무리 코드를 잘 작성했다고 하더라도 사용자가 폼을 완료하기 전에 다른 페이지로 이동하는 작업이 반복되면 HTTP 세션에는 모델 오브젝트가 쌓일 수 있다는 점이다. 또 다른 문제는 클러스터링을 통해 한 대 이상의 서버를 동시에 적용한 경우다. 서버다운 등으로 사용자가 다른 서버로 연결돼도 HTTP 세션 정보를 유지하기 위해 HTTP 세션 정보를 서버 사이에 복제하는 작업이 필요하다. WAS에 따라 다양한 기법이 있지만, 어쨌든 서버의 개수가 많아지면 HTTP 세션 정보 복제 작업이 상한한 부담이 될 수 있다. 

따라서 고성능을 요구하면서 대규모의 사용자를 처리해야 하는 서버라면 아예 @SessionAttribute 를 적용하지 않아서 HTTP 세션의 사용을 피하는 방법을 선택할 수밖에 없다. 또는 다른 더 효율적이고 빠른 방식으로 세션 정보를 저장하도록 만들어야 한다. 예를 들면 대규모 클러스터 내에서 효과적으로 정보를 공유하는 데이터 그리드에 세션 정보를 저장하거나, 메모리에 부담을 주지 않도록 별도의 위치에 세션 정보를 저장하는 방법을 사용할 수 있다.

이런 경우 SessionAttributeStore 인터페이스를 구현해서 세션정보를 저장하는 방법을 바꿀 수 있다.

또는 HTTP 세션에 저장하지만 세션 타임아웃이 되기 전에라도 일정 시간 이상 저장된 모델 오브젝트를 자동으로 삭제하게 할 수도 있다. 

구현한 SessionAttributeStore 클래스는 빈으로 등록하고 AnnotationMethodHandlerAdapter 의 sessionAttributeStore 프로퍼티로 등록해주면 디폴트 구현을 대체할 수 있다.

WebArgumentResolver

컨트롤러 메소드의 파라미터로 사용할 수 있는 타입과 애노테이션의 종류는 20여 가지나 된다. 웬만한 HTTP 요청정보는 메소드 파라미터 선언을 통해 원하는 형태로 전달받을 수 있다.  하지만 필요하다면 이를 더 확장할 수도 있다. 이때 사용하는 것이 바로 WebArgumentResolver 다. 이 인터페이스를 구현하면 애플리케이션에 특화된 컨트롤러 파라미터 타입을 추가할 수 있다.

예를 들어보자. 로그인한 사용자에 대한 정보가 암호화돼서 쿠기에 encodedUserInfo 라는 이름으로 저장되어 있다고 해보자. 로그인한 사용자를 가져오려면 일단 쿠키에서 이 값을 꺼내서 사용자 정보로 다시 변환해주는 라이브러리를 호출해야 한다. 쿠키 값은 @CookieValue 애노테이션을 이용해 가져올 수 있으니 다음과 같이 메소드를 선언하고 인증정보 암호화 오브젝트를 DI 받아서 이 쿠키 값으로부터 사용자 정보를 돌려주는 메소드를 호출해야 할 것이다.

1
2
3
4
5
6
@Autowired
private UserService userService;
 
public void add(@CookieValue String encodedUserInfo) {
    User currentUser = userService.decodeUserInfo(encodedUserInfo);
}

매번 코드를 통해 사용자 정보를 가져오는 작업이 번거롭게 느껴진다면, WebArgumentResolver 를 이용해 쿠키 값으로부터 사용자 정보를 가져와 메소드 파라미터로 전달받도록 만들 수 있다. WebArgumentResolver 는 다음과 같은 매우 단순한 메소드 하나만 구현하면 된다.

1
2
3
4
5
6
7
8
9
10
package org.springframework.web.bind.support;
 
import org.springframework.core.MethodParameter;
import org.springframework.web.context.request.NativeWebRequest;
 
public interface WebArgumentResolver {
 
    Object UNRESOLVED = new Object();
    Object resolveArgument(MethodParameter methodParameter, NativeWebRequest webRequest) throws Exception;
}

메소드 파라미터 정보와 웹 요청 정보를 받아서 파라미터 타입과 애노테이션을 참고해 오브젝트를 생성할 수 있으면 이를 리턴하고, 아니라면 UNRESOLVED 를 돌려주면 된다. 이를 이용하면 간단히 다음과 같은 메소드 파라미터를 사용할 수 있다.

public void add(@Current User user) { ... }

WebArgumentResolver 에서 파라미터 타입이 User 이고 @CurrentUser 라는 애노테이션이 붙어 있다면 쿠키에 저장한 값을 꺼내서 이를 User 로 변환해주도록 만들면 된다.10줄 미만의 코드를 작성함으로써 애플리케이션의 로그인 사용자 정보를 가져오는 방법을 깔끔하게 만들 수 있다. 메소드도 간결해졌을 뿐 아니라, 이후에 현재 로그인된 사용자 정보를 가져오는 방법이 바뀌더라도 컨트롤러는 전혀 수정할 필요가 없게 된다.

이렇게 만든 WebArgumentResolver 구현 클래스는 빈으로 등록하고 AnnotationMethodHandlerAdapter 의 customArgumentResolver 또는 customArgumentResolvers 프로퍼티에 설정해주면 된다.

WebArgumentResolver 느 스프링 @MVC 의 가장 매력적인 확장 포인트다. 이를 잘 활용하면 매우 간결한 컨트롤러 메소드를 만들 수 있다. 스프링 MVC 는 자신만의 MVC 프레임워크를 만들게 해주는 기반 프레임워크라는 사실을 잘 느끼게 해주는 멋진 기능이다.

ModelAndViewResolver

ModelAndViewResolver 는 컨트롤러 메소드의 리턴 타입과 메소드 정보, 애노테이션 정보 등을 참고해서 ModelAndView 를 생성해주는 기능을 만들 수 있다. 스프링은 7가지 정도의 리턴 방식을 지원한다. 여기에 더해서 특별한 타입의 리턴 값 또는 메소드 레벨의 애노테이션 등을 이용해  ModelAndView 를 생성하는 방법을 추가할 수 있다. 다음과 같이 ModelAndViewResolver 인터페이스를 구현하면 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package org.springframework.web.servlet.mvc.annotation;
 
import java.lang.reflect.Method;
 
import org.springframework.ui.ExtendedModelMap;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.servlet.ModelAndView;
 
public interface ModelAndViewResolver {
 
    ModelAndView UNRESOLVED = new ModelAndView();
 
    ModelAndView resolveModelAndView(Method handlerMethod,
            Class handlerType,
            Object returnValue,
            ExtendedModelMap implicitModel,
            NativeWebRequest webRequest);
}

메소드 파라미터 중에서 implicitModel 은 @ModelAttribute 파라미터처럼 스프링이 자동으로 추가해둔 모델 오브젝트가 담겨 있는 맵이다. ModelAndViewResolver 를 구현한 클래스는 빈으로 등록하고 AnnotationMethodHandlerAdapter 의 customModelAndViewResolver 나 customModelAndViewResolvers 프로퍼티에 설정해주면 적용된다. ModelAndViewResolver 는 WebArgumentResolver 보다 상대적으로 활용도가 적긴 하지만 컨트롤러의 리턴정보에서 일정한 패턴을 발견할 수 있다면 유용하게 쓸 수 있다.



출처 - http://springsource.tistory.com/91




Posted by linuxism
,