1、使用Spring Boot:
- 创建Spring Boot应用,选中我们需要的模块
- Spring Boot已经默认将这些场景配置好了,只需要在配置文件中指定少量配置就可以运行起来
- 自己编写业务代码
回顾自动配置原理
这个场景Spring Boot帮我们配置了什么?能不能修改?能修改哪些配置?能不能扩展?等等
- xxxxAutoConfiguration:帮我们给容器中自动配置组件
- xxxxProperties:配置类来封装配置文件的内容
2、Spring Boot对静态资源的映射规则
@ConfigurationProperties(
prefix = "spring.resources",
ignoreUnknownFields = false
)
public class ResourceProperties implements ResourceLoaderAware {
// 可以设置和静态资源有关的参数,比如缓存时间
...
}
2.1 依赖的方式
// WebMvcAutoConfiguration.class文件
public void addResourceHandlers(ResourceHandlerRegistry registry) {
if (!this.resourceProperties.isAddMappings()) {
logger.debug("Default resource handling disabled");
} else {
Integer cachePeriod = this.resourceProperties.getCachePeriod();
if (!registry.hasMappingForPattern("/webjars/**")) {
this.customizeResourceHandlerRegistration(registry.addResourceHandler(
new String[]{"/webjars/**"}).addResourceLocations(
new String[]{"classpath:/META-INF/resources/webjars/"}
).setCachePeriod(cachePeriod)
);
}
String staticPathPattern = this.mvcProperties.getStaticPathPattern();
if (!registry.hasMappingForPattern(staticPathPattern)) {
this.customizeResourceHandlerRegistration(registry.addResourceHandler(
new String[]{staticPathPattern}).addResourceLocations(
this.resourceProperties.getStaticLocations()
).setCachePeriod(cachePeriod)
);
}
}
}
-
所有/webjars/**,都去classpath:/META-INF/resources/webjars/找资源
webjars:以jar包的方式引入静态资源
webjars官网
访问方式:http://localhost:8088/webjars/jquery/3.5.1/jquery.js
<!-- 引入jquery-webjars -->
<dependency>
<groupId>org.webjars</groupId>
<artifactId>jquery</artifactId>
<version>3.5.1</version>
</dependency>
2.2 “/**”访问当前项目的任何资源(静态资源的文件夹)
继续读addResourceHandlers
方法,如果处理不了,就去静态staticPathPattern
路径去找
public WebMvcProperties() {
this.localeResolver = WebMvcProperties.LocaleResolver.ACCEPT_HEADER;
this.dispatchTraceRequest = false;
this.dispatchOptionsRequest = true;
this.ignoreDefaultModelOnRedirect = true;
this.throwExceptionIfNoHandlerFound = false;
this.logResolvedException = false;
this.mediaTypes = new LinkedHashMap();
this.staticPathPattern = "/**";
this.async = new WebMvcProperties.Async();
this.servlet = new WebMvcProperties.Servlet();
this.view = new WebMvcProperties.View();
}
它添加了个位置
addResourceLocations
,这个里面的resourceProperties
有个getStaticLocations()
方法,点进去,有个常量RESOURCE_LOCATIONS
static {
RESOURCE_LOCATIONS = new String[CLASSPATH_RESOURCE_LOCATIONS.length +
SERVLET_RESOURCE_LOCATIONS.length];
System.arraycopy(SERVLET_RESOURCE_LOCATIONS, 0, RESOURCE_LOCATIONS, 0,
SERVLET_RESOURCE_LOCATIONS.length);
System.arraycopy(CLASSPATH_RESOURCE_LOCATIONS, 0, RESOURCE_LOCATIONS,
SERVLET_RESOURCE_LOCATIONS.length, CLASSPATH_RESOURCE_LOCATIONS.length);
}
就找到了以下文件夹
private static final String[] SERVLET_RESOURCE_LOCATIONS = new String[]{"/"}; // 当前项目的根路径
// 和
private static final String[] CLASSPATH_RESOURCE_LOCATIONS = new String[]{
"classpath:/META-INF/resources/",
"classpath:/resources/",
"classpath:/static/",
"classpath:/public/"
};
src/mian/java
和src/main/resources
都是类路径如果要访问
localhost:8088/*.js
资源,没有人为处理,那么会自动去上面这些路径,静态资源文件夹里找这些文件
访问方式:http://localhost:8088/asserts/img/childhood_dreams.jpg
2.3 欢迎页设置,静态资源文件夹下的所有index.html页面
// WebMvcAutoConfiguration.class文件
// 配置欢迎页映射
@Bean
public WebMvcAutoConfiguration.WelcomePageHandlerMapping welcomePageHandlerMapping
(ResourceProperties resourceProperties) {
return new WebMvcAutoConfiguration.WelcomePageHandlerMapping(
resourceProperties.getWelcomePage(), this.mvcProperties.getStaticPathPattern()
);
}
private void customizeResourceHandlerRegistration(ResourceHandlerRegistration registration) {
if (this.resourceHandlerRegistrationCustomizer != null) {
this.resourceHandlerRegistrationCustomizer.customize(registration);
}
}
Spring最顶层的组件,保存每个请求谁来处理
点进
getWelcomePage()
方法
this.mvcProperties.getStaticPathPattern()
还被谁映射,点进去看源码就是/**
public Resource getWelcomePage() {
String[] var1 = this.getStaticWelcomePageLocations();
int var2 = var1.length;
for(int var3 = 0; var3 < var2; ++var3) {
String location = var1[var3];
Resource resource = this.resourceLoader.getResource(location);
try {
if (resource.exists()) {
resource.getURL();
return resource;
}
} catch (Exception var7) {
}
}
return null;
}
需要遍历,说明欢迎页还挺多的
点进
getStaticWelcomePageLocations()
方法
private String[] getStaticWelcomePageLocations() {
String[] result = new String[this.staticLocations.length];
for(int i = 0; i < result.length; ++i) {
String location = this.staticLocations[i];
if (!location.endsWith("/")) {
location = location + "/";
}
result[i] = location + "index.html";
}
return result;
}
还是这个
staticLocations
静态文件夹静态文件夹路径都拼接上
index.html
访问方式:http://localhost:8088/ 就会找index页面
2.4 网站图标
// WebMvcAutoConfiguration.class文件
// 配置喜欢的图标
@Configuration
@ConditionalOnProperty(
value = {"spring.mvc.favicon.enabled"},
matchIfMissing = true
)
public static class FaviconConfiguration {
private final ResourceProperties resourceProperties;
public FaviconConfiguration(ResourceProperties resourceProperties) {
this.resourceProperties = resourceProperties;
}
@Bean
public SimpleUrlHandlerMapping faviconHandlerMapping() {
SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping();
mapping.setOrder(-2147483647);
mapping.setUrlMap(Collections.singletonMap("**/favicon.ico",
this.faviconRequestHandler()));
return mapping;
}
@Bean
public ResourceHttpRequestHandler faviconRequestHandler() {
ResourceHttpRequestHandler requestHandler = new ResourceHttpRequestHandler();
requestHandler.setLocations(this.resourceProperties.getFaviconLocations());
return requestHandler;
}
}
所有的
**/favicon.ico
都是在静态文件夹下找
2.5 自定义路径
spring.resources.static-locations=classpath:/hello/,classpath:initializr/
多路径用都好隔开,一旦启用自定义路径,那么默认的静态资源路径就不能使用了
3、模板引擎
JSP、Velocity、Freemarker、Thymeleaf
Spring Boot推荐的Thymeleaf:语法更简单,功能更强大
3.1 引入Thymeleaf,引入starter:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
查看引入的库,发现引入的2.1.6版本的,太低了
Thymeleaf的版本发布
Spring官网Thymeleaf 3的使用
切换版本:
<properties>
...
<!-- Thymeleaf主程序 -->
<thymeleaf.version>3.0.2.RELEASE</thymeleaf.version>
<!-- Thymeleaf布局支持程序 Thymeleaf3要求layout2以上版本 -->
<thymeleaf-layout-dialect.version>2.1.1</thymeleaf-layout-dialect.version>
<!-- Thymeleaf2和layout1适配 -->
</properties>
properties里面的会属性覆盖Spring Boot默认的版本号
thymeleaf-layout-dialect的版本发布
3.2 Thymeleaf使用及语法
还是在spring-boot-autoconfigure
自动配置包里:添加组件
@Configuration
@ConditionalOnClass(
name = {"org.thymeleaf.templatemode.TemplateMode"}
)
static class Thymeleaf3Configuration {
Thymeleaf3Configuration() {
}
@Configuration
@ConditionalOnClass({Servlet.class})
@ConditionalOnWebApplication
static class Thymeleaf3ViewResolverConfiguration extends
AbstractThymeleafViewResolverConfiguration {
Thymeleaf3ViewResolverConfiguration
(ThymeleafProperties properties, SpringTemplateEngine templateEngine) {
super(properties, templateEngine);
}
protected void configureTemplateEngine
(ThymeleafViewResolver resolver, SpringTemplateEngine templateEngine) {
Method setTemplateEngine;
try {
setTemplateEngine = ReflectionUtils.findMethod(
resolver.getClass(),
"setTemplateEngine",
new Class[]{Class.forName(
"org.thymeleaf.ITemplateEngine",
true,
resolver.getClass().getClassLoader()
)
}
);
} catch (ClassNotFoundException var5) {
throw new IllegalStateException(var5);
}
ReflectionUtils.invokeMethod(setTemplateEngine, resolver, new Object[]{templateEngine});
}
}
@Configuration
@ConditionalOnMissingBean(
name = {"defaultTemplateResolver"}
)
static class DefaultTemplateResolverConfiguration extends
AbstractTemplateResolverConfiguration {
DefaultTemplateResolverConfiguration(ThymeleafProperties properties,
ApplicationContext applicationContext) {
super(properties, applicationContext);
}
@Bean
public SpringResourceTemplateResolver defaultTemplateResolver() {
SpringResourceTemplateResolver resolver = super.defaultTemplateResolver();
Method setCheckExistence = ReflectionUtils.findMethod(
resolver.getClass(),
"setCheckExistence",
new Class[]{Boolean.TYPE}
);
ReflectionUtils.invokeMethod(
setCheckExistence,
resolver,
new Object[]{this.getProperties().isCheckTemplate()}
);
return resolver;
}
}
}
因为我们引入的是3+版本,所以只有3+版本生效;
配置了哪些属性?在文件ThymeleafProperties
中:
@ConfigurationProperties(
prefix = "spring.thymeleaf"
)
public class ThymeleafProperties {
private static final Charset DEFAULT_ENCODING = Charset.forName("UTF-8");
private static final MimeType DEFAULT_CONTENT_TYPE = MimeType.valueOf("text/html");
public static final String DEFAULT_PREFIX = "classpath:/templates/";
public static final String DEFAULT_SUFFIX = ".html";
private boolean checkTemplate = true;
private boolean checkTemplateLocation = true;
private String prefix = "classpath:/templates/";
private String suffix = ".html";
private String mode = "HTML5";
private Charset encoding;
private MimeType contentType;
private boolean cache;
private Integer templateResolverOrder;
private String[] viewNames;
private String[] excludedViewNames;
private boolean enabled;
}
只要我们把HTML页面放在classpath:/templates/
,Thymeleaf就能自动渲染
3.3 代码示例
- 导入Thymeleaf的名称空间
<html lang="en" xmlns:th="http://www.thymeleaf.org">
- 使用Thymeleaf语法
<div th:text="${text}">这里是div信息</div>
3.4 语法规则
th:text
:改变当前元素里面的文本内容;
th:任意html属性;替换原生属性的值,例:th:class覆盖class
Feature | Detail | Attribute |
---|---|---|
Fragment inclusion | 片段包含:jsp:include | th:insert th:replace |
Fragment iteration | 遍历:c:forEach | th:each |
Conditional evaluation | 条件判断:c:if | th:if th:unless th:switch th:case |
Local variable definition | 声明变量:c:set | th:object th:with |
General attribute modification | 任意属性修改支持prepend, append | th:attr th:attrprepend th:attrappend |
Specific attribute modification | 修改制定属性默认值 | th:value th:href th:src … |
Text (tag body modification) | 修改标签体内容 | th:text(转义特殊字符) th:utext(不转义特殊字符) |
Fragment specification | 声明片段 | th:fragment |
Fragment removal | th:remove |
3.5 表达式
-
Simple expressions: (表达式语法)
- Variable Expressions: ${…}: 获取变量值; OGNL;
1) 获取对象的属性、调用方法 2) 使用内置的基本对象 #ctx : the context object. #vars: the context variables. #locale : the context locale. #request : (only in Web Contexts) the HttpServletRequest object. #response : (only in Web Contexts) the HttpServletResponse object. #session : (only in Web Contexts) the HttpSession object. #servletContext : (only in Web Contexts) the ServletContext object. ${session.foo} // Retrieves the session atttribute 'foo' # 附录都有演示 3) 内置的一些工具对象 #execInfo : information about the template being processed. #messages : methods for obtaining externalized messages inside variables expressions, # in the same way as they would be obtained using #{…} syntax. #uris : methods for escaping parts of URLs/URIs #conversions : methods for executing the configured conversion service (if any). #dates : methods for java.util.Date objects: formatting, component extraction, etc. #calendars : analogous to #dates , but for java.util.Calendar objects. #numbers : methods for formatting numeric objects. #strings : methods for String objects: contains, startsWith, prepending/appending, etc. #objects : methods for objects in general. #bools : methods for boolean evaluation. #arrays : methods for arrays. #lists : methods for lists. #sets : methods for sets. #maps : methods for maps. #aggregates : methods for creating aggregates on arrays or collections. #ids : methods for dealing with id attributes that might be repeated # (for example, as a result of an iteration).
- Selection Variable Expressions: *{…}: 选择表达式, 和${}在功能上是一样的
补充: 配合th:object="${session.user}"进行使用 <div th:object="${session.user}"> <p>Name: <span th:text="*{firstName}">Sebastian</span>.</p> <p>Surname: <span th:text="*{lastName}">Pepper</span>.</p> <p>Nationality: <span th:text="*{nationality}">Saturn</span>.</p> </div>
- Message Expressions: #{…}: 获取国际化内容
- Link URL Expressions: @{…}: 定义URL
@{/order/process(execId=${execId},execType='FAST')}
- Fragment Expressions: ~{…}: 片段引用表达式
# 插入片段文档 <div th:insert="~{commons :: main}">...</div>
-
Literals(字面量)
Text literals: 'one text' , 'Another one!' ,…
Number literals: 0 , 34 , 3.0 , 12.3 ,…
Boolean literals: true , false
Null literal: null
Literal tokens: one , sometext , main ,…
- Text operations:(文本操作)
String concatenation: +
Literal substitutions: |The name is ${name}|
- Arithmetic operations:(数学运算)
Binary operators: + , - , * , / , %
Minus sign (unary operator): -
- Boolean operations:(布尔运算)
Binary operators: and , or
Boolean negation (unary operator): ! , not
- Comparisons and equality:(比较运算)
Comparators: > , < , >= , <= ( gt , lt , ge , le )
Equality operators: == , != ( eq , ne )
- Conditional operators:(条件运算, 也支持3元运算符)
If-then: (if) ? (then)
If-then-else: (if) ? (then) : (else)
Default: (value) ?: (defaultvalue)
- Special tokens:(特殊符号)
No-Operation: _
4、SpringMVC自动配置
可以参阅官方文档Developing web applications
4.1 Spring Boot为Spring MVC提供了自动配置,可与大多数应用程序完美配合。
自动配置会在Spring的默认设置之上添加以下功能:
-
包含
ContentNegotiatingViewResolver
和BeanNameViewResolver Bean
。-
自动配置了ViewResolver(视图解析器:根据方法的返回值得到视图对象(就是View对象),视图对象决定如何渲染(转发?重定向?等))
-
ContentNegotiatingViewResolver
:组合所有的视图解析器的 -
如何定制:我们可以自己给容器中添加一个视图解析器,自动将其组合进来
-
Ctrl+N输入
DispatchServlet.class
在它的doDispatch
方法前面设置断点,Debug运行,浏览器访问页面,查看控制台。看DispatchServlet里用到的视图解析器是什么?得到如图:
-
-
支持提供静态资源,包括对WebJars的支持(请参见下文)。
-
自动注册
Converter
,GenericConverter
,Formatter
bean。Converter
:转换器,类型转换使用。例如前端文本转后台IntegerFormatter
:格式化器,2020.08.13===Date
// 配置文件中配置日期格式化的规则 @Bean @ConditionalOnProperty( prefix = "spring.mvc", name = {"date-format"} ) public Formatter<Date> dateFormatter() { return new DateFormatter(this.mvcProperties.getDateFormat()); // 日期格式化组件 }
- 自己添加的格式化器,我们只需要放在容器中即可
-
支持
HttpMessageConverters
(请参见下文)。- HttpMessageConverter:SpringMVC中用来转换HTTP请求和响应的;例:User对象以JSON形式写出
HttpMessageConverters
是从容器中确定的,获取所有的HttpMessageConverter- 自己给容器中添加HttpMessageConverter,只需将自己的组件注册在容器中(@Bean, @Component)
-
自动注册
MessageCodesResolver
(请参见下文)。- 定义错误代码生成规则(例:JSR303校验时)
-
静态
index.html
支持。 -
自定义
Favicon
支持(请参阅下文)。 -
自动使用
ConfigurableWebBindingInitializer
bean(请参见下文)。- 我们可以配置一个
ConfigurableWebBindingInitializer
来替换默认的;(添加到容器中) - 初始化
WebDataBinder
(web数据绑定器); - web数据绑定器的功能:请求数据=====绑定到JavaBean中
- 我们可以配置一个
org.springframework.boot.autoconfigure.web
:web的所有自动配置场景。
如果您想保留Spring Boot MVC功能,而只想添加其他MVC配置(拦截器,格式化程序,视图控制器等),则可以添加自己的类型为WebMvcConfigurerAdapter
的@Configuration
类,但无需@EnableWebMvc
。如果希望提供RequestMappingHandlerMapping
,RequestMappingHandlerAdapter
或ExceptionHandlerExceptionResolver
的自定义实例,则可以声明一个提供此类组件的WebMvcRegistrationsAdapter
实例。
如果要完全控制Spring MVC,则可以添加用@EnableWebMvc
注释的自己的@Configuration
。
4.2 扩展SpringMVC
<mvc:view-controller path="/hello" view-name="success"/>
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/hello"/>
<bean></bean>
</mvc:interceptor>
</mvc:interceptors>
要实现以上Spring配置文件的内容,编写一个配置类(@Configuration),是WebMvcConfigurerAdapter
类型;不能标注@EnableWebMvc
// 使用WebMvcConfigurerAdapter可以来扩展SpringMVC的功能
@Configuration
public class SpringMVCConfig extends WebMvcConfigurerAdapter {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
// super.addViewControllers(registry);
//浏览器发送 /addViewController 请求来到 thymeleaf
registry.addViewController("/addViewController").setViewName("thymeleaf");
}
}
既保留了所有的自动配置,也能用我们扩展的配置;
原理:
- WebMvcAutoConfiguration是SpringMVC的自动配置类
- 在做其他自动配置时会导入;@Import(EnableWebMvcConfiguration.class)
@Configuration
public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration {
...
}
点进其父类DelegatingWebMvcConfiguration
查看源码:
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite();
public DelegatingWebMvcConfiguration() {
}
//自动装配, 从容器中获取所有的WebMvcConfigurer
@Autowired(
required = false
)
public void setConfigurers(List<WebMvcConfigurer> configurers) {
if (!CollectionUtils.isEmpty(configurers)) {
this.configurers.addWebMvcConfigurers(configurers);
// 一个参考实现; 将所有的WebMvcConfigurer相关配置都来一起调用;
}
}
}
WebMvcConfigurerComposite
类中的一个实现
public void addViewControllers(ViewControllerRegistry registry) {
Iterator var2 = this.delegates.iterator();
while(var2.hasNext()) {
WebMvcConfigurer delegate = (WebMvcConfigurer)var2.next();
delegate.addViewControllers(registry);
}
}
- 容器中所有的WebMvcConfigurer都会一起起作用(包括我们自己写的);
- 我们的配置类也会被调用;
- 效果:SpringMVC的自动配置和我们的扩展配置都会起作用;
4.3 全面接管SpringMVC
SpringBoot对SpringMVC的自动配置不需要了,所有都是我们自己配置;所有的SpringMVC的自动配置都失效了(静态资源也不能访问了)
我们需要在配置类中添加@EnableWebMvc即可;
原理:为什么@EnableWebMvc自动配置就失效了?
- @EnableWebMvc的核心
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import({DelegatingWebMvcConfiguration.class})
public @interface EnableWebMvc {
}
- 查看
DelegatingWebMvcConfiguration
类的源码
@Configuration
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
...
}
- 在找到
WebMvcAutoConfiguration
类,查看签名
@Configuration
@ConditionalOnWebApplication
@ConditionalOnClass({Servlet.class, DispatcherServlet.class, WebMvcConfigurerAdapter.class})
@ConditionalOnMissingBean({WebMvcConfigurationSupport.class})
@AutoConfigureOrder(-2147483638)
@AutoConfigureAfter({DispatcherServletAutoConfiguration.class,
ValidationAutoConfiguration.class})
public class WebMvcAutoConfiguration {
...
}
@ConditionalOnMissingBean()
:容器中没有这个组件的时候,这个自动配置类才生效
- @EnableWebMvc将WebMvcConfigurationSupport组件导入进来;
- 导入的WebMvcConfigurationSupport只是SpringMVC最基本的功能。
5、如何修改Spring Boot的默认配置
模式:
- Spring Boot在自动配置很多组件时,先看容器中有没有用户自己配置的(@Bean, @Component),如果有就用用户配置的,如果没有才自动配置;如果有些组件可以有多个(ViewResolver)将用户配置的和自己默认的组合起来;
- 在SpringBoot中会有非常多的xxxConfigurer帮助我们进行扩展配置
- 在SpringBoot中会有很多的xxxCustomizer帮助我们进行定制配置