视频参照springboot2.3.4,我自己的是2.5.6
雷丰阳2021版SpringBoot2零基础入门springboot全套完整版(spring boot2)
笔记来源
springboot 的文档
学习要求
● 熟悉Spring基础
● 熟悉Maven使用
环境要求
● Java8及以上
● Maven 3.3及以上:https://docs.spring.io/spring-boot/docs/current/reference/html/getting-started.html#getting-started-system-requirements
学习资料
● 文档地址: https://www.yuque.com/atguigu/springboot
○ 文档不支持旧版本IE、Edge浏览器,请使用chrome或者firefox
● 视频地址: http://www.gulixueyuan.com/ https://www.bilibili.com/video/BV19K4y1L7MT?p=1
● 源码地址:https://gitee.com/leifengyang/springboot2
https://spring.io/projects/spring-boot
覆盖了:
web开发
数据访问
安全控制
分布式
消息服务
移动开发
批处理
…
基于Java8的一些新特性,如:接口默认实现。重新设计源码架构。
Spring Boot makes it easy to create stand-alone, production-grade Spring based Applications that you can “just run”.
能快速创建出生产级别的Spring应用
创建独立Spring应用、内嵌web服务器、 自动starter依赖与简化构建配置、 自动配置Spring以及第三方功能、
提供生产级别的监控与健康检查及外部化配置、 无代码生成与无需编写XML
SpringBoot是整合Spring技术栈的一站式框架
SpringBoot是简化Spring技术栈的快速开发脚手架
● 人称版本帝,迭代快,需要时刻关注变化
● 封装太深,内部原理复杂,不容易精通
James Lewis and Martin Fowler (2014) 提出微服务完整概念。https://martinfowler.com/microservices/
In short, the microservice architectural style is an approach to developing a single application as a suite of small services, each running in its own process and communicating with lightweight mechanisms, often an HTTP resource API. These services are built around business capabilities and independently deployable by fully automated deployment machinery. There is a bare minimum of centralized management of these services, which may be written in different programming languages and use different data storage technologies.-- James Lewis and Martin Fowler (2014)
● 微服务是一种架构风格
● 一个应用拆分为一组小型服务
● 每个服务运行在自己的进程内,也就是可独立部署和升级
● 服务之间使用轻量级HTTP交互
● 服务围绕业务功能拆分
● 可以由全自动部署机制独立部署
● 去中心化,服务自治。服务可以使用不同的语言、不同的存储技术
分布式的困难
● 远程调用
● 服务发现
● 负载均衡
● 服务容错
● 配置管理
● 服务监控
● 链路追踪
● 日志管理
● 任务调度
● …
分布式的解决
● SpringBoot + SpringCloud
原生应用如何上云。 Cloud Native
上云的困难
● 服务自愈
● 弹性伸缩
● 服务隔离
● 自动化部署
● 灰度发布
● 流量治理
● …
https://spring.io/projects/spring-boot#learn
https://docs.spring.io/spring-boot/docs/current/reference/html/index.html
查看版本新特性;
https://github.com/spring-projects/spring-boot/wiki#release-notes
● Java 8 & 兼容java14 .
● Maven 3.3+
● idea 2019.1.2
<mirrors>
<mirror>
<id>nexus-aliyun</id>
<mirrorOf>central</mirrorOf>
<name>Nexus aliyun</name>
<url>http://maven.aliyun.com/nexus/content/groups/public</url>
</mirror>
</mirrors>
<profiles>
<profile>
<id>jdk-1.8</id>
<activation>
<activeByDefault>true</activeByDefault>
<jdk>1.8</jdk>
</activation>
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<maven.compiler.compilerVersion>1.8</maven.compiler.compilerVersion>
</properties>
</profile>
需求:浏览发送/hello请求,响应 Hello,Spring Boot 2
boot-01-helloworld
new project - maven -next-信息 -next - finish
配置maven的setting.xml配置文件
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.4.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
/**
* 主程序类
* @SpringBootApplication:这是一个SpringBoot应用
*/
@SpringBootApplication
public class MainApplication {
public static void main(String[] args) {
SpringApplication.run(MainApplication.class,args);
}
}
@RestController = @RestBody+ @Controller
@RestController
public class HelloController {
@RequestMapping("/hello")
public String handle01(){
return "Hello, Spring Boot 2!";
}
}
直接运行main方法
application.properties
server.port=8888
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
把项目打成jar包,直接在目标服务器执行即可。
注意点:
● 取消掉cmd的快速编辑模式
父项目做依赖管理
依赖管理
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.4.RELEASE</version>
</parent>
他的父项目
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.3.4.RELEASE</version>
</parent>
几乎声明了所有开发中常用的依赖的版本号,自动版本仲裁机制
开发导入starter场景启动器
1、见到很多 spring-boot-starter-* : *就某种场景
2、只要引入starter,这个场景的所有常规需要的依赖我们都自动引入
3、SpringBoot所有支持的场景
https://docs.spring.io/spring-boot/docs/current/reference/html/using-spring-boot.html#using-boot-starter
4、见到的 *-spring-boot-starter: 第三方为我们提供的简化开发的场景启动器。
5、所有场景启动器最底层的依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>2.3.4.RELEASE</version>
<scope>compile</scope>
</dependency>
无需关注版本号,自动版本仲裁
1、引入依赖默认都可以不写版本
2、引入非版本仲裁的jar,要写版本号。
可以修改默认版本号
使用properties
1、查看spring-boot-dependencies里面规定当前依赖的版本 用的 key。
2、在当前项目里面重写配置
<properties>
<mysql.version>5.1.43</mysql.version>
</properties>
● 自动配好Tomcat
○ 引入Tomcat依赖。
○ 配置Tomcat
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<version>2.3.4.RELEASE</version>
<scope>compile</scope>
</dependency>
● 自动配好SpringMVC
○ 引入SpringMVC全套组件
○ 自动配好SpringMVC常用组件(功能)
● 自动配好Web常见功能,如:字符编码问题
○ SpringBoot帮我们配置好了所有web开发的常见场景
● 默认的包结构
○ 主程序所在包及其下面的所有子包里面的组件都会被默认扫描进来
○ 无需以前的包扫描配置
○ 想要改变扫描路径,@SpringBootApplication(scanBasePackages=“com.atguigu”)
■ 或者@ComponentScan 指定扫描路径
@SpringBootApplication
等同于
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan("com.atguigu.boot")
● 各种配置拥有默认值
○ 默认配置最终都是映射到某个类上,如:MultipartProperties
○ 配置文件的值最终会绑定每个类上,这个类会在容器中创建对象
● 按需加载所有自动配置项
○ 非常多的starter
○ 引入了哪些场景这个场景的自动配置才会开启
○ SpringBoot所有的自动配置功能都在 spring-boot-autoconfigure 包里面
○
● …
● 基本使用
● Full模式与Lite模式
○ 示例
○ 最佳实战-配置类
■配置类组件之间无依赖关系用Lite模式加速容器启动过程,减少判断
■ 配置类组件之间有依赖关系,方法会被调用得到之前单实例组件,用Full模式
配置类
配置类使用示例
/**
* 1、配置类里面使用@Bean标注在方法上给容器注册组件,默认也是单实例的
* 2、配置类本身也是组件
* 3、proxyBeanMethods:代理bean的方法
* Full(proxyBeanMethods = true)、【保证每个@Bean方法被调用多少次返回的组件都是单实例的】
* Lite(proxyBeanMethods = false)【每个@Bean方法被调用多少次返回的组件都是新创建的】
* 组件依赖必须使用Full模式默认。其他默认是否Lite模式
*
*
*
*/
@Configuration(proxyBeanMethods = false) //告诉SpringBoot这是一个配置类 == XML配置文件
public class MyConfig {
/**
* Full:外部无论对配置类中的这个组件注册方法调用多少次获取的都是之前注册容器中的单实例对象
* @return
*/
@Bean //给容器中添加组件。
//以方法名作为组件的id。返回类型就是组件类型。返回的值,就是组件在容器中的实例
public User user01(){
User zhangsan = new User("zhangsan", 18);
//user组件依赖了Pet组件
zhangsan.setPet(tomcatPet());
return zhangsan;
}
@Bean("tom")
public Pet tomcatPet(){
return new Pet("tomcat");
}
}
@Configuration测试代码如下########################################
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan("com.atguigu.boot")
public class MainApplication {
public static void main(String[] args) {
//1、返回我们IOC容器
ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);
//2、查看容器里面的组件
String[] names = run.getBeanDefinitionNames();
for (String name : names) {
System.out.println(name);
}
//3、从容器中获取组件
Pet tom01 = run.getBean("tom", Pet.class);
Pet tom02 = run.getBean("tom", Pet.class);
System.out.println("组件:"+(tom01 == tom02));
//4、com.atguigu.boot.config.MyConfig$$EnhancerBySpringCGLIB$$51f1e1ca@1654a892
MyConfig bean = run.getBean(MyConfig.class);
System.out.println(bean);
//如果@Configuration(proxyBeanMethods = true)代理对象调用方法。SpringBoot总会检查这个组件是否在容器中有。
//保持组件单实例
User user = bean.user01();
User user1 = bean.user01();
System.out.println(user == user1);
User user01 = run.getBean("user01", User.class);
Pet tom = run.getBean("tom", Pet.class);
System.out.println("用户的宠物:"+(user01.getPet() == tom));
}
}
都表示创建对象
@Bean使用在配置类的方法上,表示xml配置文件的bean标签
@Component,放在实体类上
@Controller, 放在这控制器controller类上
@Service ,放在service实现类上
@Repository,放在dao上
对象创建好后,通过spring的IOC、AOP来注入
* @Import({User.class, DBHelper.class})
* 给容器中自动创建出这两个类型的组件、默认组件的名字就是全类名
*/
@Import({User.class, DBHelper.class})
@Configuration(proxyBeanMethods = false) //告诉SpringBoot这是一个配置类 == 配置文件
public class MyConfig {}
@Import 高级用法: https://www.bilibili.com/video/BV1gW411W7wy?p=8
条件装配:满足Conditional指定的条件,则进行组件注入
@ConditionalOnBean: 表示组件中有某个组件,可以指定
@ConditionalOnMissingBean : 表示组件中没某个组件,可以指定
使用位置:类上,或者方法上
配置类
@Configuration(proxyBeanMethods = false) //告诉SpringBoot这是一个配置类 == 配置文件
@ConditionalOnBean(name = "tom")//如果容器中有tom组件时,下面的组件才生效。
//@ConditionalOnMissingBean(name = "tom")
public class MyConfig {
@Bean //给容器中添加组件。以方法名作为组件的id。返回类型就是组件类型。返回的值,就是组件在容器中的实例
public User user01(){
User zhangsan = new User("zhangsan", 18);
//user组件依赖了Pet组件
zhangsan.setPet(tomcatPet());
return zhangsan;
}
@Bean("tom22")
public Pet tomcatPet(){
return new Pet("tomcat");
}
}
测试类,即启动类
@SpringBootApplication
public class Application2 {
public static void main(String[] args) {
//1、返回我们IOC容器
ConfigurableApplicationContext run = SpringApplication.run(Application2.class, args);
boolean user11 = run.containsBean("user01");
System.out.println("user01= " + user11);
}
}
beans.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<bean id="haha" class="com.atguigu.boot.bean.User">
<property name="name" value="zhangsan"></property>
<property name="age" value="18"></property>
</bean>
<bean id="hehe" class="com.atguigu.boot.bean.Pet">
<property name="name" value="tomcat"></property>
</bean>
</beans>
MyConfig
@ImportResource("classpath:beans.xml")
public class MyConfig {}
boolean haha = run.containsBean("haha");
boolean hehe = run.containsBean("hehe");
System.out.println("haha:"+haha);//true
System.out.println("hehe:"+hehe);//true
如何使用Java读取到properties文件中的内容,并且把它封装到JavaBean中,以供随时使用;
public class getProperties {
public static void main(String[] args) throws FileNotFoundException, IOException {
Properties pps = new Properties();
pps.load(new FileInputStream("a.properties"));
Enumeration enum1 = pps.propertyNames();//得到配置文件的名字
while(enum1.hasMoreElements()) {
String strKey = (String) enum1.nextElement();
String strValue = pps.getProperty(strKey);
System.out.println(strKey + "=" + strValue);
//封装到JavaBean。
}
}
}
有2个方式
实体类见到@ConfigurationProperties(prefix = “mycar”),说明与springboot的核心配置文件application.properties的mycar前缀进行绑定,获取自定义配置的
方式一:@ConfigurationProperties+@Component
配置文件application.properties
mycar.brand=BWV
mycar.price=100000
实体类
@Component
@ConfigurationProperties(prefix = “mycar”)
只有在容器中的组件,才会拥有SpringBoot提供的强大功能
/**
* 描述:汽车类
* 只有在容器中的组件,才会拥有SpringBoot提供的强大功能
*
* @Component
* @ConfigurationProperties(prefix = "mycar")
*/
@Component
@ConfigurationProperties(prefix = "mycar")
public class Car implements Serializable {
private String brand;
private Integer price;
public String getBrand() {
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
public Integer getPrice() {
return price;
}
public void setPrice(Integer price) {
this.price = price;
}
public Car(String brand, Integer price) {
this.brand = brand;
this.price = price;
}
public Car() {
}
@Override
public String toString() {
return "Car{" +
"brand='" + brand + '\'' +
", price=" + price +
'}';
}
}
controller
@RestController
public class HelloController {
@Autowired
Car car;
@RequestMapping("/car")
public Car hello() {
return car;
}
}
测试
http://localhost:8080/car
可以获取配置文件的值
{“brand”:“BWV”,“price”:100000}
方式二:@EnableConfigurationProperties + @ConfigurationProperties
实体类添加 @ConfigurationProperties
配置类上添加@EnableConfigurationProperties
@EnableConfigurationProperties功能:
如
Car实体类上添加
@ConfigurationProperties(prefix = "mycar"),不能添加@Component注解
Myconfig配置类上添加
@EnableConfigurationProperties(Car.class)
还有1的配置文件application.properties、controller
测试:
http://localhost:8080/car
{“brand”:“BWV”,“price”:100000}
@SpringBootApplication
@SpringBootApplication = @SpringBootConfiguration + @EnableAutoConfiguration + @ComponentScan
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication{}
。。。。。。。。。。
@Configuration。代表当前是一个配置类
指定扫描哪些,Spring注解;
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {}
自动配置包?指定了默认的包规则
@Import(AutoConfigurationPackages.Registrar.class) //给容器中导入一个组件
public @interface AutoConfigurationPackage {}
String[] basePackages() default {};
Class<?>[] basePackageClasses() default {};
//利用Registrar给容器中导入一系列组件
//将指定的一个包下的所有组件导入进来? MainApplication(即启动类)所在包下。
1、利用getAutoConfigurationEntry(annotationMetadata);给容器中批量导入一些组件
2、调用List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes)获取到所有需要导入到容器中的配置类
3、利用工厂加载 Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader);得到所有的组件
4、从META-INF/spring.factories位置来加载一个文件。
默认扫描我们当前系统里面所有META-INF/spring.factories位置的文件
spring-boot-autoconfigure-2.3.4.RELEASE.jar包里面也有META-INF/spring.factories
文件里面写死了spring-boot一启动就要给容器中加载的所有配置类,127k
# spring-boot-autoconfigure-2.3.4.RELEASE.jar/META-INF/spring.factories
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,
虽然我们127个场景的所有自动配置启动的时候默认全部加载。xxxxAutoConfiguration
按照条件装配规则(@Conditional),最终会按需配置。
@Bean
@ConditionalOnBean(MultipartResolver.class) //容器中有这个类型组件
@ConditionalOnMissingBean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME)
//容器中没有这个名multipartResolver 的组件
public MultipartResolver multipartResolver(MultipartResolver resolver) {
//给@Bean标注的方法传入了对象参数,这个参数的值就会从容器中找。
//SpringMVC multipartResolver。防止有些用户配置的文件上传解析器不符合规范
// Detect if the user has created a MultipartResolver but named it incorrectly
return resolver;
}
给容器中加入了文件上传解析器;
@Configuration设置为配置类
@ConditionalOnProperty:如果配置属性存在的话,执行下面的方法,即aop功能
@Configuration(
proxyBeanMethods = false
)
@ConditionalOnProperty(prefix = "spring.aop", name = "auto", havingValue = "true", matchIfMissing = true)
public class AopAutoConfiguration {
public AopAutoConfiguration() {
}
...
}
CacheAutoConfiguration:默认不生效,因为CacheAspectSupport没有支持
DispatcherServletAutoConfiguration:默认支持,因为@ConditionalOnMissingBean
HttpEncodingAutoConfiguration: 默认支持,因为@ConditionalOnMissingBean
SpringBoot默认会在底层配好所有的组件。但是如果用户自己配置了,以用户的优先
使用@ConditionalOnMissingBean,如果用户没有配置,就使用默认的。否则使用用户的
@Bean
@ConditionalOnMissingBean
public CharacterEncodingFilter characterEncodingFilter() {
}
● SpringBoot先加载所有的自动配置类 xxxxxAutoConfiguration
● 每个自动配置类按照条件进行生效,默认都会绑定配置文件指定的值。xxxxProperties里面拿。xxxProperties和配置文件进行了绑定
● 生效的配置类就会给容器中装配很多组件
● 只要容器中有这些组件,相当于这些功能就有了
● 定制化配置
○ 用户直接自己@Bean替换底层的组件
○ 用户去看这个组件是获取的配置文件什么值就去修改。
xxxxxAutoConfiguration —> 组件 —> xxxxProperties里面拿值 ----> application.properties
修改 字符编码HttpEncodingAutoConfiguration
server.servlet.encoding.charset=GBK,配置文件添加配置就可以啦
如配置缓存CacheAutoConfiguration
查看CacheProperties
可以看出,缓存的所有配置:
spring.cache
使用配置文件配置:spring.cache.xxx
配置的话,查文档https://docs.spring.io/spring-boot/docs/current/reference/html/application-properties.html#application-properties,或者看底层
● 引入场景依赖
○ https://docs.spring.io/spring-boot/docs/current/reference/html/using-spring-boot.html#using-boot-starter
● 查看自动配置了哪些(选做)
○ 自己分析,引入场景对应的自动配置一般都生效了
○ 配置文件中debug=true开启自动配置报告。Negative(不生效)\Positive(生效)
● 是否需要修改
○ 参照文档修改配置项
■ https://docs.spring.io/spring-boot/docs/current/reference/html/appendix-application-properties.html#common-application-properties
■ 自己分析。xxxxProperties绑定了配置文件的哪些。
○ 自定义加入或者替换组件
■ @Bean、@Component。。。
○ 自定义器 XXXXXCustomizer;
○ …
添加依赖
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
idea中搜索安装lombok插件
实体类上添加
@NoArgsConstructor
@AllArgsConstructor
@Data
@ToString
@EqualsAndHashCode
通过键盘Alt +7,查看。
自动添加set、set方法,toString、equals、构造方法等
log.info(“请求进来了…”);
@Slf4j
@RestController
public class HelloController {
@RequestMapping("/hello")
public String handle01(@RequestParam("name") String name){
log.info("请求进来了....");
return "Hello, Spring Boot 2!"+"你好:"+name;
}
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
项目或者页面修改以后:Ctrl+F9,即build project。重新编译项目
分properties、yaml、yml
多环境的配置
同以前的properties用法
YAML 是 “YAML Ain’t Markup Language”(YAML 不是一种标记语言)的递归缩写。在开发的这种语言时,YAML 的意思其实是:“Yet Another Markup Language”(仍是一种标记语言)。
非常适合用来做以数据为中心的配置文件
● key: value;kv之间有空格
● 大小写敏感
● 使用缩进表示层级关系
● 缩进不允许使用tab,只允许空格
● 缩进的空格数不重要,只要相同层级的元素左对齐即可
● '#‘表示注释
● 字符串无需加引号,如果要加,’'与""表示字符串内容 会被 转义/不转义
字面量:单个的、不可再分的值。date、boolean、string、number、null
k: v
对象:键值对的集合。map、hash、set、object
行内写法: k: {k1:v1,k2:v2,k3:v3}
#或
k:
k1: v1
k2: v2
k3: v3
数组:一组按次序排列的值。array、list、queue
行内写法: k: [v1,v2,v3]
#或者
k:
- v1
- v2
- v3
@Data
public class Person {
private String userName;
private Boolean boss;
private Date birth;
private Integer age;
private Pet pet;
private String[] interests;
private List<String> animal;
private Map<String, Object> score;
private Set<Double> salarys;
private Map<String, List<Pet>> allPets;
}
@Data
public class Pet {
private String name;
private Double weight;
}
yaml配置文件
# yaml表示以上对象
person:
userName: zhangsan
boss: false
birth: 2019/12/12 20:12:33
age: 18
pet:
name: tomcat
weight: 23.4
interests: [篮球,游泳]
animal:
- jerry
- mario
score:
english:
first: 30
second: 40
third: 50
math: [131,140,148]
chinese: {first: 128,second: 136}
salarys: [3999,4999.98,5999.99]
allPets:
sick:
- {name: tom}
- {name: jerry,weight: 47}
health: [{name: mario,weight: 47}]
@RestController
public class HelloController {
@Autowired
Person person;
@RequestMapping("/hello")
public Person hello(){
return person;
}
}
自定义的类和配置文件绑定一般没有提示。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
springboot文档
springboot高级特性
7. Developing Web Applications
springboot为SpringMVC提供了自动配置功能,(大多场景我们都无需自定义配置)
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.
○ 内容协商视图解析器和BeanName视图解析器
● Support for serving static resources, including support for WebJars (covered later in this document)).
○ 静态资源(包括webjars)
● Automatic registration of Converter, GenericConverter, and Formatter beans.
○ 自动注册 Converter,GenericConverter,Formatter
● Support for HttpMessageConverters (covered later in this document).
○ 支持 HttpMessageConverters (后来我们配合内容协商理解原理)
● Automatic registration of MessageCodesResolver (covered later in this document).
○ 自动注册 MessageCodesResolver (国际化用)
● Static index.html support.
○ 静态index.html 页支持
● Custom Favicon support (covered later in this document).
○ 自定义 Favicon
● Automatic use of a ConfigurableWebBindingInitializer bean (covered later in this document).
○ 自动使用 ConfigurableWebBindingInitializer ,(DataBinder负责将请求数据绑定到JavaBean上)
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 type WebMvcConfigurer but without @EnableWebMvc.
不用@EnableWebMvc注解。使用 @Configuration + WebMvcConfigurer 自定义规则
If you want to provide custom instances of RequestMappingHandlerMapping, RequestMappingHandlerAdapter, or ExceptionHandlerExceptionResolver, and still keep the Spring Boot MVC customizations, you can declare a bean of type WebMvcRegistrations and use it to provide custom instances of those components.
声明 WebMvcRegistrations 改变默认底层组件
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-annotated DelegatingWebMvcConfiguration as described in the Javadoc of @EnableWebMvc.
使用 @EnableWebMvc+@Configuration+DelegatingWebMvcConfiguration 全面接管SpringMVC
springboot web项目:boot-03-web-01
Static Content
只要静态资源放在类路径下: called /static (or /public or /resources or /META-INF/resources
访问 : 当前项目根路径 / + 静态资源名
原理: 静态映射 /**
请求进来,先去找Controller看能不能处理。不能处理的所有请求又都交给静态资源处理器。静态资源也找不到则响应404页面
改变默认的静态资源路径
静态资源 默认路径的是 /resources/**
修改默认路径
spring:
mvc:
static-path-pattern: /res/**
resources:
static-locations: [classpath:/haha/]
请求需要添加res:http://localhost:8080/res/1.jpg
默认无前缀
自动映射 /webjars/**
https://www.webjars.org/
<dependency>
<groupId>org.webjars</groupId>
<artifactId>jquery</artifactId>
<version>3.5.1</version>
</dependency>
访问地址:http://localhost:8080/webjars/jquery/3.5.1/jquery.js
后面地址要按照依赖里面的包路径
● 静态资源路径下 index.html
○ 可以配置静态资源路径
○ 但是不可以配置静态资源的访问前缀。否则导致 index.html不能被默认访问
● controller能处理/index
spring:
# mvc:
# static-path-pattern: /res/** 这个会导致welcome page功能失效
resources:
static-locations: [classpath:/haha/]
index.html放在haha下,static等都可以访问。
访问: http://localhost:8080/ ,可以访问到index.html文件
优先级:配置了static-locations,以他为准。否则,在默认的static、resources/resources、public、META-INF/resources下,优先级为META-INF/resources、resources/resources、static、public
网页的图标
favicon.ico 放在静态资源目录下即可。
如static、public等
spring:
# mvc:
# static-path-pattern: /res/** 这个会导致 Favicon 功能失效
使用火狐浏览器,可以显示出图标。最好F12,network关闭缓存。
http://localhost:8080/
● SpringBoot启动默认加载 xxxAutoConfiguration 类(自动配置类)
● SpringMVC功能的自动配置类 WebMvcAutoConfiguration,生效
@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 {}
由于@ConditionalOnMissingBean,所以WebMvcAutoConfiguration 类的所有容器都生效
● 给容器中配了什么。
@Configuration(proxyBeanMethods = false)
@Import(EnableWebMvcConfiguration.class)
//@EnableConfigurationProperties({ WebMvcProperties.class, ResourceProperties.class })//2.3.4
@EnableConfigurationProperties({ WebMvcProperties.class, ResourceProperties.class,WebProperties.clsss })//boot2.5.6
@Order(0)
public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer {}
配置了WebMvcProperties、ResourceProperties、WebProperties
● 配置文件的相关属性和xxx进行了绑定。
WebMvcPropertiesspring.mvc、ResourcePropertiesspring.resources
springboot 2.5.6还有WebProperties==spring.web
//有参构造器所有参数的值都会从容器中确定
//ResourceProperties resourceProperties;获取和spring.resources绑定的所有的值的对象
//参数还有WebProperties,不过在boot2.5.6
//WebMvcProperties mvcProperties 获取和spring.mvc绑定的所有的值的对象
//ListableBeanFactory beanFactory Spring的beanFactory
//HttpMessageConverters 找到所有的HttpMessageConverters
//ResourceHandlerRegistrationCustomizer 找到 资源处理器的自定义器。=========
//DispatcherServletPath
//ServletRegistrationBean 给应用注册Servlet、Filter....
public WebMvcAutoConfigurationAdapter(ResourceProperties resourceProperties, WebMvcProperties mvcProperties,
ListableBeanFactory beanFactory, ObjectProvider<HttpMessageConverters> messageConvertersProvider,
ObjectProvider<ResourceHandlerRegistrationCustomizer> resourceHandlerRegistrationCustomizerProvider,
ObjectProvider<DispatcherServletPath> dispatcherServletPath,
ObjectProvider<ServletRegistrationBean<?>> servletRegistrations) {
this.resourceProperties = resourceProperties;
this.mvcProperties = mvcProperties;
this.beanFactory = beanFactory;
this.messageConvertersProvider = messageConvertersProvider;
this.resourceHandlerRegistrationCustomizer = resourceHandlerRegistrationCustomizerProvider.getIfAvailable();
this.dispatcherServletPath = dispatcherServletPath;
this.servletRegistrations = servletRegistrations;
}
@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));
}
}
spring:
# mvc:
# static-path-pattern: /res/**
resources:
add-mappings: false 禁用所有静态资源规则
@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;
HandlerMapping:处理器映射。保存了每一个Handler能处理哪些请求。
@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;
}
WelcomePageHandlerMapping(TemplateAvailabilityProviders templateAvailabilityProviders,
ApplicationContext applicationContext, Optional<Resource> welcomePage, String staticPathPattern) {
if (welcomePage.isPresent() && "/**".equals(staticPathPattern)) {
//要用欢迎页功能,必须是/**
logger.info("Adding welcome page: " + welcomePage.get());
setRootViewName("forward:index.html");
}
else if (welcomeTemplateExists(templateAvailabilityProviders, applicationContext)) {
// 调用Controller /index
logger.info("Adding welcome page template: index");
setRootViewName("index");
}
}
● @xxxMapping;
● Rest风格支持(使用HTTP请求方式动词来表示对资源的操作)
○ 以前:/getUser 获取用户 /deleteUser 删除用户 /editUser 修改用户 /saveUser 保存用户
○ 现在: /user GET-获取用户 DELETE-删除用户 PUT-修改用户 POST-保存用户
即@GetMapping、@DeleteMapping、@PutMapping、@PostMapping
○ 核心Filter;HiddenHttpMethodFilter
■ 用法: 表单method=post,隐藏域 _method=put
■ SpringBoot中手动开启
○ 扩展:如何把_method 这个名字换成我们自己喜欢的。
controller的rest请求,html的form表单,过滤器(可以没有),配置文件开启hiddenmethod过滤器
// @RequestMapping(value = "/user", method = RequestMethod.GET)
@GetMapping("/user")
public String getUser() {
return "GET-张三";
}
// @RequestMapping(value = "/user", method = RequestMethod.POST)
@PostMapping("/user")
public String saveUser() {
return "POST-张三";
}
// @RequestMapping(value = "/user", method = RequestMethod.PUT)
@PutMapping("/user")
public String putUser() {
return "PUT-张三";
}
// @RequestMapping(value = "/user", method = RequestMethod.DELETE)
@DeleteMapping("/user")
public String deleteUser() {
return "DELETE-张三";
}
@Bean
@ConditionalOnMissingBean(HiddenHttpMethodFilter.class)
@ConditionalOnProperty(prefix = "spring.mvc.hiddenmethod.filter", name = "enabled", matchIfMissing = false)
public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
return new OrderedHiddenHttpMethodFilter();
}
//自定义filter,可以不要,因为@ConditionalOnMissingBean
@Bean
public HiddenHttpMethodFilter hiddenHttpMethodFilter(){
HiddenHttpMethodFilter methodFilter = new HiddenHttpMethodFilter();
methodFilter.setMethodParam("_m");
return methodFilter;
}
<form action="/user" method="get">
<input type="submit" value="get submit">
form>
<form action="/user" method="post">
<input type="submit" value="post submit">
form>
<form action="/user" method="post">
<input name="_method" type="hidden" value="DELETE">
<input type="submit" value="delete submit">
form>
<form action="/user" method="post">
<input name="_method" type="hidden" value="PUT">
<input type="submit" value="put submit">
form>
value可以为大写 或 小写
Rest原理(表单提交要使用REST的时候。假如为PUT,也可以为DELETE)
● 表单提交会带上_method=PUT
● 请求过来被HiddenHttpMethodFilter拦截
○ 请求是否正常,并且是POST
■ 获取到_method的值。
■ 兼容以下请求;PUT.DELETE.PATCH
■ 原生request(post),包装模式requesWrapper重写了getMethod方法,返回的是传入的值。
■ 过滤器链放行的时候用wrapper。以后的方法调用getMethod是调用requesWrapper的。
Rest使用客户端工具,
● 如PostMan直接发送Put、delete等方式请求,无需Filter。
boot的配置文件
spring:
mvc:
hiddenmethod:
filter:
enabled: true #开启页面表单的Rest功能
需要解决的问题:
form表单的rest请求(put、delete不可用)。因为在form表单中,rest请求不能使用put、delete请求。需要开启页面表单的Rest功能
使用postman:直接访问rest的put、delete请求,不用开启表单的Rest功能
rest请求的的form表单name值_method,可以自定义吗?添加自定义的filter,自定义_method为其他值
自定义filter
/**
* 描述:创建filter,
* 作用:修改form表单默认的name值_mathod
* proxyBeanMethods: 把组件迅速放入容器中
*/
@Configuration(proxyBeanMethods = false)
public class WebConfig {
@Bean
public HiddenHttpMethodFilter hiddenHttpMethodFilter(){
HiddenHttpMethodFilter methodFilter = new HiddenHttpMethodFilter();
//设置_method为_m
methodFilter.setMethodParam("_m");
return methodFilter;
}
}
有自定义的filter,设置name后,就不用默认的了,即_method。没有自定义的,就使用默认的。
因为@ConditionalOnMissingBean(HiddenHttpMethodFilter.class)
SpringMVC功能分析都从 org.springframework.web.servlet.DispatcherServlet ----> doDispatch()开始
springboot为SpringMVC,请求就是DispatcherServlet 。
重写doGet、doPOST方法,在FrameworkServlet类中。(通过Ctrl + H查看类的继承关系,通过Ctrl +F12查看类的方法)
FrameworkServlet中有doService的抽象方法
DispatcherServlet 有doService的实现方法,doService方法有doDispatch()
请求调用顺序:FrameworkServlet 的doService -->DispatcherServlet的doService() —>doService()的doDispatch()
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
// 找到当前请求使用哪个Handler(Controller的方法)处理
mappedHandler = getHandler(processedRequest);
//HandlerMapping:处理器映射。/xxx->>xxxx
SpringBoot自动配置欢迎页的 WelcomePageHandlerMapping 。访问 /能访问到index.html;
SpringBoot自动配置了默认 的 RequestMappingHandlerMapping
请求进来,挨个尝试所有的HandlerMapping看是否有请求信息。
如果有就找到这个请求对应的handler
如果没有就是下一个 HandlerMapping
我们需要一些自定义的映射处理,我们也可以自己给容器中放HandlerMapping。
自定义 HandlerMapping
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
if (this.handlerMappings != null) {
for (HandlerMapping mapping : this.handlerMappings) {
HandlerExecutionChain handler = mapping.getHandler(request);
if (handler != null) {
return handler;
}
}
}
return null;
}