Bootstrap

微服务专题02-Spring Web MVC 视图技术

前言

前面的章节我们讲了Spring Application。本节,继续微服务专题的内容分享,共计16小节,分别是:

本节内容重点为:

  • Thymeleaf 视图技术:介绍新一代视图技术 Thymeleaf ,包括其使用场景、实际应用以及技术优势
  • 视图解析:介绍 Spring Web MVC 视图解析的过程和原理、以及内容协调视图处理器的使用场景
  • 国际化:利用Locale技术,实现视图内容的国际化

上节回顾

在上一节,主要针对于SpringBoot的main class -> SpringApplication进行了详细说明,在Spring 注解编程模型我们提到了一个元注解 @Component ,所以这里我想补充一下Spring关于这个注解的实际应用是怎么样的一个流程?

@ComponentScan -> @Confiugration Class -> basePackages -> @Component Beans ->

@ComponentScan扫描带有 @Confiugration 的类

BeanDefinition -> BeanDefinitionRegistry ->

DI

Beans -> BeanFactory -> getBean or @Autowired

IOC

实际上就是我们所谓的IoC/DI -> 注入Bean后再获取Bean,现在很多人过分强调IOC/DI,其实没必要,在IOC/DI背后更核心的内容重要的我们要理解Bean的生命周期

Bean 生命周期

实例化 :Bean Class -> Bean Object

初始化前 -> Bean before/pre init()

  • org.springframework.beans.factory.config.BeanPostProcessor#postProcessBeforeInitialization

初始化 -> init()

  • org.springframework.beans.factory.InitializingBean#afterPropertiesSet()

初始化后

  • org.springframework.beans.factory.config.BeanPostProcessor#postProcessAfterInitialization()

销毁

  • org.springframework.beans.factory.DisposableBean#destroy()

主要内容

Thymeleaf 视图技术

Thymeleaf 是新一代 Java 模板引擎,它类似于 Velocity、FreeMarker 等传统 Java 模板引擎,但是与传统 Java 模板引擎不同的是,Thymeleaf 支持 HTML 原型。

推荐一个开源项目,Spring Boot Starter 支持模板引擎 velocity 以及 velocity tools,该Starter是官方Starter的补充https://github.com/alibaba/velocity-spring-boot-project

渲染上下文(模型) Model
  1. 接口类型
  • Model
  • ModelMap
  • ModelAndView : 包含 Model 和 View 两个模块。
  1. 注解类型

@ModelAttrtibute: 常用于功能处理方法的入参上,返回值以及控制器的一般方法(非功能处理方法)上。

EL 表达式
  1. 字符值
  2. 多种数据类型
  3. 逻辑表达式(if else)
  4. 迭代表达式(for each / while)
  5. 反射(Java Reflection / CGLib)

视图解析

模板寻址

Q:如何通过配置文件设置模板的完整路径?

A:公式为:prefix(文件前缀) + view-name(视图名称) + suffix(文件后缀)。
在这里插入图片描述
模板文件存放的路径为:

classpath:/templates/thymeleaf/index.dota2

在 Spring 源码里,我们也可以看到 view 的实际存放地址,印证了我们前面的猜想:

view.setUrl(getPrefix() + viewName + getSuffix())
模板缓存

在配置文件中设置模板缓存, Cache = false/true,如下所示:

在这里插入图片描述

# Thymeleaf 配置
# 默认 Cache = true 
spring.thymeleaf.cache = false
spring.thymeleaf.prefix = classpath:/templates/thymeleaf/
spring.thymeleaf.suffix = .dota2

Spring MVC 模板渲染逻辑

接下里,分别以 M (Model)、V (View)、C (Controller)三个方面解析 Spring MVC 模板渲染的逻辑。

C (Controller)

C 指的控制层,核心控制类是:DispatcherServlet

M(Model)

在前面,有关 Spring MVC 的部分我们已经提到过,Model 可以分为接口类型和注解类型。现在我们用模板引擎来定义 Model:通用的方式就是以模板上下文形式,内建/内嵌自己工具变量。

关于模板引擎,我们这里提两种不同的实现 JSP 内置(built-in)九大变量,与 Thymeleaf 内置变量。

我们知道,JSP 其实就等同于 Servlet。所谓内置,指的是通过 JSP 模板引擎控制的。

首先看一下,Jsp 九大内置对象都有哪些?

内置对象名类型关注点
requestHttpServletRequest关注当前请求
responseHttpServletResponse关注相应结果
configServletConfig关注配置
ApplicationServletContext关注当前应用
sessionHttpSession关注当前会话
ExceptionThrowable关注异常
pageObject(this)关注 Jsp 对象
outJspWriter关注输出
pageContextPageContext关注当前页面

Q:那么 Thymeleaf 是否也像 Jsp 一样有内置变量呢?

A: 有的,上下文(模型),比如 strings、 numbers 等工具类。业务逻辑可以参考源码 StandardExpressionObjectFactory 使其构建 IExpressionContext

同样的我们也可以自定义内置对象:

   public static class StringUtil {

        public StringUtil() {
        }

        public boolean isNotBlank(String value) {
            return StringUtils.hasText(value);
        }

    }

在这里插入图片描述
在页面上引用:
在这里插入图片描述
同样的我们也可以自定义内置对象:

   public static class StringUtil {

        public StringUtil() {
        }

        public boolean isNotBlank(String value) {
            return StringUtils.hasText(value);
        }

    }

在页面上引用:
在这里插入图片描述
页面输出:
在这里插入图片描述

V(View)

我们先来说说视图对象,通常有三种,分别是 Servlet、Spring MVC 、Struts。

Servlet:

Servlet通过以下三个类处理视图逻辑,分别是:

  • RequestDispatcher#forward
  • RequestDispatcher#include
  • HttpServletResponse#sendRedirect

Spring MVC:

等同于 forward 功能的类: InternalResourceView,等同于 redirect 功能的类: RedirectView

Struts:

核心处理类是: Action,详细划分为:ForwardActionRedirectAction

接下来我们说说视图处理对象,通常有 Spring MVC 与 Struts 两种框架处理:

  • Spring MVC

Spring MVC处理流程:首先会接受到前端的请求,通过我们前面提到的正则表达式过滤拦截以 *.do 为结尾的所有请求,交给 DispatcherServlet 处理, 进而交给 Controller ,处理完毕后将 View 视图交给 ViewResolver 解析,最后通过 View#render 方法返回给 HTML ,通过 HttpServletResponse 最后完成整个调用逻辑。

这里主要讲一下 Thymeleaf 与 JSP 处理视图过程的异同。

1、Thymeleaf

Thymeleaf 两大核心处理组件。一个是 ViewResolver 进而调用 ThymeleafViewResolver。另一个是 View 进而调用 ThymeleafView

整个过程可以简单地概括为三步:

  • 通过模板名称解析模板资源(ClassPathResource),处理逻辑可以参考类 TemplateResolution
  • 读取资源,并且渲染内容 HTML。处理逻辑可以参考 IEngineContextProcessorTemplateHandler
  • HTML 内容输出到 Response

Thymeleaf 处理过程的源码路径可以参考:方法 org.thymeleaf.TemplateEngine#process(org.thymeleaf.TemplateSpec, org.thymeleaf.context.IContext, java.io.Writer) 和 方法 org.thymeleaf.engine.TemplateManager#parseAndProcess

2、JSP

通常我们可以在配置文件里对于JSP做如下配置:

  <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
      <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
      <property name="prefix" value="/WEB-INF/jsp/"/>
      <property name="suffix" value=".jsp"/>
  </bean>

区别于 Thymeleaf,JSP在处理 ViewResolver 时调用的是 InternalResourceViewResolver。处理 View 调用 JstlView。而 Thymeleaf 的重定向则依靠于 RequestDispatcher

  • Struts

Struts 处理流程:首先会接受到前端的请求,通过正则表达式过滤拦截以 *.do 为结尾的所有请求,交给 ActionServlet 处理 进而调用 Action,而 Action 会通过 ForwardAction 将请求重定向到 RequestDispatcher,进而将处理结果交给 JSP(Servlet),这样就会渲染在 HTML 页面上,通过 HttpServletResponse 最后完成整个调用逻辑。

重点内容

学习方法
学会配置代码

假设需要了解的技术是 thymeleaf ,那么可以猜想是否有 thymeleaf 的 Properties,所以果断在源码里搜索 ThymeleafProperties

第一步:找到 @ConfigurationProperties,确认前缀

@ConfigurationProperties(prefix = "spring.thymeleaf")
public class ThymeleafProperties {
    //此类说明:thymeleaf 可以通过以 spring.thymeleaf 为前缀的配置项进行个性化配置。
}

第二步:进一步确认 ThymeleafProperties 的属性字段,是否字段和属性名一一对应。

@ConfigurationProperties(prefix = "spring.thymeleaf")
public class ThymeleafProperties {
    ...
	private boolean cache = true;
    ...
    private String mode = "HTML";
    ...
}

根据此类的属性,我们可以这样配置 thymeleaf:

spring.thymeleaf.cache
spring.thymeleaf.mode=HTML

Tips:同样的,我们要学习 MessageSource ,那么也可以根据 MessageSource + Properties = MessageSourceProperties 这样的推论,依次找到配置项前缀 spring.messages 等信息。

有了这里理论依据,我们就根据此仿造写一个demo。

thymeleaf 的案例演示

父子pom配置详见本节末github地址。这里给出核心代码结构:
在这里插入图片描述
页面渲染,index.html

<!DOCTYPE html>

<html xmlns:th="http://www.thymeleaf.org">

<head>
    <title>Good Thymes Virtual Grocery</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <link rel="stylesheet" type="text/css" media="all"
          href="../../css/gtvg.css" th:href="@{/css/gtvg.css}" />
</head>

<body>

<p th:text="#{home.welcome}">Welcome to our grocery store!</p>

<p th:text="${!#strings.isEmpty(message)}">Message!</p>

</body>

</html>

IndexController

@Controller
public class IndexController {

    @GetMapping({"/", ""})
    public String index(Model model) {
        model.addAttribute("string",new StringUtil());
        return "index";
    }

    public static class StringUtil {

        public StringUtil() {
        }

        public boolean isNotBlank(String value) {
            return StringUtils.hasText(value);
        }

    }

    @ModelAttribute(name = "message")
    public String message() {
        return "Hello,World";
    }
}

index.dota2
在这里插入图片描述
走你!
在这里插入图片描述
成功渲染页面!

学会打断点

DispatcherServlet#doDispatch -> 拦截请求
在这里插入图片描述
Controller -> 执行业务,并且控制视图
在这里插入图片描述
DispatcherServlet#resolveViewName -> 视图解析

在这里插入图片描述
DispatcherServlet#render -> 视图渲染

在这里插入图片描述

接着调用DispatcherServlet#renderFragment ,处理请求地址:
在这里插入图片描述
进入实现类:

org.thymeleaf.TemplateEngine#process(org.thymeleaf.TemplateSpec, org.thymeleaf.context.IContext, java.io.Writer)

在这里插入图片描述
真正解析地址处理方法:

回到org.thymeleaf.engine.TemplateManager#parseAndProcess主流程:
在这里插入图片描述
View渲染,看看OutputBuffer(ob)里的内容不就是视图层H5页面么!
在这里插入图片描述
两相对比,不能发现,这就是整个SpringMVC的处理流程:
在这里插入图片描述

国际化

什么是 Locale (国际化)?

作为 SpringMVC 的组件- ViewResolver,其作用是根据视图名和 Locale 解析出视图,而用户的区域也称为 Locale,该信息是可以由前端直接获取的。通过这种方式,可以实现一种国际化的目的,比如针对美国用户可以提供一个视图,而针对中国用户则可以提供另一个视图。
在这里插入图片描述

国际化之 Spring MVC

看看 Locale 在 Spring MVC 中的处理:

在源码里,通过类 Servlet 调用 ServletRequest#getLocale():

在这里插入图片描述
打开浏览器开发者模式,在 Request Headers 的字段里通常有设置中文编码的字段:Accept-Language: en,zh;q=0.9,zh-TW;q=0.8。locale()应用于浏览器中的常见字段 LocaleContextHolder来自于 DispatcherServlet 进而调用 FrameworkServlet#initContextHolders 方法,信息存储通过 ThreadLocal 实现。

以之前演示的demo为例:
在这里插入图片描述
在页面配置引入:
在这里插入图片描述
最终的页面展示:
在这里插入图片描述

国际化之 Spring Boot

看看 Locale 在 Spring Boot 中的处理,这里给出处理 Locale 的层级关系。首先可以查看源码: MessageSource ,此类专门读取配置信息,其衍生类 MessageSourceAutoConfiguration 与配置项 MessageSourceProperties。在前面我们提到的 DEMO 中,就提到了需要默认配置 message.properties,这样 spring 框架才会获取到对应的配置。

同样,我们也可以自定义配置文件名命名规则。

  • message.properties(spring 默认配置)
  • message_en.properties
  • message_zh.properties
  • message_zh_CN.properties
  • message_zh_TW.properties

Thymeleaf 在这一过程中是如何处理的?
可以参考源码:org.springframework.context.support.AbstractMessageSource#getMessage(java.lang.String, java.lang.Object[], java.lang.String, java.util.Locale)。

	@Override
	public final String getMessage(String code, @Nullable Object[] args, @Nullable String defaultMessage, Locale locale) {
		String msg = getMessageInternal(code, args, locale);
		if (msg != null) {
			return msg;
		}
		if (defaultMessage == null) {
			return getDefaultMessage(code);
		}
		return renderDefaultMessage(defaultMessage, args, locale);
	}

思考人生

JSP为什么被Spring抛弃?
  • Java EE 和 Spring 竞争关系的

  • Spring 想独立门户
    所以Spring搞了一个WebFlux~

    • WebFlux支持的功能
      • Servlet 3.1
      • Reactor +Netty
    • @Controller@RequestParam
      • Spring Web MVC
      • Spring WebFlux
      • 不再看到 Servlet API
        • ServletRequest
        • ServletResponse
JSP为什么性能要好?

JSP -> JSP 模板 -> 翻译 Servlet Java 源文件 -> 编译 Servlet Class -> Servlet 加载 -> Servlet 执行(字节码执行)

不同于JSP,Thymeleaf 的作用机制:Thymeleaf -> Thymeleaf 模板 -> 解释执行模板表达式(动态运行时)

解释型语言要比编译型语言慢!

后记

下节预告:REST理论(英文)

本节示例代码:https://github.com/harrypottry/microservices-project/spring-mvc-view

更多架构知识,欢迎关注本套Java系列文章Java架构师成长之路

;