SpringBoot入门
- 一、什么是SpringBoot
- 二、第一个SpringBoot程序
-
- 2.1、项目创建
-
- 2.1.1、方式一
- 2.1.2、方式二:通过IDEA创建
- 2.2、项目结构分析
- 2.3、IDEA彩蛋
- 2.4、项目打包
- 三、运行原理初探【超重点】
-
- 3.1、pom.xml
- 3.2、启动器
- 3.3、主程序【自动装配原理】
- 3.4、spring.factories中的配置文件是如何选择性的加载的
- 3.5、SpringApplication.run方法流程分析
- 四、yaml
-
- 4.1、配置文件
- 4.2、yaml概述
- 4.3、yaml基础语法
- 4.4、yaml注入配置文件
- 4.5、配置文件占位符
- 4.6、yaml配置与properties配置的区别
- 4.7、JSR303数据校验
- 4.8、配置文件加载位置
- 4.8、多环境配置文件
- 五、SpringBoot Web开发
-
- 5.1、静态资源处理
- 5.2、首页处理
- 5.3、MVC自动配置原理【重点】
- 5.4、扩展使用SpringMVC【重点】
- 5.5、springMVC默认时间格式【重点】
- 5.6、拦截器【重点】
一、什么是SpringBoot
设计目的:是用来简化Spring应用的初始搭建以及开发过程。
SpringBoot基于Spring4.0设计,不仅继承了Spring框架原有的优秀特性,而且还
通过简化配置来进一步简化了Spring应用的整个搭建和开发过程
。另外SpringBoot通过集成大量的框架使得依赖包的版本冲突,以及引用的不稳定性等问题得到了很好的解决。
-
SpringBoot特点
(1)可以创建独立的Spring应用程序,并且基于其Maven或Gradle插件,可以创建可执行的JARs和WARs;
(2)内嵌Tomcat或Jetty等Servlet容器;
(3)提供自动配置的“starter”项目对象模型(POMS)以简化Maven配置;
(4)尽可能自动配置Spring容器;
(5)提供准备好的特性,如指标、健康检查和外部化配置;
(6)绝对没有代码生成,不需要XML配置。
-
重要策略
1、SpringBoot框架中还有两个非常重要的策略:
。开箱即用,Outofbox,是指在开发过程中,通过在Maven项目的pom文件中添加相关依赖包,然后开箱即用和约定优于配置
使用对应注解来代替繁琐的XML配置文件以管理对象的生命周期
。这个特点使得开发人员摆脱了复杂的配置工作以及依赖的管理工作,更加专注于业务逻辑。约定优于配置,Convention over configuration,是一种由SpringBoot本身来配置目标结构,由开发者在结构中添加信息的软件设计范式。这一特点虽降低了部分灵活性,增加了BUG定位的复杂性,但减少了开发人员需要做出决定的数量,同时减少了大量的XML配置,并且可以将代码编译、测试和打包等工作自动化。
2、SpringBoot应用系统开发模板的基本架构设计从前端到后台进行说明:前端常使用
,主要有FreeMarker和模板引擎
,它们都是用Java语言编写的,渲染模板并输出相应文本,使得界面的设计与应用的逻辑分离,同时前端开发还会使用到Bootstrap、AngularJS、JQuery等;在浏览器的数据传输格式上采用Json,非xml,同时提供RESTfulAPI;SpringMVC框架用于数据到达服务器后处理请求;到数据访问层主要有Hibernate、MyBatis、JPA等持久层框架;数据库常用MySQL;开发工具推荐IntelliJIDEA。Thymeleaf
- 发展历程
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLiAzNfRHLGZkRGZkRfJ3bs92YsYTMfVmepNHL5FFROdXU65UeVpHW4Z0MMBjVtJWd0ckW65UbM5WOHJWa5kHT20ESjBjUIF2X0hXZ0xCMx81dvRWYoNHLrdEZwZ1Rh5WNXp1bwNjW1ZUba9VZwlHdssmch1mclRXY39CXldWYtlWPzNXZj9mcw1ycz9WL49zZuBnL0QTM2EDM1IjM4EjNwEjMwIzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
二、第一个SpringBoot程序
2.1、项目创建
2.1.1、方式一
Spring官方提供了非常方便的工具,Spring Initializr:
https://start.spring.io/
1、打开 https://start.spring.io/
2、填写项目信息
3、点击”Generate Project“按钮生成项目;下载此项目
4、解压项目包,并用IDEA以Maven项目导入,一路下一步即可,直到项目导入完毕。
5、如果是第一次使用,可能速度会比较慢,包比较多、需要耐心等待一切就绪。
2.1.2、方式二:通过IDEA创建
2.2、项目结构分析
- 编写一个controller测试
@RestController
public class HelloController {
@RequestMapping("/hello")
public String hello(){
return "hello world";
}
}
- 从主程序启动
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
- 测试
2.3、IDEA彩蛋
如何更改启动时显示的字符拼成的字母,SpringBoot呢?也就是 banner 图案;
只需一步:到项目下的 resources 目录下新建一个
banner.txt
即可。
图案可以到:
https://www.bootschool.net/ascii
这个网站生成,然后拷贝到文件中即可!
2.4、项目打包
- 将项目打成jar包,双击package
- 打包成功
- 打包好之后在target目录下会生成对应的jar包
- 执行对应的jar包,程序依然可以运行
SpringBoot一、什么是SpringBoot二、第一个SpringBoot程序三、运行原理初探【超重点】四、yaml五、SpringBoot Web开发 SpringBoot一、什么是SpringBoot二、第一个SpringBoot程序三、运行原理初探【超重点】四、yaml五、SpringBoot Web开发
三、运行原理初探【超重点】
3.1、pom.xml
我们
导入依赖默认是不需要写版本;但是如果导入的包没有在依赖中管理着就需要手动配置版本了
3.2、启动器
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
springboot-boot-starter-xxx
:就是spring-boot的场景启动器
spring-boot-starter-web:帮我们导入了web模块正常运行所依赖的组件;
SpringBoot将所有的功能场景都抽取出来,做成一个个的starter (启动器),只需要在项目中引入这些starter即可,所有相关的依赖都会导入进来 ,
我们要用什么功能就导入什么样的场景启动器即可
;我们未来也可以自己自定义 starter;
spring官方提供的启动器介绍:
https://docs.spring.io/spring-boot/docs/current/reference/html/using.html#using.build-systems.starters
3.3、主程序【自动装配原理】
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
- @SpringBootApplication组合注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration //springboot的配置
@EnableAutoConfiguration //自动装配
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
-
@SpringBootConfiguration
-
@EnableAutoConfiguration
-
AutoConfigurationImportSelector :自动配置导入选择器
1、获得候选的配置
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
getBeanClassLoader());
Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you "
+ "are using a custom packaging, make sure that file is correct.");
return configurations;
}
/**
* Return the class used by {@link SpringFactoriesLoader} to load configuration
* candidates.
* @return the factory class
*/
protected Class<?> getSpringFactoriesLoaderFactoryClass() {
return EnableAutoConfiguration.class;
}
2、这个方法又调用了 SpringFactoriesLoader 类的静态方法!我们进入SpringFactoriesLoader类loadFactoryNames() 方法
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
ClassLoader classLoaderToUse = classLoader;
if (classLoaderToUse == null) {
classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
}
String factoryTypeName = factoryType.getName();
return loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
}
3、我们继续点击查看 loadSpringFactories 方法
private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
Map<String, List<String>> result = cache.get(classLoader);
if (result != null) {
return result;
}
result = new HashMap<>();
try {
Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
for (Map.Entry<?, ?> entry : properties.entrySet()) {
String factoryTypeName = ((String) entry.getKey()).trim();
String[] factoryImplementationNames =
StringUtils.commaDelimitedListToStringArray((String) entry.getValue());
for (String factoryImplementationName : factoryImplementationNames) {
result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>())
.add(factoryImplementationName.trim());
}
}
}
// Replace all lists with unmodifiable lists containing unique elements
result.replaceAll((factoryType, implementations) -> implementations.stream().distinct()
.collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList)));
cache.put(classLoader, result);
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
return result;
}
,
4、发现一个多次出现的文件:spring.factories,全局搜索它,
spring.factories 看到了很多自动配置的文件;这就是自动配置根源所在!
可以看到这些一个个的都是JavaConfig配置类,而且都注入了一些Bean
所以,自动配置真正实现是从classpath中搜寻所有的META-INF/spring.factories配置文件 ,并将其中对应的 org.springframework.boot.autoconfigure. 包下的配置项,通过反射实例化为对应标注了 @Configuration的JavaConfig形式的IOC容器配置类 , 然后将这些都汇总成为一个实例并加载到IOC容器中。
-
结论:
1、SpringBoot在启动的时候从类路径下的META-INF/spring.factories中获取EnableAutoConfiguration指定的值
2、将这些值作为自动配置类导入容器 , 自动配置类就生效 , 帮我们进行自动配置工作;
3、整个J2EE的整体解决方案和自动配置都在springboot-autoconfigure的jar包中;
4、它会给容器中导入非常多的自动配置类 (xxxAutoConfiguration), 就是给容器中导入这个场景需要的所有组件 , 并配置好这些组件 ;
5、有了自动配置类 , 免去了我们手动编写配置注入功能组件等的工作;
3.4、spring.factories中的配置文件是如何选择性的加载的
- 以HttpEncodingAutoConfiguration为例子
//表示这是一个配置类
@Configuration(proxyBeanMethods = false)
//自动配置属性
//启动指定类的ConfigurationProperties功能;
//进入这个ServerProperties查看,将配置文件中对应的值和ServerProperties绑定起来;
//并把ServerProperties加入到ioc容器中
@EnableConfigurationProperties(ServerProperties.class)
//Spring底层@Conditional注解(判断是否满足当前指定条件)
//根据不同的条件判断,如果满足指定的条件,整个配置类里面的配置就会生效;
//@Conditional(OnWebApplicationCondition.class)这里的意思就是判断当前应用是否是web应用,如果是,当前配置类生效
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
//判断当前项目有没有这个类CharacterEncodingFilter;SpringMVC中进行乱码解决的过滤器;
@ConditionalOnClass(CharacterEncodingFilter.class)
//判断配置文件中是否存在某个配置:spring.http.encoding.enabled;
//如果不存在,判断也是成立的
//即使我们配置文件中不配置pring.http.encoding.enabled=true,也是默认生效的;
@ConditionalOnProperty(prefix = "server.servlet.encoding", value = "enabled", matchIfMissing = true)
public class HttpEncodingAutoConfiguration {
private final Encoding properties;
public HttpEncodingAutoConfiguration(ServerProperties properties) {
this.properties = properties.getServlet().getEncoding();
}
@Bean
@ConditionalOnMissingBean
public CharacterEncodingFilter characterEncodingFilter() {
CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
filter.setEncoding(this.properties.getCharset().name());
filter.setForceRequestEncoding(this.properties.shouldForce(Encoding.Type.REQUEST));
filter.setForceResponseEncoding(this.properties.shouldForce(Encoding.Type.RESPONSE));
return filter;
}
@Bean
public LocaleCharsetMappingsCustomizer localeCharsetMappingsCustomizer() {
return new LocaleCharsetMappingsCustomizer(this.properties);
}
static class LocaleCharsetMappingsCustomizer
implements WebServerFactoryCustomizer<ConfigurableServletWebServerFactory>, Ordered {
private final Encoding properties;
LocaleCharsetMappingsCustomizer(Encoding properties) {
this.properties = properties;
}
@Override
public void customize(ConfigurableServletWebServerFactory factory) {
if (this.properties.getMapping() != null) {
factory.setLocaleCharsetMappings(this.properties.getMapping());
}
}
@Override
public int getOrder() {
return 0;
}
}
}
一句话总结 :
根据当前不同的条件判断,决定这个配置类是否生效!
1、一但这个配置类生效;这个配置类就会给容器中添加各种组件;
2、这些组件的属性是从对应的properties类中获取的,这些类里面的每一个属性又是和配置文件绑定的;
3、所有在配置文件中能配置的属性都是在xxxxProperties类中封装着;
4、配置文件能配置什么就可以参照某个功能对应的这个属性类
- 精髓
1、SpringBoot启动会加载大量的自动配置类
2、我们看我们需要的功能有没有在SpringBoot默认写好的自动配置类当中;
3、我们再来看这个自动配置类中到底配置了哪些组件;(只要我们要用的组件存在在其中,我们就不需要再手动配置了)
4、给容器中自动配置类添加组件的时候,会从properties类中获取某些属性。我们只需要在配置文件中指定这些属性的值即可;
xxxxAutoConfigurartion:自动配置类;给容器中添加组件
xxxxProperties:封装配置文件中相关属性;
-
@Conditional注解的作用
自动配置类必须在一定的条件下才能生效;
@Conditional派生注解(Spring注解版原生的@Conditional作用)
作用:
必须是@Conditional指定的条件成立,才给容器中添加组件,配置配里面的所有内容才生效
;
我们可以通过启用 debug=true属性;来让控制台打印自动配置报告,这样我们就可以很方便的知道哪些自动配置类生效;
3.5、SpringApplication.run方法流程分析
-
一部分是SpringApplication的实例化,二是run方法的执行;
-
SpringApplication类的作用
1、推断应用的类型是普通的项目还是Web项目
2、查找并加载所有可用初始化器 , 设置到initializers属性中
3、找出所有的应用程序监听器,设置到listeners属性中
4、推断并设置main方法的定义类,找到运行的主类
四、yaml
4.1、配置文件
SpringBoot使用一个全局的配置文件 , 配置文件名称是固定的:
1、
application.properties
语法结构 :key=value
2、
application.yaml
语法结构 :key:空格 value
配置文件的作用 :修改SpringBoot自动配置的默认值,因为SpringBoot在底层都给我们自动配置好了;
4.2、yaml概述
这种语言以数据作为中心,而不是以标记语言为重点!
以前的配置文件,大多数都是使用xml来配置;比如一个简单的端口配置,我们来对比下yaml和xml
- 传统xml配置:
<server>
<port>8081<port>
</server>
- yaml配置:
server:
prot: 8081
4.3、yaml基础语法
说明:语法要求严格!
1、
空格不能省略
2、
以缩进来控制层级关系,只要是左边对齐的一列数据都是同一个层级的。
3、
属性和值的大小写都是十分敏感的。
-
字面量:普通的值 [ 数字,布尔值,字符串 ]
:
字面量直接写在后面就可以 , 字符串默认不用加上双引号或者单引号;
k: v
例如:
name: qianqian
注意:
" "双引号,不会转义字符串里面的特殊字符 , 特殊字符会作为本身想表示的意思;
比如 :name: “qian\n qian” 输出 :qian换行 qian
’ '单引号,会转义特殊字符 , 特殊字符最终会变成和普通字符一样输出
比如 :name: ‘qian\n qian’ 输出 :qian\n qian
- 对象、Map(键值对)
#对象、Map格式
k:
v1:
v2:
例子
student:
name: qinjiang
age: 3
行内写法
student: {name: qinjiang,age: 3}
- 数组( List、set )
用 - 值表示数组中的一个元素,比如:
str:
- cat
- dog
- pig
行内写法
str: [cat,dog,pig]
4.4、yaml注入配置文件
yaml文件更强大的地方在于,他可以给我们的
实体类直接注入匹配值
!
- 实体类
@Data
@NoArgsConstructor
@AllArgsConstructor
@Component //注册bean
@ConfigurationProperties(prefix = "person")
public class Person {
private String name;
private Integer age;
private Boolean happy;
private Date birth;
private Map<String,Object> maps;
private List<Object> lists;
private Dog dog;
}
- yaml配置文件
person:
name: qinjiang
age: 3
happy: false
birth: 2000/01/01
maps: {k1: v1,k2: v2}
lists:
- code
- girl
- music
dog:
name: 旺财
age: 1
@ConfigurationProperties作用: 将配置文件中配置的每一个属性的值,映射到这个组件中;告诉SpringBoot将本类中的所有属性和配置文件中相关的配置进行绑定 参数prefix= “person” : 将配置文件中的person下面的所有属性一一对应
- 测试
@SpringBootTest
public class DogTest {
@Autowired
Dog dog;
@Autowired
Person person;
@Test
public void test1(){
System.out.println(dog);
System.out.println(person);
}
}
加载指定的配置文件
@PropertySource :加载指定的配置文件;
@configurationProperties:默认从全局配置文件中获取值;
@PropertySource(value = "classpath:person.properties")
4.5、配置文件占位符
person:
name: qianqian${random.uuid} # 随机uuid
age: ${random.int} # 随机int
happy: false
birth: 2000/01/01
maps: {k1: v1,k2: v2}
lists:
- code
- girl
- music
dog:
name: ${person.hello:other}_旺财
age: 1
4.6、yaml配置与properties配置的区别
【注意】properties配置文件在写中文的时候,会有乱码 , 我们需要去IDEA中设置编码格式为UTF-8;
4.7、JSR303数据校验
Springboot中可以用
@validated
来校验数据,如果数据异常则会统一抛出异常,方便异常中心统一处理。我们这里来写个注解让我们的name只能支持Email格式
注意:
springboot2.2以上需要手动导入validation启动器
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
- JSR303常用注解
空检查
@Null 验证对象是否为null
@NotNull 验证对象是否不为null, 无法查检长度为0的字符串
@NotBlank 检查约束字符串是不是Null还有被Trim的长度是否大于0,只对字符串,且会去掉前后空格.
@NotEmpty 检查约束元素是否为NULL或者是EMPTY.
Booelan检查
@AssertTrue 验证 Boolean 对象是否为 true
@AssertFalse 验证 Boolean 对象是否为 false
长度检查
@Size(min=, max=) 验证对象(Array,Collection,Map,String)长度是否在给定的范围之内
@Length(min=, max=) string is between min and max included.
日期检查
@Past 验证 Date 和 Calendar 对象是否在当前时间之前
@Future 验证 Date 和 Calendar 对象是否在当前时间之后
@Pattern 验证 String 对象是否符合正则表达式的规则
.......等等
除此以外,我们还可以自定义一些数据校验规则
- 测试
@Component //注册bean
@ConfigurationProperties(prefix = "person")
@Validated
public class Person {
@Email(message = "您输入的邮箱格式不合法")
private String name;
}
4.8、配置文件加载位置
外部加载配置文件的方式十分多,我们选择最常用的即可,在开发的资源文件中进行配置!
4.8、多环境配置文件
我们在主配置文件编写的时候,文件名可以是 application-{profile}.properties/yml , 用来指定多个环境版本;
- properties环境
例如:
application-test.properties 代表测试环境配置
application-dev.properties 代表开发环境配置
但是Springboot并不会直接启动这些配置文件,它
默认使用application.properties主配置文件
;
#比如在配置文件中指定使用dev环境,我们可以通过设置不同的端口号进行测试;
#我们启动SpringBoot,就可以看到已经切换到dev下的配置了;
spring.profiles.active=dev
-
yaml环境
使用yml去实现不需要创建多个配置文件
# 选择要激活的米快
spring:
profiles:
active: test
server:
port: 8080
--- #用---进行模块划分
# spring:
# profiles: dev #配置环境的名称
#springboot2.2之后这种方法不建议使用
server:
port: 8081
spring:
config:
activate:
on-profile: dev #配置环境的名称
---
server:
port: 8082
spring:
config:
activate:
on-profile: test
注意:如果yml和properties同时都配置了端口,并且没有激活其他环境 , 默认会使用properties配置文件的!
五、SpringBoot Web开发
5.1、静态资源处理
SpringBoot中,SpringMVC的web配置都在 WebMvcAutoConfiguration 这个配置类里面,观察一下源码,addResourceHandlers 添加资源处理
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
//如果有自定义配置,则去加载自定义配置,如果没有,则失效
if (!this.resourceProperties.isAddMappings()) {
logger.debug("Default resource handling disabled");
return;
}
//webjars 配置
addResourceHandler(registry, "/webjars/**", "classpath:/META-INF/resources/webjars/");
addResourceHandler(registry, this.mvcProperties.getStaticPathPattern(), (registration) -> {
registration.addResourceLocations(this.resourceProperties.getStaticLocations());
if (this.servletContext != null) {
ServletContextResource resource = new ServletContextResource(this.servletContext, SERVLET_LOCATION);
registration.addResourceLocations(resource);
}
});
}
- 自定义配置加载方式
SpringBoot一、什么是SpringBoot二、第一个SpringBoot程序三、运行原理初探【超重点】四、yaml五、SpringBoot Web开发 SpringBoot一、什么是SpringBoot二、第一个SpringBoot程序三、运行原理初探【超重点】四、yaml五、SpringBoot Web开发 SpringBoot一、什么是SpringBoot二、第一个SpringBoot程序三、运行原理初探【超重点】四、yaml五、SpringBoot Web开发 -
第一种静态资源映射规则- -webjars 加载方式【不常用】
Webjars本质就是以jar包的方式引入我们的静态资源
-
【第二种静态资源映射规则- -使用自己的静态资源
】常用、重点
- 我们去找staticPathPattern发现第二种映射规则 :/** , 访问当前的项目任意资源,它会去找 resourceProperties 这个类,发现resourceProperties 继承了Resources类,点进去发现相关的目录
SpringBoot一、什么是SpringBoot二、第一个SpringBoot程序三、运行原理初探【超重点】四、yaml五、SpringBoot Web开发 SpringBoot一、什么是SpringBoot二、第一个SpringBoot程序三、运行原理初探【超重点】四、yaml五、SpringBoot Web开发 - 总结:以下四个目录存放的静态资源可以被我们识别
"classpath:/META-INF/resources/"
"classpath:/resources/"
"classpath:/static/"
"classpath:/public/"
优先级:
resources>static(默认的)>public
5.2、首页处理
- 源码
@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 Resource getWelcomePage() {
for (String location : this.resourceProperties.getStaticLocations()) {
Resource indexHtml = getIndexHtml(location);
if (indexHtml != null) {
return indexHtml;
}
}
ServletContext servletContext = getServletContext();
if (servletContext != null) {
return getIndexHtml(new ServletContextResource(servletContext, SERVLET_LOCATION));
}
return null;
}
private Resource getIndexHtml(String location) {
return getIndexHtml(this.resourceLoader.getResource(location));
}
private Resource getIndexHtml(Resource location) {
try {
Resource resource = location.createRelative("index.html");
if (resource.exists() && (resource.getURL() != null)) {
return resource;
}
}
catch (Exception ex) {
}
return null;
}
总结:
index.html可以放到static/public/resources任意一个目录下
在templates目录下的所有页面,只能通过controller来跳转,但是需要模板引擎的支持,thymeleaf
5.3、MVC自动配置原理【重点】
Spring MVC Auto-configuration
// Spring Boot为Spring MVC提供了自动配置,它可以很好地与大多数应用程序一起工作。
Spring Boot provides auto-configuration for Spring MVC that works well with most applications.
// 自动配置在Spring默认设置的基础上添加了以下功能:
The auto-configuration adds the following features on top of Spring’s defaults:
// 包含视图解析器
Inclusion of ContentNegotiatingViewResolver and BeanNameViewResolver beans.
// 支持静态资源文件夹的路径,以及webjars
Support for serving static resources, including support for WebJars
// 自动注册了Converter:
// 转换器,这就是我们网页提交数据到后台自动封装成为对象的东西,比如把"1"字符串自动转换为int类型
// Formatter:【格式化器,比如页面给我们了一个2019-8-10,它会给我们自动格式化为Date对象】
Automatic registration of Converter, GenericConverter, and Formatter beans.
// HttpMessageConverters
// SpringMVC用来转换Http请求和响应的的,比如我们要把一个User对象转换为JSON字符串,可以去看官网文档解释;
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).
// 初始化数据绑定器:帮我们把请求数据绑定到JavaBean中!
Automatic use of a ConfigurableWebBindingInitializer bean (covered later in this document).
/*
如果您希望保留Spring Boot MVC功能,并且希望添加其他MVC配置(拦截器、格式化程序、视图控制器和其他功能),则可以添加自己
的@configuration类,类型为webmvcconfiguer,但不添加@EnableWebMvc。如果希望提供
RequestMappingHandlerMapping、RequestMappingHandlerAdapter或ExceptionHandlerExceptionResolver的自定义
实例,则可以声明WebMVCregistrationAdapter实例来提供此类组件。
*/
If you want to keep Spring Boot MVC features and you want to add additional MVC configuration
(interceptors, formatters, view controllers, and other features), you can add your own
@Configuration class of type WebMvcConfigurer but without @EnableWebMvc. If you wish to provide
custom instances of RequestMappingHandlerMapping, RequestMappingHandlerAdapter, or
ExceptionHandlerExceptionResolver, you can declare a WebMvcRegistrationsAdapter instance to provide such components.
// 如果您想完全控制Spring MVC,可以添加自己的@Configuration,并用@EnableWebMvc进行注释。
If you want to take complete control of Spring MVC, you can add your own @Configuration annotated with @EnableWebMvc.
- WebMvcAutoConfiguration配置类
- ContentNegotiatingViewResolver 内容协商视图解析器:自动配置了ViewResolver,即根据方法的返回值取得视图对象(View),然后由视图对象决定如何渲染(转发,重定向)。
@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
// ContentNegotiatingViewResolver使用所有其他视图解析器来定位视图,因此它应该具有较高的优先级
resolver.setOrder(Ordered.HIGHEST_PRECEDENCE);
return resolver;
}
- ContentNegotiatingViewResolver是如何解析视图的,获取候选的视图对象,得到最佳的视图对象,然后把这个对象返回
@Override
@Nullable
public View resolveViewName(String viewName, Locale locale) throws Exception {
RequestAttributes attrs = RequestContextHolder.getRequestAttributes();
Assert.state(attrs instanceof ServletRequestAttributes, "No current ServletRequestAttributes");
List<MediaType> requestedMediaTypes = getMediaTypes(((ServletRequestAttributes) attrs).getRequest());
if (requestedMediaTypes != null) {
//获取候选的视图对象
List<View> candidateViews = getCandidateViews(viewName, locale, requestedMediaTypes);
//得到最佳的视图对象,然后把这个对象返回
View bestView = getBestView(candidateViews, requestedMediaTypes, attrs);
if (bestView != null) {
return bestView;
}
}
String mediaTypeInfo = logger.isDebugEnabled() && requestedMediaTypes != null ?
" given " + requestedMediaTypes.toString() : "";
if (this.useNotAcceptableStatusCode) {
if (logger.isDebugEnabled()) {
logger.debug("Using 406 NOT_ACCEPTABLE" + mediaTypeInfo);
}
return NOT_ACCEPTABLE_VIEW;
}
else {
logger.debug("View remains unresolved" + mediaTypeInfo);
return null;
}
}
- getCandidateViews中把所有的视图解析器拿来,进行for循环,挨个解析!
private List<View> getCandidateViews(String viewName, Locale locale, List<MediaType> requestedMediaTypes)
throws Exception {
List<View> candidateViews = new ArrayList<>();
if (this.viewResolvers != null) {
Assert.state(this.contentNegotiationManager != null, "No ContentNegotiationManager set");
for (ViewResolver viewResolver : this.viewResolvers) {
View view = viewResolver.resolveViewName(viewName, locale);
if (view != null) {
candidateViews.add(view);
}
for (MediaType requestedMediaType : requestedMediaTypes) {
List<String> extensions = this.contentNegotiationManager.resolveFileExtensions(requestedMediaType);
for (String extension : extensions) {
String viewNameWithExtension = viewName + '.' + extension;
view = viewResolver.resolveViewName(viewNameWithExtension, locale);
if (view != null) {
candidateViews.add(view);
}
}
}
}
}
- List item
总结:
ContentNegotiatingViewResolver 这个视图解析器就是用来组合所有的视图解析器的
- 测试一下ContentNegotiatingViewResolver 这个视图解析器是如何自动组合起来的
- 写一个自己的视图解析器
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
//实现了视图解析器接口的类,就可以把它看作是视图解析器
@Bean
public ViewResolver myViewResolver(){
return new MyViewResolver();
}
public static class MyViewResolver implements ViewResolver{
@Override
public View resolveViewName(String viewName, Locale locale) throws Exception {
return null;
}
}
}
- 我们知道所有的请求都会经过DispatcherServlet,而DispatcherServlet最核心的方法是doDispatch,在这里打断点测试一下
SpringBoot一、什么是SpringBoot二、第一个SpringBoot程序三、运行原理初探【超重点】四、yaml五、SpringBoot Web开发
可以看到自己定义的视图解析器此时已经被加载到之中,之后会从这些所有的视图解析器之中选取一个最佳的视图解析器进行返回
5.4、扩展使用SpringMVC【重点】
编写一个@Configuration注解类,并且类型要为WebMvcConfigurer,还不能标注@EnableWebMvc注解,官方建议这样使用
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/index2").setViewName("test");
}
}
-
源码分析
1、WebMvcAutoConfiguration 是 SpringMVC的自动配置类,里面有一个类WebMvcAutoConfigurationAdapter
2、这个类上有一个注解,在做其他自动配置时会导入:@Import(EnableWebMvcConfiguration.class)
3、我们点进EnableWebMvcConfiguration这个类看一下,它继承了一个父类:DelegatingWebMvcConfigurationSpringBoot一、什么是SpringBoot二、第一个SpringBoot程序三、运行原理初探【超重点】四、yaml五、SpringBoot Web开发 从容器中获取所有的webmvcConfigurerSpringBoot一、什么是SpringBoot二、第一个SpringBoot程序三、运行原理初探【超重点】四、yaml五、SpringBoot Web开发 SpringBoot一、什么是SpringBoot二、第一个SpringBoot程序三、运行原理初探【超重点】四、yaml五、SpringBoot Web开发 SpringBoot一、什么是SpringBoot二、第一个SpringBoot程序三、运行原理初探【超重点】四、yaml五、SpringBoot Web开发 总结:SpringBoot一、什么是SpringBoot二、第一个SpringBoot程序三、运行原理初探【超重点】四、yaml五、SpringBoot Web开发 所有的WebMvcConfiguration都会被作用,不止Spring自己的配置类,我们自己的配置类当然也会被调用;
5.5、springMVC默认时间格式【重点】
- 值得注意的是,springMVC时间的默认格式是
,会发现它是以/分割,而我们前端页面一般都是以-分割,此时就会因为无法识别而不成功,需要手动修改配置文件spring.mvc.format.date=dd/MM/yyyy
spring.mvc.format.date=yyyy-MM-dd
5.6、拦截器【重点】
- 概念
拦截器顾名思义即在请求真正接口逻辑时先经过拦截器拦截,通过拦截器之后才可以访问真正的业务逻辑接口,否则返回登录界面或做其他失败处理。
- 如何使用
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
return false;
}
@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 {
}
1、
preHandle
执行时机是:在Controller中的方法调用前调用,
2、
postHandle
方法是在Controller方法执行后执行并在返回模版引擎前执行,所以第二个方法中参数多了一个ModelAndView,因此你可以在数据填充到模版之前再次进行组装数据,最后再去渲染模版。
3、
afterCompletion
是在模版引擎之后执行
总结:
我们自定义的拦截器必须要继承这个接口
- 实例
public class LoginHandlerInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//登录成功之后,应该有用户的session
Object loginUser = request.getSession().getAttribute("LoginUser");
if(loginUser==null){
request.setAttribute("msg","没有权限,请先登录");
request.getRequestDispatcher("/index.html").forward(request,response);
return false;
}
return true;
}
}
此时我们自定义的拦截器并没有注册到容器当中,需要使用springMVC扩展配置加载到容器之中
拦截器使用会把静态资源如js、css都拦截,因此我们在页面上看起来可能就没有样式或者样式很难看,我们需要实现WebMvcConfigurer接口并重写里面的添加拦截器方法,将我们刚才自定义的拦截器添加到配置里,使用excludePathPatterns来表示要过滤css、js等静态资源。
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginHandlerInterceptor()).addPathPatterns("/**").excludePathPatterns("/index.html","/","/user/login",
"/css/**","/js/**","/img/**");
}
}
总结:
1、一个拦截器,只有preHandle方法返回true,postHandle、afterCompletion才有可能被执行;如果preHandle方法返回false,则该拦截器的postHandle、afterCompletion必然不会被执行。拦截器不是Filter,却实现了Filter的功能,
其原理在于:
1.1、所有的拦截器(Interceptor)和处理器(Handler)都注册在HandlerMapping中。
1.2、SpringMVC中所有的请求都是由DispatcherServlet分发的。
1.3、当请求进入DispatcherServlet.doDispatch()时候,首先会得到处理该请求的Handler(即Controller中对应的方法)以及所有拦截该请求的拦截器。拦截器就是在这里被调用开始工作的。