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

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

前言


上篇文章已经重点讲解过了:ViewResolver视图解析器

【小家Spring】Spring MVC容器的web九大组件之—ViewResolver源码详解—视图解析器ViewResolver详解


SpringMVC用于处理视图最重要的两个接口是ViewResolver和View。ViewResolver的主要作用 是把一个逻辑上的视图名称解析为一个真正的视图,SpringMVC中用于把View对象呈现给客户端的 是View对象本身,而ViewResolver只是把逻辑视图名称解析为对象的View对象。View接口的主要 作用是用于处理视图,然后返回给客户端。


View


View是用于MVC交互的Web视图。实现负责呈现内容,并公开模型。单个视图可显示多个模型属性


视图实现可能差异很大,比如我们最基础的实现:JSP就是一种视图展示方式。当然还有后面的Jstl以及FreeMarker等。此接口旨在避免限制可能的实现范围

视图应该是bean(但不一定需要放进容器)。它们很可能被viewresolver实例化为bean。由于这个接口是无状态的,视图实现应该是线程安全的。

public interface View {
  // @since 3.0
  // HttpStatus的key,可议根据此key去获取。备注:并不是每个视图都需要实现的。目前只有`RedirectView`有处理
  String RESPONSE_STATUS_ATTRIBUTE = View.class.getName() + ".responseStatus";
  // @since 3.1  也会这样去拿:request.getAttribute(View.PATH_VARIABLES)
  String PATH_VARIABLES = View.class.getName() + ".pathVariables";
  // The {@link org.springframework.http.MediaType} selected during content negotiation
  // @since 3.2
  // MediaType mediaType = (MediaType) request.getAttribute(View.SELECTED_CONTENT_TYPE)
  String SELECTED_CONTENT_TYPE = View.class.getName() + ".selectedContentType";
  // Return the content type of the view, if predetermined(预定的)
  @Nullable
  default String getContentType() {
    return null;
  }
  // 这是最重要的 根据model里面的数据,request等  把渲染好的数据写进response里~
  void render(@Nullable Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception;
}


看看它的继承树:


image.png


可以看出来它只有两个分支:AbstractView和SmartView,而SmartView的唯一实现为:RedirectView并且它也继承自AbstractView。


我们可以粗略的认为:Spring MVC内置的所有的View都是AbstractView的子类


AbstractView


AbstractView实现了render方法,主要做的操作是将model中的参数和request中的参数全部都放到Request中,然后就转发Request就可以了


public abstract class AbstractView extends WebApplicationObjectSupport implements View, BeanNameAware {
  /** Default content type. Overridable as bean property. */
  public static final String DEFAULT_CONTENT_TYPE = "text/html;charset=ISO-8859-1";
  /** Initial size for the temporary output byte array (if any). */
  private static final int OUTPUT_BYTE_ARRAY_INITIAL_SIZE = 4096;
  // 这几个属性值,没有陌生的。在视图解析器章节里面都有解释过~~~
  @Nullable
  private String contentType = DEFAULT_CONTENT_TYPE;
  @Nullable
  private String requestContextAttribute;
  // "Static" attributes are fixed attributes that are specified in the View instance configuration
  // "Dynamic" attributes, on the other hand,are values passed in as part of the model.
  private final Map<String, Object> staticAttributes = new LinkedHashMap<>();
  private boolean exposePathVariables = true;
  private boolean exposeContextBeansAsAttributes = false;
  @Nullable
  private Set<String> exposedContextBeanNames;
  @Nullable
  private String beanName;
  // 把你传进俩的Properties 都合并进来~~~
  public void setAttributes(Properties attributes) {
    CollectionUtils.mergePropertiesIntoMap(attributes, this.staticAttributes);
  }
  ...
  @Override
  public void render(@Nullable Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
    // 合并staticAttributes、pathVars、model数据到一个Map里来
    // 其中:后者覆盖前者的值(若有相同key的话~~)也就是所谓的model的值优先级最高~~~~
    // 最终还会暴露RequestContext对象到Model里,因此model里可以直接访问RequestContext对象哦~~~~
    Map<String, Object> mergedModel = createMergedOutputModel(model, request, response);
    // 默认实现为设置几个响应头~~~
    // 备注:默认情况下pdf的view、xstl的view会触发下载~~~
    prepareResponse(request, response);
    // getRequestToExpose表示吧request暴露成:ContextExposingHttpServletRequest(和容器相关,以及容器内的BeanNames)
    // renderMergedOutputModel是个抽象方法 由子类去实现~~~~
    renderMergedOutputModel(mergedModel, getRequestToExpose(request), response);
  }
  //================下面是一些方法,父类提供  子类可以直接使用的方法==============
  // 一个temp输出流,缓冲区大小为4096  字节流
  protected ByteArrayOutputStream createTemporaryOutputStream() {
    return new ByteArrayOutputStream(OUTPUT_BYTE_ARRAY_INITIAL_SIZE);
  }
  // 把字节流写进response里面~~~
  protected void writeToResponse(HttpServletResponse response, ByteArrayOutputStream baos) throws IOException {
    // Write content type and also length (determined via byte array).
    response.setContentType(getContentType());
    response.setContentLength(baos.size());
    // Flush byte array to servlet output stream.
    ServletOutputStream out = response.getOutputStream();
    baos.writeTo(out);
    out.flush();
  }
  // 相当于如果request.getAttribute(View.SELECTED_CONTENT_TYPE) 指定了就以它为准~
  protected void setResponseContentType(HttpServletRequest request, HttpServletResponse response) {
    MediaType mediaType = (MediaType) request.getAttribute(View.SELECTED_CONTENT_TYPE);
    if (mediaType != null && mediaType.isConcrete()) {
      response.setContentType(mediaType.toString());
    }
    else {
      response.setContentType(getContentType());
    }
  }
  ...
}


该抽象类主要是提供了对render方法的模版实现,以及提供一些基础方法供给子类来使用,比如createTemporaryOutputStream()等等


AbstractJackson2View


这个是一个比较新的Viw(@since 4.1),它是基于Jackson渲染的视图。

//@since 4.1 
// Compatible with Jackson 2.6 and higher, as of Spring 4.3.
public abstract class AbstractJackson2View extends AbstractView {
  private ObjectMapper objectMapper;
  private JsonEncoding encoding = JsonEncoding.UTF8;
  @Nullable
  private Boolean prettyPrint;
  private boolean disableCaching = true;
  protected boolean updateContentLength = false;
  // 唯一构造函数,并且还是protected的~~
  protected AbstractJackson2View(ObjectMapper objectMapper, String contentType) {
    this.objectMapper = objectMapper;
    configurePrettyPrint();
    setContentType(contentType);
    setExposePathVariables(false);
  }
  ... // get/set方法
  // 复写了父类的此方法~~~   setResponseContentType是父类的哟~~~~
  @Override
  protected void prepareResponse(HttpServletRequest request, HttpServletResponse response) {
    setResponseContentType(request, response);
    // 设置编码格式,默认是UTF-8
    response.setCharacterEncoding(this.encoding.getJavaName());
    if (this.disableCaching) {
      response.addHeader("Cache-Control", "no-store");
    }
  }
  // 实现了父类的渲染方法~~~~
  @Override
  protected void renderMergedOutputModel(Map<String, Object> model, HttpServletRequest request,
      HttpServletResponse response) throws Exception {
    ByteArrayOutputStream temporaryStream = null;
    OutputStream stream;
    // 注意此处:updateContentLength默认值是false   所以会直接从response里面吧输出流拿出来   而不用temp流
    if (this.updateContentLength) {
      temporaryStream = createTemporaryOutputStream();
      stream = temporaryStream;
    }
    else {
      stream = response.getOutputStream();
    }
    Object value = filterAndWrapModel(model, request);
    // value是最终的从model中出来的~~~~这里就是把value值写进去~~~~
    // 先通过stream得到一个JsonGenerator,然后先writePrefix(generator, object)
    // 然后objectMapper.writerWithView
    // 最后writeSuffix(generator, object);  然后flush即可~
    writeContent(stream, value);
    if (temporaryStream != null) {
      writeToResponse(response, temporaryStream);
    }
  }
  // 筛选Model并可选地将其包装在@link mappingjacksonvalue容器中
  protected Object filterAndWrapModel(Map<String, Object> model, HttpServletRequest request) {
    // filterModel抽象方法,从指定的model中筛选出不需要的属性值~~~~~
    Object value = filterModel(model);
    // 把这两个属性值,选择性的放进container容器里面  最终返回~~~~
    Class<?> serializationView = (Class<?>) model.get(JsonView.class.getName());
    FilterProvider filters = (FilterProvider) model.get(FilterProvider.class.getName());
    if (serializationView != null || filters != null) {
      MappingJacksonValue container = new MappingJacksonValue(value);
      if (serializationView != null) {
        container.setSerializationView(serializationView);
      }
      if (filters != null) {
        container.setFilters(filters);
      }
      value = container;
    }
    return value;
  }
}


MappingJackson2JsonView

它是用于返回Json视图的(下面会介绍Spring MVC返回json的三种方式

// @since 3.1.2 可议看到它出现得还是比较早的~
public class MappingJackson2JsonView extends AbstractJackson2View {
  public static final String DEFAULT_CONTENT_TYPE = "application/json";
  @Nullable
  private String jsonPrefix;
  @Nullable
  private Set<String> modelKeys;
  private boolean extractValueFromSingleKeyModel = false;
  @Override
  protected Object filterModel(Map<String, Object> model) {
    Map<String, Object> result = new HashMap<>(model.size());
    Set<String> modelKeys = (!CollectionUtils.isEmpty(this.modelKeys) ? this.modelKeys : model.keySet());
    // 遍历model所有内容~ 
    model.forEach((clazz, value) -> {
      // 符合下列条件的会给排除掉~~~
      // 不是BindingResult类型 并且  modelKeys包含此key 并且此key不是JsonView和FilterProvider  这种key就排除掉~~~
      if (!(value instanceof BindingResult) && modelKeys.contains(clazz) &&
          !clazz.equals(JsonView.class.getName()) &&
          !clazz.equals(FilterProvider.class.getName())) {
        result.put(clazz, value);
      }
    });
    // 如果只需要排除singleKey,那就返回第一个即可,否则result全部返回
    return (this.extractValueFromSingleKeyModel && result.size() == 1 ? result.values().iterator().next() : result);
  }
  // 如果配置了前缀,把前缀写进去~~~
  @Override
  protected void writePrefix(JsonGenerator generator, Object object) throws IOException {
    if (this.jsonPrefix != null) {
      generator.writeRaw(this.jsonPrefix);
    }
  }
}


此视图是专门来处理作为一个json视图格式进行返回的。那么接下里有必要举例说明一下,Spring MVC返回Json格式数据的多种方式:


Spring MVC返回json的三种方式


  1. 使用MappingJackson2JsonView,其实它是相对来说比较新的一种返回json数据的放置,主要是用到了这个视图的能力。


直接使用它相对来说还是比较麻烦点的,一般都需要结合内容协商视图解析器来使用(比如把它设置默认处理json的视图),但是本文就做一个Demo,所以还是简单的处理一下吧:使用BeanNameViewResolver执行我们定义的这个视图去即可:


    @RequestMapping(value = "/json")
    public String testView(Model model) {
        // 注意Model不添加数据,将会是一个空的JSON串
        model.addAttribute("name", "fsx");
        model.addAttribute("age", 18);
        return "mappingJackson2JsonView";
    }
// 配置视图:
@Configuration
@EnableWebMvc
public class WebMvcConfig extends WebMvcConfigurerAdapter {
  // 此处为配置了一个前缀,发现前缀可以解决jsonp的问题~~~
    @Bean
    public MappingJackson2JsonView mappingJackson2JsonView() {
        MappingJackson2JsonView mappingJackson2JsonView = new MappingJackson2JsonView();
        mappingJackson2JsonView.setJsonPrefix("prefix");
        return mappingJackson2JsonView;
    }
    @Override
    public void configureViewResolvers(ViewResolverRegistry registry) {
        BeanNameViewResolver viewResolver = new BeanNameViewResolver();
        viewResolver.setOrder(10); // 这样能保证在InternalResourceViewResolver之前执行
        registry.viewResolver(viewResolver);
    }
}


浏览器访问:http://localhost:8080/demo_war_war/json可看到如下:


image.png


它提供的前缀能力,在某些特殊的场景会有用


  1. 利用HttpServletResponse,然后获取response.getOutputStream()response.getWriter()自己写json串


    @RequestMapping(value = "/json")
    public void testView(PrintWriter printWriter) {
        printWriter.write("{\"name\":\"fsx\",\"age\":18}");
    }

显然这种方式最为原始的方式,一般情况下我是不推荐这么使用的~

插一句:我曾经看到过有项目在使用Spring MVC框架的时候,还大量的使用到了Servlet规范的东西,其实这个真的是非常非常不好的做法~~


    3.@ResponseBody这种方式是当下平时我们书写使用最多的方式

相信这种方式我说一个字:“略”,应该没有人有意见吧~~~

相关文章
|
8月前
|
运维 数据可视化 C++
2025 热门的 Web 化容器部署工具对比:Portainer VS Websoft9
2025年热门Web化容器部署工具对比:Portainer与Websoft9。Portainer以轻量可视化管理见长,适合技术团队运维;Websoft9则提供一站式应用部署与容器管理,内置丰富开源模板,降低中小企业部署门槛。两者各有优势,助力企业提升容器化效率。
516 1
2025 热门的 Web 化容器部署工具对比:Portainer VS Websoft9
|
前端开发 安全 数据库
Web架构&前后端分离站&Docker容器站&集成软件站&建站分配
Web架构&前后端分离站&Docker容器站&集成软件站&建站分配
383 1
|
安全 前端开发 Java
Spring Boot 项目中触发 Circular View Path 错误的原理与解决方案
在Spring Boot开发中,**Circular View Path**错误常因视图解析与Controller路径重名引发。当视图名称(如`login`)与请求路径相同,Spring MVC无法区分,导致无限循环调用。解决方法包括:1) 明确指定视图路径,避免重名;2) 将视图文件移至子目录;3) 确保Spring Security配置与Controller路径一致。通过合理设定视图和路径,可有效避免该问题,确保系统稳定运行。
732 0
|
开发者 Docker Python
从零开始:使用Docker容器化你的Python Web应用
从零开始:使用Docker容器化你的Python Web应用
694 4
|
机器学习/深度学习 数据采集 Docker
Docker容器化实战:构建并部署一个简单的Web应用
Docker容器化实战:构建并部署一个简单的Web应用
|
存储 编译器 C++
【C++篇】揭开 C++ STL list 容器的神秘面纱:从底层设计到高效应用的全景解析(附源码)
【C++篇】揭开 C++ STL list 容器的神秘面纱:从底层设计到高效应用的全景解析(附源码)
302 2
|
前端开发 安全 Java
技术进阶:使用Spring MVC构建适应未来的响应式Web应用
【9月更文挑战第2天】随着移动设备的普及,响应式设计至关重要。Spring MVC作为强大的Java Web框架,助力开发者创建适应多屏的应用。本文推荐使用Thymeleaf整合视图,通过简洁的HTML代码提高前端灵活性;采用`@ResponseBody`与`Callable`实现异步处理,优化应用响应速度;运用`@ControllerAdvice`统一异常管理,保持代码整洁;借助Jackson简化JSON处理;利用Spring Security增强安全性;并强调测试的重要性。遵循这些实践,将大幅提升开发效率和应用质量。
239 7
|
负载均衡 网络协议 应用服务中间件
web群集--rocky9.2源码部署nginx1.24的详细过程
Nginx 是一款由 Igor Sysoev 开发的开源高性能 HTTP 服务器和反向代理服务器,自 2004 年发布以来,以其高效、稳定和灵活的特点迅速成为许多网站和应用的首选。本文详细介绍了 Nginx 的核心概念、工作原理及常见使用场景,涵盖高并发处理、反向代理、负载均衡、低内存占用等特点,并提供了安装配置教程,适合开发者参考学习。
404 1
|
开发者 容器 Docker
JSF与Docker,引领容器化浪潮!让你的Web应用如虎添翼,轻松应对高并发!
【8月更文挑战第31天】在现代Web应用开发中,JSF框架因其实用性和灵活性被广泛应用。随着云计算及微服务架构的兴起,容器化技术变得日益重要,Docker作为该领域的佼佼者,为JSF应用提供了便捷的部署和管理方案。本文通过基础概念讲解及示例代码展示了如何利用Docker容器化JSF应用,帮助开发者实现高效、便携的应用部署。同时也提醒开发者注意JSF与Docker结合使用时可能遇到的限制,并根据实际情况做出合理选择。
256 0
|
Kubernetes 应用服务中间件 nginx
基于容器化的Web服务器管理
【8月更文第28天】随着云原生技术的发展,容器化已经成为部署和管理应用程序的标准方式之一。Docker 和 Kubernetes 等工具提供了强大的容器管理和编排能力,使得开发者能够轻松地部署、扩展和维护 Web 服务器。本文将详细介绍如何使用 Docker 和 Kubernetes 实现 Web 服务器的容器化部署,并提供详细的步骤和代码示例。
597 1