본문으로 바로가기

※ HandlerMethodArgumentResolver에 관한 자세한 내용은 http://to-dy.tistory.com/57?category=700248 에서 확인할 수 있다.


1. CommandMap 클래스 생성

<annotation-driven/>을 선언하게 되면 HandlerMethodArgumentResolver는 컨트롤러의 파라미터가 Map 형식이면 안된다. 우리가 HandlerMethodArgumentResolver를 사용하여 기능을 만들었다고 해도 컨트롤러에서 Map형식으로 보내오면, 우리가 설정한 클래스가 아닌, Spring에서 기본적으로 설정된 ArgumentResolver를 거쳐가기 때문이다. 물론 <annotation-driven/>을 선언하지않으면 Map을 그대로 써도 된다. 하지만, Spring을 개발하면서 <annotation-driven/>을 선언하는 경우들이 생기기 때문에 우리는 Map 대신에 CommandMap 클래스를 만들어서 사용한다.


CommandMap.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
package tody.prjdoo.common.common;
 
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
 
public class CommandMap {
    
    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;
    }
 
}
cs

CommandMap 클래스는 내부적으로 Map을 하나 생성하고, 그 맵에 모든 데이터를 담는 역할을 한다. 중요한 것은 절대. Map을 상속받아서는 안된다. 많은 메서드들 때문에 어려워 보일텐데, 거의 대부분은 Map의 기본기능을 다시 호출하는 작업들이다. 여러곳에서 CommandMap을 Map과 똑같이 사용할 수 있게 만들어 두었다.



2. HandlerMethodArgumentResolver 작성

CustomMapArgumentResovler.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
package tody.prjdoo.common.resolver;
 
import java.util.Enumeration;
 
import javax.servlet.http.HttpServletRequest;
 
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 tody.prjdoo.common.common.CommandMap;
 
public class CustomMapArgumentResovler implements HandlerMethodArgumentResolver {
 
    @Override
    public Object resolveArgument(MethodParameter arg0, ModelAndViewContainer arg1, NativeWebRequest arg2,
            WebDataBinderFactory arg3) throws Exception {
        // TODO Auto-generated method stub
        CommandMap commandMap = new CommandMap();
        
       HttpServletRequest request = (HttpServletRequest) arg2.getNativeRequest();
       Enumeration<?> enumeration = request.getParameterNames();
        
       String key = null;
       String[] values = null;
       while(enumeration.hasMoreElements()){
           key = (String) enumeration.nextElement();
           values = request.getParameterValues(key);
           if(values != null){
               commandMap.put(key, (values.length > 1) ? values:values[0] );
           }
       }
       return commandMap;
    }
 
    @Override
    public boolean supportsParameter(MethodParameter arg0) {
        // TODO Auto-generated method stub
        return CommandMap.class.isAssignableFrom(arg0.getParameterType());
    }
 
}
cs

- 21행 : CommandMap 객체 생성한다.

- 28~30행 : request에 담겨있는 값을 iterator를 이용해서 하나씩 가져오는 로직이다.

- 32행 : request에 담겨있는 모든 key와 value를 commandMap에 저장한다.

- 35행 : 모든 파라미터가 담겨있는 commandMap을 반환한다.



3. CustomMapArgumentResovler 등록

action-servlet.xml에 아래의 CustomMapArgumentResovler를 등록하자.

<argument-resolvers> 태그를 이용해서 CustomMapArgumentResolver의 빈(bean)을 수동으로 등록한다.

1
2
3
4
5
    <annotation-driven>
        <argument-resolvers>
            <beans:bean class="tody.prjdoo.common.resolver.CustomMapArgumentResolver"></beans:bean>
        </argument-resolvers>
    </annotation-driven>
cs


action-servlet.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<?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: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">
 
    <!-- DispatcherServlet Context: defines this servlet's request-processing infrastructure -->
    
    <!-- Enables the Spring MVC @Controller programming model -->
    <annotation-driven>
        <argument-resolvers>
            <beans:bean class="tody.prjdoo.common.resolver.CustomMapArgumentResolver"></beans:bean>
        </argument-resolvers>
    </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:property name="order" value="1"/>
    </beans:bean>
    
    <context:component-scan base-package="tody" />
 
</beans:beans>
cs

- 13~17행 : CustomMapArgumentResolver를 등록한 부분이다.



4. Controller를 이용해 테스트

정상적으로 동작하는 지 테스트 해보자.


TestController.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package tody.prjdoo.common.controller;
 
import java.util.Iterator;
import java.util.Map.Entry;
 
import org.apache.log4j.Logger;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
 
import tody.prjdoo.common.common.CommandMap;
 
@Controller
public class TestController {
    
    Logger log = Logger.getLogger(this.getClass());
    
    @RequestMapping(value="/testMapArgumentResolver")
    public ModelAndView testMapArgumentResolver(CommandMap commandMap) throws Exception{
        ModelAndView mv = new ModelAndView("");
         
        if(commandMap.isEmpty() == false){
            Iterator<Entry<String,Object>> iterator = commandMap.getMap().entrySet().iterator();
            Entry<String,Object> entry = null;
            while(iterator.hasNext()){
                entry = iterator.next();
                log.debug("key : "+entry.getKey()+", value : "+entry.getValue());
            }
        }
        return mv;
    }
 
}
cs

- 18행 : 

- 19행 : CommandMap으로 파라미터를 받는다.

- 23행 : commandMap에 있는 모든 파라미터를 iterator를 이용하여 출력한다.



5. 실행

서버를 실행시켜 보자. 그럼 아래와 같이 설정한 페이지로 갈 것이다.


이제 주소창에 key=value식의 파라미터를 전송해보자.

ex) /testMapArgumentResolver?name=lovelytody

 -> key = name , value = lovelytody


404 Not Found 떴다고 당황하지 말자. 우린 jsp를 만들지 않았기때문에 이게 뜨는 게 정상이다.

우리는 이거보다 console창을 주목해야 한다.

4번째 줄에 key : name, value : loveltody 를 볼 수 있다. 우리가 get방식으로 보낸 name이라는 key와 lovelytody라는 value이다.

이렇게 뜨면, CustomMapArgumentResolver가 정상적으로 등록된 것이다.


두 개의 키와 두 개의 값을 전송해보자.

ex) /testMapArgumentResovler?aaa=hello&bbb=world

console창을 확인하자.


정상적으로 들어오는 것을 확인할 수 있다.


CustomMapArgumentResolver 클래스를 만듦으로써, 클라이언트들이 전송한 데이터들이 request에 담겨서 서버로 전송되고, 서버에서는 그 데이터를 DB에 입력하거나 수정할 수 있게 된다. 주로 게시판에 많이 사용될 것 같다. 나는 학원에서도, 그리고 주로 VO를 써왔는데 이런 형식은 처음이라 신기했다. 뭐가 더 쉬운거냐, 에 대해서는 둘 다 비슷하다. 고 답할 수 있고, 뭐가 더 좋냐, 에 대해서는 사용하는 곳에 따라 달라질 것 같다. 라고 답할 수 있다. 헤헤 사실 나도 잘 모르겠다!


참고한 블로그 :

http://addio3305.tistory.com