现在的位置: 首页 > 综合 > 正文

restful转jsonp接口的filter实现

2013年10月01日 ⁄ 综合 ⁄ 共 7141字 ⁄ 字号 评论关闭

 

     由于javaScript的限制,不能跨域post数据,因此jsonp接口都是get请求,所有如果有些restful的Post/Delete/Put请求想要实现jsonp接口,那么就必须调用jsonp接口并且以get请求的方式去实现。但是在实际的代码中,程序员只希望维护一套代码逻辑,以便避免同一问题的两次修改,同时可以提高代码的可重用性,因此可以使用Spring MVC的filter来实现restful转jsonp。

    具体的思路是建立jsonp的filter——JsonpFilter,对所有的连接都进行mapping

 <filter> 

        <filter-name>JsonpHttpMethodFilter</filter-name>  
        <filter-class>com.jeremyxu.xinterface.filter.JsonpHttpMethodFilter</filter-class>  
    </filter>
    <filter-mapping>  
        <filter-name>JsonpHttpMethodFilter</filter-name>  
        <servlet-name>restfulServlet</servlet-name>
</filter-mapping>

//如上,首先定义了filter,其次定义了filter与Servlet-name 的映射关系。

    <servlet>
        <servlet-name>restfulServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>/WEB-INF/rest-servlet.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>restfulServlet</servlet-name>
        <url-pattern>/</url-pattern>

    </servlet-mapping>

//其次,定义了Servlet 的配置文件所在路径,启动顺序,以及它与url的mapping关系。

    那么在请求过来的时候,首先调用JsonpFilter的doFilter方法,在该方法中实现了对HttpServletRequest请求的包装,这里我们自己定义了一个类HttpMethodRequestWrapper methodwrapper,它继承了HttpServletRequestWrapper类,而HttpServletRequestWrapper类又实现了HttpServletRequest的接口。我们在HttpMethodRequestWrapper中中定义了String method和String
jsonpString两个字段,并分别用url中的表示需要转换成的method方法(_method 字段值)和表示需要post的字符串内容 初始化method、jsonpString两个字段。

同时,我们分别为它们重载了HttpServletRequest 的public String getMethod()和Public ServletInputStream getInputStream方法。

    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {

        String paramValue = request.getParameter(this.methodParam);
        String jsonString = request.getParameter(this.jsonStringParam);
        JsonpResponseWrapper responseWrapper = new JsonpResponseWrapper(response);
        if ("GET".equals(request.getMethod()) && !StringUtils.isEmpty(paramValue)) {
            String method = paramValue.toUpperCase(Locale.ENGLISH);
            HttpServletRequest requestWrapper = new HttpMethodRequestWrapper(request, method, jsonString);           
            filterChain.doFilter(requestWrapper, responseWrapper);
        }
        else {
            filterChain.doFilter(request, responseWrapper);
        }
    }

//如上定义了filter对于request的封装

private class HttpMethodRequestWrapper extends HttpServletRequestWrapper {

        private
final String method;
        private final String jsonString;

        
        private String encoding = null;

        public HttpMethodRequestWrapper(HttpServletRequest request, String method, String jsonString) {
            super(request);
            this.method = method;            
            this.jsonString = jsonString == null ? "" : jsonString;
        }

        @Override   //在这里重载了HttpServletRequest的获取Method的方法
        public String getMethod()
{
            return this.method;
        }
        
        @Override   
//在这里重载了HttpServletRequest的获取InputStream的方法

        public ServletInputStream getInputStream ()
            throws IOException
{
        
            final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(
                    StringUtils.isEmpty(encoding)? jsonString.getBytes() : jsonString.getBytes(encoding));

            ServletInputStream inputStream = new ServletInputStream() {
                public int read ()
                    throws IOException {
                    return byteArrayInputStream.read();
                }
            };
            return inputStream;
        }
    }

//如上定义了HttpMethodRequestWrapper对于getMethod和getInputStream方法的重载,注意变量和方法的public/private属性

这样当JonpFilter执行完之后,会根据methodwrapper 中getMethod()方法的返回值去找到对应的 mapping关系,进而执行对应的代码段;而是post/put/(delete)方法中需要传入的字符串也可以根据getInputStream获取到。这样就实现了jsonp接口。

至于在jsonp接口的返回值中包装上callback字符串(如callback{"This is the return String!"}),就需要用到Spring MVC中Model和View相关的知识了。我们在DispatchServlet的配置文件(web.xml中标注的文件,非web.xml)/(如果为标注,则为Servlet同名的xml文件)中注明了其ViewResolver的配置。如下

    <bean
        class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver">
        <property name="favorPathExtension" value="true" />
        <property name="order" value="1" />
        <property name="mediaTypes">
            <map>
                <entry key="json" value="application/json" />                          //表示如果请求后缀有.json时,contentType为application/json
                <entry key="jsonp" value="application/javascript" />              //表示如果请求后缀有.jsonp时,contentType为application/javascript
            </map>
        </property>
        <property name="defaultViews">
            <list>
                <bean class="com.jeremyxu.xinterface.filter.CloudSyncMappingJacksonView"/>      //其类中定义了DEFAULT_CONTENT_TYPE = "application/json";
                <bean class="com.jeremyxu.xinterface.filter.MappingJacksonJsonpView" />             //其类中定义了DEFAULT_CONTENT_TYPE = "application/javascript";
            </list>
        </property>
        <property name="defaultContentType" value="application/json" />                //表示如果请求的Header中没有定义contentType,那么默认为application/json
    </bean>

如上,定义了defaultViews为CloudSyncMappingJacksonView和MappingJacksonJsonpView两个, 它们都继承了org.springframework.web.servlet.view.json.MappingJacksonJsonView。DispatchServlet在对Controller生成的Model进行处理的时候,会调用ViewResolver所对应的View类(这里为CloudSyncMappingJacksonView或MappingJacksonJsonpView)的render方法

    @Override
    public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
        String callback = request.getParameter(callBackParam);
        
        if( StringUtils.isEmpty(callback) ) {
             super.render(model, request, response);
        } else {
            
            if( response instanceof JsonpResponseWrapper ) {
                int status = ((JsonpResponseWrapper) response).getHttpStatus();
                 if( processedStatus.contains(status) ) {      //这里表示请求被处理
                     response.setStatus(HttpStatus.OK.value());
                 } else if ( noContentStatus.contains(status) ) {   //这里表示返回304请求
                     super.render(model, request, response);
                     return;
                 }
            }
            
            response.getOutputStream().write(new String(callback + "(").getBytes());
            super.render(model, request, response);         //把请求处理的响应内容写入到callback(..)里面
            response.getOutputStream().write(new String(");").getBytes());
            response.setContentType("application/javascript");
        }
    }

最后,关于如何设置Controller的Model,请注意:Model其实质上就是一个Map,可以往其中set进所有想要的数据,它的传入和传出方式比较特殊。通常,

都是在Controller的RequestMapping所对应的方法中提供一个Model的形参,并在方法中调用

    Model addAttribute(String attributeName, Object attributeValue);

    Model addAttribute(Object attributeValue);

    Model addAllAttributes(Collection<?> attributeValues);

    Model addAllAttributes(Map<String, ?> attributes);

    Model mergeAttributes(Map<String, ?> attributes);

等方法往其中set值

其实对于Controller的返回值,有如下规定:

返回一个ModelAndView,其中Model是一个Map,里面存放的是一对对的键值对,其可以直接在页面上使用,View是一个字符串,表示的是某一个View的名称
返回一个字符串,这个时候如果需要给页面传值,可以给方法一个Map参数,该Map就相当于一个Model,往该Model里面存入键值对就可以在页面上进行访问了即上面所举例子
返回一个View对象
返回一个Model也就是一个Map,这个时候将解析默认生成的view name,默认情况view name就是方法名。类似于上面标红内容
什么也不返回,这个时候可以在方法体中直接往HttpServletResponse写入返回内容,否则将会由RequestToViewNameTranslator来决定
任何其他类型的对象。这个时候就会把该方法返回类型对象当做返回Model模型的一个属性返回给视图使用,这个属性名称可以通过在方法上给定@ModelAttribute注解来指定,否则将默认使用该返回类名称作为属性名称。

疑问:如果返回的字符串为空怎么办?ViewResolver如何适配?

欢迎各位同仁帮忙解答

抱歉!评论已关闭.