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

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

FreeMarkerView


下面就以老牌模版引擎FreeMarker为例,窥探一下实现的思路:


public class FreeMarkerView extends AbstractTemplateView {
  // FreeMarker Configuration: "ISO-8859-1" if not specified otherwise
  @Nullable
  private String encoding;
  // FreeMarker的配置文件  里面极其多的配置信息~~比如文件后缀名、编码等
  @Nullable
  private Configuration configuration;
  @Nullable
  private TaglibFactory taglibFactory;
  @Nullable
  private ServletContextHashModel servletContextHashModel;
  // 就是检查这个模版存不存在~~~
  @Override
  public boolean checkResource(Locale locale) throws Exception {
    String url = getUrl();
    Assert.state(url != null, "'url' not set");
    try {
      // Check that we can get the template, even if we might subsequently get it again.
      getTemplate(url, locale);
      return true;
    }
    catch (FileNotFoundException ex) {
      // Allow for ViewResolver chaining...
      return false;
    }
    catch (ParseException ex) {
      throw new ApplicationContextException("Failed to parse [" + url + "]", ex);
    }
    catch (IOException ex) {
      throw new ApplicationContextException("Failed to load [" + url + "]", ex);
    }
  }
  ...
  // 最终会根据此模版去渲染~~~这是FreeMarker真正去做的事~~~~
  protected void processTemplate(Template template, SimpleHash model, HttpServletResponse response)
      throws IOException, TemplateException {
    template.process(model, response.getWriter());
  }
}


此处我贴一个直接使用FreeMarker的使用案例,方便小伙伴对它的使用步骤有个感性的认识~~~


  @Test
  public void testFreeMarker() throws Exception{
    // 第0步,创建模板文件(自己找个目录创建,文件一般都以.ftl结尾)
    // 第一步:创建一个Configuration对象,直接new一个对象。构造方法的参数就是freemarker对于的版本号。
    Configuration configuration = new Configuration(Configuration.getVersion());
    // 第二步:设置模板文件所在的路径。
    configuration.setDirectoryForTemplateLoading(new File("D:\\workspace\\e3-item-web\\src\\main\\webapp\\WEB-INF\\ftl"));
    // 第三步:设置模板文件使用的字符集。一般就是utf-8.
    configuration.setDefaultEncoding("utf-8");
    // 第四步:加载一个模板,创建一个模板对象。
    Template template = configuration.getTemplate("hello.ftl");
    // 第五步:创建一个模板使用的数据集,可以是pojo也可以是map。一般是Map。
    Map data = new HashMap<>();
    //向数据集中添加数据
    data.put("hello", "this is my first freemarker test!");
    // 第六步:创建一个Writer对象,一般创建一FileWriter对象,指定生成的文件名。
    Writer out = new FileWriter(new File("D:\\Freemarker\\hello.txt"));
    // 第七步:调用模板对象的process方法输出文件,生成静态页面。
    template.process(data, out);
    // 第八步:关闭流。
    out.close();
  }


TilesView



XsltView



InternalResourceView:最重要的一个视图


Internal:内部的。所以该视图表示:内部资源视图。


// @since 17.02.2003  第一版就有了
public class InternalResourceView extends AbstractUrlBasedView {
  // 指定是否始终包含视图而不是转发到视图
  //默认值为“false”。打开此标志以强制使用servlet include,即使可以进行转发
  private boolean alwaysInclude = false;
  // 设置是否显式阻止分派回当前处理程序路径 表示是否组织循环转发,比如自己转发自己
  // 我个人认为这里默认值用true反而更好~~~因为需要递归的情况毕竟是极少数~
  // 其实可以看到InternalResourceViewResolver的buildView方法里是把这个属性显示的设置为true了的~~~
  private boolean preventDispatchLoop = false;
  public InternalResourceView(String url, boolean alwaysInclude) {
    super(url);
    this.alwaysInclude = alwaysInclude;
  }
  @Override
  protected boolean isContextRequired() {
    return false;
  }
  // 请求包含、请求转发是它特有的~~~~~
  @Override
  protected void renderMergedOutputModel(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
    // Expose the model object as request attributes.
    // 把model里的数据都request.setAttribute里
    // 因为最终JSP里面取值其实都是从request等域对象里面取~
    exposeModelAsRequestAttributes(model, request);
    // Expose helpers as request attributes, if any.
    // JstlView有实现此protected方法~
    exposeHelpers(request);
    // Determine the path for the request dispatcher.
    String dispatcherPath = prepareForRendering(request, response);
    // Obtain a RequestDispatcher for the target resource (typically a JSP).  注意:此处特指JSP
    // 就是一句话:request.getRequestDispatcher(path)
    RequestDispatcher rd = getRequestDispatcher(request, dispatcherPath);
    if (rd == null) {
      throw new ServletException("Could not get RequestDispatcher for [" + getUrl() +
          "]: Check that the corresponding file exists within your web application archive!");
    }
    // If already included or response already committed, perform include, else forward.
    //useInclude:若alwaysInclude==true或者该request是incluse请求或者response.isCommitted()==true
    // 那就走incluse,否则走forward~~~~~
    if (useInclude(request, response)) {
      response.setContentType(getContentType());
      if (logger.isDebugEnabled()) {
        logger.debug("Including [" + getUrl() + "]");
      }
      rd.include(request, response);
    }
    else {
      // Note: The forwarded resource is supposed to determine the content type itself.
      if (logger.isDebugEnabled()) {
        logger.debug("Forwarding to [" + getUrl() + "]");
      }
      rd.forward(request, response);
    }
  }
  // 拿到URL,做一个循环检查~~~  若是循环转发就报错~~
  protected String prepareForRendering(HttpServletRequest request, HttpServletResponse response)
      throws Exception {
    String path = getUrl();
    Assert.state(path != null, "'url' not set");
    if (this.preventDispatchLoop) {
      String uri = request.getRequestURI();
      if (path.startsWith("/") ? uri.equals(path) : uri.equals(StringUtils.applyRelativePath(uri, path))) {
        throw new ServletException("Circular view path [" + path + "]: would dispatch back " +
            "to the current handler URL [" + uri + "] again. Check your ViewResolver setup! " +
            "(Hint: This may be the result of an unspecified view, due to default view name generation.)");
      }
    }
    return path;
  }
}


这样我们的InternalResourceView这个视图就渲染完成了,为何这么简单呢?因为它最终要么是include,要么forward掉了。交给别的Servlet去处理了。

而我们知道JSP的本质其实就是一个servlet,所以转发给它处理其实就是定位到了我们的JSP页面,它完成的对response写入动作。

比如:


    @GetMapping("/index")
    public Object index() {
        InternalResourceView view = new InternalResourceView();
        view.setUrl("/index.jsp");
        view.setPreventDispatchLoop(true);
        return view;
    }


注意:直接返回一个View是不会经过ViewResolver处理的


这样是能够正常展示出我们的jsp页面的。但是,但是,但是如果我们是一个html页面呢?比如如下:


    @GetMapping("/index")
    public Object index() {
        InternalResourceView view = new InternalResourceView();
        view.setUrl("/index.html");
        view.setPreventDispatchLoop(true);
        return view;
    }


访问直接报错:image.png


原因很简单,因为你是HTML页面,所以它并没有对应的Servlet,所以你转发的时候肯定就报错了。所以接下里的问题变成了:

如何让我们的Controller跳转到HTML页面呢???其实这个涉及到Spring MVC中对静态资源的访问问题


说在前面:因为html属于静态数据,所以一般我们需要访问的话都是通过mvc:resources等这种配置去达到目的让可议直接访问。但是不乏业务中可能也存在通过controller方法跳转到html页面的需求(虽然你可以JSP里面全是html页面),本文就实现这个效果,能加深对此视图的了解~~


参考:【小家Spring】Spring MVC控制器中Handler的四种实现方式:Controller、HttpRequestHandler、Servlet、@RequestMapping

的最后半段来了解Spring MVC对静态资源的处理


JstlView


它继承自InternalResourceView,所以还是和JSP相关的。jstl相关的jar为:jstl.jar和standard.jar。它哥俩已经老久都没有更新过了,不过可以理解。毕竟JSP都快寿终正寝了。


它还可以和国际化有关,若使用Jstl的fmt标签,需要在SpringMVC的配置文件中配置国际化资源文件。

public class JstlView extends InternalResourceView {
  ...
  public JstlView(String url, MessageSource messageSource) {
    this(url);
    this.messageSource = messageSource;
  }
  // 导出一些JSTL需要的东西
  @Override
  protected void exposeHelpers(HttpServletRequest request) throws Exception {
    if (this.messageSource != null) {
      JstlUtils.exposeLocalizationContext(request, this.messageSource);
    }
    else {
      JstlUtils.exposeLocalizationContext(new RequestContext(request, getServletContext()));
    }
  }
}


因为JSTL技术比较古老了,现在很少人使用(当然JSP的使用人群还是有不少的,需要较重点的了解一下,毕竟是java嫡系技术,在历史进程中还是很重要的存在的),所以这里也不做演示了~

ScriptTemplateView


这个是脚本渲染引擎,从Spring4.2开始提供了一个ScriptTemplateView作为脚本模版视图。

脚本渲染引擎,据我目前了解,是为Kotlin而准备的,此处一个大写的:略


总结


视图就是展示给用户看的结果。可以是很多形式,例如:html、JSP、excel表单、Word文档、PDF文档、JSON数据、freemarker模板视图等等。


视图(解析器)作为Spring MVC设计中非常优秀的一环,最重要的是这种设计思想、作者的设计意图,值得我们深思和学习

相关文章
|
2月前
|
前端开发 Java 微服务
《深入理解Spring》:Spring、Spring MVC与Spring Boot的深度解析
Spring Framework是Java生态的基石,提供IoC、AOP等核心功能;Spring MVC基于其构建,实现Web层MVC架构;Spring Boot则通过自动配置和内嵌服务器,极大简化了开发与部署。三者层层演进,Spring Boot并非替代,而是对前者的高效封装与增强,适用于微服务与快速开发,而深入理解Spring Framework有助于更好驾驭整体技术栈。
|
4月前
|
运维 数据可视化 C++
2025 热门的 Web 化容器部署工具对比:Portainer VS Websoft9
2025年热门Web化容器部署工具对比:Portainer与Websoft9。Portainer以轻量可视化管理见长,适合技术团队运维;Websoft9则提供一站式应用部署与容器管理,内置丰富开源模板,降低中小企业部署门槛。两者各有优势,助力企业提升容器化效率。
359 1
2025 热门的 Web 化容器部署工具对比:Portainer VS Websoft9
|
5月前
|
前端开发 Java API
Spring Cloud Gateway Server Web MVC报错“Unsupported transfer encoding: chunked”解决
本文解析了Spring Cloud Gateway中出现“Unsupported transfer encoding: chunked”错误的原因,指出该问题源于Feign依赖的HTTP客户端与服务端的`chunked`传输编码不兼容,并提供了具体的解决方案。通过规范Feign客户端接口的返回类型,可有效避免该异常,提升系统兼容性与稳定性。
339 0
|
5月前
|
SQL Java 数据库连接
Spring、SpringMVC 与 MyBatis 核心知识点解析
我梳理的这些内容,涵盖了 Spring、SpringMVC 和 MyBatis 的核心知识点。 在 Spring 中,我了解到 IOC 是控制反转,把对象控制权交容器;DI 是依赖注入,有三种实现方式。Bean 有五种作用域,单例 bean 的线程安全问题及自动装配方式也清晰了。事务基于数据库和 AOP,有失效场景和七种传播行为。AOP 是面向切面编程,动态代理有 JDK 和 CGLIB 两种。 SpringMVC 的 11 步执行流程我烂熟于心,还有那些常用注解的用法。 MyBatis 里,#{} 和 ${} 的区别很关键,获取主键、处理字段与属性名不匹配的方法也掌握了。多表查询、动态
157 0
|
11月前
|
SQL Java 数据库连接
对Spring、SpringMVC、MyBatis框架的介绍与解释
Spring 框架提供了全面的基础设施支持,Spring MVC 专注于 Web 层的开发,而 MyBatis 则是一个高效的持久层框架。这三个框架结合使用,可以显著提升 Java 企业级应用的开发效率和质量。通过理解它们的核心特性和使用方法,开发者可以更好地构建和维护复杂的应用程序。
564 29
|
缓存 前端开发 Java
【Java面试题汇总】Spring,SpringBoot,SpringMVC,Mybatis,JavaWeb篇(2023版)
Soring Boot的起步依赖、启动流程、自动装配、常用的注解、Spring MVC的执行流程、对MVC的理解、RestFull风格、为什么service层要写接口、MyBatis的缓存机制、$和#有什么区别、resultType和resultMap区别、cookie和session的区别是什么?session的工作原理
【Java面试题汇总】Spring,SpringBoot,SpringMVC,Mybatis,JavaWeb篇(2023版)
|
前端开发 Java 应用服务中间件
【Spring】Spring MVC的项目准备和连接建立
【Spring】Spring MVC的项目准备和连接建立
170 2
|
XML 前端开发 Java
Spring,SpringBoot和SpringMVC的关系以及区别 —— 超准确,可当面试题!!!也可供零基础学习
本文阐述了Spring、Spring Boot和Spring MVC的关系与区别,指出Spring是一个轻量级、一站式、模块化的应用程序开发框架,Spring MVC是Spring的一个子框架,专注于Web应用和网络接口开发,而Spring Boot则是对Spring的封装,用于简化Spring应用的开发。
3263 0
Spring,SpringBoot和SpringMVC的关系以及区别 —— 超准确,可当面试题!!!也可供零基础学习
|
前端开发 测试技术 开发者
MVC模式在现代Web开发中有哪些优势和局限性?
MVC模式在现代Web开发中有哪些优势和局限性?
|
Java 应用服务中间件 Apache
浅谈Tomcat和其他WEB容器的区别
Tomcat是一款轻量级的免费开源Web应用服务器,常用于中小型系统及并发访问量适中的场景,尤其适合开发和调试JSP程序。它不仅能处理HTML页面,还充当Servlet和JSP容器。相比之下,物理服务器是指具备处理器、硬盘等硬件设施的服务器,如云服务器,其设计目标是在处理能力、稳定性和安全性等方面提供高标准服务。简言之,Tomcat专注于运行Java应用,而物理服务器则提供基础计算资源。