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

相关文章
|
9天前
|
SQL Java 数据库连接
对Spring、SpringMVC、MyBatis框架的介绍与解释
Spring 框架提供了全面的基础设施支持,Spring MVC 专注于 Web 层的开发,而 MyBatis 则是一个高效的持久层框架。这三个框架结合使用,可以显著提升 Java 企业级应用的开发效率和质量。通过理解它们的核心特性和使用方法,开发者可以更好地构建和维护复杂的应用程序。
87 29
|
2月前
|
设计模式 前端开发 Java
步步深入SpringMvc DispatcherServlet源码掌握springmvc全流程原理
通过对 `DispatcherServlet`源码的深入剖析,我们了解了SpringMVC请求处理的全流程。`DispatcherServlet`作为前端控制器,负责请求的接收和分发,处理器映射和适配负责将请求分派到具体的处理器方法,视图解析器负责生成和渲染视图。理解这些核心组件及其交互原理,有助于开发者更好地使用和扩展SpringMVC框架。
63 4
|
3月前
|
前端开发 Java 开发者
Spring MVC中的请求映射:@RequestMapping注解深度解析
在Spring MVC框架中,`@RequestMapping`注解是实现请求映射的关键,它将HTTP请求映射到相应的处理器方法上。本文将深入探讨`@RequestMapping`注解的工作原理、使用方法以及最佳实践,为开发者提供一份详尽的技术干货。
215 2
|
3月前
|
负载均衡 算法 Java
除了 Ribbon,Spring Cloud 中还有哪些负载均衡组件?
这些负载均衡组件各有特点,在不同的场景和需求下,可以根据项目的具体情况选择合适的负载均衡组件来实现高效、稳定的服务调用。
187 5
|
4月前
|
JSON 前端开发 Java
SSM:SpringMVC
本文介绍了SpringMVC的依赖配置、请求参数处理、注解开发、JSON处理、拦截器、文件上传下载以及相关注意事项。首先,需要在`pom.xml`中添加必要的依赖,包括Servlet、JSTL、Spring Web MVC等。接着,在`web.xml`中配置DispatcherServlet,并设置Spring MVC的相关配置,如组件扫描、默认Servlet处理器等。然后,通过`@RequestMapping`等注解处理请求参数,使用`@ResponseBody`返回JSON数据。此外,还介绍了如何创建和配置拦截器、文件上传下载的功能,并强调了JSP文件的放置位置,避免404错误。
|
4月前
|
前端开发 Java 应用服务中间件
【Spring】Spring MVC的项目准备和连接建立
【Spring】Spring MVC的项目准备和连接建立
76 2
|
4月前
|
XML 前端开发 Java
Spring,SpringBoot和SpringMVC的关系以及区别 —— 超准确,可当面试题!!!也可供零基础学习
本文阐述了Spring、Spring Boot和Spring MVC的关系与区别,指出Spring是一个轻量级、一站式、模块化的应用程序开发框架,Spring MVC是Spring的一个子框架,专注于Web应用和网络接口开发,而Spring Boot则是对Spring的封装,用于简化Spring应用的开发。
305 0
Spring,SpringBoot和SpringMVC的关系以及区别 —— 超准确,可当面试题!!!也可供零基础学习
|
4月前
|
前端开发 Java
【案例+源码】详解MVC框架模式及其应用
【案例+源码】详解MVC框架模式及其应用
248 0
|
4月前
|
XML JSON API
ServiceStack:不仅仅是一个高性能Web API和微服务框架,更是一站式解决方案——深入解析其多协议支持及简便开发流程,带您体验前所未有的.NET开发效率革命
【10月更文挑战第9天】ServiceStack 是一个高性能的 Web API 和微服务框架,支持 JSON、XML、CSV 等多种数据格式。它简化了 .NET 应用的开发流程,提供了直观的 RESTful 服务构建方式。ServiceStack 支持高并发请求和复杂业务逻辑,安装简单,通过 NuGet 包管理器即可快速集成。示例代码展示了如何创建一个返回当前日期的简单服务,包括定义请求和响应 DTO、实现服务逻辑、配置路由和宿主。ServiceStack 还支持 WebSocket、SignalR 等实时通信协议,具备自动验证、自动过滤器等丰富功能,适合快速搭建高性能、可扩展的服务端应用。
243 3
|
3月前
|
开发框架 搜索推荐 数据可视化
Django框架适合开发哪种类型的Web应用程序?
Django 框架凭借其强大的功能、稳定性和可扩展性,几乎可以适应各种类型的 Web 应用程序开发需求。无论是简单的网站还是复杂的企业级系统,Django 都能提供可靠的支持,帮助开发者快速构建高质量的应用。同时,其活跃的社区和丰富的资源也为开发者在项目实施过程中提供了有力的保障。
151 62