【小家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;
}

相关文章
|
2月前
|
XML Java 测试技术
《深入理解Spring》:IoC容器核心原理与实战
Spring IoC通过控制反转与依赖注入实现对象间的解耦,由容器统一管理Bean的生命周期与依赖关系。支持XML、注解和Java配置三种方式,结合作用域、条件化配置与循环依赖处理等机制,提升应用的可维护性与可测试性,是现代Java开发的核心基石。
|
2月前
|
XML Java 应用服务中间件
【SpringBoot(一)】Spring的认知、容器功能讲解与自动装配原理的入门,带你熟悉Springboot中基本的注解使用
SpringBoot专栏开篇第一章,讲述认识SpringBoot、Bean容器功能的讲解、自动装配原理的入门,还有其他常用的Springboot注解!如果想要了解SpringBoot,那么就进来看看吧!
377 2
|
2月前
|
JSON 前端开发 JavaScript
Mvc视图的4种提交方式
本文介绍了jQuery中get/post与ajax提交方式,以及原生JS通过请求头和FormData对象发送数据的方法。涵盖参数配置、请求类型、回调处理等要点,适用于表单及数据提交场景。
111 0
|
XML Java 数据格式
京东一面:spring ioc容器本质是什么? ioc容器启动的步骤有哪些?
京东一面:spring ioc容器本质是什么? ioc容器启动的步骤有哪些?
|
7月前
|
XML Java 数据格式
Spring IoC容器的设计与实现
Spring 是一个功能强大且模块化的 Java 开发框架,其核心架构围绕 IoC 容器、AOP、数据访问与集成、Web 层支持等展开。其中,`BeanFactory` 和 `ApplicationContext` 是 Spring 容器的核心组件,分别定位为基础容器和高级容器,前者提供轻量级的 Bean 管理,后者扩展了事件发布、国际化等功能。
|
2月前
|
算法 Java Go
【GoGin】(1)上手Go Gin 基于Go语言开发的Web框架,本文介绍了各种路由的配置信息;包含各场景下请求参数的基本传入接收
gin 框架中采用的路优酷是基于httprouter做的是一个高性能的 HTTP 请求路由器,适用于 Go 语言。它的设计目标是提供高效的路由匹配和低内存占用,特别适合需要高性能和简单路由的应用场景。
230 4
|
6月前
|
缓存 JavaScript 前端开发
鸿蒙5开发宝藏案例分享---Web开发优化案例分享
本文深入解读鸿蒙官方文档中的 `ArkWeb` 性能优化技巧,从预启动进程到预渲染,涵盖预下载、预连接、预取POST等八大优化策略。通过代码示例详解如何提升Web页面加载速度,助你打造流畅的HarmonyOS应用体验。内容实用,按需选用,让H5页面快到飞起!
|
6月前
|
JavaScript 前端开发 API
鸿蒙5开发宝藏案例分享---Web加载时延优化解析
本文深入解析了鸿蒙开发中Web加载完成时延的优化技巧,结合官方案例与实际代码,助你提升性能。核心内容包括:使用DevEco Profiler和DevTools定位瓶颈、四大优化方向(资源合并、接口预取、图片懒加载、任务拆解)及高频手段总结。同时提供性能优化黄金准则,如首屏资源控制在300KB内、关键接口响应≤200ms等,帮助开发者实现丝般流畅体验。
|
前端开发 JavaScript Shell
鸿蒙5开发宝藏案例分享---Web页面内点击响应时延分析
本文为鸿蒙开发者整理了Web性能优化的实战案例解析,结合官方文档深度扩展。内容涵盖点击响应时延核心指标(≤100ms)、性能分析工具链(如DevTools时间线、ArkUI Trace抓取)以及高频优化场景,包括递归函数优化、网络请求阻塞解决方案和setTimeout滥用问题等。同时提供进阶技巧,如首帧加速、透明动画陷阱规避及Web组件初始化加速,并通过优化前后Trace对比展示成果。最后总结了快速定位问题的方法与开发建议,助力开发者提升Web应用性能。