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

简介: 【小家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设计中非常优秀的一环,最重要的是这种设计思想、作者的设计意图,值得我们深思和学习

相关文章
|
5月前
|
缓存 安全 Java
《深入理解Spring》过滤器(Filter)——Web请求的第一道防线
Servlet过滤器是Java Web核心组件,可在请求进入容器时进行预处理与响应后处理,适用于日志、认证、安全、跨域等全局性功能,具有比Spring拦截器更早的执行时机和更广的覆盖范围。
|
5月前
|
JSON 前端开发 JavaScript
Mvc视图的4种提交方式
本文介绍了jQuery中get/post与ajax提交方式,以及原生JS通过请求头和FormData对象发送数据的方法。涵盖参数配置、请求类型、回调处理等要点,适用于表单及数据提交场景。
171 1
|
7月前
|
运维 数据可视化 C++
2025 热门的 Web 化容器部署工具对比:Portainer VS Websoft9
2025年热门Web化容器部署工具对比:Portainer与Websoft9。Portainer以轻量可视化管理见长,适合技术团队运维;Websoft9则提供一站式应用部署与容器管理,内置丰富开源模板,降低中小企业部署门槛。两者各有优势,助力企业提升容器化效率。
485 1
2025 热门的 Web 化容器部署工具对比:Portainer VS Websoft9
|
6月前
|
存储 安全 Java
如何在 Spring Web 应用程序中使用 @SessionScope 和 @RequestScope
Spring框架中的`@SessionScope`和`@RequestScope`注解用于管理Web应用中的状态。`@SessionScope`绑定HTTP会话生命周期,适用于用户特定数据,如购物车;`@RequestScope`限定于单个请求,适合无状态、线程安全的操作,如日志记录。合理选择作用域能提升应用性能与可维护性。
260 1
|
7月前
|
存储 NoSQL Java
探索Spring Boot的函数式Web应用开发
通过这种方式,开发者能以声明式和函数式的编程习惯,构建高效、易测试、并发友好的Web应用,同时也能以较小的学习曲线迅速上手,因为这些概念与Spring Framework其他部分保持一致性。在设计和编码过程中,保持代码的简洁性和高内聚性,有助于维持项目的可管理性,也便于其他开发者阅读和理解。
219 0
|
8月前
|
前端开发 Java API
Spring Cloud Gateway Server Web MVC报错“Unsupported transfer encoding: chunked”解决
本文解析了Spring Cloud Gateway中出现“Unsupported transfer encoding: chunked”错误的原因,指出该问题源于Feign依赖的HTTP客户端与服务端的`chunked`传输编码不兼容,并提供了具体的解决方案。通过规范Feign客户端接口的返回类型,可有效避免该异常,提升系统兼容性与稳定性。
538 0
|
网络协议 Java Shell
java spring 项目若依框架启动失败,启动不了服务提示端口8080占用escription: Web server failed to start. Port 8080 was already in use. Action: Identify and stop the process that’s listening on port 8080 or configure this application to listen on another port-优雅草卓伊凡解决方案
java spring 项目若依框架启动失败,启动不了服务提示端口8080占用escription: Web server failed to start. Port 8080 was already in use. Action: Identify and stop the process that’s listening on port 8080 or configure this application to listen on another port-优雅草卓伊凡解决方案
877 7
|
Java 开发者 微服务
Spring Boot 入门:简化 Java Web 开发的强大工具
Spring Boot 是一个开源的 Java 基础框架,用于创建独立、生产级别的基于Spring框架的应用程序。它旨在简化Spring应用的初始搭建以及开发过程。
884 7
Spring Boot 入门:简化 Java Web 开发的强大工具
|
开发者 Docker Python
从零开始:使用Docker容器化你的Python Web应用
从零开始:使用Docker容器化你的Python Web应用
642 4
|
机器学习/深度学习 数据采集 Docker
Docker容器化实战:构建并部署一个简单的Web应用
Docker容器化实战:构建并部署一个简单的Web应用