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

相关文章
|
1月前
|
开发者 Docker Python
从零开始:使用Docker容器化你的Python Web应用
从零开始:使用Docker容器化你的Python Web应用
39 1
|
1月前
|
机器学习/深度学习 数据采集 Docker
Docker容器化实战:构建并部署一个简单的Web应用
Docker容器化实战:构建并部署一个简单的Web应用
|
1月前
|
前端开发 Java Docker
使用Docker容器化部署Spring Boot应用程序
使用Docker容器化部署Spring Boot应用程序
|
1月前
|
Java Docker 微服务
利用Docker容器化部署Spring Boot应用
利用Docker容器化部署Spring Boot应用
51 0
|
2月前
|
存储 编译器 C++
【C++篇】揭开 C++ STL list 容器的神秘面纱:从底层设计到高效应用的全景解析(附源码)
【C++篇】揭开 C++ STL list 容器的神秘面纱:从底层设计到高效应用的全景解析(附源码)
83 2
|
2月前
|
JavaScript Linux 开发者
使用Docker容器化Web应用:从零开始
使用Docker容器化Web应用:从零开始
|
4月前
|
Kubernetes 应用服务中间件 nginx
基于容器化的Web服务器管理
【8月更文第28天】随着云原生技术的发展,容器化已经成为部署和管理应用程序的标准方式之一。Docker 和 Kubernetes 等工具提供了强大的容器管理和编排能力,使得开发者能够轻松地部署、扩展和维护 Web 服务器。本文将详细介绍如何使用 Docker 和 Kubernetes 实现 Web 服务器的容器化部署,并提供详细的步骤和代码示例。
154 1
|
4月前
|
缓存 开发者 Docker
Dockerfile是Docker容器化过程中的核心组件,它允许开发者以一种可重复、可移植的方式自动化地构建Docker镜像
【8月更文挑战第19天】Dockerfile是构建Docker镜像的脚本文件,含一系列指令定义镜像构建步骤。每条大写指令后跟至少一个参数,按序执行,每执行一条指令即生成新的镜像层。常用指令包括:FROM指定基础镜像;RUN执行构建命令;EXPOSE开放端口;CMD指定容器启动行为等。优化策略涉及减少镜像层数、选择轻量基础镜像、利用缓存及清理冗余文件。示例:基于Python应用的Dockerfile包括设置工作目录、复制文件、安装依赖等步骤。掌握Dockerfile有助于高效自动化构建镜像,加速应用部署。
41 1
|
4月前
|
XML Java 数据格式
Spring5入门到实战------8、IOC容器-Bean管理注解方式
这篇文章详细介绍了Spring5框架中使用注解进行Bean管理的方法,包括创建Bean的注解、自动装配和属性注入的注解,以及如何用配置类替代XML配置文件实现完全注解开发。
Spring5入门到实战------8、IOC容器-Bean管理注解方式
|
4月前
|
开发者 容器 Docker
JSF与Docker,引领容器化浪潮!让你的Web应用如虎添翼,轻松应对高并发!
【8月更文挑战第31天】在现代Web应用开发中,JSF框架因其实用性和灵活性被广泛应用。随着云计算及微服务架构的兴起,容器化技术变得日益重要,Docker作为该领域的佼佼者,为JSF应用提供了便捷的部署和管理方案。本文通过基础概念讲解及示例代码展示了如何利用Docker容器化JSF应用,帮助开发者实现高效、便携的应用部署。同时也提醒开发者注意JSF与Docker结合使用时可能遇到的限制,并根据实际情况做出合理选择。
47 0