restful转jsonp接口的filter实现


由于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,response);
return;
}
}

response.getOutputStream().write(new String(callback + "(").getBytes());
super.render(model,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如何适配?

欢迎各位同仁帮忙解答

相关文章

文章浏览阅读2.4k次。最近要优化cesium里的热力图效果,浏览...
文章浏览阅读1.2w次,点赞3次,收藏19次。在 Python中读取 j...
文章浏览阅读1.4k次。首字母缩略词 API 代表应用程序编程接口...
文章浏览阅读802次,点赞10次,收藏10次。解决一个JSON反序列...
文章浏览阅读882次。Unity Json和Xml的序列化和反序列化_uni...