【小家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天前
|
前端开发 Java 测试技术
微服务——SpringBoot使用归纳——Spring Boot中的MVC支持——@RequestParam
本文介绍了 `@RequestParam` 注解的使用方法及其与 `@PathVariable` 的区别。`@RequestParam` 用于从请求中获取参数值(如 GET 请求的 URL 参数或 POST 请求的表单数据),而 `@PathVariable` 用于从 URL 模板中提取参数。文章通过示例代码详细说明了 `@RequestParam` 的常用属性,如 `required` 和 `defaultValue`,并展示了如何用实体类封装大量表单参数以简化处理流程。最后,结合 Postman 测试工具验证了接口的功能。
14 0
微服务——SpringBoot使用归纳——Spring Boot中的MVC支持——@RequestParam
|
1天前
|
JSON 前端开发 Java
微服务——SpringBoot使用归纳——Spring Boot中的MVC支持——@RequestBody
`@RequestBody` 是 Spring 框架中的注解,用于将 HTTP 请求体中的 JSON 数据自动映射为 Java 对象。例如,前端通过 POST 请求发送包含 `username` 和 `password` 的 JSON 数据,后端可通过带有 `@RequestBody` 注解的方法参数接收并处理。此注解适用于传递复杂对象的场景,简化了数据解析过程。与表单提交不同,它主要用于接收 JSON 格式的实体数据。
13 0
|
1天前
|
前端开发 Java 微服务
微服务——SpringBoot使用归纳——Spring Boot中的MVC支持——@PathVariable
`@PathVariable` 是 Spring Boot 中用于从 URL 中提取参数的注解,支持 RESTful 风格接口开发。例如,通过 `@GetMapping(&quot;/user/{id}&quot;)` 可以将 URL 中的 `{id}` 参数自动映射到方法参数中。若参数名不一致,可通过 `@PathVariable(&quot;自定义名&quot;)` 指定绑定关系。此外,还支持多参数占位符,如 `/user/{id}/{name}`,分别映射到方法中的多个参数。运行项目后,访问指定 URL 即可验证参数是否正确接收。
12 0
|
1天前
|
JSON 前端开发 Java
微服务——SpringBoot使用归纳——Spring Boot中的MVC支持——@RequestMapping
@RequestMapping 是 Spring MVC 中用于请求地址映射的注解,可作用于类或方法上。类级别定义控制器父路径,方法级别进一步指定处理逻辑。常用属性包括 value(请求地址)、method(请求类型,如 GET/POST 等,默认 GET)和 produces(返回内容类型)。例如:`@RequestMapping(value = &quot;/test&quot;, produces = &quot;application/json; charset=UTF-8&quot;)`。此外,针对不同请求方式还有简化注解,如 @GetMapping、@PostMapping 等。
12 0
|
1天前
|
JSON 前端开发 Java
微服务——SpringBoot使用归纳——Spring Boot中的MVC支持——@RestController
本文主要介绍 Spring Boot 中 MVC 开发常用的几个注解及其使用方式,包括 `@RestController`、`@RequestMapping`、`@PathVariable`、`@RequestParam` 和 `@RequestBody`。其中重点讲解了 `@RestController` 注解的构成与特点:它是 `@Controller` 和 `@ResponseBody` 的结合体,适用于返回 JSON 数据的场景。文章还指出,在需要模板渲染(如 Thymeleaf)而非前后端分离的情况下,应使用 `@Controller` 而非 `@RestController`
15 0
|
10月前
|
开发框架 前端开发 .NET
ASP.NET CORE 3.1 MVC“指定的网络名不再可用\企图在不存在的网络连接上进行操作”的问题解决过程
ASP.NET CORE 3.1 MVC“指定的网络名不再可用\企图在不存在的网络连接上进行操作”的问题解决过程
267 0
|
7月前
|
开发框架 前端开发 .NET
ASP.NET MVC WebApi 接口返回 JOSN 日期格式化 date format
ASP.NET MVC WebApi 接口返回 JOSN 日期格式化 date format
92 0
|
存储 开发框架 前端开发
[回馈]ASP.NET Core MVC开发实战之商城系统(五)
经过一段时间的准备,新的一期【ASP.NET Core MVC开发实战之商城系统】已经开始,在之前的文章中,讲解了商城系统的整体功能设计,页面布局设计,环境搭建,系统配置,及首页【商品类型,banner条,友情链接,降价促销,新品爆款】,商品列表页面,商品详情等功能的开发,今天继续讲解购物车功能开发,仅供学习分享使用,如有不足之处,还请指正。
195 0
|
开发框架 前端开发 .NET
[回馈]ASP.NET Core MVC开发实战之商城系统(三)
[回馈]ASP.NET Core MVC开发实战之商城系统(三)
126 0
|
开发框架 前端开发 .NET
[回馈]ASP.NET Core MVC开发实战之商城系统(一)
[回馈]ASP.NET Core MVC开发实战之商城系统(一)
218 0