SpringBoot2核心技术与响应式编程 · 语雀 (yuque.com)https://www.yuque.com/atguigu/springboot
基础入门
- java8
- maven3.3+
一、HelloWorld
2. pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.ice</groupId>
<artifactId>springboot-01-helloworld</artifactId>
<version>1.0-SNAPSHOT</version>
<!--不写打包方式默认就是jar-->
<packaging>jar</packaging>
<!--父工程 确定了版本号-->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.4.RELEASE</version>
</parent>
<dependencies>
<!--web场景依赖 不需要版本号,依赖于parent-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
</project>
3. 主程序
package com.ice.boot;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
//主程序类
@SpringBootApplication//标注这是一个springboot应用
public class MainApplication {
public static void main(String[] args) {
//固定写法
SpringApplication.run(MainApplication.class,args);
}
}
业务类:
package com.ice.boot.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
/*@ResponseBody//表示这个类每一个方法都是写给浏览器
@Controller*/
@RestController//前两个的合体
public class HelloController {
@RequestMapping(value = "/hello")
public String handle01(){
return "Hello, springboot 2 !";
}
}
4. 测试(直接运行主程序类里的主方法)
神奇!
不需要配置Tomcat!
控制台:
浏览器:
5. 简化配置
application.properties:
6. 简化部署
不需要配置Tomcat!
把项目打成jar包,直接在目标服务器执行即可。
pom.xml中添加:
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
启动:
文件夹中有jar:
CMD运行:
浏览器中也能访问localhost://8080/hello
注意:取消掉cmd的快速编辑模式
二、自动配置原理
2.1 springboot特点
2.1.1 依赖管理
- 父项目做依赖管理:几乎声明了所有开发中常用的依赖的版本号,自动版本仲裁机制
- 开发导入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,要写版本号。
- 可以修改默认版本号:
1、查看spring-boot-dependencies里面规定当前依赖的版本 用的 key。
2、在当前项目pom.xml里面重写配置,自定义需要的版本号
<properties>
<mysql.version>5.1.43</mysql.version>
</properties>
2.1.2 自动配置
- 自动配好Tomcat:引入、配置(引入web场景依赖的时候Tomcat依赖已经被引入)
- 自动配好SpringMVC:引入、自动配置常用组件(引入web场景依赖的时候SpringMVC依赖已经被引入)
- 自动配好Web常见功能:SpringBoot配置好了所有web开发的常见场景,如以下:
//主程序类
@SpringBootApplication//标注这是一个springboot应用
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);
}
}
}
嗯,就是啥都帮你弄好了,啥都有。
- 默认的包结构:主程序所在包及其下面的所有子包里面的组件都会被默认扫描进来,不需要配置包扫描
改变包扫描路径:
(1)
(2)
@SpringBootApplication
等同于
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan("com.ice.boot")
- 各种配置拥有默认值
1. 默认配置最终都是映射到某个类上,如:MultipartProperties
2. 配置文件(application.properties)的值最终会绑定每个类上,这个类会在容器中创建对象
- 按需加载所有自动配置项
1. 非常多的场景starter
2. 引入了哪些场景这个场景的自动配置才会开启
3. SpringBoot所有的自动配置功能都在 spring-boot-autoconfigure 包里面
2.2 容器功能
2.2.1 组件添加
1. @Configuration
Full模式与Lite模式
实例:
/**
* 1、配置类里面使用@Bean标注在方法上给容器注册组件,默认也是单实例的
* 2、配置类本身也是组件
* 3、proxyBeanMethods:代理bean的方法
* Full(proxyBeanMethods = true)【保证每个@Bean方法被调用多少次返回的组件都是单实例的】
* Lite(proxyBeanMethods = false)【每个@Bean方法被调用多少次返回的组件都是新创建的】
* 组件依赖必须使用Full模式默认。其他默认是否Lite模式
*/
@Configuration(proxyBeanMethods = true) //告诉SpringBoot这是一个配置类 == 配置文件
public class MyConfig {
/**
* Full:外部无论对配置类中的这个组件注册方法调用多少次获取的都是之前注册容器中的单实例对象
* @return
*/
@Bean //给容器中添加组件。以方法名作为组件的id。返回类型就是组件类型。返回的值,就是组件在容器中的实例
public User user01() {
User zt = new User("zt", 23);
//User组件依赖了Pet组件
zt.setPet(tomcatPet());
return zt;
}
@Bean("tom")
public Pet tomcatPet() {
return new Pet("tomcat");
}
}
测试:
//4、
//主程序类
/*
* 标注这是一个springboot应用
* 可以设置改变包扫描路径
* */
@SpringBootApplication(scanBasePackages="com.ice")
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.ice.boot.config.MyConfig$$EnhancerBySpringCGLIB$$7dcb51b5@f91da5e
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));
}
}
- 配置类组件之间无依赖关系用Lite模式加速容器启动过程,减少判断
- 配置类组件之间有依赖关系,方法会被调用得到之前单实例组件,用Full模式
2. @Bean、@Component、@Controller、@Service、@Repository
3. @ComponentScan、@Import
//5. 获取组件
//主程序类;主配置类
/*
* 标注这是一个springboot应用
* 可以设置改变包扫描路径
* */
@SpringBootApplication(scanBasePackages="com.ice")
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.ice.boot.config.MyConfig$$EnhancerBySpringCGLIB$$7dcb51b5@f91da5e
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));
//5. 获取组件
String[] names1 = run.getBeanNamesForType(User.class);
for (String name1 : names1) {
System.out.println(name1);
}
DBHelper bean1 = run.getBean(DBHelper.class);
System.out.println(bean1);
}
}
其他@Import用法可参照 Spring注解驱动开发_m0_61922004的博客-CSDN博客
4. @Conditional
条件装配:满足Conditional指定的条件,则进行组件注入
*下面代码有些问题,应该先注册@Bean(“tom”)。否则先扫描user01,就算后面注册了tom,结果都还是false。组件注册顺序很重要(@ConditionalOnBean(name="tom")写在方法上时,注册tom写在注册user01后面,结果tom是true,而user01是false)!
*试了一下,也不对……筛选条件在类上,符合条件的在类里面的方法上,都是false。@ConditionalOnBean(name="tom")放在类上好像没啥作用。
@Import({User.class, DBHelper.class})
@Configuration(proxyBeanMethods = true) //告诉SpringBoot这是一个配置类 == 配置文件
@ConditionalOnBean(name="tom")//也可以标注在类上,容器中有tom组件时方法内部的标注才生效
public class MyConfig {
/**
* Full:外部无论对配置类中的这个组件注册方法调用多少次获取的都是之前注册容器中的单实例对象
* @return
*/
//@ConditionalOnBean(name="tom")//容器中没有tom这个组件了,也就不想要user01这个用户
@Bean //给容器中添加组件。以方法名作为组件的id。返回类型就是组件类型。返回的值,就是组件在容器中的实例
public User user01() {
User zt = new User("zt", 23);
//User组件依赖了Pet组件
zt.setPet(tomcatPet());
return zt;
}
//@Bean("tom")
@Bean("tom1")
public Pet tomcatPet() {
return new Pet("tomcat");
}
}
@SpringBootApplication(scanBasePackages="com.ice")
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);
}
boolean tom = run.containsBean("tom");
System.out.println("容器中是否存在tom组件:"+tom);
boolean user01 = run.containsBean("user01");
System.out.println("容器中是否存在user01组件:"+user01);
boolean tom1 = run.containsBean("tom1");
System.out.println("容器中是否存在tom1组件:"+tom1);
}
}
2.2.2 原生配置文件引入
@ImportResource
如果以前有使用spring配置文件把bean配置到容器中(但是springboot不知道这个文件是干什么的),可以使用该注解,解析配置文件中的内容放到容器中
@ImportResource("classpath:beans.xml")
public class MyConfig {}
2.2.3 配置绑定
使用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。
}
}
}
1. @Component + @ConfigurationProperties
/**
* 只有在容器中的组件才能只用springboot的强大功能
* 为了让@ConfigurationProperties生效,必须把这个组件加入到容器中
*/
@Component
@ConfigurationProperties(prefix = "mycar")
public class Car {
private String brand;
private Integer price;
application.properties中添加:
mycar.brand = mini Cooper
mycar.price = 300000
测试:
/*@ResponseBody//表示这个类每一个方法都是写给浏览器
@Controller*/
@RestController//前两个的合体
public class HelloController {
//自动注入。因为类car上有@Component注解,已经加入IOC容器了
@Autowired
Car car;
@RequestMapping(value = "/car")
public Car car(){
return car;
}
@RequestMapping(value = "/hello")
public String handle01(){
return "Hello, springboot 2 !"+"周涛";
}
}
2. @EnableConfigurationProperties + @ConfigurationProperties
/**
* @EnableConfigurationProperties(Car.class)
* 1. 开启Car配置绑定功能
* 2. 把Car这个组件自动注册到容器中
*/
@EnableConfigurationProperties(Car.class)
public class MyConfig {
//@Component
@ConfigurationProperties(prefix = "mycar")
public class Car {
启动报错,找不到Car这个bean……
注释掉配置类MyConfig上的注解@ConditionalOnBean(name="tom")又启动成功了……
2.3 自动配置原理入门
2.3.1 引导加载自动配置类
//@SpringBootApplication(scanBasePackages="com.ice")相当于下面三个注解合起来
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan("com.ice")
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
excludeFilters = {@Filter(
type = FilterType.CUSTOM,
classes = {TypeExcludeFilter.class}
), @Filter(
type = FilterType.CUSTOM,
classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {
1. @SpringBootConfiguration
本质上还是@COnfiguration,代表当前是一个配置类
2. @ComponentScan
包扫描,指定扫描的包。可参照Spring注解驱动开发_m0_61922004的博客-CSDN博客
3. @EnableAutoConfiguration
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
(1)@AutoConfigurationPackage
自动配置包
@Import({Registrar.class})//给容器中导入一个组件
public @interface AutoConfigurationPackage {
//利用Register给容器中导入一系列组件
//将指定的一个包下的所有组件导入进来:MainApplication所在包下
(2)@Import(AutoConfigurationImportSelector.class)
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
文件里写死了springboot一启动就要给容器中加载的所有配置类:
#Auto Configure 一共127个
2.3.2 按需开启自动配置项
虽然127个场景的所有自动配置启动的时候默认全部加载:xxxxAutoConfiguration,但是最终按照条件装配规则(@Conditional),会按需配置。
2.3.3 修改默认配置
下面代码意思就是容器中没有叫规定这个名字的文件上传解析器(比如自己配置的文件上传解析器),当需要文件上传解析器组件时,springboot会找到自己配置的文件上传解析器组件,返回。
//相当于给容器中加入了文件上传解析器
@Bean
@ConditionalOnBean(MultipartResolver.class) //容器中有这个类型组件
@ConditionalOnMissingBean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME) //容器中没有这个名字 multipartResolver 的组件
public MultipartResolver multipartResolver(MultipartResolver resolver) {
//给@Bean标注的方法传入了对象参数resolver,这个参数的值就会从容器中找。
//SpringMVC multipartResolver。防止有些用户配置的文件上传解析器不符合规范
// Detect if the user has created a MultipartResolver but named it incorrectly
return resolver;
}
SpringBoot的设计模式:SpringBoot默认会在底层配好所有的组件,但是如果用户自己配置了以用户的优先
@Bean
@ConditionalOnMissingBean//如果用户没有配,底层自动配,如果用户配了,以用户配置的为准
public CharacterEncodingFilter characterEncodingFilter() {
}
总结:
- SpringBoot先加载所有的自动配置类 xxxxxAutoConfiguration
- 每个自动配置类按照条件进行生效,默认都会绑定配置文件指定的值:xxxProperties。xxxProperties和配置文件进行了绑定
- 生效的配置类就会给容器中装配很多组件
- 只要容器中有这些组件,相当于这些功能就有了
- 定制化配置
- 用户直接自己@Bean替换底层的组件(自己定制需要的规则,没定制的话底层自动已经配置过了)
- 用户也可以去看这个组件是获取的配置文件什么值就去修改。
xxxAutoConfiguration ---> 组件 ---> xxProperties里面拿值 ----> application.properties
(通过修改配置文件改变值(通过prefix找前缀),application.properties是用户自定义的配置文件)
2.3.4 最佳实践
2. 查看自动配置了那些(选做)
- 自己分析,引入场景对应的自动配置一般都生效了
- 配置文件application.properties中写“debug=true”开启自动配置报告,启动:Negative(不生效)\Positive(生效)
3. 是否需要修改
(1)参照文档修改配置
- Common Application Properties (spring.io)
https://docs.spring.io/spring-boot/docs/current/reference/html/application-properties.html#application-properties
- 自己分析。xxxxProperties绑定了配置文件的哪些。
eg: 修改启动项目后控制台打印图片
(自己去网上生成一个.txt文件 )
(2)自定义加入或者替换组件
@Bean、@Component等等
(3)自定义器 XXXXXCustomizer;
(4)……
2.4 开发小技巧
2.4.1 Lombok
1. 引入依赖
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
2. 给idea安装lombok插件
@Data//生成已有属性的getter seetter,包括toString
@ToString//编译该类是自动生成toString
@AllArgsConstructor//全参构造器
@NoArgsConstructor//无参构造器
@EqualsAndHashCode//hashcode
@ConfigurationProperties(prefix = "mycar")
public class Car {
private String brand;
private Integer price;
}
================================简化日志开发===================================
@Slf4j
@RestController
public class HelloController {
@RequestMapping(value = "/hello")
public String handle01(@RequestParam("name") String name){
log.info("HelloHelloHello!!!");
return "Hello, springboot 2 !"+name;
}
}
2.4.2 dev-tools
1. 引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
2. 项目或页面修改以后:Ctrl+Fn+F9(相当于热更新,其实是自动重启),鸡肋了感觉……
2.4.3 Spring Initializr(项目初始化向导)
自动创建项目结构:
自动生成pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.3</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.ice.boot</groupId>
<artifactId>springboot-01-helloworld-2</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springboot-01-helloworld-2</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>11</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.2</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
自动生成主配置类:
package com.ice.boot.springboot;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Springboot01Helloworld2Application {
public static void main(String[] args) {
SpringApplication.run(Springboot01Helloworld2Application.class, args);
}
}
核心功能
三、配置文件
3.1 文件类型
3.1.1 properties
和以前的properties文件使用相同。
3.1.2 yaml
1. 简介
YAML 是 "YAML Ain't Markup Language"(YAML 不是一种标记语言)的递归缩写。在开发的这种语言时,YAML 的意思其实是:"Yet Another Markup Language"(仍是一种标记语言)。它非常适合用来做以数据为中心的配置文件。
yaml更注重数据本身,而不是以标记语言为主。
2. 基本语法
- key: value;kv之间有空格
- 大小写敏感
- 使用缩进表示层级关系
- 缩进不允许使用tab,只允许空格
- 缩进的空格数不重要,只要相同层级的元素左对齐即可
- '#'表示注释
- 字符串无需加引号,如果要加,''与""表示字符串内容 会被 转义/不转义
3. 数据类型
- 字面量:单个的、不可再分的值。date、boolean、string、number、null
字面量:单个的、不可再分的值。date、boolean、string、number、null
- 对象:键值对的集合。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
4. 实例
@ConfigurationProperties(prefix = "person")
@Component
@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表示Person对象
person:
#字符串也可以加引号,单引号不能识别转义字符 双引号能识别转义字符
#例如'z \n t' 控制台原样输出 "z \n t" 控制台换行输出
userName: 周涛
boss: true
birth: 1999/03/23 20:12:33
age: 23
pet:
name: Dog
weight: 23.4
interests: [主持,dq]
animal:
- jerry
- mario
score:
english:
first: 98
second: 99
third: 99
math: [131,140,148]
chinese: {first: 148,second: 136}
salarys: [39999,49999.98,59999.99]
allPets:
sick:
- {name: tom}
- name: lily
weight: 45
- {name: jerry,weight: 47}
health: [{name: mario,weight: 47}]
@RestController
public class HelloController {
@Autowired
Person person;
@RequestMapping(value = "/person")
public Person person(){
return person;
}
}
3.2 配置提示
自定义的类和配置文件绑定一般没有提示。添加依赖,使得有提示功能:
<!--配置提示-->
<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>
四、web开发
4.1 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
andBeanNameViewResolver
beans.
- 内容协商视图解析器和BeanName视图解析器
- Support for serving static resources, including support for WebJars (covered later in this document)).
- 静态资源(包括webjars)
- Automatic registration of
Converter
,GenericConverter
, andFormatter
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 typeWebMvcConfigurer
but without@EnableWebMvc
.不用@EnableWebMvc注解。使用
@Configuration
+WebMvcConfigurer
自定义规则
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.声明
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
-annotatedDelegatingWebMvcConfiguration
as described in the Javadoc of@EnableWebMvc
.使用
@EnableWebMvc+@Configuration+DelegatingWebMvcConfiguration 全面接管SpringMVC
4.2 简单功能分析
使用Spring Initializr创建项目springboot-web-01。
4.2.1 静态资源访问
1. 静态资源目录
只要静态资源放在类路径下: /static
(or /public
or /resources
or /META-INF/resources
访问:当前项目根路径/ + 静态资源名
如果一个控制器的请求路径也是/zt1.jpg,那么访问的就是控制器。
因为静态资源的映射是/**。发起一次请求,先去找Controller看能不能处理,不能处理的所有请求才都交给静态资源处理器。静态资源也找不到则响应404页面。
改变默认的静态资源路径:这样修改后,只有在根目录下的hello文件夹里的静态资源才能访问(但是访问路径上不需要加hello),其他都不能访问了(META-INF下的resources还是能访问?????)。
2. 静态资源访问前缀
为了以后拦截器拦截某些请求方便,修改默认的静态资源映射(application.yaml),改为带前缀的:
当前项目 + static-path-pattern + 静态资源名 = 在静态资源文件夹下找
3. webjar
WebJars - Web Libraries in Jarshttps://www.webjars.org/ 添加依赖:
<!--webjars jquery-->
<dependency>
<groupId>org.webjars</groupId>
<artifactId>jquery</artifactId>
<version>3.5.1</version>
</dependency>
自动映射: /webjars/**
访问地址:http://localhost:8080/webjars/jquery/3.5.1/jquery.js 后面地址要按照依赖里面的包路径:
4.2.2 欢迎页支持
- 静态资源路径下 index.html
· 可以配置静态资源路径
· 但是不可以配置静态资源的访问前缀。否则导致 index.html不能被默认访问
就算注释了前缀,还是404。。。springboot2.6.3
不要访问路径了以后,直接加一个前缀就行了!
算了,前缀也不要了,用默认吧!前缀影响Favicon。。。
4.2.3 自定义Favicon
没图标的ctrl+F5强制清理缓存!
4.2.4 静态资源配置原理
- 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 {}
- 给容器中配了什么
@Configuration(
proxyBeanMethods = false
)
@Import({WebMvcAutoConfiguration.EnableWebMvcConfiguration.class})
@EnableConfigurationProperties({WebMvcProperties.class, WebProperties.class})
@Order(0)
public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer, ServletContextAware {}
- 配置文件的相关属性和xxx(前缀)进行了绑定。WebMvcProperties==spring.mvc、WebProperties==spring.web(springboot2.6.3源码)
1. 配置类只有(只存在)一个有参构造器(源码不太明白)
//有参构造器所有参数的值都会从容器中确定
//WebPropertiesresourceProperties;获取和spring.resources绑定的所有的值的对象
//WebMvcProperties webProperties获取和spring.mvc绑定的所有的值的对象
//ListableBeanFactory beanFactory Spring的beanFactory
//HttpMessageConverters 找到所有的HttpMessageConverters
//ResourceHandlerRegistrationCustomizer 找到 资源处理器的自定义器。=========
//DispatcherServletPath
//ServletRegistrationBean 给应用注册Servlet、Filter....
public WebMvcAutoConfigurationAdapter(WebProperties webProperties, WebMvcProperties mvcProperties, ListableBeanFactory beanFactory, ObjectProvider<HttpMessageConverters> messageConvertersProvider, ObjectProvider<WebMvcAutoConfiguration.ResourceHandlerRegistrationCustomizer> resourceHandlerRegistrationCustomizerProvider, ObjectProvider<DispatcherServletPath> dispatcherServletPath, ObjectProvider<ServletRegistrationBean<?>> servletRegistrations) {
this.resourceProperties = webProperties.getResources();
this.mvcProperties = mvcProperties;
this.beanFactory = beanFactory;
this.messageConvertersProvider = messageConvertersProvider;
this.resourceHandlerRegistrationCustomizer = (WebMvcAutoConfiguration.ResourceHandlerRegistrationCustomizer)resourceHandlerRegistrationCustomizerProvider.getIfAvailable();
this.dispatcherServletPath = dispatcherServletPath;
this.servletRegistrations = servletRegistrations;
this.mvcProperties.checkConfiguration();
}
2. 资源处理的默认规则
public void addResourceHandlers(ResourceHandlerRegistry registry) {
if (!this.resourceProperties.isAddMappings()) {
logger.debug("Default resource handling disabled");
} else {
this.addResourceHandler(registry, "/webjars/**", "classpath:/META-INF/resources/webjars/");
this.addResourceHandler(registry, this.mvcProperties.getStaticPathPattern(), (registration) -> {
registration.addResourceLocations(this.resourceProperties.getStaticLocations());
if (this.servletContext != null) {
ServletContextResource resource = new ServletContextResource(this.servletContext, "/");
registration.addResourceLocations(new Resource[]{resource});
}
});
}
}
application.yaml:
spring:
# mvc:
# static-path-pattern: "/res/**"
# web:
# resources:
# static-locations:[classpath:/hello/]
web:
resources:
add-mappings: false 禁用所有静态资源规则
public static class Resources {
private static final String[] CLASSPATH_RESOURCE_LOCATIONS
= new String[]{"classpath:/META-INF/resources/",
"classpath:/resources/",
"classpath:/static/",
"classpath:/public/"};
private String[] staticLocations;
3. 欢迎页的处理规则
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构造器
WelcomePageHandlerMapping(TemplateAvailabilityProviders templateAvailabilityProviders,
ApplicationContext applicationContext, Resource welcomePage, String staticPathPattern) {
if (welcomePage != null && "/**".equals(staticPathPattern)) {
//要用欢迎页功能,必须是/**,所以前面加前缀后404了!
logger.info("Adding welcome page: " + welcomePage);
setRootViewName("forward:index.html");
}
else if (welcomeTemplateExists(templateAvailabilityProviders, applicationContext)) {
// 调用Controller /index
logger.info("Adding welcome page template: index");
setRootViewName("index");
}
}
4. favicon
浏览器会发送 /favicon.ico 请求获取到图标,整个session期间不再获取。
4.3 请求参数处理
这部分基本都是SpringMVC的内容,可参照SpringMVC_m0_61922004的博客-CSDN博客和语雀文档相关内容。
4.3.0 请求映射
1. REST使用原理
- @xxxMapping;
- Rest风格支持(使用HTTP请求方式动词来表示对资源的操作)
- 以前:/getUser 获取用户 /deleteUser 删除用户 /editUser 修改用户 /saveUser 保存用户
- 现在: /user GET-获取用户 DELETE-删除用户 PUT-修改用户 POST-保存用户
- 核心Filter:HiddenHttpMethodFilter
- 用法: 表单method=post,隐藏域 _method=put
- SpringBoot中手动开启
如何把_method 这个名字换成自己习惯的?
//@RequestMapping(value = "/user",method = RequestMethod.GET)
@GetMapping(value = "/user")
public String getUser(){
return "GET-周涛";
}
@RequestMapping(value = "/user",method = RequestMethod.POST)
public String saveUser(){
return "POST-周涛";
}
@RequestMapping(value = "/user",method = RequestMethod.PUT)
public String putUser(){
return "PUT-周涛";
}
@RequestMapping(value = "/user",method = RequestMethod.DELETE)
public String deleteUser(){
return "DELETE-周涛";
}
index.html:
测试rest风格
<form action="/user" method="get">
<input value="rest-get-提交" type="submit">
</form>
<form action="/user" method="post">
<input value="rest-post-提交" type="submit">
</form>
<form action="/user" method="post">
<input name="_method" value="DELETE" type="hidden">
<input value="rest-delete-提交" type="submit">
</form>
<form action="/user" method="post">
<input name="_method" value="PUT" type="hidden">
<input value="rest-put-提交" type="submit">
</form>
源码:(默认该filter是不开启的,需要在配置文件中开启)
@Bean
@ConditionalOnMissingBean(HiddenHttpMethodFilter.class)
@ConditionalOnProperty(prefix = "spring.mvc.hiddenmethod.filter", name = "enabled", matchIfMissing = false)
public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
return new OrderedHiddenHttpMethodFilter();
}
application.yaml:
spring:
mvc:
hiddenmethod:
filter:
enabled: true
表单提交要使用REST原理:
表单提交要使用REST时,表单提交会带上_method=PUT,请求过来被HiddenHttpMethodFilter拦截,检查请求是否正常并且为POST请求方式。若是,获取到_method的值。兼容以下请求:PUT.DELETE.PATCH。原生request是post方式,使用包装模式requesWrapper重写了getMethod方法,返回的是传入的值(包装了原生的request,带上参数_method再返回)。过滤器链放行的时候用wrapper。以后的方法调用getMethod是调用requesWrapper的。
Rest使用客户端工具:
如PostMan直接发送Put、delete等方式请求,无需Filter。
也可以自定义_method的名字:(个人不喜欢自定义,喜欢默认)
//自定义filter
@Configuration
public class WebConfig {
@Bean
public HiddenHttpMethodFilter hiddenHttpMethodFilter(){
HiddenHttpMethodFilter hiddenHttpMethodFilter = new HiddenHttpMethodFilter();
hiddenHttpMethodFilter.setMethodParam("_m");
return hiddenHttpMethodFilter;
}
}
2. 请求映射原理
SpringMVC功能分析核心:
org.springframework.web.servlet.DispatcherServlet-->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
……
}
所有的请求映射都在HandlerMapping中 !
- SpringBoot自动配置欢迎页的 WelcomePageHandlerMapping 。访问 / 能访问到index.html
- SpringBoot自动配置了默认的 RequestMappingHandlerMapping
- 也可以自定义HandlerMapper
- 请求进来,挨个尝试所有的HandlerMapping看是否有请求信息:
- 如果有就找到这个请求对应的handler
- 如果没有就是下一个 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;
}
4.3.1 普通参数与基本注解
- 注解
@PathVariable、@RequestHeader、@ModelAttribute、@RequestParam、@MatrixVariable、@CookieValue、@RequestBody
@Controller
public class RequestController {
@GetMapping(value = "/goto")
public String goToPage(HttpServletRequest httpServletRequest){
httpServletRequest.setAttribute("msg","msg");
httpServletRequest.setAttribute("code",200);
return "forward:/success";//转发到 /success
}
@ResponseBody
@GetMapping(value = "/success")
public Map success(@RequestAttribute("msg") String msg,
@RequestAttribute("code") Integer code,
//不使用注解,也可以使用原生的HttpServletRequest
HttpServletRequest httpServletRequest){
Object msg1 = httpServletRequest.getAttribute("msg");
Object code1 = httpServletRequest.getAttribute("code");
Map<String,Object> map = new HashMap<>();
map.put("@RequestAttribute_msg",msg);
map.put("httpServletRequest_msg",msg1);
return map;
}
}
在网页开发中,如果cookie被禁用了,session里面的内容应该怎么使用?
session.set(a,b)-->jsessionid-->cookie-->每次发请求都会携带cookie
解决方法:url重写
/abc;jsessionid=xxx(用;分割,把cookie的值使用矩阵变量的值进行传递)
路径中的;都是矩阵变量的方式
- /cars/sell;low=34;brand=byd,audi,benze
- /cars/sell;low=34;brand=byd;brand=audi;brand=benze
@RestController
public class ParameterTestController {
//1、语法: 请求路径:/cars/sell;low=34;brand=byd,audi,yd
//2、SpringBoot默认是禁用了矩阵变量的功能
// 手动开启:原理。对于路径的处理。UrlPathHelper进行解析。
// removeSemicolonContent(移除分号内容)支持矩阵变量的
//3、矩阵变量必须有url路径变量才能被解析
@GetMapping("/cars/{path}")
public Map carsSell(@MatrixVariable("low") Integer low,
@MatrixVariable("brand") List<String> brand,
@PathVariable("path") String path){
Map<String,Object> map = new HashMap<>();
map.put("low",low);
map.put("brand",brand);
map.put("path",path);
return map;
}
}
手动开启矩阵变量功能:
@Configuration
public class WebConfig implements WebMvcConfigurer {
/*个人喜欢默认,不喜欢改变!
@Bean
public HiddenHttpMethodFilter hiddenHttpMethodFilter(){
HiddenHttpMethodFilter hiddenHttpMethodFilter = new HiddenHttpMethodFilter();
hiddenHttpMethodFilter.setMethodParam("_m");
return hiddenHttpMethodFilter;
}*/
//1. @Bean WebMvcConfigurer
//2. 本类为配置类,可以实现WebMvcConfigurer接口,接口中的方法都有默认实现(jdk8之后的特性)
// 所以可以只实现我们需要的方法
/*//1. 给容器中放入WebMvcConfigurer组件
public WebMvcConfigurer webMvcConfigurer(){
return new WebMvcConfigurer(){
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
UrlPathHelper urlPathHelper = new UrlPathHelper();
//设置不移除URL ;后面内容,使得矩阵变量生效
urlPathHelper.setRemoveSemicolonContent(false);
configurer.setUrlPathHelper(urlPathHelper);
}
};
}*/
//2.实现WebMvcConfigurer接口,重写相关方法
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
UrlPathHelper urlPathHelper = new UrlPathHelper();
//设置不移除URL ;后面内容,使得矩阵变量生效
urlPathHelper.setRemoveSemicolonContent(false);
configurer.setUrlPathHelper(urlPathHelper);
}
}
矩阵变量路径中有两个同名参数值:/boss/1;age=20/2;age=10
// /boss/1;age=20/2;age=10
@GetMapping("/boss/{bossId}/{empId}")
public Map boss(@MatrixVariable(value = "age",pathVar = "bossId") Integer bossAge,
@MatrixVariable(value = "age",pathVar = "empId") Integer empAge){
Map<String,Object> map = new HashMap<>();
map.put("bossAge",bossAge);
map.put("empAge",empAge);
return map;
}
(看完矩阵变量,后面源码先不看了。。)
- Servlet API
WebRequest、ServletRequest、MultipartRequest、 HttpSession、javax.servlet.http.PushBuilder、Principal、InputStream、Reader、HttpMethod、Locale、TimeZone、ZoneId
- 复杂参数
跳(。。)
4.3.2 POJO封装过程(跳。。)
4.3.3 参数处理原理 (跳。。)
4.4 数据响应与内容协商(跳。。)
4.5 视图解析与模块引擎
4.5.1 视图解析
SpringBoot默认不支持 JSP,需要引入第三方模板引擎技术实现页面渲染。
视图处理方式:转发、重定向、自定义视图
1. 视图解析原理流程
- 目标方法处理的过程中,所有数据都会被放在 ModelAndViewContainer 里面。包括数据和视图地址
- 方法的参数是一个自定义类型对象(值从请求参数中确定的),会把它重新放在 ModelAndViewContainer
- 任何目标方法执行完成以后都会返回 ModelAndView(数据和视图地址)。
- processDispatchResult 处理派发结果(页面该如何响应):
视图解析几个关键点:
- 返回值以 forward: 开始: new InternalResourceView(forwardUrl); --> 转发request.getRequestDispatcher(path).forward(request, response);
- 返回值以 redirect: 开始: new RedirectView() --> render就是重定向
- 返回值是普通字符串: new ThymeleafView()-->
2. 模板引擎-thymeleaf
Thymeleafhttps://www.thymeleaf.org/
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(ThymeleafProperties.class)
@ConditionalOnClass({ TemplateMode.class, SpringTemplateEngine.class })
@AutoConfigureAfter({ WebMvcAutoConfiguration.class, WebFluxAutoConfiguration.class })
public class ThymeleafAutoConfiguration {}
在线文档:Tutorial: Using Thymeleaf
(1)引入依赖
<!--thymeleaf-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
(2)springboot已经自动配置好了thymeleaf
- 所有thymeleaf的配置值都在ThymeleafProperties中
- 配置好了 SpringTemplateEngine
- 配好了 ThymeleafViewResolver
- 我们只需要直接开发页面
@ConfigurationProperties(prefix = "spring.thymeleaf")
public class ThymeleafProperties {
private static final Charset DEFAULT_ENCODING = StandardCharsets.UTF_8;
public static final String DEFAULT_PREFIX = "classpath:/templates/";
public static final String DEFAULT_SUFFIX = ".html"; ///xx.html
(3)页面开发
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www/thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>success</title>
</head>
<body>
<h1 th:text="${msg}">哈哈</h1>
<h2>
<a href="www.baidu.com" th:href="${link}">去百度</a> <br/>
<a href="www.baidu.com" th:href="@{link}">去百度2</a>
</h2>
</body>
</html>
3. 构建后台管理系统
(1)项目创建
springboot-web-admin
thymeleaf、web-starter、devtools、lombok
(2)静态资源处理
自动配置好,只需要把所有静态资源放到 static 文件夹下
(3)路径构建
th:action="@{/login}"
(4)模板抽取
th:insert/replace/include
(5)页面跳转
4.6 拦截器
1. HandlerInterceptor接口
/**
* 登录检查
* 1. 配置拦截器要拦截哪些请求
* 2. 把相关配置都放在容器中
*/
@Slf4j
public class LoginInterceptor implements HandlerInterceptor {
/**
* 目标方法执行之前
* @param request
* @param response
* @param handler
* @return
* @throws Exception
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
log.info("拦截的请求路径:"+request.getRequestURI());
//登录检查
HttpSession session = request.getSession();
Object loginUser = session.getAttribute("loginUser");
if(loginUser!=null){
return true;//登录,放行
}
// session.setAttribute("msg","当前还未登录,请登录!");
// response.sendRedirect("/"); 重定向相当于重新发请求,前端页面中${msg}拿不到session的值了
request.setAttribute("msg","当前还未登录,请登录!");
request.getRequestDispatcher("/").forward(request,response);
return false;//拦截,跳转登录页
}
/**
* 目标方法执行之后
* @param request
* @param response
* @param handler
* @param modelAndView
* @throws Exception
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
log.info("postHandle执行{}",modelAndView);
}
/**
* 页面渲染之后
* @param request
* @param response
* @param handler
* @param ex
* @throws Exception
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
log.info("afterCompletion执行异常{}",ex);
}
}
2. 配置拦截器
/**
* 1、编写一个拦截器实现HandlerInterceptor接口
* 2、拦截器注册到容器中(实现WebMvcConfigurer的addInterceptors)
* 3、指定拦截规则【如果是拦截所有,静态资源也会被拦截】
*/
@Configuration
public class AdminWebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor())
.addPathPatterns("/**")// /**拦截所有请求,包括静态资源
.excludePathPatterns("/","/login","/css/**","/fonts/**","/images/**","/js/**");//放行登录页
}
}
3. 验证拦截器
4. 拦截器原理(SpringMVC学过源码!牛蛙)
(1)根据当前请求找到HandlerExecutionChain【可以处理请求的handler以及handler的所有拦截器】
(2)先来顺序执行 所有拦截器的 preHandle方法
- 如果当前拦截器prehandler返回为true。则执行下一个拦截器的preHandle
- 如果当前拦截器返回为false。直接倒序执行所有已经执行了的拦截器的 afterCompletion;
(3)如果任何一个拦截器返回false,直接跳出不执行目标方法
(4)所有拦截器都返回True,执行目标方法
(5)倒序执行所有拦截器的postHandle方法
(6)前面的步骤有任何异常都会直接倒序触发 afterCompletion
(7)页面成功渲染完成以后,也会倒序触发 afterCompletion
4.7 文件上传
4.7.1 表单
<form role="form" th:action="@{/upload}" method="post" enctype="multipart/form-data">
<div class="form-group">
<label for="exampleInputEmail1">邮箱</label>
<input type="email" name="email" class="form-control" id="exampleInputEmail1" placeholder="Enter email">
</div>
<div class="form-group">
<label for="exampleInputPassword1">名字</label>
<input type="text" name="username" class="form-control" id="exampleInputPassword1" placeholder="Password">
</div>
<div class="form-group">
<label for="exampleInputFile">头像</label>
<input type="file" name="headerImg" id="exampleInputFile">
</div>
<div class="form-group">
<label for="exampleInputFile">生活照</label>
<input type="file" name="photos" multiple>
</div>
<div class="checkbox">
<label>
<input type="checkbox"> Check me out
</label>
</div>
<button type="submit" class="btn btn-primary">提交</button>
</form>
4.7.2 文件上传代码
application.yaml:
#修改文件上传大小限制
spring:
servlet:
multipart:
max-request-size: 100MB
max-file-size: 10MB
/**
* 文件上传
*/
@Slf4j
@Controller
public class FormTestController {
@GetMapping(value = "/form_layouts")
public String from_layouts(){
return "form/form_layouts";
}
/**
* MultipartFile 自动封装上传过来的文件
* @param email
* @param username
* @param headerImg
* @param photos
* @return
*/
@PostMapping("/upload")
public String upload(@RequestParam("email") String email,
@RequestParam("username") String username,
@RequestPart("headerImg") MultipartFile headerImg,
@RequestPart("photos") MultipartFile[] photos) throws IOException {
log.info("上传的信息:email={},username={},headerImg={},photos={}",
email,username,headerImg.getSize(),photos.length);
if(!headerImg.isEmpty()){
//保存到文件服务器,OSS服务器
String originalFilename = headerImg.getOriginalFilename();
headerImg.transferTo(new File("D:\\Desktop\\"+originalFilename));
}
if(photos.length > 0){
for (MultipartFile photo : photos) {
if(!photo.isEmpty()){
String originalFilename = photo.getOriginalFilename();
photo.transferTo(new File("D:\\Desktop\\"+originalFilename));
}
}
}
return "main";
}
}
4.7.3 自动配置原理
文件上传自动配置类:MultipartAutoConfiguration---MultipartProperties
springboot已经自动配置好了StandardServletMultipartResolver 【文件上传解析器】。
原理步骤:
- 请求进来使用文件上传解析器判断(isMultipart)并封装(resolveMultipart,返回MultipartHttpServletRequest)文件上传请求
- 参数解析器来(文件上传解析器就是参数解析器的一种)解析请求中的文件内容封装成MultipartFile
- 将request中文件信息封装为一个Map:MultiValueMap<String, MultipartFile>
最终使用FileCopyUtils,实现文件流的拷贝 。
4.8 异常处理
4.9 Web原生组件注入(Servlet、Filter、Listener)
4.9.1 使用Servlet API(推荐)
主类上加注解@ServletComponentScan(basePackages = "com.atguigu.admin") :指定原生Servlet组件都放在哪里(必须有这个注解,才能扫描下面三个注解)
servlet类上加下列注解:
- @WebServlet(urlPatterns = "/my"):效果:直接响应,没有经过Spring的拦截器(我觉得就是因为它是原生的servlet,并不是包装后的DIspatchServlet)
- @WebFilter(urlPatterns={"/css/*","/images/*"})
- @WebListener
扩展:DispatchServlet 如何注册进来 ?
- 容器中自动配置了 DispatcherServlet 属性绑定到 WebMvcProperties,对应的配置文件配置项是 spring.mvc。
- 通过 ServletRegistrationBean<DispatcherServlet> 把 DispatcherServlet 配置进来。
- 默认映射的是 / 路径。
Tomcat-Servlet:
多个Servlet都能处理到同一层路径,精确优选原则
A: /my/
B: /my/1
4.9.2 使用RegistrationBean
ServletRegistrationBean
, FilterRegistrationBean
, ServletListenerRegistrationBean
@Configuration
public class MyRegistConfig {
@Bean
public ServletRegistrationBean myServlet(){
MyServlet myServlet = new MyServlet();
return new ServletRegistrationBean(myServlet,"/myServlet","/my02");
}
@Bean
public FilterRegistrationBean myFilter(){
MyFilter myFilter = new MyFilter();
// return new FilterRegistrationBean(myFilter,myServlet());
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(myFilter);
filterRegistrationBean.setUrlPatterns(Arrays.asList("/myServlet","/css/*"));
return filterRegistrationBean;
}
@Bean
public ServletListenerRegistrationBean myListener(){
MyServletContextListener myServletContextListener = new MyServletContextListener();
return new ServletListenerRegistrationBean(myServletContextListener);
}
}
4.10 嵌入式web容器
4.10.1 切换嵌入式Servlet容器
1. 默认支持的webServer
Tomcat
,Jetty
, orUndertow
ServletWebServerApplicationContext 容器启动寻找ServletWebServerFactory 并引导创建服务器
2. 切换服务器
原理:
- SpringBoot应用启动发现当前是Web应用,web场景包-导入tomcat
- web应用会创建一个web版的ioc容器
ServletWebServerApplicationContext
ServletWebServerApplicationContext
启动的时候寻找ServletWebServerFactory
(Servlet 的web服务器工厂 生产 Servlet 的web服务器)
- SpringBoot底层默认有很多的WebServer工厂:
TomcatServletWebServerFactory
,JettyServletWebServerFactory
, 或者UndertowServletWebServerFactory
底层直接会有一个自动配置类:ServletWebServerFactoryAutoConfiguration
ServletWebServerFactoryAutoConfiguration导入了ServletWebServerFactoryConfiguration(配置类)
ServletWebServerFactoryConfiguration 配置类动态判断系统中到底导入了哪个Web服务器的包(默认是web-starter导入tomcat包,容器中自然就有 TomcatServletWebServerFactory)
TomcatServletWebServerFactory 创建出Tomcat服务器并启动。TomcatWebServer 的构造器拥有初始化方法initialize---this.tomcat.start();
内嵌服务器就是手动把启动服务器的代码调用(前提是tomcat核心jar包存在)
spring-boot-starter-web中封装了Tomcat:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<version>2.6.3</version>
<scope>compile</scope>
</dependency>
4.10.2 定制Servlet容器
- 实现 WebServerFactoryCustomizer<ConfigurableServletWebServerFactory>(把配置文件的值和
ServletWebServerFactory 进行绑定
)
- 修改配置文件 server.xxx
- 直接自定义 ConfigurableServletWebServerFactory
//xxxxxCustomizer:定制化器,可以改变xxxx的默认规则
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.boot.web.servlet.server.ConfigurableServletWebServerFactory;
import org.springframework.stereotype.Component;
@Component
public class CustomizationBean implements WebServerFactoryCustomizer<ConfigurableServletWebServerFactory> {
@Override
public void customize(ConfigurableServletWebServerFactory server) {
server.setPort(8080);
}
}
4.11 定制化原理
4.11.1 定制化的常见方式
- 修改配置文件application.properties或者application.yaml
- xxxxxCustomizer;
- 编写自定义的配置类 xxxConfiguration,+ @Bean替换或增加容器中默认组件,如视图解析器
- Web应用 :编写一个配置类实现 WebMvcConfigurer 即可定制化web功能;+ @Bean给容器中再扩展一些组件(常用)
@Configuration
public class AdminWebConfig implements WebMvcConfigurer
- @EnableWebMvc + WebMvcConfigurer —— @Bean 可以全面接管SpringMVC,所有规则全部自己重新配置,实现定制和扩展功能(慎用啊!)
原理:
- 1、WebMvcAutoConfiguration 默认的SpringMVC的自动配置功能类。静态资源、欢迎页.....
- 2、一旦使用 @EnableWebMvc 。会 @Import(DelegatingWebMvcConfiguration.class)
- 3、DelegatingWebMvcConfiguration 的 作用,只保证SpringMVC最基本的使用
- 把所有系统中的 WebMvcConfigurer 拿过来。所有功能的定制都是这些 WebMvcConfigurer 合起来一起生效
- 自动配置了一些非常底层的组件。RequestMappingHandlerMapping、这些组件依赖的组件都是从容器中获取
- public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport
- 4、WebMvcAutoConfiguration 里面的配置要能生效 必须 @ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
- 5、@EnableWebMvc 导致了 WebMvcAutoConfiguration 没有生效。
使用了 @EnableWebMvc相当于容器中有了DelegatingWebMvcConfiguration,有了DelegatingWebMvcConfiguration相当于容器中有了WebMvcConfigurationSupport,有了WebMvcConfigurationSupport意味着不满足@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)这个条件了,所以 WebMvcAutoConfiguration就不生效了!
4.11.2 原理分析套路*
场景starter - xxxxAutoConfiguration - 导入xxx组件 - 绑定xxxProperties -- 绑定配置文件项
五、数据访问
6.1 SQL
6.1.1 数据源的自动配置
1. 导入JDBC场景
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>
没有connector?因为它不知道你用的MySQL还是Oracle。需要自己添加相关依赖:(官方已经依据父工程仲裁了版本)
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--想要修改版本
1、直接依赖引入具体版本(maven的就近依赖原则)
2、重新声明版本(maven的属性的就近优先原则)-->
<properties>
<java.version>1.8</java.version>
<mysql.version>5.1.49</mysql.version>
</properties>
2. 分析自动配置
DataSourceAutoConfiguration : 数据源的自动配置
- 修改数据源相关的配置:修改配置文件中以spring.datasource开头
- 数据库连接池的配置,是自己容器中没有DataSource才自动配置的
- 底层配置好的连接池是:HikariDataSource
@Configuration(proxyBeanMethods = false)
@Conditional(PooledDataSourceCondition.class)
@ConditionalOnMissingBean({ DataSource.class, XADataSource.class })
@Import({ DataSourceConfiguration.Hikari.class, DataSourceConfiguration.Tomcat.class,
DataSourceConfiguration.Dbcp2.class, DataSourceConfiguration.OracleUcp.class,
DataSourceConfiguration.Generic.class, DataSourceJmxConfiguration.class })
protected static class PooledDataSourceConfiguration {
}
DataSourceTransactionManagerAutoConfiguration: 事务管理器的自动配置
JdbcTemplateAutoConfiguration: JdbcTemplate的自动配置,可以来对数据库进行crud
- 可以修改这个配置项@ConfigurationProperties(prefix = "spring.jdbc") 来修改JdbcTemplate
- @Bean@Primary JdbcTemplate;容器中有这个组件
JndiDataSourceAutoConfiguration: jndi的自动配置
XADataSourceAutoConfiguration: 分布式事务相关配置
3. 修改配置项
spring:
datasource:
url: jdbc:mysql://localhost:3306/ssm_crud?useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT
username: root
password: 123456
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
4. 测试
@Slf4j
@SpringBootTest
class SpringbootWebAdminApplicationTests {
@Autowired
JdbcTemplate jdbcTemplate;
@Test
void contextLoads() {
String sql = "select count(*) from tbl_dept";
Long aLong = jdbcTemplate.queryForObject(sql, Long.class);
log.info("记录数:",aLong);
System.out.println("总记录数:"+aLong);
}
}
奇怪哇!log打印不出来aLong,控制台打印好着。。。。
6.1.2 使用Druid数据源
1. 官网网址
整合第三方技术的两种方式
- 自定义
- 找starter
2. 自定义方式
(1)创建数据源
<!--druid-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.17</version>
</dependency>
@Configuration
public class MyDataSourceConfig {
//springboot默认配置:ConditionalOnMissingBean(DataSource.class)
//判断容器中没有DataSource才会自动配置
@ConfigurationProperties("spring.datasource")//和配置文件中以spring.datasource开头的属性绑定
@Bean
public DataSource dataSource(){
DruidDataSource druidDataSource = new DruidDataSource();
return druidDataSource;
}
}
(2)StatViewServlet
StatViewServlet的用途包括:
- 提供监控信息展示的html页面
- 提供监控信息的JSON API
DataSource要加入监控功能:
@Configuration
public class MyDataSourceConfig {
//springboot默认配置:ConditionalOnMissingBean(DataSource.class)
//判断容器中没有DataSource才会自动配置
@ConfigurationProperties("spring.datasource")//和配置文件中以spring.datasource开头的属性绑定
@Bean
public DataSource dataSource() throws SQLException {
DruidDataSource druidDataSource = new DruidDataSource();
//加入监控功能,防火墙功能
druidDataSource.setFilters("stat,wall");
return druidDataSource;
}
/**
* 配置Druid监控页
* @return
*/
@Bean
public ServletRegistrationBean statViewBean(){
StatViewServlet statViewServlet = new StatViewServlet();
ServletRegistrationBean<StatViewServlet> servletServletRegistrationBean = new ServletRegistrationBean<StatViewServlet>(statViewServlet,"/druid/*");
return servletServletRegistrationBean;
}
}
测试:
@Autowired
JdbcTemplate jdbcTemplate;
@ResponseBody
@GetMapping("/sql")
public String druidTest(){
String sql = "select count(*) from tbl_dept";
Long aLong = jdbcTemplate.queryForObject(sql, Long.class);
return aLong.toString();
}
(3)StatFilter
用于统计监控信息;如SQL监控、URI监控
/**
* WebStatFilter 用于采集web-jdbc关联监控的信息
* @return
*/
@Bean
public FilterRegistrationBean webStatFilter(){
WebStatFilter webStatFilter = new WebStatFilter();
FilterRegistrationBean<WebStatFilter> webStatFilterFilterRegistrationBean = new FilterRegistrationBean<WebStatFilter>(webStatFilter);
webStatFilterFilterRegistrationBean.setUrlPatterns(Arrays.asList("/*"));
webStatFilterFilterRegistrationBean.addInitParameter("exclusion","*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");
return webStatFilterFilterRegistrationBean;
}
这些setXxx的配置也可以在配置文件中进行配置。
3. starter
(1)引入druid-starter
<!--druid-->
<!--<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.17</version>
</dependency>-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.17</version>
</dependency>
(2)分析自动配置
- 扩展配置项 spring.datasource.druid
- DruidSpringAopConfiguration.class:监控SpringBean的。配置项:spring.datasource.druid.aop-patterns
- DruidStatViewServletConfiguration.class:监控页的配置,spring.datasource.druid.stat-view-servlet,默认开启
- DruidWebStatFilterConfiguration.class:web监控配置。spring.datasource.druid.web-stat-filter,默认开启
- DruidFilterConfiguration.class}) 所有Druid自己filter的配置
private static final String FILTER_STAT_PREFIX = "spring.datasource.druid.filter.stat";
private static final String FILTER_CONFIG_PREFIX = "spring.datasource.druid.filter.config";
private static final String FILTER_ENCODING_PREFIX = "spring.datasource.druid.filter.encoding";
private static final String FILTER_SLF4J_PREFIX = "spring.datasource.druid.filter.slf4j";
private static final String FILTER_LOG4J_PREFIX = "spring.datasource.druid.filter.log4j";
private static final String FILTER_LOG4J2_PREFIX = "spring.datasource.druid.filter.log4j2";
private static final String FILTER_COMMONS_LOG_PREFIX = "spring.datasource.druid.filter.commons-log";
private static final String FILTER_WALL_PREFIX = "spring.datasource.druid.filter.wall";
private static final String FILTER_WALL_CONFIG_PREFIX = "spring.datasource.druid.filter.wall.config";
(3)配置示例
spring:
datasource:
url: jdbc:mysql://localhost:3306/ssm_crud?useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
druid:
aop-patterns: com.ice.admin.* #监控SpringBean
filters: stat,wall # 底层开启功能,stat(sql监控),wall(防火墙)
stat-view-servlet: # 配置监控页功能
enabled: true
login-username: admin
login-password: admin
resetEnable: false
web-stat-filter: # 监控web
enabled: true
urlPattern: /*
exclusions: '*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*'
filter:
stat: # 对上面filters里面的stat的详细配置
slow-sql-millis: 1000
logSlowSql: true
enabled: true
wall:
enabled: true
config:
drop-table-allow: false #删除表操作不允许
SpringBoot配置示例:
6.1.3 整合Mybatis操作
https://github.com/mybatishttps://github.com/mybatis
SpringBoot官方的Starter:spring-boot-starter-*
第三方的Starter: *-spring-boot-starter
引入依赖:
<!--mybatis-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.2</version>
</dependency>
1. 配置模式
- 全局配置文件
- SqlSessionFactory: 自动配置好了
- SqlSession:自动配置了 SqlSessionTemplate 组合了SqlSession
- @Import(AutoConfiguredMapperScannerRegistrar.class);
- Mapper: 只要写的操作MyBatis的接口标注了 @Mapper 就会被自动扫描进来
@EnableConfigurationProperties(MybatisProperties.class) : MyBatis配置项绑定类。
@AutoConfigureAfter({ DataSourceAutoConfiguration.class, MybatisLanguageDriverAutoConfiguration.class })
public class MybatisAutoConfiguration{}
@ConfigurationProperties(prefix = "mybatis")
public class MybatisProperties
//可以修改配置文件中 mybatis 开始的所有
application.yaml:
mybatis:
config-location: classpath:mybatis/mybatis-config.xml #全局配置文件位置
mapper-locations: classpath:mybatis/mapper/*.xml #sql映射文件位置
mybatis-config.xml:(都已经配置好了)
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
</configuration>
EmployeeMapper.xml:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ice.admin.mapper.EmployeeMapper">
<select id="getEmp" resultType="com.ice.admin.bean.Employee">
select * from tbl_emp where id=#{id}
</select>
</mapper>
测试:分别新建service和mapper:
@Mapper
public interface EmployeeMapper {
public Employee getEmp(Integer id);
}
//====================================================
@Service
public class EmployeeService {
@Autowired
EmployeeMapper employeeMapper;//红色下划线提示不用管
public Employee getEmpById(Integer emp_id){
Employee emp = employeeMapper.getEmp(emp_id);
return emp;
}
}
//====================================================
@Autowired
EmployeeService employeeService;
@ResponseBody
@GetMapping("/emp")
public Employee getById(@RequestParam("emp_id") Integer emp_id){
return employeeService.getEmpById(emp_id);
}
启动报错:Property 'configuration' and 'configLocation' can not specified with together
原因:想开启驼峰命名,发现这俩属性不能写一起。指定了配置文件就要把相关配置写在配置文件里!可以不写全局配置文件,直接在yaml文件里的configuration配置项后配置,方便!
测试成功!
使用步骤:
- 导入mybatis官方starter
- 编写mapper接口。标注@Mapper注解
- 编写sql映射文件并绑定mapper接口
- 在application.yaml中指定Mapper配置文件的位置,以及指定全局配置文件的信息 (建议:不指定全局配置文件,直接配置在mybatis.configuration)
2. 注解模式和混合模式
注解就是只使用注解,混合就是配置和注解都用。下面的例子就是混合模式。
Spring Initializr中可以直接整合mybatis。。。白学。
注解方式我觉得不如mapper.xml好。
@Mapper
public interface EmployeeMapper {
public Employee getEmp(Integer id);
@Select("select * from tbl_emp where emp_id=#{id}")
public Employee getById(Integer id);
}
终极使用步骤:
- 引入mybatis-starter
- 配置application.yaml中,指定mapper-location位置即可
- 编写Mapper接口并标注@Mapper注解
- 简单方法直接注解方式
- 复杂方法编写mapper.xml进行绑定映射
- @MapperScan("com.atguigu.admin.mapper") 【直接标注在主方法上】简化,其他的接口就可以不用标注@Mapper注解
6.1.4 整合Mybatis-Plus完成CRUD
mybatis-plus只能用于单表查询。。。
1. 什么是Mybatis-Plus
MyBatis-Plus:GitHub
MyBatis-Plus (baomidou.com):官网
Mybatis-Plus(简称 MP)是一个mybatis的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。
建议:安装 MybatisX 插件,你就会拥有一只愤怒的小鸟。
2. 整合Mybatis-Plus
<!--mybatis-plus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.1</version>
</dependency>
自动配置:
- MybatisPlusAutoConfiguration 配置类,MybatisPlusProperties 配置项绑定。前缀mybatis-plus:xxx 就是对mybatis-plus的定制
- SqlSessionFactory 自动配置好。底层是容器中默认的数据源
- mapperLocations 自动配置好的。默认:classpath*:/mapper/**/*.xml,任意包的类路径下的所有mapper文件夹下任意路径下的所有xml都是sql映射文件。 建议以后sql映射文件,放在 mapper下
- 容器中也自动配置好了 SqlSessionTemplate
- @Mapper 标注的接口也会被自动扫描,建议直接 @MapperScan("com.ice.admin.mapper") 批量扫描就行
优点: 只需要自己写的Mapper继承 BaseMapper 就可以拥有crud能力
public interface UserMapper extends BaseMapper<User> {
//指定为User表
}
如果数据库中表名变了(和实体类不对应)可以在实体类上加注解:
表示User和数据库表tbl_user对应。
@Autowired
UserMapper userMapper;
@Test
void testUserMapper(){
User user = userMapper.selectById(1);
System.out.println("user :"+user);
}
3. CRUD
public interface UserMapper extends BaseMapper<User> {
}
//======================================================
//IService顶级service
public interface UserService extends IService<User> {
}
//======================================================
/*
* ServiceImpl<UserMapper, User>
ServiceImpl是实现了IService接口里的所有方法
* 第一个参数:操作哪张表就用哪个mapper
* 第二个参数:返回的数据类型
* */
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
}
//=============================================================
//mybatis-plus分页插件(写这样一个配置类,就可以直接在controller中完成分页功能)
@Configuration
public class MyBatisConfig {
@Bean
public MybatisPlusInterceptor paginationInterceptor(){
MybatisPlusInterceptor plusInterceptor = new MybatisPlusInterceptor();
//分页拦截器
PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor();
paginationInnerInterceptor.setOverflow(true);//最大页后跳回首页
paginationInnerInterceptor.setMaxLimit(100L);//每页限制最多数据,-1表示无限制
plusInterceptor.addInnerInterceptor(paginationInnerInterceptor);
return plusInterceptor;
}
}
@Autowired
UserService userService;
@GetMapping("/dynamic_table")
public String dynamic_table(@RequestParam(value = "pn",defaultValue = "1")Integer pn,Model model){
//从数据库ssm_crud中拿出user表中的数据
List<User> userList = userService.list();
//model.addAttribute("users",userList);
//分页
Page<User> userPage = new Page<User>(pn,2);
//没有查询条件,写null
//得到分页查询结果
Page<User> page = userService.page(userPage, null);
model.addAttribute("users",page);
return "table/dynamic_table";
}
@GetMapping("/responsive_table")
public String responsive_table(){
return "table/responsive_table";
}
@GetMapping("/editable_table")
public String editable_table(){
return "table/editable_table";
}
@GetMapping("/user/delete/{id}")
public String deleteUser(@PathVariable("id")Long id, @RequestParam("pn") Integer pn, RedirectAttributes ra ){
userService.removeById(id);
ra.addAttribute("pn",pn);//把pn参数传给重定向url
return "redirect:/dynamic_table";
}
6.2 NoSQL
Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。 它支持多种类型的数据结构,如 字符串(strings), 散列(hashes), 列表(lists), 集合(sets), 有序集合(sorted sets) 与范围查询, bitmaps, hyperloglogs 和 地理空间(geospatial) 索引半径查询。 Redis 内置了 复制(replication),LUA脚本(Lua scripting), LRU驱动事件(LRU eviction),事务(transactions) 和不同级别的 磁盘持久化(persistence), 并通过 Redis哨兵(Sentinel)和自动 分区(Cluster)提供高可用性(high availability)。
1. Redis自动配置(没学,跳过)
2. RedisTemplate与Lettuce
3. 切换至jedis
六、单元测试
Spring Boot 2.2.0 版本开始引入 JUnit 5 作为单元测试默认库
6.1 JUnit5 的变化
作为最新版本的JUnit框架,JUnit5与之前版本的Junit框架有很大的不同,由三个不同子项目的几个不同模块组成。
JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage
- JUnit Platform: Junit Platform是在JVM上启动测试框架的基础,不仅支持Junit自制的测试引擎,其他测试引擎也都可以接入。
- JUnit Jupiter: JUnit Jupiter提供了JUnit5的新的编程模型,是JUnit5新特性的核心。内部包含了一个测试引擎,用于在Junit Platform上运行。
- JUnit Vintage: 由于JUint已经发展多年,为了照顾老的项目,JUnit Vintage提供了兼容JUnit4.x,Junit3.x的测试引擎。
SpringBoot 2.4 以上版本移除了默认对 Vintage 的依赖,如果需要兼容junit4需要自行引入 。
如果需要继续兼容junit4需要自行引入vintage 。
<dependency>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-core</artifactId>
</exclusion>
</exclusions>
</dependency>
现在的使用方法:
<!--test-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
可以看到Junit5:
@SpringBootTest//表示测试类
class SpringbootWebAdminApplicationTests {
@Test
void contextLoads() {
}
}
SpringBoot整合Junit以后:
- 编写测试方法:@Test标注(注意需要使用junit5版本的注解)
- Junit类具有Spring的功能,比如 @Autowired、 @Transactional 标注测试方法,测试完成后自动回滚
6.2 JUnit5 的常用注解
JUnit 5 User Guidehttps://junit.org/junit5/docs/current/user-guide/#writing-tests-annotations
- @Test :表示方法是测试方法。但是与JUnit4的@Test不同,他的职责非常单一不能声明任何属性,拓展的测试将会由Jupiter提供额外测试
- @ParameterizedTest :表示方法是参数化测试,下方会有详细介绍
- @RepeatedTest :表示方法可重复执行,下方会有详细介绍
- @DisplayName :为测试类或者测试方法设置展示名称
- @BeforeEach :表示在每个单元测试之前执行
- @AfterEach :表示在每个单元测试之后执行
- @BeforeAll :表示在所有单元测试之前执行(标注的方法必须是static)
- @AfterAll :表示在所有单元测试之后执行(标注的方法必须是static)
- @Tag :表示单元测试类别,类似于JUnit4中的@Categories
- @Disabled :表示测试类或测试方法不执行,类似于JUnit4中的@Ignore
- @Timeout :表示测试方法运行如果超过了指定时间将会返回错误
- @ExtendWith :为测试类或测试方法提供扩展类引用
6.3 断言(assertions)
懒得搬运了……
07、单元测试 · 语雀 (yuque.com)https://www.yuque.com/atguigu/springboot/ksndgx#yH8Rk
七、指标监控
web监控health(健康检查):有一个组件有问题就是DOWN,都没问题才是UP。
八、原理解析
8.1 springboot的启动原理
8.1.1 第一步:创建SpringApplication
- 保存一些信息
- 判定当前应用的类型,使用ClassUtils(工具类),当前为Servlet
- bootstrappers(现在是BootstrapRegistryInitializer):初始启动引导器(List<Bootstrapper>):去spring.factories文件中找 org.springframework.boot.Bootstrapper
- 找 ApplicationContextInitializer:去spring.factories找 ApplicationContextInitializer(List<ApplicationContextInitializer<?>> initializers)
- 找 ApplicationListener :应用监听器。去spring.factories找 ApplicationListener (List<ApplicationListener<?>> listeners)
8.1.2 第二步:启动SpringApplication
- 记录应用的启动时间(现在不用stopwatch了)
- 创建引导上下文(Context环境)createBootstrapContext()
- 获取到所有之前的 bootstrappers(BootstrapRegistryInitializer) 挨个执行 intitialize() 来完成对引导启动器上下文环境设置
@FunctionalInterface
public interface BootstrapRegistryInitializer {
void initialize(BootstrapRegistry registry);
}
- 当前应用进入headless模式。java.awt.headless
- 获取所有 RunListener(运行监听器)【为了方便所有Listener进行事件感知】
- getSpringFactoriesInstances 去spring.factories找 SpringApplicationRunListener
- 遍历 SpringApplicationRunListener 调用 starting 方法
- 相当于通知所有系统正在启动过程感兴趣的人,项目正在 starting
- 保存命令行参数,ApplicationArguments
- 准备环境 prepareEnvironment()
- 返回或者创建基础环境信息对象。StandardServletEnvironment
- 配置环境信息对象。读取所有的配置源的配置属性值。
- 绑定环境信息
- 监听器调用 listener.environmentPrepared();通知所有的监听器当前环境准备完成
- 创建IOC容器(createApplicationContext())
- 准备ApplicationContext IOC容器的基本信息 prepareContext()
- 刷新IOC容器。refreshContext
- 容器刷新完成后工作?afterRefresh
- 所有监听 器 调用 listeners.started(context); 通知所有的监听器 started
- 调用所有runners,callRunners()
- 如果以上有异常,调用Listener 的 failed
- 调用所有监听器的 running 方法 listeners.running(context); 通知所有的监听器 running
- running如果有问题。继续通知 failed 。调用所有 Listener 的 failed;通知所有的监听器 failed