一、时代背景
1、微服务时代
-
微服务是一种架构风格
-
一个应用拆分为一组小型服务
-
每个服务运行在自己的进程内,也就是可独立部署和升级
-
服务之间使用轻量级HTTP交互(轻量级HHTP主要指:是指REST API))
-
服务围绕业务功能拆分
-
可以由全自动部署机制独立部署
-
去中心化,服务自治。服务可以使用不同的语言、不同的存储技术
2、分布式
-
分布式的困难:
-
远程调用 :一般使用http进行服务交互
-
服务发现 :就是看哪些服务是可用的
-
负载均衡 :让多台服务器动起来
-
服务容错 :各种错误情况下的处理方式
-
配置管理 : 配置中心,修改配置让服务们自己同步
-
服务监控 : 多个服务以及云平台的资源消耗和健康状况
-
链路追踪 :一个复杂的业务流程可能需要连续调用多个微服务,我们需要记录一个完整业务逻辑涉及的每一个微服务的运行状态,再通过可视化链路图展现,帮助软件工程师在系统出错时分析解决问题,常见的解决方案有Zipkin,SkyWalking。
-
日志管理 : 微服务架构默认将应用日志分散保存在每一个微服务节点上,当系统进行用户行为分析、数据统计时必须收集所有节点日志数据,非常不方便。这时候我们需要一个独立的日志平台,收集所有节点的日志数据并可方便对其进行汇总分析,然后进行可视化展示,常见的解决方案有:
ELK(Elasticsearch+Logstash+Kibana),EFK(Elasticsearch+Fluentd+Kibana)。
-
任务调度
-
…
-
-
分布式的解决
SpringBoot + SpringCloud
3、云原生
-
原生应用如何上云。 Cloud Native
-
上云的困难:
-
服务自愈:其中一个服务出现错误怎么复原
-
弹性伸缩:根据不同的性能需求分配更多的服务器
-
服务隔离:服务之间不相互影响
-
自动化部署:自动化部署
-
灰度发布:同样的服务有多台服务器运行,先把服务部署在其中一两台上看运行效果,没有问题了再慢慢全部升级
-
流量治理:控制流量
-
…
-
二、springboot
- springboot官网地址:https://spring.io/projects/spring-boot#overview
- 查看版本新特性地址:https://github.com/spring-projects/spring-boot/wiki#release-notes
1、SpringBoot2入门
1.系统要求
- Java 8 & 兼容java14 .
- Maven 3.3+
修改maven 的配置文件 settings.xml
<mirrors>
<!-- mirror
| Specifies a repository mirror site to use instead of a given repository. The repository that
| this mirror serves has an ID that matches the mirrorOf element of this mirror. IDs are used
| for inheritance and direct lookup purposes, and must be unique across the set of mirrors.
|
<mirror>
<id>mirrorId</id>
<mirrorOf>repositoryId</mirrorOf>
<name>Human Readable Name for this Mirror.</name>
<url>http://my.repository.com/repo/path</url>
</mirror>
-->
<!--只需在mirrors标签下加入以下内容,加入后导入下载jar速度更快-->
<mirror>
<id>alimaven</id>
<name>aliyun maven</name>
<url>http://maven.aliyun.com/nexus/content/groups/public/</url>
<mirrorOf>central</mirrorOf>
</mirror>
</mirrors>
<profiles>
<!--只需在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>
</profiles>
2、HelloWorld
需求:浏览发送/hello请求,响应 Hello,Spring Boot 2
1、创建maven工程
2、引入依赖
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.3</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<dependencies>
<dependency>
<!--springboot web场景的依赖-->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<!--springboot测试依赖-->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
3、创建主程序
/**
* 主程序类
* @SpringBootApplication:标记这是一个SpringBoot应用
*/
@SpringBootApplication
public class Springboot_1Application {
public static void main(String[] args) {
SpringApplication.run(Springboot_1Application.class, args);
}
}
4、编写业务代码
@RestController //标记为controller 且该类返回为 json
public class HelloWordController {
@RequestMapping("/hello")
public String handle01(){
return "Hello, Spring Boot 2!";
}
}
5、运行&测试
- 运行
MainApplication
类 - 浏览器输入
http://localhost:8080/hello
,将会输出Hello, Spring Boot 2!
。
6、springboot配置
完成后,可以对springboot项目进行配置 在maven工程的resource文件夹中创建application.properties文件。
所有配置的地址:https://docs.spring.io/spring-boot/docs/2.3.7.RELEASE/reference/html/appendix-application-properties.html#common-application-properties-server
# 设置端口号 访问需要 http://localhost:8888/hello
server.port=8888
7、简化部署(把项目打成jar包,直接在目标服务器执行即可)
在IDEA的Maven插件上点击运行 clean 、package,把helloworld工程项目的打包成jar包,
打包好的jar包被生成在helloworld工程项目的target文件夹内。
用cmd运行java -jar boot-01-helloworld-1.0-SNAPSHOT.jar,既可以运行helloworld工程项目。
将jar包直接在目标服务器执行即可。
<!-- 导入依赖插件 -->
<build>
<plugins>
<!--2.3.4.RELEASE 版本的springboot可以不用导 2.5.3 需要导入-->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>3.1.0</version>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
2、了解自动配置原理
1、SpringBoot特点
1.1、依赖管理
1、父项目做依赖管理
springboot 依赖的父项目 父项目一般就是来做依赖管理的
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.3</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
上面的父项目依赖于下面的父项目 它几乎声明了所有开发中常用的依赖的版本号,自动版本仲裁机制
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.5.3</version>
</parent>
2、开发导入starter场景启动器
-
1、见到很多 spring-boot-starter-* : *就某种场景 (jdbc、tomcat、web、json、thymeleaf等)
-
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.5.3</version> </dependency>
3、无需关注版本号,自动版本仲裁
1、引入依赖默认都可以不写版本
2、引入非版本仲裁的jar,要写版本号。(可以在 spring-boot-dependencies.xml文件搜索版本号,如果没有则需要自己配置,如果有则无需自己配置)
4、可以修改默认版本号
1、查看spring-boot-dependencies里面规定当前依赖的版本 用的 key。
2、在当前项目里面重写配置
//在自己从的项目xml中配置一个propertys 版本就近依赖原则 就近原则,在pom中修改之后,先按照pom中的版本号,否则遵循父类中的版本。
<properties>
<mysql.version>5.1.43</mysql.version>
</properties>
2.1自动配置
1、自动配好Tomcat
- 引入Tomcat依赖。 想要自动配置好Tomcat就需要引入Tomcat依赖,但是我们在依赖管理引入web场景的时候,就已经引入了Tomcat场景。
- 配置Tomcat
spring-boot-dependencies 自动引入了Tomact依赖
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<version>2.5.3</version>
</dependency>
2、自动配好SpringMVC
-
引入SpringMVC全套组件
-
自动配好SpringMVC常用组件(功能)
-
如下:
前端控制器DispatcherServlet:拦截所有的前端的请求;
字符编码characterEncodingFilter:解决返回中文字符串乱码问题;
视图解析器viewResolver:对返回的视图进行渲染呈现;
文件上传解析器multipatResolver:文件上传;
3、自动配好Web常见功能,如:字符编码问题
- SpringBoot帮我们配置好了所有web开发的常见场景
@SpringBootApplication
public class Springboot_1Application {
public static void main(String[] args) {
//返回IOC容器
ConfigurableApplicationContext run =
SpringApplication.run(Springboot_1Application.class, args);
//获取容器中的所有组件
String[] names = run.getBeanDefinitionNames();
for (String name : names) {
System.out.println(name);
}
}
}
4、默认的包结构
-
主程序所在包及其下面的所有子包里面的组件都会被默认扫描进来 这点特别重要,一定要将主程序放对位置,不然一定扫描不到 如主程序在 com.pj.boot包下,该包下或子包下的所有类标注了组件注解的都会自动扫描进来
-
无需以前的包扫描配置
-
想要改变扫描路径
@SpringBootApplication(scanBasePackages=“com.lun”)
@ComponentScan 指定扫描路径 -
@SpringBootApplication等同于
@SpringBootConfiguration(声明为一个配置类,会被扫描到容器中)
@EnableAutoConfiguration(自动载入应用程序所需的所有Bean) @EnableConfigurationProperties(MultipartProperties.class) 就会将 MultipartProperties 就会载入到当前类
@ComponentScan(“com.lun”)(扫描路径)
5、各种配置拥有默认值
-
-
默认配置最终都是映射到某个类上,如:MultipartProperties
-
配置文件的值最终会绑定某个类上,这个类会在容器中创建对象
-
6、按需加载所有自动配置项
-
-
非常多的 starter 如:starter-web、starter-batch
-
引入了哪些场景这个场景的自动配置才会开启
引入批处理场景 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-batch</artifactId> <version>2.4.4</version> </dependency>
-
-
-
SpringBoot所有的自动配置功能都在 spring-boot-autoconfigure 包里面(SpringBoot所有的自动配置都依赖
@EnableAutoConfiguration)
-
3、容器功能 (com.pj.boot.container 代码全在改包下)
2.1、组件添加
1、@Configuration
-
基本使用
Full模式与Lite模式: -
Full模式和Lite模式是针对spring配置而言的,和xml配置无关。
-
何时为Lite模式:
- 1.类上有@Component注解
- 2.类上有@ComponentScan注解
- 3.类上有@Import注解
- 4.类上有@ImportResource注解
- 5.类上没有任何注解,但是类中存在@Bean方法
- 6.类上有@Configuration(proxyBeanMethods = false)注解
Lite总结:运行时不用生成CGLIB子类,提高运行性能,降低启动时间,可以作为普通类使用。但是不能声明@Bean之间的依赖
- 何时为Full模式:
标注有@Configuration或者@Configuration(proxyBeanMethods = true)的类被称为Full模式的配置类。
Full模式总结:单例模式能有效避免Lite模式下的错误。性能没有Lite模式
-
代码
- TestMyConfigConfiguration启动类
//@SpringBootApplication 这个注解相当于下面三个注解 @SpringBootApplication(exclude = {RedisAutoConfiguration.class}) //排出莫项自动配置
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(basePackages = "com.pj.boot")
public class Springboot_1Application {
public static void main(String[] args) {
//返回IOC容器
ConfigurableApplicationContext run =
SpringApplication.run(Springboot_1Application.class, args);
//获取容器中的所有组件
String[] names = run.getBeanDefinitionNames();
for (String name : names) {
System.out.println(name);
}
System.out.println("======container======");
//从容器中获取组件
User user01 = run.getBean("user01", User.class);
User user02 = run.getBean("user01", User.class);
//如果@Configuration(proxyBeanMethod = true)代理对象调用方法,springboot总会检查这个组件是否在容器中有 保持组件的单实例
//在 full下 在lite下 都为 true 因为容器中的对象都是同一个对象
System.out.println("container:user01 == user02;"+ (user01 == user02));
MyConfig myconfig = run.getBean(MyConfig.class);
/*
full : myconfigcom.pj.boot.container.config.MyConfig$$EnhancerBySpringCGLIB$$ead26534@5308decf 代理类
lite : myconfigcom.pj.boot.container.config.MyConfig@4e2eb16 不是代理对象 会直接调用方法 重新new一个对象
*/
System.out.println("myconfig"+myconfig);
User user011 = myconfig.user01();
User user012 = myconfig.user01();
//在 full下 为true ,在lite下 为false 配置类对象调用方法 才会产生不同效果
System.out.println("container:user011 == user012:"+ (user011 == user012));
Pet pet = run.getBean("pet", Pet.class);
Pet pet2 = run.getBean("pet", Pet.class);
//在 full下 在lite下 都为 true 因为容器中的对象都是同一个对象 System.out.println("container:pet == pet2:"+ (pet == pet2));
//在 full下 为true ,在lite下 为false 当类之间有依赖关系使用full模式保证对象的单实例(会使用代理类会检查是否有该对象) 如果没有依赖关系,使用lite模式 保证springboot启动更快
System.out.println("container:user01.getPet()==pet:"+(user01.getPet()==pet));
}
}
- MyConfigConfiguration 配置类
/*
@Configuration : 标记为一个配置类
自定义配置配置类
1、配置类里面使用@bean标注在方法上给容器注册组件,默认是单实例的(标记了@bean的方法,在容器中只会产生一个对象 无论 proxyBeanMethods 为true还是false)
2、配置类本身也是组件 会被扫描进入容器中
3、proxyBeanMethods:代理bean的方法 容器对象调用方法才不同 即MyConfig类调用方法就不同
full:(proxyBeanMethods = true) 保证每个@bena方法被调用多少次返回的组件都是单实例的
lite:(proxyBeanMethods = false) 每个@bean方法被调用多少次返回的组件都是新创建的
*/
@Configuration(proxyBeanMethods = true) //告诉springboot这是一个配置类==配置文件
public class MyConfig {
/**
* full (proxyBeanMethods = true): 外部无论对配置类中的这个组件注册方法调用对少次获取的都是之前注册容器中的单实例对象
* lite (proxyBeanMethods = false): 外部获取一次就是一个新对象.
* @return
*/
@Bean //给容器中添加组件。以方法名作为组件的id。返回类型就是组件类型,返回值,就是组件在容器中的实例
public User user01(){
User zs = new User(1 ,"张三",12);
//容器user依赖组件 pet
zs.setPet(pet());
return zs;
}
@Bean(value = "pet") //默认是以方法名作为组件的id , 也可以自定义组件id value ="组件名"
public Pet pet(){
Pet pet = new Pet(1,"tom");
return pet;
}
}
-
示例
最佳实战配置
1、配置类组件之间无依赖关系用Lite模式加速容器启动过程,减少判断(当某个pojo依赖了默认pojo,某个对象可能需要用到另一个对象,会检查容器中是否有已经注册了的实例)
2、配置类组件之间有依赖关系,方法会被调用得到之前单实例组件,用Full模式(Full每次都要检查,会慢Lite每次不检查,快)
2、@Bean、@Component、@Controller、@Service、@Repository
它们是Spring的基本标签,在Spring Boot中并未改变它们原来的功能。
3、@ComponentScan、@Import
-
@ComponentScan: @ComponentScan(basePackages = “com.pj.boot”) 扫描指定包下的所有组件
-
@Import的好处是可以引入外部类 给容器中导入一个组件
-
代码
-
MyConfigImport 配置类
/* @Import({User.class, DBHelper.class}) : 会将 User 和 DBHelper 加载到容器中 */ @Import({User.class, DBHelper.class}) @Configuration public class MyConfigImport { }
-
TestMyConfigConfiguration启动类(添加如下代码)
System.out.println("======TestMyConfigImport======"); DBHelper dbHelper = run.getBean(DBHelper.class); //TestMyConfigImport:dbHelper:ch.qos.logback.core.db.DBHelper@4a81af81 System.out.println("TestMyConfigImport:dbHelper:"+dbHelper);
-
导入的类
-
4、@Conditional
-
条件装配:满足Conditional指定的条件,则进行组件注入
-
放在配置类上表示,当容器中满足条件时,配置类中的组件才生效;放在配置方法上的时候,表示的意思是当满足条件的时候配置方法才生效;
-
代码及结果
-
MyConfigImport 测试 Conditional
/* @Import({User.class, DBHelper.class}) : 会将 User 和 DBHelper 加载到容器中 */ @Import({User.class, DBHelper.class}) @Configuration @ConditionalOnMissingBean(name = "pet") //容器中没有pet名字的bean MyConfigImport配置类的才能生效 在MyConfigConfiguration 中注入了pet组件id 所以当前类中不会有 DBHelper、User 如果将 MyConfigConfiguration注入的pet1组件id 就会有DBHelper、User public class MyConfigImport { }
-
MyConfigConfiguration 是否给容器中放入组件id为 pet
@Configuration(proxyBeanMethods = true) //告诉springboot这是一个配置类==配置文件 public class MyConfigConfiguration { @Bean(value = "pet") //给容器中注入pet // @Bean(value = "pet1") //给容器中注入pet1 public Pet pet(){ Pet pet = new Pet(1,"tom"); return pet; } }
-
主程序测试类
System.out.println("======TestConditional======"); DBHelper dbHelper = run.getBean(DBHelper.class); //获取DBHlper System.out.println("TestMyConfigImport:dbHelper:"+dbHelper);
-
容器中有组件id为pet的结果
-
容器中没有组件id为pet的结果
-
2.2、原生配置文件引入
1、含义:
指以.xml结尾的配置文件,通过@ImportResource导入后SpringBoot进行解析,完成对应的组件注册位置:在主配置类的上方
2、@ImportResource导入Spring配置文件
-
@ImportResource(“classpath:beans.xml”):导入spring的配置文件来进行生效,比如:公司使用bean.xml文件生成配置bean,然而你为了省事,想继续复用bean.xml,@ImportResource粉墨登场。
-
代码及结果
-
MyConfigImport 导入配置文件
@ImportResource("classpath:beans.xml") //会自动将类路径下的beans.xml配置文件里面的内容加载给容器 //@ImportResource("classpath:com/pj/boot/container/resource/beans.xml") public class MyConfigImport { }
-
TestMyConfigConfiguration 启动类测试
System.out.println("======TestImportResource======"); User haha = run.getBean("haha", User.class); System.out.println(haha); System.out.println(run.getBean("hehe", Pet.class));
-
bean.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" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="haha" class="com.pj.boot.container.pojo.User"> <property name="name" value="zhangsan"></property> <property name="age" value="18"></property> </bean> <bean id="hehe" class="com.pj.boot.container.pojo.Pet"> <property name="name" value="tomcat"></property> </bean> </beans>
-
结果
-
2.3、配置绑定
1、含义:
如何使用Java读取到properties文件中的内容,并且把它封装到JavaBean中,以供随时使用。场景例子:我们习惯将经常爱变化的东西写在.properties配置文件中,比如与数据库相关的信息(连接池、URL等)配置到配置文件中,为了方便我们会将配置文件中的内容解析到JavaBean中。这个过程使用java原生代码较为麻烦。
2、原生传统的获取配置文件的方式
/*
使用原生的方式获取 properties文件
*/
public class TestProperties {
public static void main(String[] args) throws IOException {
Properties properties = new Properties();
properties.load(new FileInputStream("a.properties"));
Enumeration<String > enumeration = (Enumeration<String>) properties.propertyNames();
while (enumeration.hasMoreElements()){
String key = enumeration.nextElement();
String value = properties.getProperty(key);
System.out.println(key +"="+value);
//封装到bean
}
}
}
3、Spring Boot一种配置配置绑定:第一种实现方式:@Component + @ConfigurationProperties
-
实体类 Car
/* 配置绑定测试pojo 只有在容器中的组件,才会拥有SpringBoot提供的强大功能 */ @Component //必须注入到容器中 不然下面注解会报错 @ConfigurationProperties(prefix = "mycar") //将配置文件中 以mycar开头的项 注入到改pojo中 开启Car配置绑定功能 public class Car { private int id; private String name; private int age; }
-
测试类
System.out.println("======Test自动配置ConfigurationProperties======"); /* ======Test自动配置ConfigurationProperties====== Test自动配置ConfigurationProperties:carcom.pj.boot.container.pojo.Car@4258c97a */ System.out.println("Test自动配置ConfigurationProperties:car"+run.getBean(Car.class));
-
application.properties 配置文件
#给 pojo car注入属性值 mycar.id=1 mycar.name=xiaohu mycar.age=12
4、Spring Boot一种配置配置绑定:第二种实现方式:@EnableConfigurationProperties + @ConfigurationProperties
-
实体类 Car
/* 配置绑定测试pojo 只有在容器中的组件,才会拥有SpringBoot提供的强大功能 */ @ConfigurationProperties(prefix = "mycar") //将配置文件中 以mycar开头的项 注入到改pojo中 开启Car配置绑定功能0 public class Car { private int id; private String name; private int age; }
-
配置类 MyConfigImport
@Configuration @EnableConfigurationProperties({Car.class}) // 把这个Car这个组件自动注册到容器中 必须标记为配置类或者启动类才能使用 因为这样才会被加载到器中 如果未加载到容器中 获取时会抛出异常 public class MyConfigImport { }
2.4、自动配置原理
1、引导加载自动配置类(SpringBootApplication)
-
@SpringBootApplication源码
@SpringBootConfiguration //标记为一个配置类 注解源码中使用了 @Configuration @ SpringBootConfiguration只是Spring标准@Configuration批注的替代方法。 两者之间的唯一区别是@SpringBootConfiguration允许自动找到配置。 @EnableAutoConfiguration @ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class), @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) }) //指定扫描那些,spring注解 public @interface SpringBootApplication {}
-
@SpringBootConfiguration源码
@Configuration //标记为一个配置类 @Indexed public @interface SpringBootConfiguration {}
-
@EnableAutoConfiguration源码
@AutoConfigurationPackage @Import(AutoConfigurationImportSelector.class) public @interface EnableAutoConfiguration {}
-
重点分析
@AutoConfigurationPackage
,
@Import(AutoConfigurationImportSelector.class)`。 -
@AutoConfigurationPackage 标签名直译为:自动配置包,指定了默认的包规则。自动包规则原理
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @Import(AutoConfigurationPackages.Registrar.class) //给容器中导入一个组件 使用@import将AutoConfigurationPackages包下的Registrar类作为组件导入到容器中,然后使用Registrar中的方法批量完成组件的注册。 public @interface AutoConfigurationPackage { String[] basePackages() default {}; Class<?>[] basePackageClasses() default {}; }
-
利用Registrar给容器中导入一系列组件
-
AutoConfigurationPackages类 重要源码
//该类将扫描启动类所在包下的所有组件 static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports { @Override /* metadate参数指的是注解源 new PackaImports(metadata)导入包中的组件 getPackageNames()获得包名 toArray封装到数组中。 最终注册进去 */ public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0])); } }
-
将指定的一个包下的所有组件导入进MainApplication所在包下。
-
-
@Import(AutoConfigurationImportSelector.class)`。
-
利用getAutoConfigurationEntry(annotationMetadata);给容器中批量导入一些组件 初始加载自动配置类
-
调用List configurations = getCandidateConfigurations(annotationMetadata, attributes)获取到所有需要导入到容器中的配置类 134个配置类自动装配
-
利用工厂加载 Map<String, List> loadSpringFactories(@Nullable ClassLoader classLoader);得到所有的组件
-
从META-INF/spring.factories位置来加载一个文件。
-
默认扫描我们当前系统里面所有META-INF/spring.factories位置的文件
-
spring-boot-autoconfigure-2.3.4.RELEASE.jar包里面也有META-INF/spring.factories
# 文件里面写死了spring-boot一启动就要给容器中加载的所有配置类 # 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,\ ...
-
-
总流程
-
-
2、按需开启自动配置项
虽然我们134(版本不同加载个数不同)个场景的所有自动配置启动的时候默认全部加载,但是xxxxAutoConfiguration
按照条件装配规则(@Conditional
),如AopAutoConfiguration
类:,最终会按需配置。
#Auto Configure 默认装载的所有类 131个 可能文件中也有其他的自动配置类
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\
org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\
org.springframework.boot.autoconfigure.context.LifecycleAutoConfiguration,\
org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration,\
org.springframework.boot.autoconfigure.couchbase.CouchbaseAutoConfiguration,\
org.springframework.boot.autoconfigure.dao.PersistenceExceptionTranslationAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraReactiveDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraReactiveRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseReactiveDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseReactiveRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ReactiveElasticsearchRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ReactiveElasticsearchRestClientAutoConfiguration,\
org.springframework.boot.autoconfigure.data.jdbc.JdbcRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.ldap.LdapRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoReactiveDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoReactiveRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.neo4j.Neo4jDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.neo4j.Neo4jReactiveDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.neo4j.Neo4jReactiveRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.neo4j.Neo4jRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.r2dbc.R2dbcDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.r2dbc.R2dbcRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisReactiveAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.rest.RepositoryRestMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.data.web.SpringDataWebAutoConfiguration,\
org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchRestClientAutoConfiguration,\
org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration,\
org.springframework.boot.autoconfigure.freemarker.FreeMarkerAutoConfiguration,\
org.springframework.boot.autoconfigure.groovy.template.GroovyTemplateAutoConfiguration,\
org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration,\
org.springframework.boot.autoconfigure.h2.H2ConsoleAutoConfiguration,\
org.springframework.boot.autoconfigure.hateoas.HypermediaAutoConfiguration,\
org.springframework.boot.autoconfigure.hazelcast.HazelcastAutoConfiguration,\
org.springframework.boot.autoconfigure.hazelcast.HazelcastJpaDependencyAutoConfiguration,\
org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration,\
org.springframework.boot.autoconfigure.http.codec.CodecsAutoConfiguration,\
org.springframework.boot.autoconfigure.influx.InfluxDbAutoConfiguration,\
org.springframework.boot.autoconfigure.info.ProjectInfoAutoConfiguration,\
org.springframework.boot.autoconfigure.integration.IntegrationAutoConfiguration,\
org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.JndiDataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.XADataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.JmsAutoConfiguration,\
org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.JndiConnectionFactoryAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.activemq.ActiveMQAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.artemis.ArtemisAutoConfiguration,\
org.springframework.boot.autoconfigure.jersey.JerseyAutoConfiguration,\
org.springframework.boot.autoconfigure.jooq.JooqAutoConfiguration,\
org.springframework.boot.autoconfigure.jsonb.JsonbAutoConfiguration,\
org.springframework.boot.autoconfigure.kafka.KafkaAutoConfiguration,\
org.springframework.boot.autoconfigure.availability.ApplicationAvailabilityAutoConfiguration,\
org.springframework.boot.autoconfigure.ldap.embedded.EmbeddedLdapAutoConfiguration,\
org.springframework.boot.autoconfigure.ldap.LdapAutoConfiguration,\
org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration,\
org.springframework.boot.autoconfigure.mail.MailSenderAutoConfiguration,\
org.springframework.boot.autoconfigure.mail.MailSenderValidatorAutoConfiguration,\
org.springframework.boot.autoconfigure.mongo.embedded.EmbeddedMongoAutoConfiguration,\
org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration,\
org.springframework.boot.autoconfigure.mongo.MongoReactiveAutoConfiguration,\
org.springframework.boot.autoconfigure.mustache.MustacheAutoConfiguration,\
org.springframework.boot.autoconfigure.neo4j.Neo4jAutoConfiguration,\
org.springframework.boot.autoconfigure.netty.NettyAutoConfiguration,\
org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration,\
org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration,\
org.springframework.boot.autoconfigure.r2dbc.R2dbcAutoConfiguration,\
org.springframework.boot.autoconfigure.r2dbc.R2dbcTransactionManagerAutoConfiguration,\
org.springframework.boot.autoconfigure.rsocket.RSocketMessagingAutoConfiguration,\
org.springframework.boot.autoconfigure.rsocket.RSocketRequesterAutoConfiguration,\
org.springframework.boot.autoconfigure.rsocket.RSocketServerAutoConfiguration,\
org.springframework.boot.autoconfigure.rsocket.RSocketStrategiesAutoConfiguration,\
org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration,\
org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration,\
org.springframework.boot.autoconfigure.security.servlet.SecurityFilterAutoConfiguration,\
org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration,\
org.springframework.boot.autoconfigure.security.reactive.ReactiveUserDetailsServiceAutoConfiguration,\
org.springframework.boot.autoconfigure.security.rsocket.RSocketSecurityAutoConfiguration,\
org.springframework.boot.autoconfigure.security.saml2.Saml2RelyingPartyAutoConfiguration,\
org.springframework.boot.autoconfigure.sendgrid.SendGridAutoConfiguration,\
org.springframework.boot.autoconfigure.session.SessionAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.client.servlet.OAuth2ClientAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.client.reactive.ReactiveOAuth2ClientAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.resource.servlet.OAuth2ResourceServerAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.resource.reactive.ReactiveOAuth2ResourceServerAutoConfiguration,\
org.springframework.boot.autoconfigure.solr.SolrAutoConfiguration,\
org.springframework.boot.autoconfigure.sql.init.SqlInitializationAutoConfiguration,\
org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration,\
org.springframework.boot.autoconfigure.task.TaskSchedulingAutoConfiguration,\
org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration,\
org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration,\
org.springframework.boot.autoconfigure.transaction.jta.JtaAutoConfiguration,\
org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration,\
org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration,\
org.springframework.boot.autoconfigure.web.embedded.EmbeddedWebServerFactoryCustomizerAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.HttpHandlerAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.ReactiveWebServerFactoryAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.error.ErrorWebFluxAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.function.client.ClientHttpConnectorAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.HttpEncodingAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.MultipartAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.websocket.reactive.WebSocketReactiveAutoConfiguration,\
org.springframework.boot.autoconfigure.websocket.servlet.WebSocketServletAutoConfiguration,\
org.springframework.boot.autoconfigure.websocket.servlet.WebSocketMessagingAutoConfiguration,\
org.springframework.boot.autoconfigure.webservices.WebServicesAutoConfiguration,\
org.springframework.boot.autoconfigure.webservices.client.WebServiceTemplateAutoConfiguration
@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty( //有下面的条件配置了才会生效
prefix = "spring.aop", //绑定配置文件中以 spring.aop开头的
name = "auto",
havingValue = "true",
matchIfMissing = true //默认是开启的
)
public class AopAutoConfiguration {}
如CacheAutoConfiguration
类:条件未成立所以不会进行加载
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(CacheManager.class) //有这个CacheManager类在容器中 下面配置才生效 CacheManager spirng核心包下的类默认加载了 条件成立
@ConditionalOnBean(CacheAspectSupport.class) //测试类中可以测试当前类是否存在 结果不存在 所以该类不会被加载
@ConditionalOnMissingBean(value = CacheManager.class, name = "cacheResolver") //容器中没存在CacheManager类 下面才生效
@EnableConfigurationProperties(CacheProperties.class)
@AutoConfigureAfter({ CouchbaseDataAutoConfiguration.class, HazelcastAutoConfiguration.class,
HibernateJpaAutoConfiguration.class, RedisAutoConfiguration.class })
@Import({ CacheConfigurationImportSelector.class, CacheManagerEntityManagerFactoryDependsOnPostProcessor.class })
public class CacheAutoConfiguration {}
测试 CacheAspectSupport在容器中是否存在 :不存在
/*
判断 CacheAspectSupport 是否存在 返回为0
*/
String[] beanNamesForType = run.getBeanNamesForType(CacheAspectSupport.class);
System.out.println("======"+beanNamesForType.length); //0
如DispatcherServletConfiguration类:条件成立所以会进行加载
@Configuration(proxyBeanMethods = false)
@Conditional(DefaultDispatcherServletCondition.class)
@ConditionalOnClass(ServletRegistration.class)//判断ServletRegistration.class存在 是tomcat核心包下的 成立
@EnableConfigurationProperties(WebMvcProperties.class) //开启配置绑定 绑定WebMvcProperties.class存在
protected static class DispatcherServletConfiguration {}
测试 DispatcherServletConfiguration在容器中是否存在 :存在
/*
从容器中获取组件 WebMvcProperties 判断是否存在 返回1 存在
*/
String[] beanNamesForType1 = run.getBeanNamesForType(WebMvcProperties.class);
System.out.println("======"+beanNamesForType1.length); //1
3、修改自动默认配置
-
以
DispatcherServletAutoConfiguration
的内部类DispatcherServletConfiguration
为例子:主要体现在@ConditionalOnMissingBean,如果没有存在这个bean,那么springboot就会自动帮你配置,存在就是用我们自己的@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE) //优先级 @Configuration(proxyBeanMethods = false) //标记为配置类 @ConditionalOnWebApplication(type = Type.SERVLET) //判断当前项目是原生的 SERVLET severlet 是就加会加载这个类 不是会加载 @ConditionalOnClass(DispatcherServlet.class) //判断容器中是否有 DispatcherServlet 导入了springmvc肯定有 生效 @AutoConfigureAfter(ServletWebServerFactoryAutoConfiguration.class) //在配置完ServletWebServerFactoryAutoConfiguration这个之后才来配置这个类 public class DispatcherServletAutoConfiguration { //总类满足上述条件下面才能生效 现在是满足的 @Configuration(proxyBeanMethods = false) @Conditional(DefaultDispatcherServletCondition.class) @ConditionalOnClass(ServletRegistration.class) //是有ServletRegistration这个组件 没有则不会配置 是Tomact核心包下的类 导入了Tomact核心包就会有 @EnableConfigurationProperties(WebMvcProperties.class) //开启配置绑定的WebMvcProperties 放入容器中 protected static class DispatcherServletConfiguration { //配置DispatchServlet 一些初始化工作 @Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME) public DispatcherServlet dispatcherServlet(WebMvcProperties webMvcProperties) { DispatcherServlet dispatcherServlet = new DispatcherServlet(); dispatcherServlet.setDispatchOptionsRequest(webMvcProperties.isDispatchOptionsRequest()); dispatcherServlet.setDispatchTraceRequest(webMvcProperties.isDispatchTraceRequest()); dispatcherServlet.setThrowExceptionIfNoHandlerFound(webMvcProperties.isThrowExceptionIfNoHandlerFound()); dispatcherServlet.setPublishEvents(webMvcProperties.isPublishRequestHandledEvents()); dispatcherServlet.setEnableLoggingRequestDetails(webMvcProperties.isLogRequestDetails()); return dispatcherServlet; } @Bean //注入到容器中 组件id默认为 multipartResolver @ConditionalOnBean(MultipartResolver.class) //容器中有这类型的组件 有才生效 @ConditionalOnMissingBean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME)//容器中没有这个名字的 multipartResolver 的组件 MULTIPART_RESOLVER_BEAN_NAME = multipartResolver 如果容器中有id为multipartResolver的组件就不会使用默认的自动配置 下面就不生效 有就使用自己配置 public MultipartResolver multipartResolver(MultipartResolver resolver) { //给@Bean标注的方法传入了对象参数,multipartResolver这个参数的值就会从容器中找。 //SpringMVC multipartResolver。防止有些用户配置的文件上传解析器不符合规范 即使没有这个名为multipartResolver组件 也会给你在容器中自动配置 return resolver;//给容器中加入了文件上传解析器; } } }
-
SpringBoot默认会在底层配好所有的组件,但是如果用户自己配置了以用户的优先。约定大于配置
//controller发请求测试 @Controller public class TestController { /** * 测试自动配置的 字符编码过滤器 */ @ResponseBody @GetMapping("/testCharacterEncodingFilter") public String testCharacterEncodingFilter(){ return "你好···springboot"; } } //配置类 @Configuration public class MyConfigTest { /* 自定义字符编码过滤器 */ @Bean // @ConditionalOnMissingBean //如果容器中没有就帮你配 public CharacterEncodingFilter characterEncodingFilter() { CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter(); filter.setEncoding("ISO-8859-1"); return filter; } }
4、自动配置总结:
- SpringBoot先加载所有的自动配置类 xxxxxAutoConfiguration 组件中经常变化的值是从application.properties中来的
- 每个自动配置类按照条件进行生效,默认都会绑定配置文件指定的值。(xxxxProperties里面读取,xxxProperties和配置文件进行了绑定)
- 生效的配置类就会给容器中装配很多组件
- 只要容器中有这些组件,相当于这些功能就有了
- 定制化配置
- 用户直接自己@Bean替换底层的组件
- 用户去看这个组件是获取的配置文件什么值就去修改。可以去@ConfigurationPropeties中的xxxProperties.class中的中找到相关的propeties.class,再在application中去配置
- 大致总流程:xxxxxAutoConfiguration —> 组件 —> xxxxProperties里面拿值 ----> application.properties (通过配置文件进行自定义配置的思路,当然也可以通过自定义Configuration类实现对应接口(如实现WebMvcConfigurer接口进行MVC定制化配置)进行配置,稍复杂。)
5、最佳实践-SpringBoot应用如何编写
1、引入场景依赖
https://docs.spring.io/spring-boot/docs/current/reference/html/using-spring-boot.html#using-boot-starter
2、查看自动配置了哪些(选做)判断场景自动配置那些生效那些没生效.
-
自己分析,引入场景对应的自动配置一般都生效了
-
配置文件中debug=true开启自动配置报告。Negative(不生效)Positive(生效)
#查看哪些自动配置类注入的生效或者不生效 debug=true
在application.properties配置文件中新增一行 debug=true,当应用程序运行时,可以在控制台看到哪些组件生效(Positive matches),那些组件不生效(Negative matches)
3、是否需要修改某些配置项
-
参照文档修改配置项
-
https://docs.spring.io/spring-boot/docs/current/reference/html/appendix-application-properties.html#common-application-properties
#修改springboot的默认图片 spring.banner.image.location=classpath:banner.txt
-
自己分析。xxxxProperties绑定了配置文件的哪些。通过查看XxxProperties.java类的属性,查看可以绑定application.properties配置文件中的哪些值
-
4、自定义加入或者替换组件,可以使用@bean添加新的组件或者替换原来默认的组件
@Bean、@Component…
5、自定义器 XXXXXCustomizer;
6、开发小技巧
1、Lombok
步骤:
-
导入依赖 idea 下载lombok插件
不需要引入版本号 springboot 自动仲裁了版本号 <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency>
-
使用
/*=======================简化JavaBean开发======================== @Data---帮助生产getset方法 @ToString---帮助生成ToString方法 @AllArgsConstructor---生成有参构造器 @NoArgsConstructor---生成无参构造方法 @EqualsAndHashCode---生成Equals、HashCode方法 */ @Data @NoArgsConstructor @AllArgsConstructor //@ToString //@EqualsAndHashCodepublic class User { private int id; private String name; private int age; private Pet pet; public User(int id, String name, int age) { this.id = id; this.name = name; this.age = age; }}
2、dev-tools 虚假的热更新工具(需重启项目)
导入依赖即可 在IDEA中,项目或者页面修改以后:Ctrl+F9 会自动重启项目。
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency></dependencies>
3、Spring Initailizr(项目初始化向导 快速开发springboot项目)
自动创建项目结构、自动编写好主配置类、自动依赖引入
4、配置文件
1.1、文件类型
1、properties
同以前的properties用法
2、yaml
-
简介:YAML 是 “YAML Ain’t Markup Language”(YAML 不是一种标记语言(标记语言:标签比xml, 语法更简洁,更轻量级))的递归缩写。在开发的这种语言时,YAML 的意思其实是:“Yet Another Markup Language”(仍是一种标记语言)。非常适合用来做以数据为中心的配置文件
-
基本语法
-
key: value;kv之间有空格
-
大小写敏感
-
使用缩进表示层级关系
-
缩进不允许使用tab,只允许空格
-
缩进的空格数不重要,只要相同层级的元素左对齐即可
-
‘#’ 表示注释
-
字符串无需加引号,如果要加,’'与""表示字符串内容 会被 转义/不转义
-
-
数据类型
-
字面量:单个的、不可再分的值。date、boolean、string、number、null kv之间有空格
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
-
-
案例
pojo类
/* 测试 yaml配置文件 */ @Data @Component @ConfigurationProperties("my.person") //绑定properties配置文件 以my.person开头的 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; }
启动类;
@SpringBootConfiguration@EnableAutoConfiguration@ComponentScan(basePackages = "com.pj.boot.yaml") public class TestMyYaml { public static void main(String[] args) { //返回IOC容器 ConfigurableApplicationContext run = SpringApplication.run(Springboot_1Application.class, args); }}
Controller类
@Controller public class TestYamlController { //自动注入person @Autowired Person person; /** * 测试yaml配置文件 * @return */ @ResponseBody @GetMapping("testYaml") public Person testYaml(){ //单引号输入:zhangsan \n aaa //双引号输出:zhangsan // aaa System.out.println(person.getUserName()); return person; } }
application.yaml
my: person: userName: "zhangsan \n aaa" #单引号输出 "userName": "zhangsan \\n aaa", 会将 \n 以字符串输出 #双引号输出 "userName": "zhangsan \n aaa", 会将 换行输出 双引号不会转义 单引号会转义 boss: true birth: 2019/12/9 age: 18 #private String[] interests; #interests:[篮球,游泳] 两种方式都可以 interests: - 篮球 - 游泳 #private List<String> animal; animal: [jerry,mario] #private Pet pet; pet: name: 阿毛 weight: 20.02 #private Map<String, Object> score; # score: # english: 80 # math: 90 score: { english: 80,math: 90} #private Set<Double> salarys; salarys: - 999 - 7788 #private Map<String, List<Pet>> allPets; allPets: sick: - {name: a,weight: 12} - name: 阿猫 weight: 13 - name: 啊虫 weight: 14 health: - {name: b,weight: 15} - {name: c,weight: 16}
1.2、自定义类绑定的配置提示
自定义的类和配置文件绑定一般没有提示。若要提示,添加如下依赖:配置对应的pom文件,且打包时排除
<!--自定义类配置提示-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<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>
5、WEB开发
1、SpringMVC自动配置概览
1.1、SpringMVC大多场景我们都无需自定义配置
- 内容协商视图解析器和BeanName视图解析器
- 静态资源(包括webjars)
- 自动注册
Converter,GenericConverter,Formatter
(转换器、格式化器) - 支持
HttpMessageConverters
(后来我们配合内容协商理解原理) - 自动注册
MessageCodesResolver
(国际化用) - 静态index.html (欢迎页)页支持
- 自定义
Favicon(网站图标)
- 自动使用
ConfigurableWebBindingInitializer
(DataBinder负责将请求数据绑定到JavaBean上)
1.2、自订化配置
-
不用@EnableWebMvc注解。使用
@Configuration
+WebMvcConfigurer
自定义规则(WebMvcConfigurer是一个接口。里面的方法都是default的方法,可以有选择的实现。我们是通过重写里面的方法来修改mvc的配置的。所以可以实现这个接口,配合@Configuration注解自定义mvc组件。)(向容器中添加该类型的组件或者配置类实现该接口都可以达到定制Mvc的目的) -
声明
WebMvcRegistrations
改变默认底层组件 - 使用
@EnableWebMvc+@Configuration+DelegatingWebMvcConfiguration 全面接管SpringMVC
2、简单功能分析
2.1、静态资源
-
静态资源目录(静态资源一般包括图片、视频、js、css文件)
-
只要静态资源放在类路径下: resources
/static
(or/public
or/resources
or/META-INF/resources
静态资源访问路径优先级:
- META-INF/resources
- resources
- static
- public
-
访问 : 当前项目根路径/ + 静态资源名 如:http://localhost:8888/a2.jpg
-
原理: 静态映射/**。
-
/** 是Ant风格的路径配置,两个星号代表匹配任意层级的路径
-
请求进来,先去找Controller看能不能处理。不能处理的所有请求又都交给静态资源处理器。静态资源也找不到则响应404页面。(先去找动态资源,找不到后在去找静态资源)
Conroller请求类:TestWebController
@Controller public class TestWebController { //测试静态资源(有个同名的静态资源a.jpg),测试先访问请求还是先访问静态资源 //结果:先访问请求 @ResponseBody @GetMapping("a.jpg") public String testGetStaticResource(){ return "Hello a.jpg"; } }
启动类:TestWeb
@SpringBootConfiguration @EnableAutoConfiguration @ComponentScan(basePackages = "com.pj.boot.web") public class TestWeb { public static void main(String[] args) { //返回IOC容器 ConfigurableApplicationContext run = SpringApplication.run(Springboot_1Application.class, args); } }
-
也可以改变默认的静态资源路径,默认配置的资源路径
/static
,/public
,/resources
,/META-INF/resources
失效application.yaml:
spring: web: resources: #配置静态资源默认路径 可配置多个 数组形式的 配置后 默认的其他的会失效 #点击查看源码 能发现默认配置路径的规则 只改变存储的路径 static-locations: [classpath:/haha/,classpath:/hehe/]
-
2.2、静态资源访问前缀
当前项目 + static-path-pattern(/res/**) + 静态资源名 = 静态资源文件夹下找 static-path-patten是虚拟的路径 不是真实存在的 如:http://localhost:8888/res/a2.jpg 不需要创建文件夹
为了让拦截时能区分出静态资源和动态资源,所以规定静态资源前面加个前缀,拦截器在看到指定前缀时就放行,从而达到动态静态分开的目的。
application.yaml:
#配置静态资源访问的前缀
spring:
mvc:
static-path-pattern: /res/**
2.3、webjar
-
可用jar方式添加css,js等资源文件
-
官方地址:https://www.webjars.org/
-
例如,添加jquery依赖,jQuery相关的js文件都会自动给你导入
访问地址:http://localhost:8888/webjars/jquery/3.5.1/jquery.js 即可访问到js文件(后面地址要按照依赖里面的包路径。)
pom.xml
<!--webjars 导入jquery--> <dependency> <groupId>org.webjars</groupId> <artifactId>jquery</artifactId> <version>3.5.1</version> </dependency>
2.4、欢迎页支持
官网地址:https://docs.spring.io/spring-boot/docs/2.3.8.RELEASE/reference/htmlsingle/#boot-features-spring-mvc-welcome-page
1、静态资源路径下 index.html
-
可以配置静态资源路径
-
但是不可以配置静态资源的访问前缀。否则导致 index.html不能被默认访问
spring: mvc: #静态资源访问前缀 不可以配置 否则欢迎页会失效# static-path-pattern: /res/** web: resources: #配置静态资源默认路径 以配置 但欢迎页需要在以下文件夹中 hahaha或者hehe或者static static-locations: [classpath:/hahaha/,classpath:/hehe/,classpath:/static]
2、controller能处理/index。
有一种方法:把映射路径设为*@RequestMapping*("/"),后面return要访问的欢迎页
2.5、自定义Favicon
1、指网页标签上的小图标。favicon.ico 放在静态资源目录下即可。
- 可以配置静态资源路径 (指定静态文件所在的文件夹)
- 但是不可以配置静态资源的访问前缀。否则导致favicon.ico 失效
3、静态资源配置原理
3.1、SpringBoot启动默认加载 xxxAutoConfiguration 类(自动配置类)
3.2、SpringMVC功能的自动配置类**WebMvcAutoConfiguration**
,生效
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)//是原生的Type.SERVLET 应用嘛 是就生效 不是不生效 当前的是
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer .class }) //导入了springmvc的依赖 自然会导入DispatcherServlet、WebMvcConfigurer 容器中有这三个类才会生效
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class) //容器中没有这个组件的时候才生效 可以全面接管springmvc,会需要这个类
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class, ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {}
3.3、WebMvcAutoConfiguration 给容器中配置了什么
-
rest风格过滤器和格式转换器等
-
WebMvcAutoConfiguration的内部类WebMvcAutoConfigurationAdapter
@Configuration(proxyBeanMethods = false) //是一个配置类 @Import(EnableWebMvcConfiguration.class) @EnableConfigurationProperties({ WebMvcProperties.class, org.springframework.boot.autoconfigure.web.ResourceProperties.class, WebProperties.class }) //当前配置类 绑定了 WebMvcProperties和ResourceProperties和WebProperties 三个类 并将三个类放到容器中 @Order(0) public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer, ServletContextAware {}
-
配置文件的相关属性和xxx前缀进行了绑定: WebMvcProperties绑定配置文件前缀为:spring.mvc,ResourceProperties绑定配置文件前缀为spring.resources,WebProperties绑定配置文件前缀为spring.web
-
和静态资源相关的方法,都会有缓存策略(WebMvcAutoConfigurationAdapter 中的方法)
@Override public void addResourceHandlers(ResourceHandlerRegistry registry) { if (!this.resourceProperties.isAddMappings()) { //resourceProperties中有个属性为 addMappings 可以禁用默认静态资源配置 可在properties或者yaml中配置 addMappings设置为false 进入if if后面的配置就不生效了 访问静态资源就会报404 logger.debug("Default resource handling disabled"); return; } addResourceHandler(registry, "/webjars/**", "classpath:/META-INF/resources/webjars/"); //如果导入了 webjars的依赖 就会将 访问 /webjars/** 下的所有请求映射到 /META-INF/resources/webjars/下 就可以访问到下面的静态资源 解释了如何访问 webjars下的资源 是要以http://localhost:8888/webjars开头 /* getStaticPathPattern()方法获取 mvcProperties类中属性 为staticPathPattern(默认值:private String staticPathPattern = "/**";) 属性的中 可在properties或者yaml中配置 staticPathPattern(静态资源访问前缀)值 配置了则会获取我们配置的 将staticPathPattern 映射到 resourceProperties类中属性为 staticLocations(默认值 private String[] staticLocations = { "classpath:/META-INF/resources/","classpath:/resources/", "classpath:/static/", "classpath:/public/" }; 解释了静态资源为什么可以放到以上目录,就可以直接访问了) , 可在properties或者yaml中配置 staticLocations(静态资源访问路径)值 配置了则会获取我们配置的 也有缓存策略 */ 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); } }); } private void addResourceHandler(ResourceHandlerRegistry registry, String pattern, String... locations) { addResourceHandler(registry, pattern, (registration) -> registration.addResourceLocations(locations)); } private void addResourceHandler(ResourceHandlerRegistry registry, String pattern, Consumer<ResourceHandlerRegistration> customizer) { if (registry.hasMappingForPattern(pattern)) { return; } ResourceHandlerRegistration registration = registry.addResourceHandler(pattern); customizer.accept(registration); registration.setCachePeriod(getSeconds(this.resourceProperties.getCache().getPeriod()));//加入到缓存中 可以在properties或者yaml中配置 resourceProperties 中属性的值period 设置缓存时间 registration.setCacheControl(this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl()); registration.setUseLastModified(this.resourceProperties.getCache().isUseLastModified()); customizeResourceHandlerRegistration(registration); }
application.yaml:
spring: mvc: # 静态资源访问前缀 static-path-pattern: /res/** web: resources: #配置静态资源默认路径 static-locations: [classpath:/hahaha/,classpath:/hehe/,classpath:/static] #设置静态资源缓存时间 cache: period: 1100 #关闭默认静态资源的配置 add-mappings: false
-
欢迎页的配置
- WebMvcAutoConfiguration的内部类EnableWebMvcConfiguration
=============EnableWebMvcConfiguration=============== @Configuration(proxyBeanMethods = false) @EnableConfigurationProperties(WebProperties.class) public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration implements ResourceLoaderAware { //HandlerMapping:处理器映射。保存了每一个Handler能处理哪些请求 @Bean //在容器中放入WelcomePageHandlerMapping public WelcomePageHandlerMapping welcomePageHandlerMapping(ApplicationContext applicationContext, FormattingConversionService mvcConversionService, ResourceUrlProvider mvcResourceUrlProvider) { /* this.mvcProperties.getStaticPathPattern() 也会获取 静态资源前缀的值 WelcomePageHandlerMapping 源码如下 */ 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)) //如果静态访问资源前缀不是/** 则欢迎页会失效 解释了 欢迎页时不能在配置文件中配置 staticPathPattern 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"); } }
-
favicon
浏览器会发送 /favicon 请求获取到图标,整个session期间不再获取 ,不归springboot管
-
扩展:
- 配置类 WebMvcAutoConfigurationAdapter 只有一个有参构造器
//有参构造器所有参数的值都会从容器中确定(前提条件 只有一个有参构造器) /* resourceProperties: 获取和spring.resources绑定的所有的对象的 webProperties:获取和spring.web绑定的所有的对象的 mvcProperties:获取和spring.mvc绑定的所有的值的对象 ListableBeanFactory: Spring的beanFactory(bean工厂) messageConvertersProvider:找到所有的HttpMessageConverters resourceHandlerRegistrationCustomizerProvider:找到资源处理器的自定义器。(和静态资源相关) dispatcherServletPath: 前端控制器 servletRegistrations: 给应用注册原生的Servlet、Filter… 需要自定义配置改类 */ public WebMvcAutoConfigurationAdapter( org.springframework.boot.autoconfigure.web.ResourceProperties resourceProperties, WebProperties webProperties, WebMvcProperties mvcProperties, ListableBeanFactory beanFactory, ObjectProvider<HttpMessageConverters> messageConvertersProvider, ObjectProvider<ResourceHandlerRegistrationCustomizer> resourceHandlerRegistrationCustomizerProvider, ObjectProvider<DispatcherServletPath> dispatcherServletPath, ObjectProvider<ServletRegistrationBean<?>> servletRegistrations) { this.resourceProperties = resourceProperties.hasBeenCustomized() ? resourceProperties : webProperties.getResources(); this.mvcProperties = mvcProperties; this.beanFactory = beanFactory; this.messageConvertersProvider = messageConvertersProvider; this.resourceHandlerRegistrationCustomizer = resourceHandlerRegistrationCustomizerProvider.getIfAvailable(); this.dispatcherServletPath = dispatcherServletPath; this.servletRegistrations = servletRegistrations; this.mvcProperties.checkConfiguration(); }
- 结论:某个配置类如果只有一个有参构造器,有参构造器所有参数的值都会从容器中确定
4、请求参数处理
4.1、请求映射
1、 rest使用与原理
-
@xxxMapping: @GetMapping、@PostMapping、@PutMapping、@DeleteMapping
-
Rest风格支持(使用HTTP请求方式动词来表示对资源的操作)
- 以前:
- /getUser 获取用户
- /deleteUser 删除用户
- /editUser 修改用户
- /saveUser保存用户
- 现在: /user
- GET-获取用户
- DELETE-删除用户
- PUT-修改用户
- POST-保存用户
- 核心Filter;HiddenHttpMethodFilter
- 以前:
-
用法
-
开启页面表单的Rest功能(默认是不开启的 需要在配置文件中开启)
spring: #开启rest风格的过滤器 mvc: hiddenmethod: filter: enabled: true #默认值为false
-
页面 form的属性method=post,隐藏域 _method=put、delete等(如果直接get或post,无需隐藏域)
========index.html======= <h2>Rest风格测试</h2> <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" type="hidden" value="DELETE"/> <input value="REST-DELETE 提交" type="submit"/> </form> <form action="/user" method="post"> <input name="_method" type="hidden" value="PUT"/> <input value="REST-PUT提交" type="submit"/> </form>
-
编写请求映射
@RestControllerpublic class TestWebController { @PostMapping("/user") //@RequestMapping(value = "/user",method = RequestMethod.POST) public String saveUser(){ return "POST-张三"; } @PutMapping("/user") //@RequestMapping(value = "/user",method = RequestMethod.PUT) public String putUser(){ return "PUT-张三"; } @DeleteMapping("/user") //@RequestMapping(value = "/user",method = RequestMethod.DELETE) public String deleteUser(){ return "DELETE-张三"; } }
-
-
Rest原理(表单提交要使用REST的时候)
- 表单提交会带上_method=PUT
- 请求过来被HiddenHttpMethodFilter拦截
- 请求是否正常,并且是POST
- 获取到_method的值。
- 兼容以下请求;PUT.DELETE.PATCH
- 原生request(post),包装模式requesWrapper重写了getMethod方法,返回的是传入的值。
- 过滤器链放行的时候用wrapper。以后的方法调用getMethod是调用requesWrapper的。
====== WebMvcAutoConfiguration 类下的方法=================== @Bean @ConditionalOnMissingBean(HiddenHttpMethodFilter.class) //绑定了 HiddenHttpMethodFilter类 @ConditionalOnProperty(prefix = "spring.mvc.hiddenmethod.filter", name = "enabled" /*, matchIfMissing = false*/)//以spring.mvc.hiddenmethod.filter 为前缀的配置都会映射到HiddenHttpMethodFilter类 的属性上 没配置matchIfMissing属性的值,默认为false, 默认是不开启的 需要在配置文件中开启 public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() { return new OrderedHiddenHttpMethodFilter(); } ====== HiddenHttpMethodFilter 类下的方法=================== public class HiddenHttpMethodFilter extends OncePerRequestFilter { private static final List<String> ALLOWED_METHODS = Collections.unmodifiableList(Arrays.asList(HttpMethod.PUT.name(), HttpMethod.DELETE.name(), HttpMethod.PATCH.name())); /** Default method parameter: {@code _method}. */ public static final String DEFAULT_METHOD_PARAM = "_method"; private String methodParam = DEFAULT_METHOD_PARAM; /** * Set the parameter name to look for HTTP methods. * @see #DEFAULT_METHOD_PARAM */ public void setMethodParam(String methodParam) { Assert.hasText(methodParam, "'methodParam' must not be empty"); this.methodParam = methodParam; } @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { HttpServletRequest requestToUse = request; if ("POST".equals(request.getMethod()) && request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) == null) {//是不是POST方式提交和是否有异常 String paramValue = request.getParameter(this.methodParam);//获取 _method 的参数的值 if (StringUtils.hasLength(paramValue)) {//判断是获取的 _method 参数的值长度是否大于0 String method = paramValue.toUpperCase(Locale.ENGLISH);//转成大写 if (ALLOWED_METHODS.contains(method)) {//判断获取的参数值是不是允许的 请求方式 允许的请求方式有 :PUT ,DELETE,PATCH requestToUse = new HttpMethodRequestWrapper(request, method); //使用构造器的改变请求方式,包装了request,重写了里面的get方法 ,包请求方式method值 改成了自己传入的值 } } } filterChain.doFilter(requestToUse, response); //放行的是自己包装之后的request,以后使用的就是这个request对象 } /** * Simple {@link HttpServletRequest} wrapper that returns the supplied method for * {@link HttpServletRequest#getMethod()}. */ private static class HttpMethodRequestWrapper extends HttpServletRequestWrapper { private final String method; public HttpMethodRequestWrapper(HttpServletRequest request, String method) { super(request); this.method = method; } @Override public String getMethod() { return this.method; } } }
-
Rest使用客户端工具。
- 如PostMan可直接发送put、delete等方式请求。不会走上述的包装 request,直接可发送。
-
怎么改变默认的携带的_method
-
@ConditionalOnMissingBean(HiddenHttpMethodFilter.class)意味着在没有HiddenHttpMethodFilter时,才执行hiddenHttpMethodFilter()。因此,我们可以自定义filter,改变默认的_method
/* web的配置类 */ @Configuration(proxyBeanMethods = false) public class WebConfig { @Bean public HiddenHttpMethodFilter hiddenHttpMethodFilter(){ //这样不会执行hiddenHttpMethodFilter()方法 HiddenHttpMethodFilter filter = new HiddenHttpMethodFilter(); filter.setMethodParam("_m"); //将获取的参数值设置为自己的值 return filter; } }
<form action="/user" method="post"> <input name="_m" type="hidden" value="DELETE"/> //能获取到delete <input value="REST-DELETE 提交" type="submit"/> </form> <form action="/user" method="post"> <input name="_method" type="hidden" value="PUT"/>//不能获取到put 因为没改变参数名 "_m" <input value="REST-PUT提交" type="submit"/> </form>
-
2、 请求映射原理
-
SpringMVC功能分析都从
org.springframework.web.servlet.DispatcherServlet
-> `doDispatch()`` -
DispatchServlet --》doDispatch()源码的一部分
=========DispatchServlet类 下的方法======= protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { HttpServletRequest processedRequest = request; HandlerExecutionChain mappedHandler = null; boolean multipartRequestParsed = false; //判断是不是文件上传请求 默认false WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); //是不是异步请求 try { ModelAndView mv = null; Exception dispatchException = null; try { processedRequest = checkMultipart(request); //检测是不是 文件上传请求 multipartRequestParsed = (processedRequest != request); // 确定当前请求的处理程序。 HandlerMapping中找到一个HandlerExecutionChain 链对象,能处理请求的Handler(Controller.method())的请求的controller和获取所有的拦截器。 mappedHandler = getHandler(processedRequest); //找到那个Hnadler(Controller处理器(类))可以执行当前的请求 ..... } }
-
图解流程
-
this.handlerMappings
在Debug模式下展现的内容是 @RequestMapping
和handler
的映射规则。 -
所有的请求映射都在HandlerMapping中:
-
SpringBoot自动配置欢迎页的 WelcomePageHandlerMapping 。访问 /能访问到index.html;
-
SpringBoot自动配置了默认 的 RequestMappingHandlerMapping
-
请求进来,挨个尝试所有的HandlerMapping ,看当前的HandlerMapping 能否处理当前发送的请求。
- 如果能处理就找到这个请求对应的handlerMapping,获取后,在获取能够处理当前请求的Handler(Controller类)对应的方法
- 如果没有就循环遍历下一个 HandlerMapping
-
我们需要一些自定义的映射处理,我们也可以自己给容器中放HandlerMapping。自定义 HandlerMapping(有的时候比如说同一组api有不同的版本,比如v1,v2我们可以在controller中写两组mapping(比如v1/user,v2/user),但同时我们也可以放在两个包下,都是/user,这个时候我们就可以自定义handlermapping,把v1/user映射到一个包下的/user,把v2/user映射到另外一个包下的/user)
-
4.2、普通参数与基本注解
1常用参数注解使用
-
@PathVariable 路径变量((rest风格 {…}代表一个参数)
@RequestHeader 获取请求头
@RequestParam 获取请求参数(指问号后的参数,url?a=1&b=2)
@CookieValue 获取Cookie值
@RequestBody 获取请求体[POST]
@ModelAttribute参数解析大概原理:26个参数解析器,采用循环遍历的方式找对应的参数解析器,(参数解析器完成第一次加载后,会进到缓存中),15个返回值处理器,最常用的返回值类型:ModelAndView, Model, View, ResponseBody
// rest形如:car/2/owner/zhangsan @GetMapping("/car/{id}/owner/{username}") public Map<String ,Object> testParameter(@PathVariable("id") int id, @PathVariable("username") String name, @PathVariable Map<String ,String > map,//获取所以rest风格传递的参数键值对 id:值 username:值 @RequestHeader("User-Agent") String userAgent, //获取单个头 @RequestHeader Map<String ,String > heads, //获取所以请求头 @RequestParam("age") int age, //参数是单个值的获取 @RequestParam("like")List<String> likes, //参数是多个值的获取 @RequestParam Map<String , String > params,//获取所有的请求参数值 @CookieValue("Idea-f4788bfe") String cookie, //获取cookie的值转成字符串 @CookieValue("Idea-f4788bfe") Cookie cookie2 //获取的cookie转成对象 ){ Map<String,Object> maps = new HashMap<>(); maps.put("id", id); maps.put("username", name); maps.put("map", map); maps.put("userAgent", userAgent); maps.put("heads", heads); maps.put("age", age); maps.put("likes", likes); maps.put("params", params); map.put("cookie",cookie); System.out.println(cookie2); map.put("cookieName",cookie2.getName()); return maps; } @PostMapping("/testRequestBody") public Map<String ,Object> testParameter2(@RequestBody String content){ //获取请求体(POST请求才有体) "content": "username=dsadsa&password=dasdsa" Map<String,Object> maps = new HashMap<>(); maps.put("content", content); return maps; }
index.html
<h2>Test请求参数注解</h2><a href="car/2/owner/zhangsan?age=12&like=nv&like=启程">Test PathVariable</a><br/><a href="car/2/owner/zhangsan?age=12&like=nv&like=启程">Test RequestHeader</a>
<br/>
<a href="car/2/owner/zhangsan?age=12&like=nv&like=启程">Test RequestParam</a>
<br/>
<a href="car/2/owner/zhangsan?age=12&like=nv&like=启程">Test CookieValue</a>
<br/>
<form action="testRequestBody" method="post">
<input name="username" type="text">
<input name="password" type="password">
<input type="submit" value="Test RequestBody">
</form>
- @RequestAttribute 获取request域属性
@GetMapping("goto")public String goToPage(HttpServletRequest request){ request.setAttribute("msg", "成功了..."); request.setAttribute("code", 200); return "forward:/success";//转发到 /success请求}@GetMapping("/params")public String testParam(Map<String,Object> map, Model model, HttpServletRequest request, HttpServletResponse response) { map.put("hello", "world666"); model.addAttribute("world", "hello666"); request.setAttribute("message", "HelloWorld"); Cookie cookie = new Cookie("c1", "v1"); response.addCookie(cookie); return "forward:/success";}//<-----------------主角@RequestAttribute在这个方法@ResponseBody@GetMapping("/success")public Map success(@RequestAttribute(value = "msg",required = false) String msg, @RequestAttribute(value = "code",required = false)String code, @RequestAttribute(value = "hello" ,required = false) String hello, @RequestAttribute(value = "world" ,required = false) String world, @CookieValue("c1") String c1, HttpServletRequest request){ Object msg1 = request.getAttribute("msg"); Map<String,Object> map = new HashMap<>(); Object hello1 = request.getAttribute("hello"); Object world1 = request.getAttribute("world"); Object message = request.getAttribute("message"); map.put("annotation_msg",msg); map.put("annotation_code",code); map.put("annotation_hello",hello); map.put("annotation_world",world); map.put("reqMethod_msg1",msg1); map.put("reqMethod_hello1",hello1); map.put("reqMethod_world1",world1); map.put("message",message); map.put("cookie",c1); return map;}
index.html
<a href="goto">Test RequestAttribute</a>><br/><a href="params">Test RequestAttribute2</a>><br/>
-
@MatrixVariable 矩阵变量:默认是功能时关闭的需要手动开启
-
语法: 请求路径:
/cars/sell;low=34;brand=byd,audi,yd
映射路径:/cars/{path} /boss/1;age=20/2;age=10 /boss/{path1}/{path2}
-
SpringBoot默认是禁用了矩阵变量的功能
手动开启:原理。对于路径的处理。UrlPathHelper的removeSemicolonContent设置为false,让其支持矩阵变量的。矩阵变量必须有url路径变量才能被解析
-
手动开启矩阵变量:
========WebMvcConfigurer========@Configuration(proxyBeanMethods = false)public class WebConfig implements WebMvcConfigurer { /* 手动开启矩阵变量 第一种方法: @Bean 在容器中洪注入一个 WebMvcConfigurer(一个接口) 第二种方法 实现 WebMvcConfigurer 重写需要修改的方法 configurePathMatch */ /* @Bean public WebMvcConfigurer webMvcConfigurer(){ return new WebMvcConfigurer(){ @Override public void configurePathMatch(PathMatchConfigurer configurer) { UrlPathHelper urlPathHelper = new UrlPathHelper(); urlPathHelper.setRemoveSemicolonContent(false); //修改默认值 相当于矩阵变量功能生效 即不移除分号;的内容 configurer.setUrlPathHelper(urlPathHelper); } }; }*/ @Override public void configurePathMatch(PathMatchConfigurer configurer) { UrlPathHelper urlPathHelper = new UrlPathHelper(); urlPathHelper.setRemoveSemicolonContent(false); //修改默认值 相当于矩阵变量功能生效 即不移除分号;的内容 configurer.setUrlPathHelper(urlPathHelper); }}=========TestWebController 类=========== ///cars/sell;low=34;brand=byd,audi,yd @GetMapping("/cars/{path}") //不能写成 cars/sell 一个sell;low=34;brand=byd,audi,yd 绑定一个{path} 否则会出现404 @ResponseBody 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; } // /boss/1;age=20/2;age=10 @GetMapping("/boss/{bossId}/{empId}") @ResponseBody public Map boss(@MatrixVariable(value = "age" ,pathVar = "bossId") Integer boosId, @MatrixVariable(value = "age" ,pathVar = "empId") Integer empId){ Map<String ,Object> map = new HashMap<>(); map.put("boosId", boosId); map.put("empId", empId); return map; }
index.html
/cars/{path}?xxx=xx&aaa=ccc queryString 查询字符串:@RequestParam;<br>/cars/sell;low=34;brand=byd,audi,yd; 矩阵变量<br>页面开发,cookie禁用,session里面的内容怎么使用;session.set(a,b)---->jsessionid=xx--->cookie---->每次发请求携带 禁用cookie 导致整条链失效<br>url重写 : /abc;jsessionid=xxx 把cookie的值使用矩阵遍历的方式进行传递<br><a href="/cars/sell;low=34;brand=byd,audi,yd">Test MatrixVariable</a><br><a href="/cars/sell;low=34;brand=byd,audi,yd;brand=aa">Test MatrixVariable</a><br><a href="/boss/1;age=20/2;age=10">Test MatrixVariable /boss/{bossId}/{bossId2}</a><br>
-
2、请求参数注解的原理
-
这要从
DispatcherServlet
开始说起:请求访问都是从DispatcherServlet====================DispatchServlet类 下的===============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); //HandlerMapping中找到一个HandlerExecutionChain 链对象,能处理请求的Handler(Controller.method())的请求的controller和获取所有的拦截器。 mappedHandler = getHandler(processedRequest); if (mappedHandler == null) { noHandlerFound(processedRequest, response); return; } // 确定当前请求的处理程序适配器。 HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); // 处理上次修改的标头(如果句柄支持) String method = request.getMethod(); boolean isGet = HttpMethod.GET.matches(method); //判断是不是get请求 if (isGet || HttpMethod.HEAD.matches(method)) { long lastModified = ha.getLastModified(request, mappedHandler.getHandler()); if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) { return; } } //执行拦截器的applyPreHandle方法,如果方法返回false就会被拦截 if (!mappedHandler.applyPreHandle(processedRequest, response)) { return; } // 正真执行目标方法 调用我们自己写的controller中的方法 当中会进行参数解析(处理参数解析的方法) 返回值解析等流程 mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); ......
-
找寻合适 的适配器(前面找寻Handler的流程不在重复赘述)
-
HandlerMapping
中找到能处理请求的Handler
(Controller.method())。(前面的流程rest风格原理详细解析过)- 为当前Handler 找一个适配器
HandlerAdapter
,用的最多的是RequestMappingHandlerAdapter。(默认会加载所有HandlerAdapter
)- 0下标的支持方法上标注
@RequestMapping
- 1下标的支持函数式编程的
- 3下标 访问静态资源
- 0下标的支持方法上标注
- 为当前Handler 找一个适配器
- 使用适配器执行目标方法并确定方法参数的每一个值。
-
-
调用执行自己编写的controller中的请求方法(本节重点就是这个 mv = ha.handle(processedRequest, response, mappedHandler.getHandler());)
-
获取参数解析器、返回值处理器处理 上述方法的 invokeHandlerMethod()方法源码如下:
-
获取参数解析器和返回值处理器,SpringMVC目标方法能写多少种参数类型。取决于参数解析器argumentResolvers。都有多个 参数解析器argumentResolvers。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mRTxnnvE-1642384456581)(https://gitee.com/pjgitee/note/raw/master/springboot%E7%AC%94%E8%AE%B0.assets/image-20210830120914785.png)]
-
*
this.argumentResolvers(参数解析器)**
在 RequestMappingHandlerAdapterafterPropertiesSet()
方法内初始化-
if (this.argumentResolvers != null) {}//<-----关注点\
public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter implements BeanFactoryAware, InitializingBean { @Nullable private HandlerMethodArgumentResolverComposite argumentResolvers; @Override public void afterPropertiesSet() { ... if (this.argumentResolvers == null) {//初始化argumentResolvers List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers(); this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers); } ... } //初始化了一堆的实现HandlerMethodArgumentResolver接口的 private List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() { List<HandlerMethodArgumentResolver> resolvers = new ArrayList<>(30); //基于注释的参数解析 默认的就是添加了这些 resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), false)); resolvers.add(new RequestParamMapMethodArgumentResolver()); resolvers.add(new PathVariableMethodArgumentResolver()); resolvers.add(new PathVariableMapMethodArgumentResolver()); resolvers.add(new MatrixVariableMethodArgumentResolver()); resolvers.add(new MatrixVariableMapMethodArgumentResolver()); resolvers.add(new ServletModelAttributeMethodProcessor(false)); resolvers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice)); resolvers.add(new RequestPartMethodArgumentResolver(getMessageConverters(), this.requestResponseBodyAdvice)); resolvers.add(new RequestHeaderMethodArgumentResolver(getBeanFactory())); resolvers.add(new RequestHeaderMapMethodArgumentResolver()); resolvers.add(new ServletCookieValueMethodArgumentResolver(getBeanFactory())); resolvers.add(new ExpressionValueMethodArgumentResolver(getBeanFactory())); resolvers.add(new SessionAttributeMethodArgumentResolver()); resolvers.add(new RequestAttributeMethodArgumentResolver()); // Type-based argument resolution resolvers.add(new ServletRequestMethodArgumentResolver()); resolvers.add(new ServletResponseMethodArgumentResolver()); resolvers.add(new HttpEntityMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice)); resolvers.add(new RedirectAttributesMethodArgumentResolver()); resolvers.add(new ModelMethodProcessor()); resolvers.add(new MapMethodProcessor()); resolvers.add(new ErrorsMethodArgumentResolver()); resolvers.add(new SessionStatusMethodArgumentResolver()); resolvers.add(new UriComponentsBuilderMethodArgumentResolver()); if (KotlinDetector.isKotlinPresent()) { resolvers.add(new ContinuationHandlerMethodArgumentResolver()); } // Custom arguments if (getCustomArgumentResolvers() != null) { resolvers.addAll(getCustomArgumentResolvers()); } // Catch-all resolvers.add(new PrincipalMethodArgumentResolver()); resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), true)); resolvers.add(new ServletModelAttributeMethodProcessor(true)); return resolvers; }}
-
-
HandlerMethodArgumentResolver 参数解析器的接口定义 (当前解析器 判断 是否支持解析这种参数如果支持就调用 resolveArgument 解析)
-
返回值处理器 this.returnValueHandlers 在 RequestMappingHandlerAdapter
afterPropertiesSet()
方法内初始化if (this.returnValueHandlers != null) {}//<—关注点
public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter implements BeanFactoryAware, InitializingBean { @Nullable private HandlerMethodReturnValueHandlerComposite returnValueHandlers; @Override public void afterPropertiesSet() { ... if (this.returnValueHandlers == null) { List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers(); this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers); } } //初始化了一堆的实现HandlerMethodReturnValueHandler接口的 private List<HandlerMethodReturnValueHandler> getDefaultReturnValueHandlers() { List<HandlerMethodReturnValueHandler> handlers = new ArrayList<>(20); // Single-purpose return value types handlers.add(new ModelAndViewMethodReturnValueHandler()); handlers.add(new ModelMethodProcessor()); handlers.add(new ViewMethodReturnValueHandler()); handlers.add(new ResponseBodyEmitterReturnValueHandler(getMessageConverters(), this.reactiveAdapterRegistry, this.taskExecutor, this.contentNegotiationManager)); handlers.add(new StreamingResponseBodyReturnValueHandler()); handlers.add(new HttpEntityMethodProcessor(getMessageConverters(), this.contentNegotiationManager, this.requestResponseBodyAdvice)); handlers.add(new HttpHeadersReturnValueHandler()); handlers.add(new CallableMethodReturnValueHandler()); handlers.add(new DeferredResultMethodReturnValueHandler()); handlers.add(new AsyncTaskMethodReturnValueHandler(this.beanFactory)); // Annotation-based return value types handlers.add(new ServletModelAttributeMethodProcessor(false)); handlers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(), this.contentNegotiationManager, this.requestResponseBodyAdvice)); // Multi-purpose return value types handlers.add(new ViewNameMethodReturnValueHandler()); handlers.add(new MapMethodProcessor()); // Custom return value types if (getCustomReturnValueHandlers() != null) { handlers.addAll(getCustomReturnValueHandlers()); } // Catch-all if (!CollectionUtils.isEmpty(getModelAndViewResolvers())) { handlers.add(new ModelAndViewResolverMethodReturnValueHandler(getModelAndViewResolvers())); } else { handlers.add(new ServletModelAttributeMethodProcessor(true)); } return handlers; }}
-
HandlerMethodReturnValueHandler 返回值处理器接口的定义
-
-
-
正真执行我们自定义的方法
-
上述的 getMethodArgumentValues() 方法的源码
============InvocableHandlerMethod 该方法作用:确定每个参数的对应的值==============protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { MethodParameter[] parameters = getMethodParameters(); //获取所有参数的详细信息 包括类型 标注了什么注解等 if (ObjectUtils.isEmpty(parameters)) { //判断是否有参数 没参数直接返回空参数 return EMPTY_ARGS; } Object[] args = new Object[parameters.length]; //创建一个数组 用于保存各个参数的值 for (int i = 0; i < parameters.length; i++) { //遍历所有参数 MethodParameter parameter = parameters[i]; //获取第i个参数 parameter.initParameterNameDiscovery(this.parameterNameDiscoverer); args[i] = findProvidedArgument(parameter, providedArgs); if (args[i] != null) { continue; } if (!this.resolvers.supportsParameter(parameter)) { //判断各个参数解析器(我的版本27个)是否支持这个参数类型的解析(前面有参数解析的截图) 为什么能够支持那么多参数类型,就是对这个接口的实现有很多种,每一种针对不同的参数类型(注解)进行支持、实现,然后解析。 throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver")); } try { args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory); //解析参数值 这是参数解析接口中的方法 解析参数值 } catch (Exception ex) { // Leave stack trace for later, exception may actually be resolved and handled... if (logger.isDebugEnabled()) { String exMsg = ex.getMessage(); if (exMsg != null && !exMsg.contains(parameter.getExecutable().toGenericString())) { logger.debug(formatArgumentError(parameter, exMsg)); } } throw ex; } } return args;
-
上述源码:if (!this.resolvers.supportsParameter(parameter)) 调用的方法源码
-
getArgumentResolver()方法源码
====HandlerMethodArgumentResolverComposite的方法 作用:是寻找一个合适的参数解析器解析各个参数===========private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) { HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter); //第一次进去缓存为空,找缓存 能够提高效率 if (result == null) { for (HandlerMethodArgumentResolver resolver : this.argumentResolvers) {//argumentResolvers 27个参数解析器 if (resolver.supportsParameter(parameter)) { //循环遍历每个参数解析器 每个参数解析判断能否处理当前的参数 方法中的每个参数(parameter当前的参数)都会循环判断(上述的getMethodArgumentValues()方法是对每个参数进行循环判断的) 找到一个能够处理当前参数的解析器就直接返回 27个参数解析器判断能否解析的当前参数的方法实现不同 result = resolver; this.argumentResolverCache.put(parameter, result); //放入缓存中 break; } } } return result;//返回的是能够解析当前参数的参数解析器}
-
-
返回(上述的result)参数解析器后,会调用参数解析器的解析方法 resolveArgument()源码如下图:
args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory); //解析参数值 这是参数解析接口中的方法 解析参数值(也是getMethodArgumentValues()方法中的源码) args数组中存储的是能够解析参数的解析器
......try { args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory); //解析参数值 这是参数解析接口中的方法 解析参数值 } catch (Exception ex) { // Leave stack trace for later, exception may actually be resolved and handled... if (logger.isDebugEnabled()) { String exMsg = ex.getMessage(); if (exMsg != null && !exMsg.contains(parameter.getExecutable().toGenericString())) { logger.debug(formatArgumentError(parameter, exMsg)); } } throw ex; } } return args;
-
resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory); 方法源码如下
-
-
解析完成当前参数后,会解析下一个参数,依次循环上述步骤(依次解析各个参数)
-
-
上述步骤完成后,解析各个参数完成
-
-
接下面的复杂参数的源码
3、复杂的参数和原理
-
Map、**Model(map、model里面的数据会被放在request的请求域 request.setAttribute)、**Errors/BindingResult、RedirectAttributes( 重定向携带数据)、ServletResponse(response)、SessionStatus、UriComponentsBuilder、ServletUriComponentsBuilder
-
请求代码
//测试复杂参数@GetMapping("/params") public String testParam(Map<String,Object> map, Model model, HttpServletRequest request, //三个参数类型都能往request域中放入数据 request.getAttribute()可获取到 HttpServletResponse response) { map.put("hello", "world666"); model.addAttribute("world", "hello666"); request.setAttribute("message", "HelloWorld"); Cookie cookie = new Cookie("c1", "v1"); cookie.setDomain("localhost"); response.addCookie(cookie); return "forward:/success"; }
-
匹配到参数解析器后,调用当前解析器的解析方法(继续跟上面源码分析的流程) (这里解析参数相关)
-
Map、Model类型的参数,会返回 mavContainer.getModel();—> BindingAwareModelMap 是Model 也是Map
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sRR9Id3R-1642384456589)(https://gitee.com/pjgitee/note/raw/master/springboot%E7%AC%94%E8%AE%B0.assets/image-20210830160544756.png)]
-
参数解析完成后得到的 args(保存的是各个参数的值)
-
-
各个参数解析完成后,会执行自己编写的controller请求方法 处理完成后 会将需要放入到request域中的值存储到modelAndViewContainer中对象中 获取到返回值 在处理返回值结果
-
上述的 图片中 handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest); 处理方法截图
-
上述方法执行完成 mavContainer()对象的视图名也设置好了
-
-
invocableMethod.invokeAndHandle(webRequest, mavContainer);上述是该方法完成之后,处理完调用自己的方法之后的操作,接上面3-1(3步骤的1(请求参数注解的原理模块))操作之后执行
调用的是getModelAndView(mavContainer, modelFactory, webRequest);方法,改方法执行完成返回 ModelAndView 对象
DispatchServlet的 mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); 执行完成
====================DispatchServlet类 下的===============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); //HandlerMapping中找到一个HandlerExecutionChain 链对象,能处理请求的Handler(Controller.method())的请求的controller和获取所有的拦截器。 mappedHandler = getHandler(processedRequest); if (mappedHandler == null) { noHandlerFound(processedRequest, response); return; } // 确定当前请求的处理程序适配器。 HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); //处理参数解析的方法 // 处理上次修改的标头(如果句柄支持) String method = request.getMethod(); boolean isGet = HttpMethod.GET.matches(method); //判断是不是get请求 if (isGet || HttpMethod.HEAD.matches(method)) { long lastModified = ha.getLastModified(request, mappedHandler.getHandler()); if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) { return; } } //执行拦截器的applyPreHandle方法,如果方法返回false就会被拦截 if (!mappedHandler.applyPreHandle(processedRequest, response)) { return; } // 正真执行目标方法 调用我们自己写的controller中的方法 当中会进行参数解析 返回值解析等流程 返回一个modelAndView对象 mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); if (asyncManager.isConcurrentHandlingStarted()) { return; } applyDefaultViewName(processedRequest, mv); //处理拦截器的方法 mappedHandler.applyPostHandle(processedRequest, response, mv); } catch (Exception ex) { dispatchException = ex; } catch (Throwable err) { // As of 4.3, we're processing Errors thrown from handler methods as well, // making them available for @ExceptionHandler methods and other scenarios. dispatchException = new NestedServletException("Handler dispatch failed", err); } //处理开发结果 会把model的数据放到域中 processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); }......
-
上述的 processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);的源码如下图
-
上述 view.render()方法的源码如下:
-
renderMerOutputModel()方法源码如下:存储数据到了request域中
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-w0bSx8fZ-1642384456598)(https://gitee.com/pjgitee/note/raw/master/springboot%E7%AC%94%E8%AE%B0.assets/image-20210830171618012.png)]
-
4、Servlet API:
-
WebRequest、ServletRequest、MultipartRequest、 HttpSession、javax.servlet.http.PushBuilder、Principal、InputStream、Reader、HttpMethod、Locale、TimeZone、ZoneId
-
**ServletRequestMethodArgumentResolver **参数解析器用来处理以上的参数:可通过debug测试上述的"goto"请求
5、自定义参数绑定原理 POJO
-
实践代码
pojo类
@Datapublic class Person { private String userName; private Integer age; private Date birth; private Pet pet;}@Datapublic class Pet { private String name; private String age;}
controller类:
/* 测试封装pojo 数据绑定:页面提交的请求数据(GET、POST)都可以和对象属性进行绑定 @param person @return */@ResponseBody@PostMapping("testPojo")public Person testPojo(Person person){ return person;}
index.html
<h2>Test POJO</h2><form action="testPojo" method="post"> 姓名: <input name="userName"/> <br/> 年龄: <input name="age"/> <br/> 生日: <input name="birth"/> <br/> 宠物姓名:<input name="pet.name"/><br/> 宠物年龄:<input name="pet.age"/> <input type="submit" value="提交"></form>
-
找到可以解析pojo类的参数解析器 参数解析器为:ServletModelAttributeMethodProcessor 解析器
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zcCjInj6-1642384456600)(C:\Users\彭俊\AppData\Roaming\Typora\typora-user-images\image-20210831105928008.png)]
-
找到解析器后,调用当前解析器的解析方法 args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory); //解析参数值 这是参数解析接口中的方法 解析参数值
ServletModelAttributeMethodProcessor 参数解析器解析参数的方法 resolveArgument() 解析源码如下:
public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception { Assert.state(mavContainer != null, "ModelAttributeMethodProcessor requires ModelAndViewContainer"); Assert.state(binderFactory != null, "ModelAttributeMethodProcessor requires WebDataBinderFactory"); String name = ModelFactory.getNameForParameter(parameter); //通过传递过来的参数 获取参数类型的名称 ModelAttribute ann = parameter.getParameterAnnotation(ModelAttribute.class); if (ann != null) {//判断是否包含 ModelAttribute注解 mavContainer.setBinding(name, ann.binding()); } Object attribute = null; BindingResult bindingResult = null; if (mavContainer.containsAttribute(name)) { //如果 mavContainer 包含了当前参数类型的名称 将会和mavContainer 中的整合 attribute = mavContainer.getModel().get(name); } else { try { // 创建一个类型为参数类型的空的实例 attribute = createAttribute(name, parameter, binderFactory, webRequest); } catch (BindException ex) { if (isBindExceptionRequired(parameter)) { // No BindingResult parameter -> fail with BindException throw ex; } // Otherwise, expose null/empty value and associated BindingResult if (parameter.getParameterType() == Optional.class) { attribute = Optional.empty(); } else { attribute = ex.getTarget(); } bindingResult = ex.getBindingResult(); } } if (bindingResult == null) { //Bean 属性绑定和验证; // 在施工绑定失败的情况下跳过。 //binder web数据绑定 作用:将每个传递过来的 参数转换、封装到 attribute中(这是一个循环的过程,每个参数都需要进行转换、封装) //webRequest:原生的request,包含了需要封装的数据 WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name); if (binder.getTarget() != null) { if (!mavContainer.isBindingDisabled(name)) { bindRequestParameters(binder, webRequest);//进行 转换 、封装,先将传过来的参数类型转换成目标数据类型 然后在封装到 对象中 改方法结束 attribute中的各个属性的值就已经封装完成 } validateIfApplicable(binder, parameter); if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) { throw new BindException(binder.getBindingResult()); } } // Value type adaptation, also covering java.util.Optional if (!parameter.getParameterType().isInstance(attribute)) { attribute = binder.convertIfNecessary(binder.getTarget(), parameter.getParameterType(), parameter); } bindingResult = binder.getBindingResult(); } // Add resolved attribute and BindingResult at the end of the model Map<String, Object> bindingResultModel = bindingResult.getModel(); mavContainer.removeAttributes(bindingResultModel); mavContainer.addAllAttributes(bindingResultModel); return attribute;}
-
WebDataBinder 利用它里面的 Converters 将请求数据转成指定的数据类型。再次封装到JavaBean中
在过程当中,用到GenericConversionService:在设置每一个值的时候,找它里面的所有converter那个可以将这个数据类型(request带来参数的字符串)转换到指定的类型**(如:JavaBean 中的 Integer 类型)**
转换器总接口:@FunctionalInterface public interface Converter<S, T> S 源数据类型 T目标数据类型
-
未来我们可以给WebDataBinder里面放自己的Converter;
private static final class StringToNumber<T extends Number> implements Converter<String, T> Stirng(字符串)—>Number(数字)
-
自定义 Converter
-
实践代码
index.html
<form action="testPojo" method="post"> 姓名: <input name="userName"/> <br/> 年龄: <input name="age"/> <br/> 生日: <input name="birth"/> <br/> <!--宠物姓名:<input name="pet.name"/><br/> 宠物年龄:<input name="pet.age"/>--> 宠物:<input name="pet" value="阿猫,12"> <input type="submit" value="提交"></form>
上述如果不自定义类型转换器会报错如下:
向容器中添加一个自定义的 Converter 转换器
@Configuration(proxyBeanMethods = false)public class WebConfig implements WebMvcConfigurer { @Override public void addFormatters(FormatterRegistry registry) { registry.addConverter(new Converter<String, Pet>(){ @Override public Pet convert(String source) { if (StringUtils.hasLength(source)){ Pet pet = new Pet(); String[] split = source.split(","); pet.setName(split[0]); pet.setAge(split[1]); return pet; } return null; } }); }}
-
-
5、数据响应与内容协商
5.1、响应JSON
1、jackson.jar+@ResponseBody
-
web依赖引入后,自动导入了json场景
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId></dependency><-- 引入了上面的web场景 会自动导入json场景 --><dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-json</artifactId> <version>2.5.3</version> <scope>compile</scope> </dependency>
-
controller代码 测试
/* 测试返回json */@ResponseBody@GetMapping("testJson")public Person testJson(){ Person person = new Person(); person.setUserName("张三"); person.setAge(12); person.setBirth(new Date()); return person;}
index.html
<h2>Test Json</h2><a href="testJson">Test Json</a>
-
标注了@ResponseBody ,可以直接给前端返回json数据
2、响应json原理解析
-
返回值处理器的总接口 HandlerMethodReturnValueHandler
-
获取所有的返回值处理器,调用自己写的请求方法(前面的流程和上述请求参数注解的原理一样)
-
选择一个合适的返回值处理器方法(上述 handleReturnValue() 方法的源码)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9bWjlWSd-1642384456610)(https://gitee.com/pjgitee/note/raw/master/springboot%E7%AC%94%E8%AE%B0.assets/image-20210831155048480.png)]
-
上述找到合适的返回值处理器后,调用这个返回值处理器的处理方法
handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
-
源码如下:进行返回值的处理
-
消息转换器总接口
HttpMessageConverter
: 看是否支持将 此Class
类型的对象,转为MediaType
类型的数据。例子:
Person
对象转为JSON,或者 JSON转为Person
,这将用到MappingJackson2HttpMessageConverter
-
接上述writeWithMessageConverters()方法的内容(中间有些代码可能省略了) 重要点:MediaType 媒体(内容)类型
内容协商:浏览器默认会以头的方式告诉服务器能够接收什么样的内容类型
服务器会自己产生能够响应给浏览器的类型有哪些
双方就需要协商(循环遍历),得到一个协商结果,就是浏览器既能接收的 ,服务器又可以响应的数据类型
源码分析见 3 内容协商原理解析:
内容协商循环遍历:
循环遍历系统所有的消息转换器,找到一个能够处理当前响应类型(在上述的流程已经确定了(内容协商确定了))的消息转换器
系统默认的消息转换器支持的各种数据类型如下:
0 - 只支持Byte类型的
1 - String
2 - String
3 - Resource
4 - ResourceRegion
5 - DOMSource.class \ SAXSource.class) \ StAXSource.class \StreamSource.class \Source.class
6 - MultiValueMap
7 - true
8 - true
9 - 支持注解方式xml处理的。 -
找到了适合的消息转换器后,调用消息转换器write()方法 将其数据转换后写出去 (响应出去)
上述图片中 writeInternal()方法的源码:最终 MappingJackson2HttpMessageConverter 把对象转为JSON(利用底层的jackson的objectMapper转换的)
-
-
-
spring能支持那些返回值类型
ModelAndViewModelViewResponseEntity ResponseBodyEmitterStreamingResponseBodyHttpEntityHttpHeadersCallableDeferredResultListenableFutureCompletionStageWebAsyncTask有 @ModelAttribute 且为对象类型的@ResponseBody 注解 ---> RequestResponseBodyMethodProcessor;
-
总结:返回值处理器 ReturnValueHandler 原理
- 返回值处理器判断是否支持这种类型返回值 supportsReturnType()方法判断
- 上述判断成功,返回值处理器调用 handleReturnValue() 进行处理
- RequestResponseBodyMethodProcessor 可以处理返回值标了@ResponseBody 注解的。
- 利用 MessageConverters 消息转换器 进行处理 将数据写为json
- 内容协商(浏览器默认会以请求头的方式告诉服务器他能接受什么样的内容类型)
- 服务器最终根据自己自身的能力,决定服务器能生产出什么样内容类型的数据,
- SpringMVC会挨个遍历所有容器底层的 HttpMessageConverter ,看谁能处理?
- 得到MappingJackson2HttpMessageConverter可以将对象写为json
- 利用MappingJackson2HttpMessageConverter将对象转为json再写出去。
- 利用 MessageConverters 消息转换器 进行处理 将数据写为json
3、内容协商原理解析
-
根据客户端接(浏览器)收能力不同,返回不同媒体类型的数据。若客户端无法解析服务端返回的内容,即媒体类型未匹配,那么响应406
-
引入响应客服端(浏览器)xml的依赖
可用Postman软件分别测试返回json和xml:只需要改变请求头中Accept字段(application/json、application/xml)。
Http协议中规定的,Accept字段告诉服务器本客户端可以接收的数据类型。
<!--引入 能够响应 xml 的依赖 版本仲裁了--><dependency> <groupId>com.fasterxml.jackson.dataformat</groupId> <artifactId>jackson-dataformat-xml</artifactId></dependency>
-
引入上述依赖后 ,使用浏览器发送 请求 "testJson "请求
-
使用Postman发送 "testJson "请求
-
上述就证明了,消息转换器 会根据 客户端接收能力的不同 使用不同的消息转换器 将需要响应的数据转成客户端选择的接收的类型的数据
-
内容协商原理分析:
-
acceptableTypes = getAcceptableMediaTypes(request); 源码 获取客户端(浏览器、postMan)可以接收的媒体类型(具体可见 4 小章节:基于请求参数的内容协商原理 6 小章节:自定义convert原理解析 7 小章节:浏览器与PostMan内容协商完全适配)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1abXLSiB-1642384456622)(https://gitee.com/pjgitee/note/raw/master/springboot%E7%AC%94%E8%AE%B0.assets/image-20210831192049564.png)]
-
List producibleTypes = getProducibleMediaTypes(request, valueType, targetType); 源码。获取服务器能够处理当前返回值类型的消息转换器,获取到消息转换器后,获取消息转换器能够响应的媒体类型,存储到媒体类型的集合中,返回。
-
mediaTypesToUse 是服务器能响应的数据类型和客户端能接收的数据类型循环最佳匹配后的得到的结果(只有响应和接收的数据类型相同就添加到这个mediaTypesToUse 媒体集合中) ,然后在 获取具体的响应数据的类型 ,只要不是 * 就选择哪一个
-
-
解释 使用浏览器发送 请求 "testJson "请求 返回的是xml 数据
-
总结
- 判断当前响应头中是否已经有确定的媒体类型MediaType。
- 获取客户端(PostMan、浏览器)支持接收的内容类型。(获取客户端Accept请求头字段application/xml)(这一步在下一节有详细介绍)
- contentNegotiationManager 内容协商管理器 默认使用基于请求头的策略
- HeaderContentNegotiationStrategy 确定客户端可以接收的内容类型
- 获取服务器能够响应的媒体数据类型
- 遍历循环所有当前系统的 MessageConverter,看谁支持操作这个对象(Person)
- 找到支持操作Person的converter,把converter支持的媒体类型统计出来。
- 客户端需要application/xml,服务端有10种MediaType。
- 进行内容协商的最佳匹配媒体类型
- 遍历循环所有当前系统的 MessageConverter,用 支持 将对象转为 最佳匹配媒体类型 的converter。调用它进行转化 。MessageConverter用了两次循环比较
4、基于请求参数的内容协商原理
-
未开启基于参数请求时,默认使用的是基于头的内容协商管理策略(HeaderContentNegotiationStrategy),分析如下:
-
由于浏览器无法支持我们自己发送请求头(除了ajax添加content-type字段),所以springboot提供了我们基于请求参数的内容协商管理器
为了方便内容协商,开启基于请求参数的内容协商功能。
spring: mvc: contentnegotiation: favor-parameter: true #开启请求参数内容协商模式 默认值为false
浏览器地址输入带format参数的URL:
http://localhost:8080/test/person?format=json 或 http://localhost:8080/test/person?format=xml
-
开启基于请求参数的内容协商功能后,默认会有两个内容协商管理器
ParameterContentNegotiationStrategy(由Spring容器注入,基于参数的内容协商管理器)
HeaderContentNegotiationStrategy(基于请求头的内容协商管理器)
就会循环遍历这两个内容协商管理器,由于会优先遍历 ParameterContentNegotiationStrategy,这个就会获取参数名为 format 参数的值,来确定客户端需要接收的数据类型是什么,参数值只能是:json、或者xml
5、自定义converter
-
实现多协议数据兼容。json、xml、myPj(这个是自创的)
- @ResponseBody 响应数据出去 调用 RequestResponseBodyMethodProcessor 处理 方法返回值
- Processor 处理方法返回值。通过 MessageConverter处理服务器响应,客户端接收的协商操作
- 所有 MessageConverter 合起来可以支持各种媒体类型数据的操作(读、写)
- 内容协商找到最终的 messageConverter,将其响应出去或者读到请求方法的参数中
-
系统默认添加的一些 convert
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HgjZxYD3-1642384456630)(https://gitee.com/pjgitee/note/raw/master/springboot%E7%AC%94%E8%AE%B0.assets/image-20210901100207974.png)]
-
自定义convert步骤
-
自定义一个 convert 类,实现 消息转换器的总接口HttpMessageConverter 实现里面的方法
/* 自定义convert 实现 消息转换器的总接口 HttpMessageConverter */public class MyConvert implements HttpMessageConverter<Person> { @Override public boolean canRead(Class<?> clazz, MediaType mediaType) { //读 @Response 能读能写 读标注在方法的参数上 可以把前端(json、xml等类型的)的参数值封装到参数上 //false 代表不可以读浏览器发送过来的数据 return false; } public boolean canWrite(Class<?> clazz, MediaType mediaType) { //服务器是否可以响应出去 返回true 代表什么类型都可以响应出去 //是person类型才可以服务器才可以响应出去 application/myPj 这种媒体类型 return clazz.isAssignableFrom(Person.class); } /* 服务器要统计所有MessageConverter都能写出哪些内容类型 */ public List<MediaType> getSupportedMediaTypes() { return MediaType.parseMediaTypes("application/myPj");//增加媒体类型 accept头发送的是这个类型的接收 就会和这个进行匹配 ,匹配成功 就调用 canWrite()、canRead()方法 返回了true的对应的write()、read()方法 } //服务器获取到所有的messageConvert后,会循环判断每个messageConvert能不能读,能不能写,满足了才会调用下面的方法 /* 能读 canRead()返回true 才会调用此方法 */ public Person read(Class<? extends Person> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException { return null; } /* 能读 canWrite()返回true 才会调用此方法 */ public void write(Person person, MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException { OutputStream body = outputMessage.getBody();//获取流数据 //自定义响应格式的内容 String result = person.getUserName()+";"+person.getAge()+";"+ person.getBirth(); //响应出去出去 body.write(result.getBytes()); }}
-
向springmvc中添加这个messageConvert:SpringMVC的什么功能,一个入口给容器中添加一个
WebMvcConfigurer
,重写里面的方法即可,添加、修改springmvc的特定功能/* web的配置类 */@Configuration(proxyBeanMethods = false)public class WebConfig implements WebMvcConfigurer { /* 覆盖所有的 convert 这个方法重写 系统默认的所有messageConvert都会失效 @Override public void configureMessageConverters(List<HttpMessageConverter<?>> converters) { }*/ /* 在原有的基础上扩展一些convert */ @Override public void extendMessageConverters(List<HttpMessageConverter<?>> converters) { converters.add(new MyConvert()); //将自定义的convert添加到服务器中 服务器就可以响应 application/myPj 类型的请求了 }}
-
编写请求方法
/* 自定义 MessageConverter 1、浏览器发请求直接返回 xml [application/xml] jacksonXmlConverter 2、如果是ajax请求 返回 json [application/json] jacksonJsonConverter 3、如果硅谷app发请求,返回自定义协议数据 [appliaction/myPj] xxxxConverter 自定义响应的格式:属性值1;属性值2;属性值3;... 步骤: 1、添加自定义的MessageConverter进系统底层 2、系统底层就会统计出所有MessageConverter能操作哪些类型 3、客户端和服务器内容协商 [myPj--->myPj] */@ResponseBody@GetMapping("testMyConvert")public Person testMyConvert(){ Person person = new Person(); person.setUserName("李四"); person.setAge(6); person.setBirth(new Date()); return person;}
-
测试
-
6、自定义convert原理解析
- 找到客户端接收的类型(acceptableTypes)和服务器响应的类型(producibleTypes)之后,就开始做协商(循环遍历匹配),精确匹配后,确定响应的类型就是 application/myPj ,然后就再循环所有的messageConvert寻找能够处理当前响应类型的messageConvert,匹配到了就 调用当前messageConvert的write()方法进行格式化 最后响应出去 交个客户端
7、浏览器与PostMan内容协商完全适配
-
浏览器 通过format 指定需要响应的内容是什么,但是format的值只能为 json或者xml 如果值是其他就响应不了(如何以参数的方式进行内容协商)
ParameterContentNegotiationStrategy 内容协商器 支持 响应两种数据类型
-
改变默认浏览器请求参数的协商步骤:
-
必须开启基于请求参数的内容协商
spring: mvc: contentnegotiation: favor-parameter: true #开启请求参数内容协商模式 默认值为false
-
修改 内容协商 策略
- 第一种办法:
/* web的配置类 */@Configuration(proxyBeanMethods = false)public class WebConfig implements WebMvcConfigurer { /* 在原有的基础上扩展一些convert */ @Override public void extendMessageConverters(List<HttpMessageConverter<?>> converters) { converters.add(new MyConvert()); //将自定义的convert添加到服务器中 服务器就可以响应 application/myPj 类型的请求了 } /** * 配置内容协商选项 * 如何以参数的方式进行内容协商 */ public void configureContentNegotiation(ContentNegotiationConfigurer configurer) { Map<String, MediaType> mediaTypes = new HashMap<>(); mediaTypes.put("json", MediaType.APPLICATION_JSON);//方法参数的值为 json 映射到json媒体类型 mediaTypes.put("xml", MediaType.APPLICATION_XML);//方法参数的值为 xml 映射到xml媒体类型 mediaTypes.put("gg", MediaType.parseMediaType("application/myPj")); //方法参数的值为 gg 映射到添加自定义的媒体类型 //参数需要 Map<String, MediaType> mediaTypes ParameterContentNegotiationStrategy parameterStrategy = new ParameterContentNegotiationStrategy(mediaTypes); parameterStrategy.setParameterName("format"); //设置请球参数的名称 //参数需要 List<ContentNegotiationStrategy> configurer.strategies(Arrays.asList(parameterStrategy)); }}
-
第二种办法 配置文件中添加 内容协商策略
spring: mvc: contentnegotiation: favor-parameter: true media-types: gg: application/myPj
-
修改 内容协商策略后,ParameterContentNegotiationStrategy 内容协商器 支持 响应三种数据类型
-
-
产生的问题,使用postMan发送请求 ,不管accept头是什么数据类型,返回都是json,通过上图也看到,没有
HeaderContentNegotiationStrategy(基于请求头的内容协商管理器),所以导致,没有能够解析请求头的内容协商管理器,就会返回 /
产生的问题原因:有可能我们添加的自定义的功能会覆盖默认很多功能,导致一些默认的功能失效。
解决办法:添加的解析基于请求头的内容协商管理器
/* web的配置类 */@Configuration(proxyBeanMethods = false)public class WebConfig implements WebMvcConfigurer { /* 在原有的基础上扩展一些convert */ @Override public void extendMessageConverters(List<HttpMessageConverter<?>> converters) { converters.add(new MyConvert()); //将自定义的convert添加到服务器中 服务器就可以响应 application/myPj 类型的请求了 } /** * 配置内容协商选项 * 如何以参数的方式进行内容协商 */ public void configureContentNegotiation(ContentNegotiationConfigurer configurer) { Map<String, MediaType> mediaTypes = new HashMap<>(); mediaTypes.put("json", MediaType.APPLICATION_JSON);//方法参数的值为 json 映射到json媒体类型 mediaTypes.put("xml", MediaType.APPLICATION_XML);//方法参数的值为 xml 映射到xml媒体类型 mediaTypes.put("gg", MediaType.parseMediaType("application/myPj")); //方法参数的值为 gg 映射到添加自定义的媒体类型 //参数需要 Map<String, MediaType> mediaTypes ParameterContentNegotiationStrategy parameterStrategy = new ParameterContentNegotiationStrategy(mediaTypes); parameterStrategy.setParameterName("format"); //设置请球参数的名称 HeaderContentNegotiationStrategy headerStrategy = new HeaderContentNegotiationStrategy();//添加的解析基于请求头的内容协商管理器 //参数需要 List<ContentNegotiationStrategy> configurer.strategies(Arrays.asList(parameterStrategy,headerStrategy)); }}
-
解决后的结果
-
大家考虑,上述功能除了我们完全自定义外?SpringBoot有没有为我们提供基于配置文件的快速修改媒体类型功能?怎么配置呢?【提示:参照SpringBoot官方文档web开发内容协商章节】
spring.mvc.contentnegotiation.media-types: {gg: application/x-guigu}
spring: mvc: contentnegotiation: favor-parameter: true media-types: gg: application/myPj
5.2、视图解析器与模板引擎
SpringBoot默认不支持 JSP,需要引入第三方模板引擎技术实现页面渲染。
1、模板引擎-Thymeleaf
-
Thymeleaf官网文档使用地址:https://www.thymeleaf.org/documentation.html
- 简介:Thymeleaf 是适用于 Web 和独立环境的现代服务器端 Java 模板引擎。
- Thymeleaf的主要目标是将优雅的自然模板引入到您的开发工作流中——HTML可以在浏览器中正确显示,也可以作为静态原型,允许开发团队进行更强的协作。
- 不适合高并发的项目,适合单体项目。
-
基本语法
-
官网基础语法网址:https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html#setting-attribute-values
-
表达式
表达式名字 语法 用途 变量取值 ${…} 获取请求域、session域、对象等值 选择变量 *{…} 获取上下文对象值 消息 #{…} 获取国际化等值 链接 @{…} 生成链接 片段表达式 ~{…} jsp:include 作用,引入公共页面片段 -
字面量
- 文本值: ‘one text’ , 'Another one!'
- 数字: 0 , 34 , 3.0 , 12.3
- 布尔值: true , false
- 空值: null
- 变量: one,two,… 变量不能有空格
-
文本操作
- 字符串拼接: +
- 变量替换: |The name is ${name}|
-
数学运算
- 运算符: + , - , * , / , %
-
布尔运算
- 运算符: and , or
- 一元运算: ! , not
-
比较运算
- 比较: > , < , >= , <= ( gt , lt , ge , le )
- 等式: == , != ( eq , ne )
-
条件运算
- If-then: (if) ? (then)
- If-then-else: (if) ? (then) : (else)
- Default: (value) ?: (defaultvalue)
-
特殊操作
- 无操作: _
-
设置属性值 语法:th:attr
-
设置单个值
<form action="subscribe.html" th:attr="action=@{/subscribe}"> <fieldset> <input type="text" name="email" /> <input type="submit" value="Subscribe!" th:attr="value=#{subscribe.submit}"/> </fieldset> </form>
-
设置多个值
<img src="../../images/gtvglogo.png" th:attr="src=@{/images/gtvglogo.png},title=#{logo},alt=#{logo}" />
-
-
所有h5兼容的标签写法
https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html#setting-value-to-specific-attributes
-
迭代(循环)
<tr th:each="prod : ${prods}"> <td th:text="${prod.name}">Onions</td> <td th:text="${prod.price}">2.41</td> <td th:text="${prod.inStock}? #{true} : #{false}">yes</td> </tr> <tr th:each="prod,iterStat : ${prods}" th:class="${iterStat.odd}? 'odd'"> <td th:text="${prod.name}">Onions</td> <td th:text="${prod.price}">2.41</td> <td th:text="${prod.inStock}? #{true} : #{false}">yes</td> </tr>
-
条件运算
<a href="comments.html" th:href="@{/product/comments(prodId=${prod.id})}" th:if="${not #lists.isEmpty(prod.comments)}">view</a> <div th:switch="${user.role}"> <p th:case="'admin'">User is an administrator</p> <p th:case="#{roles.manager}">User is a manager</p> <p th:case="*">User is some other thing</p></div>
-
属性优先级
-
-
Thymeleaf的使用
-
引入Starter
<!--引入Thymeleaf的依赖--><dependency> <groupId>org.springframework.boot</groupId> <artifactId>pring-boot-starter-thymeleaf</artifactId></dependency>
-
Thymeleaf 就已经配置好了
===========ThymeleafAutoConfiguration 自动配置类==================@Configuration(proxyBeanMethods = false) //配置类@EnableConfigurationProperties(ThymeleafProperties.class) // 自动载入应用程序所需的所有Bean @ConditionalOnClass({ TemplateMode.class, SpringTemplateEngine.class })@AutoConfigureAfter({ WebMvcAutoConfiguration.class, WebFluxAutoConfiguration.class })public class ThymeleafAutoConfiguration {}
-
ThymeleafAutoConfiguration自动配置类中 自动配置好了以下东西
-
绑定了类 ThymeleafProperties,所有的设置都在该类中
-
配置好了 SpringTemplateEngine 模板引擎
-
配好了 ThymeleafViewResolver 视图解析器
视图解析器的前后缀
ThymeleafProperties 类下的属性默认值public static final String DEFAULT_PREFIX = "classpath:/templates/";//模板放置处public static final String DEFAULT_SUFFIX = ".html";//文件的后缀名
-
-
我们只需要直接开发页面
-
页面代码
controller.java
/* thymeleaf 模板引擎的测试 */ @GetMapping("testThymeleaf") public String testThymeleaf(Model model){ model.addAttribute("msg", "呵呵呵"); model.addAttribute("link", "www.baidu.com"); return "success"; //会频写前后缀 classpath:/templates/success.html }
success.html (必须引入的名称空间:xmlns:th=“http://www.thymeleaf.org”)
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <!--el表达式写法:<h1>${msg}</h1>--> <h1 th:text="${msg}">哈哈</h1><br> <a href="www.aitigui.com" th:href="${link}">去百度</a><br/> <!--以存储的值的形式显示--> <a href="www.aitigui.com" th:href="@{link}">去百度2</a><!--已存储的键表示访问链接 会动态加上键前面的字符串 如果项目有前置访问路径,也会被添加上--> </body> </html>
-
给项目添加前置访问路径,添加后访问当前项目都得加上改前置路径
server: servlet: context-path: /myPj #添加项目的前置路径
这个设置后,URL要插入
/myPj
, 如 http://localhost:8888/myPj/testThymeleaf
-
-
2、后台管理系统(Springboot-2项目)
-
表单重复提交问题
@Controller public class ThymeleafController { @GetMapping(value = {"/","/login"}) public String goToLogin(){ return "login"; } /* 直接去main页面 会导致登陆成功后,按刷新按钮 导致页面重复提交 */ /* @PostMapping("/login") public String login(User user){ return "main"; }*/ /* 登陆成功 重定向到 /main.html 请求 在跳转到main页面,如果点刷新按钮 也不会刷新当前请求 只会刷新 /main.html 该请求就直接跳转到 main页面 */ /* @PostMapping("/login") public String login(User user){ return "redirect:/main.html"; } /* 重定向防止登陆重复提交 最终去main页面的方法 这样会导致 不用登录也能去main页面 */ /* @GetMapping("/main.html") public String goToMain(){ return "main"; }*/ 上述会导致 不用登录也能去main页面
-
解决不用登录去mian页面的问题
@PostMapping("/login") public String login(User user , HttpSession session, Model model){ //判断输入的用户名和密码是否为空 密码是 123 就登陆成功 if (StringUtils.hasLength(user.getPassword()) && "123".equals(user.getPassword())){ //不为空 //把登陆的用户放到session中 session.setAttribute("loginUser", user); //跳转到主页面 return "redirect:/main.html"; }else { //为空 //保存错误性返回登录页 model.addAttribute("errorMessage", "用户名密码错误"); return "login"; } } @GetMapping("/main.html") public String goToMain(HttpSession session,Model model){ Object loginUser = session.getAttribute("loginUser"); if (loginUser!=null){ //登陆成功采访问 main页面 一般用拦截器‘过滤器做 return "main"; }else { //保存错误性返回登录页 model.addAttribute("errorMessage", "未登录"); return "login"; } }}
-
thyeleaf行内写法,直接获取某个域中的值
-
抽取公共页面的内容(左侧导航和头部)
-
抽取公共页官方文档:https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html#template-layout
-
将某段代码标记为公共页(第一种方式):使用 thymeleaf 的 th:fragment =" 公共模块名称" 属性 标记为公共标签
-
引入公共标签:使用thymeleaf 的 th:insert=“页面名称 :: 公共模块名称” th:replace=“页面名称 :: 公共模块名称” th:include=“页面名称 :: 公共模块名称”
-
将某段代码标记为公共页(第二种方式):使用 标签 的 id=" id名称" 属性 标记为公共标签
-
引入公共标签:使用 thymeleaf 的 th:insert=“页面名称 :: #id名称” th:replace=“页面名称 :: #id名称” th:include=“页面名称 :: #id名称”
-
-
循环遍历数据 使用 thymeleaf 的 th:each=“遍历单个对象的名称1,状态名(需要时在写)