1. 使用Spring Boot(三步走)
- 用Spring Initializer创建Spring Boot应用,选中需要的模块
- Spring Boot默认将这些场景自动配置(xxxAutoConfiguration注入组件+xxxProperties封装配置),只需要在主配置文件中指定少量配置就可以运行
- 编写业务逻辑代码
2. RESTful-CRUD实例
2.1 准备
2.1.1 使用Spring Initializer快速创建自带web模块的Spring Boot项目
2.1.2 Spring Boot对静态资源的映射规则
//用来设置和静态资源有关的参数,如缓存时间等
@ConfigurationProperties(prefix = "spring.resources",ignoreUnknownFields = false)
public class ResourceProperties implements ResourceLoaderAware, InitializingBean
//org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration
//静态资源处理器
public void addResourceHandlers(ResourceHandlerRegistry registry) {
if (!this.resourceProperties.isAddMappings()) {
logger.debug("Default resource handling disabled");
} else {
Integer cachePeriod = this.resourceProperties.getCachePeriod();
//规则一:localhost:8080/webjars/jquery/3.2.1/jquery.js
if (!registry.hasMappingForPattern("/webjars/**")) {
this.customizeResourceHandlerRegistration(registry.addResourceHandler(new String[]{
"/webjars/**"}).addResourceLocations(new String[]{
"classpath:/META-INF/resources/webjars/"}).setCachePeriod(cachePeriod));
}
//规则二:去ResourceProperties里配置拿staticLocation,这是一个常量数组RESOURCE_LOCATIONS={"/","classpath:/META-INF/resources","classpath:/resources/","classpath:/static/","classpathL/public/"},这些位置就是存放静态资源的位置
String staticPathPattern = this.mvcProperties.getStaticPathPattern();
if (!registry.hasMappingForPattern(staticPathPattern)) {
this.customizeResourceHandlerRegistration(registry.addResourceHandler(new String[]{
staticPathPattern}).addResourceLocations(this.resourceProperties.getStaticLocations()).setCachePeriod(cachePeriod)); //staicLocation->ResourceProperty#RESOURCE_LOCATIONS,是一个常量数组
}
}
}
-
规则一:所有匹配/webjars/**的资源请求都去 classpath:/META-INF/resources/webjars/ 找资源。
【webjars】:以jar包的方式引入静态资源:https://www.webjars.org/ ,可以查询到所需资源的对应版本并将dependency引入pom.xml,如jquery。<dependency> <groupId>org.webjars</groupId> <artifactId>jquery</artifactId> <version>3.2.1</version> </dependency>
导入后的jar包目录结构
只需要写wejars下面的资源路径即可,访问路径举例:localhost:8080/webjars/jquery/3.2.1/jquery.js -
/** 访问当前项目的任何资源,如果没有处理,将从以下静态资源文件夹中查找
classpath:/META-INF/resources/
classpath:/resources/
classpath:/static/
classpath:/public/
/ (即当前项目的根路径)
注意,上面的静态资源文件夹的名字不在访问路径中出现,访问路径举例:localhost:8080/assets/js/Chart.min.js
//配置欢迎页映射
@Bean
public WebMvcAutoConfiguration.WelcomePageHandlerMapping welcomePageHandlerMapping(ResourceProperties resourceProperties) {
//底层调用返回的是,第一个“存在的index.html”的Resource对象,与getStaticPathPattern()返回的/**映射
return new WebMvcAutoConfiguration.WelcomePageHandlerMapping(resourceProperties.getWelcomePage(), this.mvcProperties.getStaticPathPattern());
}
/*底层的核心调用
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 + "/";
}
//将静态资源文件夹的路径都拼接上index.html,返回{/index.html,classpath:/META-INF/resource/index.html,classpath:/resource/index.html,classpath:/static/index.html,classpath:/public/index.html}
result[i] = location + "index.html";
}
*/
- 欢迎页:静态资源文件夹下的所有index.html页面;被/**映射。 访问路径举例:localhost:8080/ => index.html
//配置图标
@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);
//将所有**/favicon.ico映射给faviconRequestHandler,底层核心调用resourceProperties.getFaviconLocations()中,还是拿静态资源文件夹的路径来取得图标路径
mapping.setUrlMap(Collections.singletonMap("**/favicon.ico",
this.faviconRequestHandler()));
return mapping;
}
/*底层核心调用
List<Resource> getFaviconLocations() {
List<Resource> locations = new ArrayList(this.staticLocations.length + 1);
if (this.resourceLoader != null) {
String[] var2 = this.staticLocations;
int var3 = var2.length;
for(int var4 = 0; var4 < var3; ++var4) {
String location = var2[var4];
locations.add(this.resourceLoader.getResource(location));
}
}
*/
- 所有的**/favicon.ico都是在静态资源文件夹下找
- 通过在主配置文件中修改spring.resources.static-locations(就是前面源码中ResourcePropeties一直在用的staticLocations)自定义静态资源文件夹(配置之后,之前默认的静态资源文件夹路径都不可用),可以定义多个,因为上面看到了它本身是一个数组(用逗号分隔)。
2.2 Spring Boot默认不支持JSP => 推荐模板引擎(ViewResolver)Thymeleaf
2.2.1 引入Thymeleaf-starter
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!--默认引用2.1.6,过低,需要切换3.x。在当前工程pom.xml抽取的<properties>中加入如下配置来覆盖父依赖-->
<thymeleaf.version>3.0.2.RELEASE</thymeleaf.version>
<!--布局功能的支持程序,thymeleaf3主程序,layout2以上版本-->
<thymeleaf-layout-dialect.version>2.1.1</thymeleaf-layout-dialect.version>
2.2.2 使用
- 只要把html页面置于classpath:/templates/中,thymeleaf便能自动渲染。
- 在h5页面的<html>中导入thymeleaf的命名空间以获取语法提示。
<html lang="en" xmlns:th="http://www.thymeleaf.org">
- Thymeleaf配置封装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"); //只要把HTML页面置于classpath:/templates/中,thymeleaf便能自动渲染 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; }
2.2.3 语法(详见Thymeleaf官网pdf)
-
th:text 替换当前元素内的文本内容
==推广至==>“th:任意属性” 可以对html原生对应属性进行替换(只有经过模板引擎渲染的访问才会发生属性替换,直接访问单纯的静态 html不会替换)。 -
属性解析的优先级
-
表达式
Simple Expression 简单表达式 Variable Expression 变量表达式 ${} 底层是OGNL表达式,OGNL功能
内置Basic Object:
#ctx: the context object
#vars: the context variables
#locale: the context locale
#request: HttpServletRequest
#response: HttpServeltResponse
#session: HttpSession
#servletContext: ServletContext
内置工具对象:
#execInfo: information about the template being processed.
#message: methods for obtaining externalized messages inside variables expression, in the same way as they would be obtained using #{…}
#uris: methods for escaping parts of URLs/URIs
#conversions: methods for executingthe configured conversion service(if any)
#dates: methods for java.util.Date objects: formatting, component extraction…
#calendars: analogous to #date, but for java.util.Calendar objects
#numbers: methods for formatting numeric objects
#strings: methods for String objects: contains, startWith, prepending/appending…
#objects: methods for objects in general
#bools: method for boolean evaluation
#arrays: method for arrays
#lists
#sets
#maps
#aggregates: methods for creating aggregates on array of collection
#ids: methods for dealing with id attributes that might be repeatedSelecting Variable Expression 选择表达式 基本功能和${}一样,但补充了功能:配合th:object进行使用,在外层div属性th:object指定对象后,可以直接在内部元素直接使用*{字段名}取到其内部属性值 Message Expression 国际化表达式 #{},获取国际化内容 Link URL Expression @{},定义URL,th:href
@{/order/process(execId=${execId},execType=‘FAST’)},execId和execType是传递的两个参数,用逗号隔开,可以套用${}动态取参数Fragment Expression 文档片段表达式 ~{…} 字面量表达式,数学表达式,布尔表达式,运算符表达式、条件运算等与OGNL一致
【Special tokes 特殊表达式(表示无操作,如在三元运算中):_】 -
常见场景实例
<body>
success!
<!-- th:text 将div内的文本内容设置为指定值-->
<div th:text="${hello}"></div>
<div th:utext="${hello}"></div>
<hr/>
<!--与增强foreach相似的遍历th:each, 每次遍历都会生成当前标签-->
<h4 th:text="${user}" th:each="user:${users}"></h4>
<hr>
<h4>
<!--[[]]与th:text等价,[()]与th:utext等价 -->
<span th:each="user:${users}">[[${user}]]</span>
</h4>
</body>
2.3 底层 SpringMVC 自动配置WebMvcAutoConfiguration
Spring Boot provides auto-configuration for Spring MVC that works well with most applications.
The auto-configuration adds the following features on top of Spring’s defaults:
- Inclusion of ContentNegotiatingViewResolver and BeanNameViewResolver beans.
(自动配置视图解析器ViewResolver。ContentNegotiatingViewResolver将获取容器中所有的ViewResolver。=>只要在配置类中向容器注入ViewResolver的实现类对象,就可以被ContentNegotiatingViewResolver自动组合进来。)
- Support for serving static resources, including support for WebJars (see below).
- Static index.html support.
- Custom Favicon support (see below).
(对webjars、静态资源文件夹、首页、图标等支持)
- Automatic registration of Converter, GenericConverter, Formatter beans.
(自动配置了映射绑定时的类型转换Converter和数据格式化Formatter=>只要在配置类中向容器注入Converter或Formatter的实现类对象,就可以添加自定义转换器)
- Support for HttpMessageConverters (see below).
(自动配置对JSON和POJO的转换器=>只要在配置类中注入HttpMessageConverter,就可以添加自定义HttpMessageConverter)
- Automatic registration of MessageCodesResolver (see below).
定义错误代码生成规则,如JSR303数据校验
- Automatic use of a ConfigurableWebBindingInitializer bean (see below).
(自动配置ConfigurableWebBindingInitializer用来初始化WebDataBinder=>只要在配置类中注入ConfigurableWebBindingInitializer,就可以自定义WebDataBinder来替换默认的)
- 自动配置视图解析器ViewResolver
//WebMVCAutoConfiguration.java //1.1 ContentNegotiatingViewResolver:组合所有的视图解析器 @Bean @ConditionalOnBean({ ViewResolver.class}) @ConditionalOnMissingBean(name = { "viewResolver"},value = { ContentNegotiatingViewResolver.class}) public ContentNegotiatingViewResolver viewResolver(BeanFactory beanFactory) { ContentNegotiatingViewResolver resolver = new ContentNegotiatingViewResolver(); resolver.setContentNegotiationManager((ContentNegotiationManager)beanFactory .getBean(ContentNegotiationManager.class)); resolver.setOrder(-2147483648); return resolver; } //ContentNegotiatingViewResolver.java //1.1.1 ContentNegotiatingViewResolver的核心方法:解析最佳视图实现 public View resolveViewName(String viewName, Locale locale) throws Exception { RequestAttributes attrs = RequestContextHolder.getRequestAttributes(); Assert.state(attrs instanceof ServletRequestAttributes, "No current ServletRequestAttributes"); List<MediaType> requestedMediaTypes = this.getMediaTypes(((ServletRequestAttributes)attrs).getRequest()); if (requestedMediaTypes != null) { //获取候选的View对象 List<View> candidateViews = this.getCandidateViews(viewName, locale, requestedMediaTypes); /* 底层getCadidateViews()的核心代码 private List<View> getCandidateViews(String viewName, Locale locale, List<MediaType> requestedMediaTypes) throws Exception { List<View> candidateViews = new ArrayList(); Iterator var5 = this.viewResolvers.iterator(); //遍历所有的ViewResolver进行解析 while(var5.hasNext()) { ViewResolver viewResolver = (ViewResolver)var5.next(); View view = viewResolver.resolveViewName(viewName, locale); if (view != null) { candidateViews.add(view); } Iterator var8 = requestedMediaTypes.iterator(); while(var8.hasNext()) { MediaType requestedMediaType = (MediaType)var8.next(); List<String> extensions = this.contentNegotiationManager.resolveFileExtensions(requestedMediaType); Iterator var11 = extensions.iterator(); while(var11.hasNext()) { String extension = (String)var11.next(); String viewNameWithExtension = viewName + '.' + extension; view = viewResolver.resolveViewName(viewNameWithExtension, locale); if (view != null) { candidateViews.add(view); } } } } }*/ //从候选View中选出最佳View对象后返回 View bestView = this.getBestView(candidateViews, requestedMediaTypes, attrs); if (bestView != null) { return bestView; } } //... ... } //1.1.2 ContentNegotiatingViewResolver如何获取所有的视图解析器ViewResolver? //如何自定义视图解析器?给容器注入自定义ViewResolver实现类,ContentanargotiatingViewResolver自动组合进来 protected void initServletContext(ServletContext servletContext) { //用BeanFacotryUtils工具获取容器中所有ViewResolver的实现类,这就是candidateViewResolvers Collection<ViewResolver> matchingBeans = BeanFactoryUtils .beansOfTypeIncludingAncestors(this.getApplicationContext(), ViewResolver.class) .values(); if (this.viewResolvers == null) { this.viewResolvers = new ArrayList(matchingBeans.size()); Iterator var3 = matchingBeans.iterator(); while(var3.hasNext()) { ViewResolver viewResolver = (ViewResolver)var3.next(); if (this != viewResolver) { this.viewResolvers.add(viewResolver); } } } else { for(int i = 0; i < this.viewResolvers.size(); ++i) { if (!matchingBeans.contains(this.viewResolvers.get(i))) { String name = ((ViewResolver)this.viewResolvers.get(i)).getClass().getName() + i; this.getApplicationContext().getAutowireCapableBeanFactory().initializeBean(this.viewResolvers.get(i), name); } } }
- 自动配置类型转换器Converter与格式化器Formatter ===> ConverterFormatter
//WebMvcAutoConfiguration.java //2.1 DateFormatter @Bean @ConditionalOnProperty(prefix = "spring.mvc",name = { "date-format"})//需要在配