Thymeleaf 整合 SpringBoot
Thymeleaf 整合 SpringBoot
操作
直接引入 starter。TODO
原理
主要就是看 ThymeleafAutoConfiguration
自动配置类
在与 Spring 进行整合的时候,大部分的组件都进行了拓展,然后使用拓展后的以 Spring 开头的组件,比如 SpringXXX
。并注册为 bean
-
SpringResourceTemplateResolver
-
SpringTemplateEngine
同时还注册了视图解析接口 ViewResolver
在 Thymeleaf
下的实现 ThymeleafViewResolver
的 ben,
我们要分析 ThymeleafAutoConfiguration
,主要也是分析这几个 bean,
SpringTemplateEngine
@AutoConfiguration(after = { WebMvcAutoConfiguration.class, WebFluxAutoConfiguration.class })
@EnableConfigurationProperties(ThymeleafProperties.class)
@ConditionalOnClass({ TemplateMode.class, SpringTemplateEngine.class })
@Import({ TemplateEngineConfigurations.ReactiveTemplateEngineConfiguration.class,
TemplateEngineConfigurations.DefaultTemplateEngineConfiguration.class })
public class ThymeleafAutoConfiguration {
其中导入了 TemplateEngineConfigurations.DefaultTemplateEngineConfiguration
和 TemplateEngineConfigurations.ReactiveTemplateEngineConfiguration
为不同的 Web 环境导入模板引擎
class TemplateEngineConfigurations {
@Configuration(proxyBeanMethods = false)
static class DefaultTemplateEngineConfiguration {
// 应用于Servlet类型的Web环境
// 重点看这个,常用这个
@Bean
@ConditionalOnMissingBean(ISpringTemplateEngine.class)
SpringTemplateEngine templateEngine(ThymeleafProperties properties, ObjectProvider<ITemplateResolver> templateResolvers, ObjectProvider<IDialect> dialects) {
SpringTemplateEngine engine = new SpringTemplateEngine();
engine.setEnableSpringELCompiler(properties.isEnableSpringElCompiler());
engine.setRenderHiddenMarkersBeforeCheckboxes(properties.isRenderHiddenMarkersBeforeCheckboxes());
// 我们可以通过自定义 ITemplateResolver 类型的 bean,结合@Order注解,来自定义模板解析器同时指定顺序
templateResolvers.orderedStreamaddTemplateResolver;
// 我们可以通过自定义 IDialect 类型的 bean,结合@Order注解,来自定义方言同时指定顺序
dialects.orderedStreamaddDialect;
return engine;
}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.REACTIVE)
@ConditionalOnProperty(name = "spring.thymeleaf.enabled", matchIfMissing = true)
static class ReactiveTemplateEngineConfiguration {
// 应用于 reactive类型的web环境
@Bean
@ConditionalOnMissingBean(ISpringWebFluxTemplateEngine.class)
SpringWebFluxTemplateEngine templateEngine(ThymeleafProperties properties, ObjectProvider<ITemplateResolver> templateResolvers, ObjectProvider<IDialect> dialects) {
SpringWebFluxTemplateEngine engine = new SpringWebFluxTemplateEngine();
engine.setEnableSpringELCompiler(properties.isEnableSpringElCompiler());
engine.setRenderHiddenMarkersBeforeCheckboxes(properties.isRenderHiddenMarkersBeforeCheckboxes());
templateResolvers.orderedStreamaddTemplateResolver;
dialects.orderedStreamaddDialect;
return engine;
}
}
}
现在我们来看看初始化方法,直接看 SpringTemplateEngine#initializeSpecific
关于,父方法
TemplateEngine#initializeSpecific
以及TemplateEngine
的初始化流程,请看《Thymeleaf 模板引擎.md》的TemplateEngine
小节
@Override
protected final void initializeSpecific() {
// First of all, give the opportunity to subclasses to apply their own configurations
// 空方法,钩子,待子类来实现
initializeSpringSpecific();
// Once the subclasses have had their opportunity, compute configurations belonging to SpringTemplateEngine
// 调用父类也就是 TemplateEngine的initializeSpecific方法,本身就是空实现,所以这里调用了也相当于啥也没干
super.initializeSpecific();
// 如果 templateEngineMessageSource 为null,则使用 messageSource,否则使用 templateEngineMessageSource
// 这里默认情况下就会使用 messageSource,而且此时messageSource不为空,且值为当前应用上下文 原因是,
// SpringTemplateEngine 实现了 MessageSourceAware 接口,在 TemplateEngineConfigurations$DefaultTemplateEngineConfiguration 的 templateEngine 方法将其注册为bean之后,
// Spring的Aware机制会调用 SpringTemplateEngine#setMessageSource 方法设置 messageSource 的值,
// 在我们第一次渲染页面,也就是第一次调用 TemplateEngine#process的方法的时候,开始初始化 TemplateEngine,调用 TemplateEngine#initialize 方法,调用到了子类的 SpringTemplateEngine#initializeSpecific方法,
// 此时,templateEngineMessageSource 为空 ,messageSource不为空
final MessageSource messageSource = this.templateEngineMessageSource == null ? this.messageSource : this.templateEngineMessageSource;
final IMessageResolver messageResolver;
if (messageSource != null) {
final SpringMessageResolver springMessageResolver = new SpringMessageResolver();
// 其实 SpringMessageResolver 也实现了 MessageSourceAware 接口,但是 springMessageResolver 不是bean实例,所以这里手动设置 messageSource
springMessageResolver.setMessageSource(messageSource);
// 将 springMessageResolver 最为消息解析器
messageResolver = springMessageResolver;
} else {
// 如果 messageSource 为空,则使用 thymeleaf 自带的消息解析器
messageResolver = new StandardMessageResolver();
}
// 设置消息解析器
super.setMessageResolver(messageResolver);
}
其实整个初始化方法就只做了一件事,就是设置消息解析器,而正常情况下,消息解析器的类型就是 SpringMessageResolver
。
SpringMessageResolver
为适配 Spring,自定义的消息解析器。
IMessageResolver
接口的实现,它将标准的 Spring
的消息解析方式整合到了 Thymeleaf
的消息解析中。
基于模板的解析是通过使用可用的 Spring 配置的 MessageSource
对象完成的。
Spring 中
MessageSource
相关原理,请看《Spring-MessageSource 相关解析.md》
基于 origin 参数的解析与 StandardMessageResolver
中的方法完全相同。
origin 参数表示消息请求的来源,通常是一个处理器或表达式类。可以为空。
直接看核心方法 SpringMessageResolver#resolveMessage
,可以看到先通过 Spring 的 messageSource 来解析,然后再用 Thymeleaf 默认的 StandardMessageResolver 来解析消息。
public final String resolveMessage( final ITemplateContext context, final Class<?> origin, final String key, final Object[] messageParameters) {
Validate.notNull(context.getLocale(), "Locale in context cannot be null");
Validate.notNull(key, "Message key cannot be null");
/*
* FIRST STEP: Look for the message using template-based resolution
*/
// 先通过Spring的 messageSource 来解析
if (context != null) {
// 检查 messageSource 是否为空,为空就报错
checkMessageSourceInitialized();
if (logger.isTraceEnabled()) {
logger.trace( "[THYMELEAF][{}] Resolving message with key \"{}\" for template \"{}\" and locale \"{}\". " + "Messages will be retrieved from Spring's MessageSource infrastructure.", new Object[]{TemplateEngine.threadIndex(), key, context.getTemplateData().getTemplate(), context.getLocale()});
}
try {
// 直接通过 messageSource.getMessage 来获取消息值
return this.messageSource.getMessage(key, messageParameters, context.getLocale());
} catch (NoSuchMessageException e) {
// Try other methods
}
}
/*
* SECOND STEP: Look for the message using origin-based resolution, delegated to the StandardMessageResolver
*/
// 然后再用Thymeleaf默认的 StandardMessageResolver 来解析消息
if (origin != null) {
// We will be disabling template-based resolution when delegating in order to use only origin-based
// 使用Thymeleaf默认的消息解析方式来解析
final String message = this.standardMessageResolver.resolveMessage(context, origin, key, messageParameters, false, true, true);
if (message != null) {
return message;
}
}
/*
* NOT FOUND, return null
*/
return null;
}
SpringResourceTemplateResolver
ThymeleafAutoConfiguration$DefaultTemplateResolverConfiguration
的 defaultTemplateResolver
方法。
@Bean
SpringResourceTemplateResolver defaultTemplateResolver() {
SpringResourceTemplateResolver resolver = new SpringResourceTemplateResolver();
resolver.setApplicationContext(this.applicationContext);
// 对应配置 spring.thymeleaf.prefix
resolver.setPrefix(this.properties.getPrefix());
// 对应配置 spring.thymeleaf.suffix
resolver.setSuffix(this.properties.getSuffix());
// 对应配置 spring.thymeleaf.mode
resolver.setTemplateMode(this.properties.getMode());
// 对应配置 spring.thymeleaf.encoding
if (this.properties.getEncoding() != null) {
resolver.setCharacterEncoding(this.properties.getEncoding().name());
}
// 对应配置 spring.thymeleaf.cache
resolver.setCacheable(this.properties.isCache());
// 对应配置 spring.thymeleaf.templateResolverOrder
Integer order = this.properties.getTemplateResolverOrder();
if (order != null) {
resolver.setOrder(order);
}
// // 对应配置 spring.thymeleaf.checkTemplate
resolver.setCheckExistence(this.properties.isCheckTemplate());
return resolver;
}
ThymeleafViewResolver
@Bean
@ConditionalOnMissingBean(name = "thymeleafViewResolver")
ThymeleafViewResolver thymeleafViewResolver(ThymeleafProperties properties, SpringTemplateEngine templateEngine) {
ThymeleafViewResolver resolver = new ThymeleafViewResolver();
// 这里将模板引擎实例设置到了 ViewResolver 的 templateEngine 属性中
resolver.setTemplateEngine(templateEngine);
// 对应配置 spring.thymeleaf.encoding
resolver.setCharacterEncoding(properties.getEncoding().name());
// 对应配置 spring.thymeleaf.servlet.content-type
resolver.setContentType( appendCharset(properties.getServlet().getContentType(), resolver.getCharacterEncoding()));
// 对应配置 spring.thymeleaf.servlet.produce-partial-output-while-processing
resolver.setProducePartialOutputWhileProcessing( properties.getServlet().isProducePartialOutputWhileProcessing());
// 对应配置 spring.thymeleaf.excludedViewNames
resolver.setExcludedViewNames(properties.getExcludedViewNames());
// 对应配置 spring.thymeleaf.viewNames
resolver.setViewNames(properties.getViewNames());
// This resolver acts as a fallback resolver (e.g. like a
// InternalResourceViewResolver) so it needs to have low precedence
resolver.setOrder(Ordered.LOWEST_PRECEDENCE - 5);
// 对应配置 spring.thymeleaf.cache
resolver.setCache(properties.isCache());
return resolver;
}
注意,这里在创建 ThymeleafViewResolver
实例的时候将前面配置的模板引擎实例 SpringTemplateEngine
设置到了视图解析器的 ThymeleafViewResolver#templateEngine
字段中,然后在 ThymeleafViewResolver#loadView
方法根据视图名称(viewName
参数)来创建视图对象的时候(View
),会将模板引擎设置到视图的 AbstractThymeleafView#templateEngine
字段中。在最终将视图的内容输出到响应的消息提中的时候,会调用 ThymeleafView#renderFragment
方法,其中会调用 ITemplateEngine#process
方法将视图内容渲染,然后写入到 Writer
中
// 是不是有熟悉的感觉了?
viewTemplateEngine.process(templateName, processMarkupSelectors, context, templateWriter);
// If a buffer was used, write it to the web server's output buffers all at once
if (!producePartialOutputWhileProcessing) {
// 写入到响应中
response.getWriter().write(templateWriter.toString());
response.getWriter().flush();
}
具体的 Thymeleaf 的视图的相关分析,请看《SpringMVC- 第五篇:视图.md》的
ThymeleafViewResolver源码
小节
ThymeleafProperties 配置类
略