由于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如何适配?
欢迎各位同仁帮忙解答 (编辑:李大同)
【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!
|