본문 바로가기
Project/jp

(9)HandlerMethodArgumentResolver 적용

by 김뚱 2018. 7. 21.

1. HandlerMethodArgumentResolver


HandlerMethodArgumentResolver는 스프링 3.1에서 추가된 메서드이다.

스프링 3.1 이전에는 WebArgumentResolver 라는 인터페이스였고 이후에 HandlerMethodArgumentResolver로 이름이 바뀌었다. 


HandlerMethodArgumentResolver의 역할은 스프링 사용 시, Controller에 들어오는 Parameter를 수정 또는 공통적으로 추가해주어야 하는 경우가 많은데 HandlerMethodArgumentResolver는 사용자 요청이 Controller에 도달하기 전에 Parameter들을 수정할 수 있도록 해준다.


예를 들어, 로그인한 사용자의 정보를 가져와야 하는데 이 정보들은 보통 세션(Session)에 담아놓고 사용한다. 

이를 사용하기 위해서는 세션(Session)에서 값을 꺼내와서 파라미터로 추가해야한다. 

이러한 작업을 여러번 반복해야하는 것은 비효율적이다. 때문에 HandlerMethodArgumentResolver를 사용하여 처리하는 것이 좋다.


스프링 3.1에서는 컨트롤러의 파라미터가 Map 형식이면 사용자정의 클래스가 아닌 스프링에서 기본으로 설정된 ArgumentResolver를

거치게 된다. 즉, HandlerMethodArgumentResolver는 컨트롤러의 Parameter가 Map형식이면 동작하지 않는다. 이는 web.xml에서 <mvc:annotation-driven></mvc:annotation-driven>을 통해 사용자정의 Map을 지정하여 사용할 수 있다.



2. Request에 담겨있는 Parameter들을 Map<String, Object> 형태로 담아주는 클래스를 생성한다.

내부적으로 Map을 하나 생성하고 그 Map에 모든 데이터를 담는 역할을 만들어 준다.

여기서 주의할 점은 Map을 상속받지 않는 것이다. Map을 상속받게 되면, 사용자가 작성하는 ArgumentResolver를 거치지 않는다.


>>>JP 프로젝트 ParamCollector


package jp.com.resolver;

import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

public class ParamCollector {

    Map<String, Object> map = new HashMap<String, Object>();

    public Object get(String key) {
        return map.get(key);
    }

    public void put(String key, Object value) {
        map.put(key, value);
    }

    public Object remove(String key) {
        return map.remove(key);
    }

    public boolean containsKey(String key) {
        return map.containsKey(key);
    }

    public boolean containsValue(Object value) {
        return map.containsValue(value);
    }

    public void clear() {
        map.clear();
    }

    public Set<Entry<String, Object>> entrySet() {
        return map.entrySet();
    }

    public Set<String> keySet() {
        return map.keySet();
    }

    public boolean isEmpty() {
        return map.isEmpty();
    }

    public void putAll(Map<? extends String, ? extends Object> m) {
        map.putAll(m);
    }

    public Map<String, Object> getMap() {
        return map;
    }

}



3. HandlerMethodArgumentResolver 인터페이스를 상속하여 메서드를 구현한다.

Controller에 클래스가 실행하기전에 실행되는 클래스이다. 

- supportsParameter : Resolver가 적용 가능한지 검사하는 역할
- resolveArgument : 파라미터와 기타 정보를 받아서 실제 객체를 반환

웹브라우저에서 요청을 하면 어떤 방식으로 요청되는지 복습해보자.
기본적으로 Post방식은 Form태그의 데이터는 Form data에서 데이터가 요청되면서 파라미터를 넘겨준다. 
Get 방식은 url 뒤에 querystring으로 요청을 하면서 파라미터를 넘겨준다.
그러나 Post 방식에서 form data를 view source로 보면 querystring으로 보낸다.

method type에는 post와 get 말고도 delete, put, options와 같은 것들이 더 있다. 이를 restful 방식이라고 한다. 

post와 get으로 데이터가 넘어올때 request에서 header부분에서 정보가 실려온다.
put과 delete는 body 부분에서 정보가 실려온다. 
즉, 각각 data를 받아오는 부분이 달라서 다음과 같이 각각 data를 받아준다.

String key = null;
String[] values = null;
while (enumeration.hasMoreElements()) {
key = (String) enumeration.nextElement();
values = request.getParameterValues(key);
if (values != null) {
collector.put(key, (values.length > 1) ? values : values[0]);
}
}

enumeration = session.getAttributeNames();
while (enumeration.hasMoreElements()) {
key = (String) enumeration.nextElement();
values = (String[]) session.getAttribute(key);
if (values != null) {
collector.put(key, (values.length > 1) ? values : values[0]);
}
}

그러면 Parameter 값은 요청한 request Parameter에만 받아주면 되는 것일까?
세션의 정보도 Parameter로 사용하기 때문에 같은 Parameter에 담아줘야 한다. 
때문에 위의 소스와 같이 session의 값도 담아주는 로직이 추가된 것이다.

>>>JP 프로젝트 ParamCollectorArgumentResolver

package jp.com.resolver;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;

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

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.Logger;
import org.springframework.core.MethodParameter;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;

import jp.com.utils.JsonUtil;
import net.minidev.json.JSONObject;

public class ParamCollectorArgumentResolver implements HandlerMethodArgumentResolver {

    private static final Logger log = Logger.getLogger(ParamCollectorArgumentResolver.class);
    private static final String JSON_BODY_ATTRIBUTE = "JSON_REQUEST_BODY";

    @Override
    public Object resolveArgument(MethodParameter prameter, ModelAndViewContainer mavContainer,
            NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
        // TODO Auto-generated method stub
        HttpServletRequest request = (HttpServletRequest) webRequest.getNativeRequest();
        HttpSession session = request.getSession();
        ParamCollector collector = new ParamCollector();
        Enumeration<?> enumeration = request.getParameterNames();

        String key = null;
        String[] values = null;
        while (enumeration.hasMoreElements()) {
            key = (String) enumeration.nextElement();
            values = request.getParameterValues(key);
            if (values != null) {
                collector.put(key, (values.length > 1) ? values : values[0]);
            }
        }
        
        String body = getRequestBody(webRequest);
        if (StringUtils.isNotEmpty(body)) {
            Gson gson = new Gson();
            Map<String, Object> map = new HashMap<String, Object>();
            map = gson.fromJson(body, map.getClass());
            collector.putAll(map);
        }

        enumeration = session.getAttributeNames();
        while (enumeration.hasMoreElements()) {
            key = (String) enumeration.nextElement();
            values = (String[]) session.getAttribute(key);
            if (values != null) {
                collector.put(key, (values.length > 1) ? values : values[0]);
            }
        }
        
        if (log.isDebugEnabled()) {
            log.debug(" Request Params \t: " + collector.getMap());
            log.debug(" Request Session \t: " + session.getAttribute("g_user_info"));
        }

        return collector;
    }

    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        // TODO Auto-generated method stub
        return ParamCollector.class.isAssignableFrom(parameter.getParameterType());
    }
    
    /**
     * 웹에서 Ajax 호출시 메소드가 'put'이거나 'delete'인 경우 데이터가 담겨오는 그릇이 다르므로
     * 따로 담아서 String형태로 리턴
     *
     * @param NativeWebRequest
     * @return String
     */
    private String getRequestBody(NativeWebRequest webRequest) {
        HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
        String body = (String) webRequest.getAttribute(JSON_BODY_ATTRIBUTE, NativeWebRequest.SCOPE_REQUEST);

        if (body == null) {
            try {
                body = IOUtils.toString(servletRequest.getInputStream());
                webRequest.setAttribute(JSON_BODY_ATTRIBUTE, body, NativeWebRequest.SCOPE_REQUEST);
                return body;
            } catch (IOException e) {
                throw new RuntimeException();
            }
        }

        return body;
    }

    private static Map<String, String> splitQuery(String query) throws UnsupportedEncodingException {
     Map<String, String> query_pairs = new LinkedHashMap<String, String>();
     String[] pairs = query.split("&");
     for (String pair : pairs) {
     int idx = pair.indexOf("=");
     query_pairs.put(URLDecoder.decode(pair.substring(0, idx), "UTF-8"), URLDecoder.decode(pair.substring(idx + 1), "UTF-8"));
     }
     return query_pairs;
    }
}



4. ParamCollectorArgumentResolver을 action-servlet.xml에 등록

<mvc:annotation-driven>
<mvc:argument-resolvers>
<bean class="jp.com.resolver.ParamCollectorArgumentResolver" />
</mvc:argument-resolvers>
</mvc:annotation-driven>



728x90
반응형

'Project > jp' 카테고리의 다른 글

(8)데이터베이스(Oracle) 연결  (0) 2018.07.20
(7)인터셉터(Interceptor) 설정하기  (0) 2018.07.19
(6)로그(log4j) 설정하기  (0) 2018.07.19
(5)web.xml 설정하기  (0) 2018.07.19
(4)pom.xml 설정하기  (0) 2018.07.14

댓글