(一)简介
SpringBoot进行Web开发的基本步骤
① 创建SpringBoot应用,选中所需模块;
② SpringBoot会默认将所需的环境配置好,只需要通过配置文件进行少量的配置即可让项目运行起来;
③ 编写主要业务代码;
对SpringBoot进行配置时,配置项的信息和配置内容可以通过参考SpringBoot的自动配置原理进行配置。
SpringBoot自动配置原理的使用:
① 此场景中SpringBoot已经配置好了什么?
② 已经配置好的场景是否可以修改?能修改那些内容?
③ 是否可以对当前场景进行扩展?
xxxAutoConfiguration
:SpringBoot中的自动配置类,帮我们给容器中自动配置组件;
xxxProperties
:自动配置类中对应的配置属性类,配置属性类来封装配置文件的内容;
(二)SpringBoot对静态资源的映射规则
1. 静态资源的存放路径
WebMvcAutoConfiguration:WebMVC自动配置类,负责完成WebMVC的自动配置
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {
......
// 配置默认静态文件夹的位置
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
if (!this.resourceProperties.isAddMappings()) {
logger.debug("Default resource handling disabled");
return;
}
Duration cachePeriod = this.resourceProperties.getCache().getPeriod();
CacheControl cacheControl = this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl();
// 配置webjars类型的静态资源的映射路径
if (!registry.hasMappingForPattern("/webjars/**")) {
customizeResourceHandlerRegistration(registry.addResourceHandler("/webjars/**")
.addResourceLocations("classpath:/META-INF/resources/webjars/")
.setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl));
}
String staticPathPattern = this.mvcProperties.getStaticPathPattern();
// 配置普通静态资源的映射路径
if (!registry.hasMappingForPattern(staticPathPattern)) {
customizeResourceHandlerRegistration(registry.addResourceHandler(staticPathPattern)
.addResourceLocations(getResourceLocations(this.resourceProperties.getStaticLocations()))
.setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl));
}
}
......
// 配置欢迎页面的查找路径
@Bean
public WelcomePageHandlerMapping welcomePageHandlerMapping(ApplicationContext applicationContext,
FormattingConversionService mvcConversionService, ResourceUrlProvider mvcResourceUrlProvider) {
WelcomePageHandlerMapping welcomePageHandlerMapping = new WelcomePageHandlerMapping(
new TemplateAvailabilityProviders(applicationContext), applicationContext, getWelcomePage(),
this.mvcProperties.getStaticPathPattern());
welcomePageHandlerMapping.setInterceptors(getInterceptors(mvcConversionService, mvcResourceUrlProvider));
welcomePageHandlerMapping.setCorsConfigurations(getCorsConfigurations());
return welcomePageHandlerMapping;
}
......
}
从上面的源码可以看出,webjars类型的静态资源的查找路径为
classpath:/META-INF/resources/webjars/
webjars:通过jar包引入前端静态资源
关于webjars的博客:WebJars介绍
在自动配置类WebMvcAutoConfiguration
中 addResourceHandlers
方法调用 getStaticLocations
方法,来获取属性配置类 ResourceProperties
中配置的静态资源映射路径;
@ConfigurationProperties(prefix = "spring.resources", ignoreUnknownFields = false)
public class ResourceProperties {
private static final String[] CLASSPATH_RESOURCE_LOCATIONS = { "classpath:/META-INF/resources/","classpath:/resources/", "classpath:/static/", "classpath:/public/" };
/**
* Locations of static resources. Defaults to classpath:[/META-INF/resources/,
* /resources/, /static/, /public/].
*/
private String[] staticLocations = CLASSPATH_RESOURCE_LOCATIONS;
......
public String[] getStaticLocations() {
return this.staticLocations;
}
......
// 配置欢迎页面的路径映射
@Bean
public WelcomePageHandlerMapping welcomePageHandlerMapping(ApplicationContext applicationContext,
FormattingConversionService mvcConversionService, ResourceUrlProvider mvcResourceUrlProvider) {
WelcomePageHandlerMapping welcomePageHandlerMapping = new WelcomePageHandlerMapping(
new TemplateAvailabilityProviders(applicationContext), applicationContext, getWelcomePage(),
this.mvcProperties.getStaticPathPattern());
welcomePageHandlerMapping.setInterceptors(getInterceptors(mvcConversionService, mvcResourceUrlProvider));
welcomePageHandlerMapping.setCorsConfigurations(getCorsConfigurations());
return welcomePageHandlerMapping;
}
// 获取欢迎页面所在的路径
private Optional<Resource> getWelcomePage() {
String[] locations = getResourceLocations(this.resourceProperties.getStaticLocations());
return Arrays.stream(locations).map(this::getIndexHtml).filter(this::isReadable).findFirst();
}
// 获取欢迎页面index.html文件
private Resource getIndexHtml(String location) {
return this.resourceLoader.getResource(location + "index.html");
}
......
}
通过上面的源码分析可以清晰的得到,静态资源查找的文件目录:
"classpath:/META-INF/resources/", "classpath:/resources/", "classpath:/static/", "classpath:/public/"
2. 静态资源路径的映射
静态资源和存放路径
① 所有浏览器 webjars/**
URI下的资源对应 classpath:/META-INF/resources/webjars/
路径的资源,即浏览器URI localhost:8080/webjars/**
对应资源路径 classpath:/META-INF/resources/webjars/
例如:使用webjars导入jquery,并在浏览器同归URI进行访问
1)配置pom.xml导入jquery
<!-- 使用webjar的方式导入jQuery -->
<dependency>
<groupId>org.webjars</groupId>
<artifactId>jquery</artifactId>
<version>3.2.0</version>
</dependency>
2)导入后的包结构
3)在浏览器中输入URI访问
② 所有浏览器 /**
URI下的资源对应以下路径的资源
classpath:/META-INF/resources/
classpath:/resources/
classpath:/static/
classpath:/public/
例如:在static文件夹中新建 hello.html
,并在浏览器访问
1)hello.html
所处的文件路径
2)浏览器的访问URI
③ 配置欢迎页;静态资源路径下所有的 index.html
都会被浏览器的 /**
路径映射
例如:在public路径下新建 index.html
,并在浏览器访问
1)index.html
文件所在路径
2)浏览器访问URI
(三)模板引擎
1. 模板引擎简介
模板引擎的目标是为了将显示与数据分离,模板引擎的种类多种多样如:JSP、Velocity、Freemarker、Thymeleaf等,但本质上是将模板文件和数据通过模板引擎进行组合生成最终的HTML代码。
SpringBoot整合Thymeleaf作为模板引擎,但在前后端分离的主流环境下,为了适合灵活多变的环境越来越多的公司选择一套后端配合多套前端提供服务,服务于单体应用的模板引擎使用场景变得越来越少。此处仅对Thymeleaf模板引擎进行简单的介绍和使用。
关于模板引擎的博文:浅谈模板引擎
2. Thymeleaf模板引擎
Thymeleaf是适用于Web和独立环境的现代服务器端Java模板引擎。
Thymeleaf的主要目标是为您的开发工作流程带来优雅的自然模板-HTML可以在浏览器中正确显示,并且可以作为静态原型工作,从而可以在开发团队中加强协作。
Thymeleaf拥有用于Spring Framework的模块,与您喜欢的工具的大量集成以及插入您自己的功能的能力,对于现代HTML5 JVM Web开发而言,Thymeleaf是理想的选择-尽管它可以做很多事情。
3. Thymeleaf的语法规则
① th:text
:改变当前元素内的文本内容。其中 th:xxx
可以放到任意HTML元素中,用替换原生属性的值。
th:xxx
属性的功能
② Thymeleaf中使用表达式
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}
------------------------------------
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:条件运算(三元运算符)
If‐then: (if) ? (then)
If‐then‐else: (if) ? (then) : (else)
Default: (value) ?: (defaultvalue)
Special tokens:(特殊标记)
No‐Operation: _
此处对于Thymeleaf的语法并没有介绍完全,感兴趣的同学可以参看官方文档:Thymeleaf使用参考文档
4. Thymeleaf的使用
Thymeleaf模板引擎的使用:
① 在pom.xml文件中引入thymeleaf依赖;
② 参照Thymeleaf的语法书写模板页面;
① 在pom.xml中导入依赖,导入依赖即可无需进行配置,SpringBoot会自动配置好
<!-- 导入thymeleaf包 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
在SpringBoot2.3.4.RELEASE中,整合的Thymeleaf版本为3.0.11.RELEASE是官网上的最新版本
② 书写模板页面 success.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>success</title>
</head>
<body>
<h1>Success!!!</h1>
<div th:utext="${hello}">21313</div>
<div>
<h3>my name is</h3>
<h3 th:text="${name}"></h3>
<hr/>
<!-- th:each每次遍历都会生成当前这个标签: 3个h4 -->
<h4 th:text="${user}" th:each="user:${users}"></h4>
<hr/>
<h4>
<a th:each="user:${users}"> [[${user}]] </a>
</h4>
</div>
</body>
</html>
③ 编写调用模板页面的方法success
@Controller
public class HelloController {
@ResponseBody
@RequestMapping("/hello")
public String hello() {
return "hello";
}
@RequestMapping("/success")
public String success(Map<String,Object> map){
map.put("name","zhangsan");
map.put("hello","<h3>你好</h3>");
map.put("users", Arrays.asList("zhangsan","lisi","wangwu"));
return "success";
}
}
④ 启动项目,在浏览器访问
(四)SpringMVC自动配置
1. SpringMVC Auto-Configuration
SpringBoot官方文档中的说明:
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
andBeanNameViewResolver
beans.- Support for serving static resources, including support for WebJars (covered later in this document)).
- Automatic registration of
Converter
,GenericConverter
, andFormatter
beans.- Support for
HttpMessageConverters
(covered later in this document).- Automatic registration of
MessageCodesResolver
(covered later in this document).- Static
index.html
support.- Custom
Favicon
support (covered later in this document).- Automatic use of a
ConfigurableWebBindingInitializer
bean (covered later in this document).If you want to keep those Spring Boot MVC customizations and make more MVC customizations (interceptors, formatters, view controllers, and other features), you can add your own
@Configuration
class of typeWebMvcConfigurer
but without@EnableWebMvc
.If you want to provide custom instances of
RequestMappingHandlerMapping
,RequestMappingHandlerAdapter
, orExceptionHandlerExceptionResolver
, and still keep the Spring Boot MVC customizations, you can declare a bean of typeWebMvcRegistrations
and use it to provide custom instances of those components.If you want to take complete control of Spring MVC, you can add your own
@Configuration
annotated with@EnableWebMvc
, or alternatively add your own@Configuration
-annotatedDelegatingWebMvcConfiguration
as described in the Javadoc of@EnableWebMvc
.原文翻译如以下:
Spring Boot自动配置了SpringMVC,使其可以与其他应用共同使用
Spring Boot自动配置在默认配置的基础上还配置了以下特性:
- 包含
ContentNegotiatingViewResolver
和BeanNameViewResolver
组件;- 支持静态资源,也包含对Webjars的支持;
- 自动注册
Converter
,GenericConverter
, 和Formatter
组件;- 支持
HttpMessageConverters
;- 自动注册
MessageCodesResolver
;- 支持静态的
index.html
页面;- 支持用户的
Favicon
;- 自动使用
ConfigurableWebBindingInitializer
组件;如果您想保留Spring Boot的自定义配置并且扩展MVC自定义配置(拦截器,格式化器,视图解析器,和其他特性),你可以添加自己的
WebMvcConfigurer
类型的带有@Configuration
注解的配置类,并且不使用@EnableWebMvc
注解接管SpringMVC。如果您想使用自定义实例
RequestMappingHandlerMapping
,RequestMappingHandlerAdapter
, 或ExceptionHandlerExceptionResolver
并仍然使用Spring Boot MVC配置,你可以声明一个WebMvcRegistrations
类型的bean,并且使用它提供的自定义配置代替原有的配置。如果您想完全控制Spring MVC,您可以添加您自己的带有
@EnableWebMvc
注释的配置类,或者添加您自己的带有@Configuration
注释的DelegatingWebMvcConfiguration
,例如:在@EnableWebMvc
的Javadoc中所描述的那样。
下面我们来看看其中提到的一些配置:
① ContentNegotiatingViewResolver
:组合所有的视图解析器
@Bean
@ConditionalOnBean(ViewResolver.class)
@ConditionalOnMissingBean(name = "viewResolver", value = ContentNegotiatingViewResolver.class)
public ContentNegotiatingViewResolver viewResolver(BeanFactory beanFactory) {
ContentNegotiatingViewResolver resolver = new ContentNegotiatingViewResolver();
resolver.setContentNegotiationManager(beanFactory.getBean(ContentNegotiationManager.class));
// ContentNegotiatingViewResolver uses all the other view resolvers to locate
// a view so it should have a high precedence
resolver.setOrder(Ordered.HIGHEST_PRECEDENCE);
return resolver;
}
② ViewResolver
:视图解析器:根据方法的返回值得到视图对象(View),视图对象决定如何渲染(转发?重定向?),SpringBoot对视图解析器进行自动化配置
public interface ViewResolver {
/**
* Resolve the given view by name.
* <p>Note: To allow for ViewResolver chaining, a ViewResolver should
* return {@code null} if a view with the given name is not defined in it.
* However, this is not required: Some ViewResolvers will always attempt
* to build View objects with the given name, unable to return {@code null}
* (rather throwing an exception when View creation failed).
* @param viewName name of the view to resolve
* @param locale the Locale in which to resolve the view.
* ViewResolvers that support internationalization should respect this.
* @return the View object, or {@code null} if not found
* (optional, to allow for ViewResolver chaining)
* @throws Exception if the view cannot be resolved
* (typically in case of problems creating an actual View object)
*/
@Nullable
View resolveViewName(String viewName, Locale locale) throws Exception;
}
③ Converter
:转化器;当在浏览器和bean之间的转化时会用到转化器,配置了很多类型之间转化的转化器
④ Formatter
:格式化器,用于格式化数据,例如:将2020-11-11格式化为Date类型的数据
@Bean
@Override
public FormattingConversionService mvcConversionService() {
Format format = this.mvcProperties.getFormat();
WebConversionService conversionService = new WebConversionService(new DateTimeFormatters()
.dateFormat(format.getDate()).timeFormat(format.getTime()).dateTimeFormat(format.getDateTime()));
addFormatters(conversionService);
return conversionService;
}
我们可以添加自定义格式化器和转化器,只要放到容器中即可。
⑤ HttpMessageConverter
:SpringMVC用来转换Http请求和响应,例如:把实体类转化为JSON数组
HttpMessageConverters
从容器中确定的,获取所有的 HttpMessageConverters
@Bean
@ConditionalOnMissingBean
public HttpMessageConverters messageConverters(ObjectProvider<HttpMessageConverter<?>> converters) {
return new HttpMessageConverters(converters.orderedStream().collect(Collectors.toList()));
}
因此我们也可以给容器中添加
HttpMessageConverter
,只需要将自己的组件注册容器中(@Bean,@Component)SpringBoot开发Web应用官方文档:SpringMVC配置
2. 扩展SpringMVC
以前没有使用SpringBoot时使用SpringMVC需要书写的配置信息
<mvc:view-controller path="/hello" view-name="success"/>
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/hello"/>
<bean></bean>
</mvc:interceptor>
</mvc:interceptors>
<mvc:default-servlet-handler/>
使用配置文件对SpringMVC功能进行扩展
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
/**
* super.addViewControllers(registry);
* 浏览器发送 /atguigu 请求来到 success
* @param registry
*/
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/Bruce").setViewName("ok");
}
}
有些博客可能介绍的是继承
WebMvcConfigurerAdapter
类,但WebMvcConfigurerAdapter
在Spring5.0 后放弃使用改为使用WebMvcConfigurer
接口代替。
使用SpringBoot时我们可以编写 WebMvcConfigurer
类型的配置类(带有 @Configuration
注解的类)并且不标注 @EnableWebMvc
注解(标注此注解则意味着我们的配置全面接管SpringBoot配置)这样我们书写的配置类既保留了SpringBoot的所有自动配置,也能使用我们扩展的配置;
自动配置和自定义配置可以同时保留的原因:
WebMvcAutoConfigurationAdapter
中导入了 EnableWebMvcConfiguration
类用于加载自定义配置信息;
@Configuration(proxyBeanMethods = false)
@Import(EnableWebMvcConfiguration.class)
@EnableConfigurationProperties({ WebMvcProperties.class, ResourceProperties.class })
@Order(0)
public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer {
.......
}
EnableWebMvcConfiguration
类继承 DelegatingWebMvcConfiguration
类以实现加载自定义MVC配置;
@Configuration(proxyBeanMethods = false)
public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration implements ResourceLoaderAware {
......
}
DelegatingWebMvcConfiguration
类中包含 setConfigurers
方法,能够将所有自定义配置和SpringBoot自动配置同时加载,以实现自动配置和自定义配置同时生效;
@Configuration(proxyBeanMethods = false)
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite();
@Autowired(required = false)
public void setConfigurers(List<WebMvcConfigurer> configurers) {
if (!CollectionUtils.isEmpty(configurers)) {
this.configurers.addWebMvcConfigurers(configurers);
}
}
}
关于加载配置的总结:
① WebMvcAutoConfiguration是SpringMVC的自动配置类
② 在进行自动配置时,会导入 @Import(EnableWebMvcConfiguration.class)
③ 在进行配置时会加载容器中的所有的 WebMvcConfigurer
类型的配置
④ 我们自己书写的配置类因此也就被加载了
结果:SpringBoot中对SpringMVC的自动配置和我们自定义的配置信息都会起作用
自动配置和自定义配置继承关系图
3. 全面接管SpringMVC
我们可以完全不使用SpringBoot对SpringMVC的自动配置,而是使用我们自己对SpringMVC的配置内容。
我们可以在我们自己的配置类中添加 @EnableWebMVC
注解,这时时SpringBoot对SpringMVC的配置就完全失效了,只有我们自己的SpringMVC配置会生效。
@EnableWebMVC
注解让SpringBoot自动配置失效的原因:
① EnableWebMVC
注解中导入了 DelegatingWebMvcConfiguration
类
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(DelegatingWebMvcConfiguration.class)
public @interface EnableWebMvc {
}
② 而 DelegatingWebMvcConfiguration
类继承了 WebMvcConfigurationSupport
,WebMvcConfigurationSupport
仅包含SpringMVC的基本配置
@Configuration(proxyBeanMethods = false)
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
......
}
③ 而 WebMvcAutoConfiguration
配置类中,包含条件注解 @ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,
ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {
......
}
总结:
@Enable
注解直接中导入的DelegatingWebMvcConfiguration
类中继承了WebMvcConfigurationSupport
类;- 而
WebMvcAutoConfiguration
配置类中的条件注解@ConditionalOnMissingBean
要求不包含WebMvcConfigurationSupport
类型的bean;- 所以标注
@Enable
注解的自定义配置类会让WebMvcAutoConfiguration
自动配置类失效,也就是SpringBoot对SpringMVC的自动配置失效;
4. 修改SpringBoot中的默认配置
SpringBoot加载组件的原理:
SpringBoot在进行自动配置组件的时候,会先查看容器中是否有用户自己配置的组件即带有 @Bean
、@Component
注解的配置类)如果没有则进行自动配置;
对组件是否加载依赖于 @Conditition
注解的扩展注解实现;
SpringBoot还支持把有些组件的自动配置和用户配置组合起来如 ViewResovler
组件;
基本思路:
xxxAutoConfiguration
:是SpringBoot的自动配置类,对相关的组件进行配置,配置类所标注的条件注解可以帮助我们理解配置类的作用和要求;
xxxProperties
:是SpringBoot中自动配置类的属性类,属性类中的配置项是我们可以在外部配置文件中配置的内容;
xxxConfiguration
:SpringBoot中的扩展配置类,用于对自动配置进行扩展配置。我们可以查看这些扩展配置类的构成,书写我们自己的配置类来对SpringBoot配置类进行扩展配置;
xxxCustomizer
:SpringBoot中的定制器类,可以用来帮助我们定制某些配置;
(五)RestfulCRUD示例项目
1. 设置默认访问首页
我们可以向容器中注入自定义WebMvc配置类对原有的WebMvc扩展配置,SpringBoot会同时加载自动配置和自定义配置。例如:书写自定义配置类配置视图映射,修改默认的首页访问模板信息。使用的原理是我们上面提到的修改默认配置的原理。
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
/**
* 所有的WebMVCConfiguration组件会一起加载
* @return
*/
@Bean
public WebMvcConfigurer webMvcConfigurer() {
/**
* 添加视图映射
*/
WebMvcConfigurer webMvcConfigurer = new WebMvcConfigurer() {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
// 将默认的访问修改指向login模板
registry.addViewController("/").setViewName("login");
registry.addViewController("/login.html").setViewName("login");
registry.addViewController("/main.html").setViewName("dashboard");
}
};
return webMvcConfigurer;
}
}
2. 国际化配置
① 编写国际化配置文件;
② 使用 ResourceBundleMessageSource
管理国际化资源文件;
③ 在页面中使用 fmt:message
取出国际化内容;
④ 点击链接切换国际化
主要步骤:
① 编写国际化配置文件,抽取页面需要显示的配置信息
② SpringBoot自动配置管理国际化资源文件的组件 MessageSourceAutoConfiguration
配置了国际化配置内容
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(name = AbstractApplicationContext.MESSAGE_SOURCE_BEAN_NAME, search = SearchStrategy.CURRENT)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Conditional(ResourceBundleCondition.class)
@EnableConfigurationProperties
public class MessageSourceAutoConfiguration {
......
@Bean
public MessageSource messageSource(MessageSourceProperties properties) {
ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
if (StringUtils.hasText(properties.getBasename())) {
// 配置国际化文件默认加载位置通过basebame属性进行设置
messageSource.setBasenames(StringUtils
.commaDelimitedListToStringArray(StringUtils.trimAllWhitespace(properties.getBasename())));
}
if (properties.getEncoding() != null) {
// 设置国际化资源文件的基础名(去除语言国家代码)
messageSource.setDefaultEncoding(properties.getEncoding().name());
}
messageSource.setFallbackToSystemLocale(properties.isFallbackToSystemLocale());
Duration cacheDuration = properties.getCacheDuration();
if (cacheDuration != null) {
messageSource.setCacheMillis(cacheDuration.toMillis());
}
messageSource.setAlwaysUseMessageFormat(properties.isAlwaysUseMessageFormat());
messageSource.setUseCodeAsDefaultMessage(properties.isUseCodeAsDefaultMessage());
return messageSource;
}
......
}
在 MessageSourceProperties
属性配置文件中配置
public class MessageSourceProperties {
/**
* Comma-separated list of basenames (essentially a fully-qualified classpath
* location), each following the ResourceBundle convention with relaxed support for
* slash based locations. If it doesn't contain a package qualifier (such as
* "org.mypackage"), it will be resolved from the classpath root.
*/
// 这个属性是配置国际化配置文件所在位置属性的配置信息
private String basename = "messages";
.......
}
因此我们可以在配置文件中配置国际化语言的路径
server.port=9080
server.servlet.context-path=/crud
spring.thymeleaf.cache=false
#配置SpringBoot默认的国际化语言切换路径
spring.messages.basename=i18n.login
③ 修改登录页的显示,用国际化配置属性进行显示替换。
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="">
<meta name="author" content="">
<title>Signin Template for Bootstrap</title>
<!-- Bootstrap core CSS -->
<link th:href="@{/webjars/bootstrap/4.0.0/css/bootstrap.css}" rel="stylesheet"/>
<!-- Custom styles for this template -->
<link th:href="@{/asserts/css/signin.css}" rel="stylesheet"/>
</head>
<body class="text-center">
<form class="form-signin" action="dashboard.html">
<img class="mb-4" th:src="@{/asserts/img/bootstrap-solid.svg}" alt="" width="72" height="72">
<!-- 配置国际化信息内容,用Thymeleaf语法调用国际化属性信息,下面的方法是一样的 -->
<h1 class="h3 mb-3 font-weight-normal" th:text="#{login.tip}">Please sign in</h1>
<!-- 错误信息提示 -->
<p style="color: red" th:text="${msg}" th:if="${not #strings.isEmpty(msg)}"></p>
<label class="sr-only" th:text="#{login.username}">Username</label>
<input type="text" name="username" class="form-control" placeholder="Username"
th:placeholder="#{login.username}" required="" utofocus="">
<label class="sr-only" th:text="#{login.password}">Password</label>
<input type="password" name="password" class="form-control" placeholder="Password"
th:placeholder="#{login.password}" required="">
<div class="checkbox mb-3">
<label>
<input type="checkbox" value="remember-me"> [[#{login.remember}]]
</label>
</div>
<button class="btn btn-lg btn-primary btn-block" type="submit" th:text="#{login.btn}">Sign in</button>
<p class="mt-5 mb-3 text-muted">© 2017-2018</p>
<!-- 配置点击中英文切换显示信息,利用发送的请求头中的信息配置显示内容 -->
<a class="btn btn-sm" th:href="@{/login.html(l='zh_CN')}">中文</a>
<a class="btn btn-sm" th:href="@{/login.html(l='en_US')}">English</a>
</form>
</body>
</html>
④ 自定义配置 LocateResolver
实现点击链接切换国际化信息
public class MyLocaleRrsovler implements LocaleResolver {
@Override
public Locale resolveLocale(HttpServletRequest request) {
// huu'o'qu
String l = request.getParameter("l");
Locale locale = Locale.getDefault();
if (!StringUtils.isEmpty(l)) {
String[] split = l.split("_");
locale = new Locale(split[0], split[1]);
}
return locale;
}
@Override
public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) {
}
}
在自定义配置类 MyMvcConfig
中将我们自定义的 LocateResolver
组件加载到容器中
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
/**
* 所有的WebMVCConfiguration组件会一起加载
* @return
*/
@Bean
public WebMvcConfigurer webMvcConfigurer() {
/**
* 添加视图映射
*/
WebMvcConfigurer webMvcConfigurer = new WebMvcConfigurer() {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("login");
registry.addViewController("/login.html").setViewName("login");
registry.addViewController("/main.html").setViewName("dashboard");
}
};
return webMvcConfigurer;
}
/**
* 将我们自定义的LocateResolver组件加载到容器中
* @return
*/
@Bean
public LocaleResolver localeResolver() {
return new MyLocaleRrsovler();
}
}
⑤ 最终的效果:点击中英且换界面会根据语言切换显示效果
国际化切换原理:
AutoWebMvcConfiguration
中配置了国际化 Locate
(区域对象)和 LocateResolver
(获取区域信息对象)
@Bean
@ConditionalOnMissingBean
@ConditionalOnProperty(prefix = "spring.mvc", name = "locale")
public LocaleResolver localeResolver() {
if (this.mvcProperties.getLocaleResolver() == WebMvcProperties.LocaleResolver.FIXED) {
return new FixedLocaleResolver(this.mvcProperties.getLocale());
}
// 默认根据请求头带来的区域信息获取Locale进行国际化
AcceptHeaderLocaleResolver localeResolver = new AcceptHeaderLocaleResolver();
localeResolver.setDefaultLocale(this.mvcProperties.getLocale());
return localeResolver;
}
LocateResolver
的继承关系
3. 登录配置
配置的方式:
① 修改页面的配置信息
② 书写登录相关的controller
③ 配置登录拦截器
① 修改页面的配置信息,添加action即:请求的接口并设置请求方式为post
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="">
<meta name="author" content="">
<title>Signin Template for Bootstrap</title>
<!-- Bootstrap core CSS -->
<link th:href="@{/webjars/bootstrap/4.0.0/css/bootstrap.css}" rel="stylesheet"/>
<!-- Custom styles for this template -->
<link th:href="@{/asserts/css/signin.css}" rel="stylesheet"/>
</head>
<body class="text-center">
<!-- 配置提交接口action,发送post请求 -->
<form class="form-signin" action="dashboard.html" th:action="@{/user/login}" method="post">
<img class="mb-4" th:src="@{/asserts/img/bootstrap-solid.svg}" alt="" width="72" height="72">
<!-- 配置国际化信息内容,用Thymeleaf语法调用国际化属性信息,下面的方法是一样的 -->
<h1 class="h3 mb-3 font-weight-normal" th:text="#{login.tip}">Please sign in</h1>
<!-- 错误信息提示 -->
<p style="color: red" th:text="${msg}" th:if="${not #strings.isEmpty(msg)}"></p>
<label class="sr-only" th:text="#{login.username}">Username</label>
<input type="text" name="username" class="form-control" placeholder="Username"
th:placeholder="#{login.username}" required="" utofocus="">
<label class="sr-only" th:text="#{login.password}">Password</label>
<input type="password" name="password" class="form-control" placeholder="Password"
th:placeholder="#{login.password}" required="">
<div class="checkbox mb-3">
<label>
<input type="checkbox" value="remember-me"> [[#{login.remember}]]
</label>
</div>
<button class="btn btn-lg btn-primary btn-block" type="submit" th:text="#{login.btn}">Sign in</button>
<p class="mt-5 mb-3 text-muted">© 2017-2018</p>
<!-- 配置点击中英文切换显示信息,利用发送的请求头中的信息配置显示内容 -->
<a class="btn btn-sm" th:href="@{/login.html(l='zh_CN')}">中文</a>
<a class="btn btn-sm" th:href="@{/login.html(l='en_US')}">English</a>
</form>
</body>
</html>
② 配置 LoginController
设置登陆的请求规则
@Controller
public class LoginController {
//@RequestMapping(value = "/user/login",method = RequestMethod.POST)
@PostMapping(value = "/user/login")
public String login(@RequestParam("username") String username,
@RequestParam("password") String password,
Map<String,Object> map, HttpSession session){
if(!StringUtils.isEmpty(username) && "123456".equals(password)){
//登陆成功,防止表单重复提交,可以重定向到主页
session.setAttribute("loginUser",username);
return "redirect:/main.html";
}else{
//登陆失败
map.put("msg","用户名密码错误");
return "login";
}
}
}
③ 配置登录拦截器 LoginHandlerInterceptor
拦截未登陆的用户的请求访问
public class LoginHandlerInterceptor implements HandlerInterceptor {
/**
* 在执行目标方法前进行操作
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
Object user = request.getSession().getAttribute("loginUser");
if(user == null){
//未登陆,返回登陆页面
request.setAttribute("msg","没有权限请先登陆");
request.getRequestDispatcher("/index.html").forward(request,response);
return false;
}else{
//已登陆,放行请求
return true;
}
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
}
}
④ 在自定义配置WebMvcConfig中将拦截器注册到容器中
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
/**
* 所有的WebMVCConfiguration组件会一起加载
* @return
*/
@Bean
public WebMvcConfigurer webMvcConfigurer() {
/**
* 添加视图映射
*/
WebMvcConfigurer webMvcConfigurer = new WebMvcConfigurer() {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("login");
registry.addViewController("/index.html").setViewName("login");
registry.addViewController("/main.html").setViewName("dashboard");
}
/**
* 注册拦截器
* @param registry
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 拦截除去登陆页面的,所有的页面请求,只有登录后才能访问全部的页面
registry.addInterceptor(new LoginHandlerInterceptor()).addPathPatterns("/**")
.excludePathPatterns("/index.html", "/", "/user/login");
}
};
return webMvcConfigurer;
}
/**
* 将我们自定义的LocateResolver组件加载到容器中
* @return
*/
@Bean
public LocaleResolver localeResolver() {
return new MyLocaleRrsovler();
}
}
4. Restful风格请求访问
4.1 CRUD员工列表
URI:/资源名称/资源标识,使用HTTP请求的方式对资源CRUD操作。资源操作:使用POST、DELETE、PUT、GET,使用不同方法对资源进行操作。 分别对应 添加、 删除、修改、查询。
普通CRUD(通过uri区分操作) | RestfulCRUD | |
---|---|---|
查询 | getEmployees | employee---GET |
添加 | addEmployee?xxx | employee---POST |
修改 | updateEmployee?id=xxx&xxx=xxxx | empoyee/{id}---PUT |
删除 | deleteEmployee?id=1 | employee/{id}---DELETE |
关于Restful风格介绍的博客:restful风格
员工CRUD的请求设计
请求接口的功能 | 请求URI | 请求方式 |
---|---|---|
查询所有员工 | emps | GET |
查询某个员工(在修改页面显示) | emp/1 | GET |
请求添加页面 | emp | GET |
添加员工信息 | emp | POST |
进入修改页面(同时查询显示员工信息) | emp/1 | GET |
修改员工信息 | emp | PUT |
删除某个员工 | emp/1 | DELETE |
Thymeleaf公共页面抽取
1、抽取公共片段
<div th:fragment="copy">
© 2020 The Good Thymes Virtual Grocery
</div>
2、引入公共片段
<div th:insert="~{footer :: copy}"></div>
~{templatename::selector}:模板名::选择器
~{templatename::fragmentname}:模板名::片段名
3、默认效果:
insert的公共片段在div标签中
如果使用th:insert等属性进行引入,可以不用写~{}:
行内写法可以加上:[[~{}]];[(~{})];
三种引入公共片段的th属性:
th:insert
:将公共片段整个插入到声明引入的元素中
th:replace
:将声明引入的元素替换为公共片段
th:include
:将被引入的片段的内容包含进这个标签中
代码示例:
<footer th:fragment="copy">
© 2011 The Good Thymes Virtual Grocery
</footer>
<!-- 引入方式 -->
<div th:insert="footer :: copy"></div>
<div th:replace="footer :: copy"></div>
<div th:include="footer :: copy"></div>
<!-- 效果 -->
<div>
<footer>
© 2011 The Good Thymes Virtual Grocery
</footer>
</div>
<footer>
© 2011 The Good Thymes Virtual Grocery
</footer>
<div>
© 2011 The Good Thymes Virtual Grocery
</div>
片段的引入也可以传递参数
<div th:replace="commons/bar::#sidebar(activeUri='emps')"></div>
4.2 页面和接口设计
具体的页面和接口的设计信息,内容较多,请查看具体的代码: Restful风格CRUD,员工基本CRUD