【小家Spring】Spring MVC容器的web九大组件之---ViewResolver源码详解---视图View详解(中)

本文涉及的产品
容器镜像服务 ACR,镜像仓库100个 不限时长
简介: 【小家Spring】Spring MVC容器的web九大组件之---ViewResolver源码详解---视图View详解(中)

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;
    }


效果如下:


image.png


源码分析:


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请求呢?原来我百度了一下,是网上有不少误导性的文章,比如:


image.png


纠正: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;
}

相关文章
|
1月前
|
XML Java 数据格式
spring基础之常用组件
spring基础之常用组件
|
3月前
|
负载均衡 Java 开发者
【分布式】Spring Cloud 组件综述
【1月更文挑战第25天】【分布式】Spring Cloud 组件综述
|
3月前
|
Dubbo Java 应用服务中间件
微服务框架(十六)Spring Boot及Dubbo zipkin 链路追踪组件埋点
此系列文章将会描述Java框架Spring Boot、服务治理框架Dubbo、应用容器引擎Docker,及使用Spring Boot集成Dubbo、Mybatis等开源框架,其中穿插着Spring Boot中日志切面等技术的实现,然后通过gitlab-CI以持续集成为Docker镜像。 本文第一部分为调用链、OpenTracing、Zipkin和Jeager的简述;第二部分为Spring Boot及Dubbo zipkin 链路追踪组件埋点
|
1月前
|
SQL 存储 数据库
基于Web技术的在线考试系统的设计与实现(论文+源码)_kaic
基于Web技术的在线考试系统的设计与实现(论文+源码)_kaic
|
1月前
|
Java 关系型数据库 MySQL
基于Web的影院信息管理系统设计与实现(论文+源码)_kaic
基于Web的影院信息管理系统设计与实现(论文+源码)_kaic
|
7天前
|
前端开发 Java Spring
数据之桥:深入Spring MVC中传递数据给视图的实用指南
数据之桥:深入Spring MVC中传递数据给视图的实用指南
25 3
|
1月前
|
SpringCloudAlibaba Java 持续交付
【Springcloud Alibaba微服务分布式架构 | Spring Cloud】之学习笔记(一)基础知识+各个组件介绍+聚合父工程创建
【Springcloud Alibaba微服务分布式架构 | Spring Cloud】之学习笔记(一)基础知识+各个组件介绍+聚合父工程创建
79 1
|
1月前
|
小程序 前端开发 定位技术
【微信小程序】-- 常用视图容器类组件介绍 -- view、scroll-view和swiper(六)
【微信小程序】-- 常用视图容器类组件介绍 -- view、scroll-view和swiper(六)
|
1月前
|
Java 开发者 容器
【Java】深入了解Spring容器的两个关键组件
【Java】深入了解Spring容器的两个关键组件
10 0
|
1月前
|
存储 测试技术 数据库
基于WEB的院校课程管理系统设计与实现(论文+源码)_kaic
基于WEB的院校课程管理系统设计与实现(论文+源码)_kaic