본문 바로가기
  • 실행력이 모든걸 결정한다
Spring Series/Spring Framework

[Spring] Controller의 종류와 다양한 전략

by 김코더 김주역 2022. 7. 16.
반응형

1. Controller의 종류

1) Servlet

- javax.servlet.Servlet을 상속한 클래스를 컨트롤러로 사용한다.

@Component("/home") // bean 이름을 이용한 핸들러 매핑 방식 이용
public class HomeServlet extends HttpServlet {
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        ...
    }
}

- Servlet을 DispatcherServlet에 연결해주기 위해 SimpleServletHandlerAdapter가 bean으로 등록되어 있어야 한다.

- 서블릿 클래스 코드를 유지하면서 bean으로 등록할 수 있다는 것이 장점인데, 이렇게 되면 단계적으로 서블릿에서 담당했던 기능을 다른 종류의 컨트롤러로 이전할 수 있게 된다.

- 서블릿이 컨트롤러 bean으로 등록된 경우에는 init(), destory()와 같은 생명주기 메소드가 자동으로 호출되지 않기 때문에 생명주기 메소드를 xml 또는 어노테이션 설정을 이용하여 따로 지정해줘야 한다.

- 메소드에서 리턴은 따로 하지 않고 결과가 있다면 HttpServletResponse에 넣을 뿐이다.

 

 

2) HttpRequestHandler

- HttpRequestHandler 인터페이스를 구현한 클래스를 컨트롤러로 사용한다.

package org.springframework.web;

public interface HttpRequestHandler {
    void handleRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException;
}

- Servlet처럼 동작하는 컨트롤러를 만들거나 HTTP 프로토콜 기반 서비스를 만들 때 사용된다.

- RMI(Remote Method Invocation)를 이용한 서비스를 컨트롤러로 등록할 수도 있다.

※ RMI 서비스로는 HTTP Invoker, Hessian, Burlap 등이 있다.

- HttpRequestHandler을 DispatcherServlet에 연결해주기 위한 HttpRequestHandlerAdapter은 이미 bean으로 등록되어 있다.

 

 

3) Controller

- Spring MVC의 가장 대표적인 컨트롤러다.

- Controller 인터페이스를 구현한 클래스를 컨트롤러로 사용한다. 반복되는 코드들이 있다면 Controller를 구현한 기반 컨트롤러를 만들어놓고, 개별 컨트롤러가 이 기반 컨트롤러를 상속받는 방법을 사용할 수 있다.

package org.springframework.web.servlet.mvc;
...

public interface Controller {
    ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception;
}

- AbstractController은 대표적인 Controller의 구현 클래스로, 컨트롤러로서의 필수 기능들이 구현되어 있다. 세션에 대한 동기화 설정(synchronizeOnSession), HTTP 메소드 지정(supportedMethods), 캐시 설정(useExpiresHeader, useCacheControlHeader, useCacheControlNoStore, cacheSeconds)을 적용할 수 있다.

- Controller를 DispatcherServlet에 연결해주기 위한 SimpleControllerHandlerAdapter은 이미 bean으로 등록되어 있다.

※ SimpleControllerHandlerAdapter은 LastModified 인터페이스도 제공한다. 동일한 요청에 대한 결과가 마지막으로 변경된 시간이 이전 변경시간과 차이가 없을 경우에는 HTTP 304 Not Modified 코드를 클라이언트로 보내주는 로직을 getLastModified() 메소드로 구현하여 성능을 높일 수도 있다.

 

 

4) 어노테이션 방식의 컨트롤러

- AnnotationMethodHandlerAdapter 핸들러 어댑터가 사용된다. 컨트롤러의 타입에는 제한이 없는 대신, 클래스와 메소드에 어노테이션을 붙여서 컨트롤러에 대한 정보를 추가해줘야 한다. 이 핸들러 어댑터 역시 이미 bean으로 등록되어 있다.

- URL의 매핑을 컨트롤러 단위가 아닌 메소드 단위로 설정할 수 있기 때문에 효율이 좋다. 

- DefaultAnnotationHandlerMapping 핸들러 매핑과 함께 사용해야 한다.

@Controller
public class HomeController {
    
    @RequestMapping("/home")
    public String home(@RequestParam("name") String name, Model model) {
        ...
        return "home"; // view 처리 위임
    }
}

 

 

 

2. HandlerMapping 전략

- HandlerMapping은 URL과 요청 정보를 기준으로 어떤 컨트롤러를 사용할 것인지 결정하는 전략으로, HandlerMapping 인터페이스를 구현한다.

- 기본적으로 BeanNameUrlHandlerMapping과 DefaultAnnotationHandlerMapping 2개가 지정되어 있다.

 

1) BeanNameUrlHandlerMapping

- Bean의 이름에 URL 표현식을 넣는 전략이다. 표현식에는 *, **, ?와 같은 와일드카드를 포함할 수 있다.

- URL 표현식에 맞는 모든 URL 요청이 해당 컨트롤러 bean으로 매핑된다.

- 복잡한 애플리케이션에서는 잘 사용되지 않는다.

 

 

2) ControllerBeanNameHandlerMapping

- Bean의 id나 name으로 URL에 매핑하는 전략이다.

- 자동으로 bean id의 앞에 "/"를 붙여주기 때문에 URL 매핑에도 활용할 수 있다.

@Component("home") // "/home"에 매핑
public class HomeController implements Controller {
    ...
}

- urlPrefix와 urlSuffix 프로퍼티를 활용하여 bean 이름 앞뒤에 붙일 수 있는 내용을 지정할 수 있다.

<bean class="org.springframework.web.servlet.mvc.support.ControllerBeanNameHandlerMapping">
    <property name="urlPrefix" value="/app/" />
</bean>

※ 위와 같이 디폴트 전략이 아닌 특정 전략을 bean으로 등록하면 모든 디폴트 전략들은 무시된다.

 

 

3) ControllerClassNameHandlerMapping

- 클래스 이름을 URL에 매핑하는 전략이다.

- 기본적으로 클래스 이름이 통째로 URL이 되는데, "Controller"로 끝나는 클래스라면 "Controller"의 앞 부분만 URL이 된다.

※ 예) HomeController 컨트롤러 클래스는 "/home"으로 매핑된다.

 

 

4) SimpleUrlHandlerMapping

- URL과 컨트롤러 클래스의 매핑정보를 한곳에 모아놓는 전략이다.

- 매핑 정보는 아래 예시와 같이 SimpleUrlHanderMapping bean의 mappings 프로퍼티에 넣으면 된다. mappings 프로퍼티는 Properties 타입이다.

<bean class="org.springframework.web.servlet.handler.SimpleUrlHanderMapping">
    <property name="mappings">
        <props>
            <prop key="/home">homeController</prop>
            ...
        </props>
    </property>
</bean>

<bean id="homeController" ... />

참고로, mappings 프로퍼티가 Properties 타입이기 때문에 프로퍼티 파일 포맷을 이용하여 작성할 수도 있다.

<bean class="org.springframework.web.servlet.handler.SimpleUrlHanderMapping">
    <property name="mappings">
        <value>
            /home=homeController
            ...
        </value>
    </property>
</bean>

<bean id="homeController" ... />

 

 

5) DefaultAnnotationHandlerMapping

- @RequestMapping 어노테이션을 컨트롤러 클래스나 메소드에 지정하여 설정하는 전략이다. URL 뿐만 아니라 HTTP 메소드, 파라미터와 헤더 정보까지 매핑에 이용할 수 있다.

 

 

+ 추가) 핸들러 매핑 클래스들의 주요 공통 프로퍼티

(1) order

- 2개 이상의 핸들러 매핑을 사용하는 경우에 핸들러 매핑의 우선순위를 지정한다.

- 디폴트 핸들러라도 order 프로퍼티를 설정하려면 bean으로 따로 등록 해줘야 한다.

 

(2) defaultHandler

- URL을 매핑할 컨트롤러를 찾지 못했을 경우에 선택할 디폴트 컨트롤러를 지정한다.

- 404 에러를 반환하는 대신 안내 메시지 작성 등의 별도의 처리를 할 경우에 사용된다.

 

(3) alwaysUseFullPath

- Context path와 서블릿 매핑 path까지 반영한 전체 URL을 이용하여 URL 매핑을 진행할 경우에 사용된다. 프로퍼티 값을 true로 설정하면 된다.

 

(4) detectHandlerInAncestorContexts

- 기본적으로 매핑 대상 컨트롤러는 서블릿 컨텍스트에서만 찾는다. 프로퍼티 값을 true로 설정한다면 매핑 대상 컨트롤러의 탐색 범위를 루트 컨텍스트까지로 넓힌다.

 

 

 

3. HandlerInterceptor

1) HandlerInterceptor란?

- DispatcherServlet이 컨트롤러를 호출하기 전과 후에 호출되는 필터 역할을 한다. 이를 이용하여 요청이나 응답을 참조하고 가공할 수 있다.

- 핸들러 매핑은 DispatcherServlet으로부터 매핑 작업을 요청받으면 핸들러 실행 체인을 돌려주며, 이 핸들러 실행 체인에 하나 이상의 핸들러 인터셉터를 거쳐서 컨트롤러가 실행될 수 있도록 구성되어 있다.

- HttpServletRequest, HttpServletResponse, 컨트롤러 bean, ModelAndView과 예외 등을 제공받을 수 있기 때문에 서블릿 필터보다 더 정교하게 인터셉터를 만들 수 있다.

- HandlerInterceptor은 bean으로 등록되어 핸들러 매핑 bean의 interceptors 프로퍼티에 사용된다.

- HandlerInterceptor 인터페이스를 구현해서 만든다.

 

 

2) HandlerInterceptor 인터페이스의 메소드

(1) preHandle

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

- 컨트롤러가 호출되기 전에 실행되는 메소드로, 요청에 대한 정보를 가공할 때 쓰인다.

- 3번째 인자인 Object에는 핸들러 매핑이 찾아준 컨트롤러가 들어간다.

- true를 반환한다면 핸들러 실행 체인을 이어가고, false를 반환하면 핸들러 실행 체인을 중단한다. 핸들러 실행 체인이 중단되면 남은 인터셉터들과 컨트롤러는 실행되지 않는다.

 

(2) postHandle

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

- 컨트롤러를 실행한 후에 실행되는 메소드로, 후처리 작업에 쓰인다. 컨트롤러가 반환한 ModelAndView도 제공된다. 

 

(3) afterCompletion

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

- 뷰에서 최종 결과를 생성하는 작업까지 모두 완료된 후에 실행되는 메소드로, 리소스를 반환할 때 사용된다.

 

+ 추가)

- preHandle 메소드는 인터셉터의 등록 순서대로 실행되며, postHandler 메소드와 afterCompletion 메소드는 그 반대 순으로 실행된다.

- org.springframework.web.method.HandlerMethod의 getMethodAnnotation() 메소드를 활용하여 특정 어노테이션이 붙은 @RequestMapping 메소드에만 로직을 적용할 수 있다.

HandlerMethod hm = (HandlerMethod)handler; // Object handler;
if(hm.getMethodAnnotation(Audit.class)!=null) {...}

 

 

3) HandlerInterceptor의 적용

- 아까도 언급했듯이 HandlerInterceptor은 bean으로 등록되어 핸들러 매핑 bean의 interceptors 프로퍼티에 사용된다.

예시

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

<bean id="logInterceptor" ... />
<bean id="eventInterceptor" ... />

- Spring 3.0에서는 HandlerInterceptor를 URL 패턴을 이용하여 여러 핸들러 매핑에 적용하는 기능이 추가됐다.

- HandlerInterceptor를 사용하는 대신 컨트롤러에 AOP를 적용하는 방법도 있다. 하지만, 컨트롤러는 타입, 메소드명, 파라미터와 리턴 값이 너무 제각각이기 때문에 포인트컷 작성이 어렵다.

 

 

 

 

● 참고 자료 : 토비의 스프링 3.1 Vol.2

반응형

댓글