目录导航
前言
前面的章节我们讲了Spring Application。本节,继续微服务专题的内容分享,共计16小节,分别是:
- 微服务专题01-Spring Application
- 微服务专题02-Spring Web MVC 视图技术
- 微服务专题03-REST
- 微服务专题04-Spring WebFlux 原理
- 微服务专题05-Spring WebFlux 运用
- 微服务专题06-云原生应用(Cloud Native Applications)
- 微服务专题07-Spring Cloud 配置管理
- 微服务专题08-Spring Cloud 服务发现
- 微服务专题09-Spring Cloud 负载均衡
- 微服务专题10-Spring Cloud 服务熔断
- 微服务专题11-Spring Cloud 服务调用
- 微服务专题12-Spring Cloud Gateway
- 微服务专题13-Spring Cloud Stream (上)
- 微服务专题14-Spring Cloud Bus
- 微服务专题15-Spring Cloud Stream 实现
- 微服务专题16-Spring Cloud 整体回顾
本节内容重点为:
- 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
- 接口类型
- Model
- ModelMap
- ModelAndView : 包含 Model 和 View 两个模块。
- 注解类型
@ModelAttrtibute
: 常用于功能处理方法的入参上,返回值以及控制器的一般方法(非功能处理方法)上。
EL 表达式
- 字符值
- 多种数据类型
- 逻辑表达式(if else)
- 迭代表达式(for each / while)
- 反射(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 九大内置对象都有哪些?
内置对象名 | 类型 | 关注点 |
---|---|---|
request | HttpServletRequest | 关注当前请求 |
response | HttpServletResponse | 关注相应结果 |
config | ServletConfig | 关注配置 |
Application | ServletContext | 关注当前应用 |
session | HttpSession | 关注当前会话 |
Exception | Throwable | 关注异常 |
page | Object(this) | 关注 Jsp 对象 |
out | JspWriter | 关注输出 |
pageContext | PageContext | 关注当前页面 |
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
,详细划分为:ForwardAction
和 RedirectAction
。
接下来我们说说视图处理对象,通常有 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。处理逻辑可以参考
IEngineContext
与ProcessorTemplateHandler
。 - 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
- WebFlux支持的功能
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架构师成长之路