zoukankan      html  css  js  c++  java
  • SpringMVC HandlerMethodArgumentResolver自定义参数转换器 针对HashMap失效的问题

    自定义Spring MVC3的参数映射和返回值映射 + fastjson

    自定义Spring MVC3的参数映射和返回值映射 + fastjson首先说一下场景:在一些富客户端Web应用程序中我们会有比较多的Ajax调用,并且希望与服务器交互的数据需要是复杂的JSON对象。 fastjon是一个非常高效的JSON序列化和反序列化库,我希望我们输入的JSON串能通过fastjson直接反序列化为一个复杂的JavaBean对象,同时我的返回值能够能通过fastjson序列化为JSON串。所谓复杂的JavaBean就是,不仅仅只有一层属性,而是属性也是JavaBean的情况, 例如:
    public class FooBean {
        private String name;
    
        private Long id;
    
        private Date birthday;
    
        private List<Address> addresses;
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public Long getId() {
            return id;
        }
    
        public void setId(Long id) {
            this.id = id;
        }
    
        public Date getBirthday() {
            return birthday;
        }
    
        public void setBirthday(Date birthday) {
            this.birthday = birthday;
        }
    
        public List<Address> getAddresses() {
            return addresses;
        }
    
        public void setAddresses(List<Address> addresses) {
            this.addresses = addresses;
        }
    
        @Override
        public String toString() {
            return "FooBean{" +
                    "name='" + name + ''' +
                    ", id=" + id +
                    ", birthday=" + birthday +
                    ", addresses=" + addresses +
                    '}';
        }
    }
    public class Address {
        private String street;
        private int number;
    
        public String getStreet() {
            return street;
        }
    
        public void setStreet(String street) {
            this.street = street;
        }
    
        public int getNumber() {
            return number;
        }
    
        public void setNumber(int number) {
            this.number = number;
        }
    
        @Override
        public String toString() {
            return "Address{" +
                    "street='" + street + ''' +
                    ", number=" + number +
                    '}';
        }
    }

    当然,结构还可以再复杂,Adress对象里还可以又复杂JavaBean的属性。

    在SpringMVC3中我们可以把输入简单的映射为某个Action方法的参数, 例如:

    @RequestMapping(value="/someAction", method=RequestMethod.POST)
    public String processSubmit(FooBean fooBean, Model model) {
    // 利用fooBean
        return “views/some_page”;
    }

    用Spring MVC3, 我们可以把Form里的字段轻松的映射到JavaBean的属性。 Spring MVC3 提供了丰富的参数映射机制, 详细信息可以参见这里

    同时对于Spring MVC3默认的提供的映射机制不能涵盖的对象,我们可以通过扩展HandlerMethodArgumentResolver和HttpMessageConverter的机制来实现。

    HandlerMethodArgumentResolver对应输入, HttpMessageConverter对应输出

    假设对于上面的FooBean, 我们有这样一个JSON对象和它对应:

    var data = {
        name : "matianyi",
        id : 12345,
        birthday : "1983-07-01 01:12:12",
        addresses : [
            {
                street : "street1",
                number : 1
            },
            {
                street : "street2",
                number : 2
            }
        ]
    };

    Spring MVC3 本身也提供直接把JSON对象映射到JavaBean的功能,例如MappingJackson2HttpMessageConverter和MappingJackson2JsonView。

    在这里我们希望通过fastjson来实现序列化和反序列化。所以我们要自定义一个HandlerMethodArgumentResolver用来指定HttpServletRequest的Body映射到一个JavaBean。并且返回的JavaBean通过fastjson序列化。

    方法的定义是这样的:

    @RequestMapping(value = "/fastjson", method = RequestMethod.POST)
    public @ResponseBody FooBean fastjson2(@FastJson FooBean foo) {
        System.out.println(foo);
        return foo;
    }

    首先这里有个@FastJson的标注,这是我们为了让自己的HandlerMethodArgumentResolver能够识别这个参数是需要自己来处理而定义的一个Annotation

    @Target(ElementType.PARAMETER)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface FastJson {
    }

    然后就是定义一个FastJsonArgumentResolver,来对HttpServletRequest的body进行解析:

    public class FastJsonArgumentResolver implements HandlerMethodArgumentResolver {
        @Override
        public boolean supportsParameter(MethodParameter parameter) {
            return parameter.getParameterAnnotation(FastJson.class) != null;
        }
    
        @Override
        public Object resolveArgument(MethodParameter parameter,
                                      ModelAndViewContainer mavContainer,
                                      NativeWebRequest webRequest,
                                      WebDataBinderFactory binderFactory) throws Exception {
    
            HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
            // content-type不是json的不处理
            if (!request.getContentType().contains("application/json")) {
                return null;
            }
    
            // 把reqeust的body读取到StringBuilder
            BufferedReader reader = request.getReader();
            StringBuilder sb = new StringBuilder();
    
            char[] buf = new char[1024];
            int rd;
            while((rd = reader.read(buf)) != -1){
                sb.append(buf, 0, rd);
            }
    
            // 利用fastjson转换为对应的类型
            if(JSONObjectWrapper.class.isAssignableFrom(parameter.getParameterType())){
                return new JSONObjectWrapper(JSON.parseObject(sb.toString()));
            } else {
                return JSON.parseObject(sb.toString(), parameter.getParameterType());
            }
        }
    }

    在这里,我们只针对content-type是application/json的对象做处理,最后通过JSON.parseObject方法简单的把JSON串反序列化为指定的类型。

    这里有一个JSONObjectWrapper对象需要解释一下。 原本我是想如果Action方法的参数的类型是JSONObject这样的原始类型的话就直接利用JSON.parseObject(sb.toString())映射过去。 但是由于JSONObject实现了Map结果,所以Spring MVC3的默认处理器MapMethodProcessor会先起作用,这样就不能正常的映射成JSONObject对象了。 没有办法做了一个简单的JSONObject包装类,以使MapMethodProcessor不能对其进行处理。

    public class JSONObjectWrapper {
        private JSONObject jsonObject;
    
        public JSONObjectWrapper(JSONObject jsonObject) {
            this.jsonObject = jsonObject;
        }
    
        public JSONObject getJSONObject() {
            return jsonObject;
        }
    }

    这里顺便提一下,Spring MVC自己的HandlerMethodArgumentResolver有哪些,并且会以什么样的顺序执行呢?
    其实定义在RequestMappingHandlerAdapter里:

    /**
     * Return the list of argument resolvers to use including built-in resolvers
     * and custom resolvers provided via {@link #setCustomArgumentResolvers}.
     */
    private List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() {
        List<HandlerMethodArgumentResolver> resolvers = new ArrayList<HandlerMethodArgumentResolver>();
    
        // Annotation-based argument resolution
        resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), false));
        resolvers.add(new RequestParamMapMethodArgumentResolver());
        resolvers.add(new PathVariableMethodArgumentResolver());
        resolvers.add(new PathVariableMapMethodArgumentResolver());
        resolvers.add(new MatrixVariableMethodArgumentResolver());
        resolvers.add(new MatrixVariableMapMethodArgumentResolver());
        resolvers.add(new ServletModelAttributeMethodProcessor(false));
        resolvers.add(new RequestResponseBodyMethodProcessor(getMessageConverters()));
        resolvers.add(new RequestPartMethodArgumentResolver(getMessageConverters()));
        resolvers.add(new RequestHeaderMethodArgumentResolver(getBeanFactory()));
        resolvers.add(new RequestHeaderMapMethodArgumentResolver());
        resolvers.add(new ServletCookieValueMethodArgumentResolver(getBeanFactory()));
        resolvers.add(new ExpressionValueMethodArgumentResolver(getBeanFactory()));
    
        // Type-based argument resolution
        resolvers.add(new ServletRequestMethodArgumentResolver());
        resolvers.add(new ServletResponseMethodArgumentResolver());
        resolvers.add(new HttpEntityMethodProcessor(getMessageConverters()));
        resolvers.add(new RedirectAttributesMethodArgumentResolver());
        resolvers.add(new ModelMethodProcessor());
        resolvers.add(new MapMethodProcessor());
        resolvers.add(new ErrorsMethodArgumentResolver());
        resolvers.add(new SessionStatusMethodArgumentResolver());
        resolvers.add(new UriComponentsBuilderMethodArgumentResolver());
    
        // Custom arguments
        if (getCustomArgumentResolvers() != null) {
            resolvers.addAll(getCustomArgumentResolvers());
        }
    
        // Catch-all
        resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), true));
        resolvers.add(new ServletModelAttributeMethodProcessor(true));
    
        return resolvers;
    }

    在这里我们可以看到:

    • Spring MVC本身提供了非常丰富的HandlerMethodArgumentResolver实现。
    • HandlerMethodArgumentResolver是按顺序执行,当然为了性能Spring本身是有Cache的,一旦确定了某一个参数可以应用的HandlerMethodArgumentResolver,下次就不会再遍历这个List了。
    • 自定的HandlerMethodArgumentResolver会晚于Spring自己的被执行,这也是上面提到的JSONObject会被MapMethodProcessor先处理的原因。
    • Spring自己的JSON映射机制是通过RequestResponseBodyMethodProcessor + AllEncompassingFormHttpMessageConverter来实现的
    • 很不幸这是一个private方法, 你没有办法简单的改变Spring MVC的默认行为,除非你重写RequestMappingHandlerAdapter

    好了,有了FastJsonArgumentResolver, 接下来我们要让它生效:

    <mvc:annotation-driven>
        <mvc:argument-resolvers>
            <beans:bean class="org.springframework.samples.mvc.fastjson.FastJsonArgumentResolver"/>
        </mvc:argument-resolvers>
        <mvc:message-converters>
            <beans:bean class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter"/>
        </mvc:message-converters>
    </mvc:annotation-driven>

    就是个Spring的配置,这里就不多讲了。除了FastJsonArgumentResolver,我们还配置了FastJsonHttpMessageConverter来对返回值进行序列化。

    本来我是想自己写一个FastJsonHttpMessageConverter, 后来发现fastjson库里已经存在了, 我就不自己造轮子了。我们自己来看看实现吧,截取了一部分:

    @Override
    protected void writeInternal(Object obj, HttpOutputMessage outputMessage) throws IOException,
                                                                             HttpMessageNotWritableException {
        OutputStream out = outputMessage.getBody();
        String text = JSON.toJSONString(obj, features);
        byte[] bytes = text.getBytes(charset);
        out.write(bytes);
    }

    实现很简单, 就不详细说了。

    最后来看看如何通过Ajax调用上面的Action方法:

    var data = {
        name : "matianyi",
        id : 12345,
        birthday : "1983-07-01 01:12:12",
        addresses : [
            {
                street : "street1",
                number : 1
            },
            {
                street : "street2",
                number : 2
            }
        ]
    };
    var link = $(this);
    $.ajax({
        url:"/spring-sample/fastjson1",
        dataType:"json",
        type:"POST",
        contentType: "application/json",
        data : JSON.stringify(data),
        success : function(obj){
            console.log(obj);
        }
    });

    两点需要注意:

    • contentType: “application/json”
    • data : JSON.stringify(data)

    这样JavaScript的对象会被转换为JSON串,并且最为HttpRequest的BODY传给服务器。

  • 相关阅读:
    Java正则表达式进阶教程之构造方法
    command模式
    黑马程序员面向对象继承
    Spring+Quartz实现定时任务的配置方法
    NIO学习笔记3(UDP)
    java继承总结
    轻松快速掌握JAVA设计模式
    Struts2>OGNL 小强斋
    Struts2>标签 小强斋
    Struts2>fielderror显示处理 小强斋
  • 原文地址:https://www.cnblogs.com/daxin/p/3296493.html
Copyright © 2011-2022 走看看