MappingJackson2XmlView
它主要处理:
public static final String DEFAULT_CONTENT_TYPE = "application/xml";
大致逻辑是同上。只不过它用的是XmlMapper而已~~~
AbstractPdfView
处理PDF:"application/pdf"。依赖jar是com.lowagie
MarshallingView
Marshaller在国内使用非常少,忽略
AbstractXlsView
这个依赖于Apache的POI库,处理Excel等。
Spring MVC 中对于输出格式为pdf和xsl的view,提供了两个abstract的view类供继承分别为AbstractPdfView和AbstractXlsView。
AbstractFeedView
和com.rometools包的WireFeed有关,忽略。
FastJsonJsonView
它不是位于Spring包内,位于aliabba包内。因为它也是一个json视图,所以没有太多可说的:
public class FastJsonJsonView extends AbstractView { public static final String DEFAULT_CONTENT_TYPE = "application/json;charset=UTF-8"; // 这个是专门处理jsonp的 public static final String DEFAULT_JSONP_CONTENT_TYPE = "application/javascript"; private static final Pattern CALLBACK_PARAM_PATTERN = Pattern.compile("[0-9A-Za-z_\\.]*"); ... @Override protected void renderMergedOutputModel(Map<String, Object> model, // HttpServletRequest request, // HttpServletResponse response) throws Exception { Object value = filterModel(model); String jsonpParameterValue = getJsonpParameterValue(request); if (jsonpParameterValue != null) { JSONPObject jsonpObject = new JSONPObject(jsonpParameterValue); jsonpObject.addParameter(value); value = jsonpObject; } ByteArrayOutputStream outnew = new ByteArrayOutputStream(); // 它依赖的是这个静态方法,把value值写进去的~~~~ int len = JSON.writeJSONString(outnew, // fastJsonConfig.getCharset(), // value, // fastJsonConfig.getSerializeConfig(), // fastJsonConfig.getSerializeFilters(), // fastJsonConfig.getDateFormat(), // JSON.DEFAULT_GENERATE_FEATURE, // fastJsonConfig.getSerializerFeatures()); if (this.updateContentLength) { // Write content length (determined via byte array). response.setContentLength(len); } // Flush byte array to servlet output stream. ServletOutputStream out = response.getOutputStream(); outnew.writeTo(out); outnew.close(); out.flush(); } }
AbstractUrlBasedView
下面来到我们最为重要的一个分支:AbstractUrlBasedView。因为前面讲到过UrlBasedViewResolver这个分支是最重要的视图处理器,所以自然而然这个相关的视图也是最为重要的~~~
AbstractPdfStamperView
这个和AbstractPdfView有点类似,不过它出来相对较晚。因为它可以基于URL去渲染PDF,它也是个抽象类,Spring MVC并没有PDF的具体的视图实现~~
RedirectView(SmartView)
这个视图和SmartView一起讲解一下。首先SmartView是一个子接口,增加了一个方法:
// @since 3.1 接口出来较晚,但是RedirectView早就有了的~~~ public interface SmartView extends View { boolean isRedirectView(); }
顾名思义RedirectView是用于页面跳转使用的。重定向我们都不陌生,因此我们下面主要看看RedirectView它的实现:
重定向在浏览器可议看到两个毫不相关的request请求。跳转的请求会丢失原请求的所有数据,一般的解决方法是将原请求中的数据放到跳转请求的URL中这样来传递,下面来看看RediectView是怎么优雅的帮我们解决这个问题的~~~
我们的重定向例子:
@GetMapping("/index") public Object index(Model model) { RedirectView redirectView = new RedirectView("/index.jsp"); redirectView.setContextRelative(true); //因为我们希望加上ServletContext 所以这个设置为true 并且以/打头 redirectView.setHttp10Compatible(false); //不需要兼容http1.0 所以http状态码一般返回303 // 给些参数 最终会拼接到URL后面去~ model.addAttribute("name", "fsx"); model.addAttribute("age", 18); return redirectView; }
效果如下:
源码分析:
public class RedirectView extends AbstractUrlBasedView implements SmartView { private static final Pattern URI_TEMPLATE_VARIABLE_PATTERN = Pattern.compile("\\{([^/]+?)\\}"); private boolean contextRelative = false; // 是否兼容http1.0 private boolean http10Compatible = true; private boolean exposeModelAttributes = true; // 如果你不设置,默认就是ISO-8859-1 @Nullable private String encodingScheme; @Nullable private HttpStatus statusCode; private boolean expandUriTemplateVariables = true; // 当设置为@code true时,将追加当前URL的查询字符串,从而传播到重定向的URL。 private boolean propagateQueryParams = false; @Nullable private String[] hosts; // 此处exposePathVariables设置为了true public RedirectView() { setExposePathVariables(false); } // 此处需要注意的是:给定的URL将被视为相对于Web服务器,而不是相对于当前Servletcontext public RedirectView(String url) { super(url); setExposePathVariables(false); } // contextRelative:true表示为将URL解释为相对于当前ServletContext上下文 它的默认这是false public RedirectView(String url, boolean contextRelative) { super(url); this.contextRelative = contextRelative; setExposePathVariables(false); } ... // 配置与应用程序关联的一个或多个主机。所有其他主机都将被视为外部主机。 public void setHosts(@Nullable String... hosts) { this.hosts = hosts; } // 显然此复写 永远返回true @Override public boolean isRedirectView() { return true; } // 父类ApplicationObjectSupport的方法 // 此视图并不要求有ApplicationContext @Override protected boolean isContextRequired() { return false; } // 这个就是吧Model里的数据 转换到 request parameters去~ @Override protected void renderMergedOutputModel(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws IOException { // 构建目标URL,若以/开头并且contextRelative=true,那就自动会拼上getContextPath(request)前缀 否则不拼 // encoding以自己set的为准,否则以request的为准,若都为null。那就取值:WebUtils.DEFAULT_CHARACTER_ENCODING // 2、从当前request里面拿到UriVariables,然后fill到新的url里面去~ // 3、把当前request的url后的参数追加到新的url后面(默认是不会追加的~~~) 把propagateQueryParams属性值set为true就会追加了~~ // 4、exposeModelAttributes默认值是true,会吧model里的参数都合理的拼接到URL后面去~~~(这步非常重要,处理逻辑也是较为复杂的) // 注意Bean的名字必须叫RequestContextUtils.REQUEST_DATA_VALUE_PROCESSOR_BEAN_NAME 否则此处也不会执行的~~~ String targetUrl = createTargetUrl(model, request); // 它主要是找Spring容器里是否有`RequestDataValueProcessor`的实现类,然后`processUrl`处理一下 // 备注Spring环境默认没有它的实现,但是`Spring Security`对他是有实现的。比如大名鼎鼎的:`CsrfRequestDataValueProcessor` targetUrl = updateTargetUrl(targetUrl, model, request, response); // Save flash attributes // 此处因为request.getAttribute(DispatcherServlet.OUTPUT_FLASH_MAP_ATTRIBUTE)拿到的Map都是空的,所以此处也不会像里放了 // FlashMap主要是用来解决`post/redrect/get`问题的,而现在都是ajax所以用得很少了~但Spring3.1之后提出了这个方案还是很优秀的 RequestContextUtils.saveOutputFlashMap(targetUrl, request, response); // Redirect sendRedirect(request, response, targetUrl, this.http10Compatible); } protected void sendRedirect(HttpServletRequest request, HttpServletResponse response, String targetUrl, boolean http10Compatible) throws IOException { // 这个isRemoteHost很有意思。若getHosts()为空,就直接返回false了 // 然后看它是否有host,若没有host(相对路径)那就直接返回false // 若有host再看看这个host是否在我们自己的getHosts()里面,若在里面也返回fasle(表示还是内部的嘛) // 只有上面都没有return 就返回true // 比如此处值为:/demo_war_war/index.jsp String encodedURL = (isRemoteHost(targetUrl) ? targetUrl : response.encodeRedirectURL(targetUrl)); // 这里是兼容Http1.0的做法 看一下即可~~~ if (http10Compatible) { HttpStatus attributeStatusCode = (HttpStatus) request.getAttribute(View.RESPONSE_STATUS_ATTRIBUTE); if (this.statusCode != null) { response.setStatus(this.statusCode.value()); response.setHeader("Location", encodedURL); } else if (attributeStatusCode != null) { response.setStatus(attributeStatusCode.value()); response.setHeader("Location", encodedURL); } else { // Send status code 302 by default. // 大部分情况下我们都会走这里,所以我们看到的Http状态码都是302~~~~ response.sendRedirect(encodedURL); } } // Http1.1 else { // getHttp11StatusCode:若我们自己指定了status就以指定的为准 // 否则看这里有没有:request.getAttribute(View.RESPONSE_STATUS_ATTRIBUTE) // 最后都没有,就是默认值HttpStatus.SEE_OTHER 303 HttpStatus statusCode = getHttp11StatusCode(request, response, targetUrl); response.setStatus(statusCode.value()); response.setHeader("Location", encodedURL); } } }
备注:若你方法只是:redirect:xxx这种形式,最终都会转换成一个RedirectView,所以不再去单独说明。参见:ViewNameMethodReturnValueHandler有这个转化过程
这样整个RedirectView就算是看完了。刚到这,就有小伙伴问:如何重定向到POST请求?
what还能这么玩?于是乎 我研究了一番:不可能
我在想为何为问出这样的问题呢?302属于浏览器客户端的行为,咋可能发POST请求呢?原来我百度了一下,是网上有不少误导性的文章,比如:
纠正:exposeModelAttributes属性表示是否吧model里的值拼接到URL后面,默认是true会拼接的。若你改成fasle,最多也就是不拼接而已,浏览器还是会给你发送一个GET请求的。
关于Spring MVC中的Flash Attribute,可参考文章:
Spring MVC Flash Attribute 的讲解与使用示例
但其实现在的ajax承担了很大一部分原来的工作,几乎没有post/redirect/get这种问题了~~~
提问:重定向传值普通值我们好解决,但如果是一个对象呢?比如User对象里面有很多属性?
方案一:序列化成json串传递
方案二:使用RedirectAttributes#addFlashAttribute + @ModelAttribute的方式(具体做法小伙伴们可议尝试尝试,其原理是基于FlashMapManager和FlashMap的)
提示一点,方案二默认是基于sesson的,所以分布式环境需谨慎使用。
其实像这种重定向还需要传大量数据的方案,一般本身就存在问题,建议遇上此问题的选手多思考,是否合理???
AbstractTemplateView
关于模版引擎渲染的抽象。它主要做两件事:
public abstract class AbstractTemplateView extends AbstractUrlBasedView { @Override protected final void renderMergedOutputModel( Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception { //1、exposeRequestAttributes,通过request.getAttributeNames()把请求域里面的attr都暴露出去 //2、exposeSessionAttributes,session.getAttributeNames()把session域里面所有的attr都暴露出去 //3、exposeSpringMacroHelpers,把RequestContext暴露出去(上两个默认值都是false,这个默认值是true) ... renderMergedTemplateModel(model, request, response); } // 模版方法 各个模版自己去实现~~~ protected abstract void renderMergedTemplateModel( Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception; }