Fork me on Github
欢迎来到zhang-shuai的博客
zhangshuai
晓看天色暮看云,行也思君,坐也思君

springBoot

学习要求

    • 熟悉Spring基础
    • 熟悉Maven使用

 

环境要求

 

学习资料

      • 文档不支持旧版本IE、Edge浏览器,请使用chrome或者firefox

 

第一章 SpringBoot2核心技术-基础入门

1 Spring与SpringBoot

1.1 Spring能做什么

1)  Spring的能力

 

 

 2)  Spring的生态

https://spring.io/projects/spring-boot

 

覆盖了:

web开发

数据访问

安全控制

分布式

消息服务

移动开发

批处理

......

3)  Spring5重大升级  

  • 响应式编程

1.2 为什么用SpringBoot

1)  SpringBoot优点

SpringBoot是整合Spring技术栈的一站式框架

SpringBoot是简化Spring技术栈的快速开发脚手架

  • Create stand-alone Spring applications

创建独立Spring应用

  • Embed Tomcat, Jetty or Undertow directly (no need to deploy WAR files)

内嵌web服务器

  • Provide opinionated 'starter' dependencies to simplify your build configuration

自动starter依赖,简化构建配置

  • Automatically configure Spring and 3rd party libraries whenever possible

自动配置Spring以及第三方功能

  • Provide production-ready features such as metrics, health checks, and externalized configuration

提供生产级别的监控、健康检查及外部化配置

  • Absolutely no code generation and no requirement for XML configuration

无代码生成、无需编写XML

2)  SpringBoot缺点

    • 人称版本帝,迭代快,需要时刻关注变化
    • 封装太深,内部原理复杂,不容易精通

1.3 时代背景

1)  微服务

James Lewis and Martin Fowler (2014)  提出微服务完整概念。https://martinfowler.com/microservices/
In short, the microservice architectural style is an approach to developing a single application as a suite of small services,
each running in its own process and communicating with lightweight mechanisms, often an HTTP resource API.
These services are built around business capabilities and independently deployable by fully automated deployment machinery.
There is a bare minimum of centralized management of these services, which may be written in different programming languages and use different data storage technologies.-- James Lewis and Martin Fowler (2014)
  1. 微服务是一种架构风格
  2. 一个应用拆分为一组小型服务
  3. 每个服务运行在自己的进程内,也就是可独立部署和升级
  4. 服务之间使用轻量级HTTP交互
  5. 服务围绕业务功能拆分
  6. 可以由全自动部署机制独立部署
  7. 去中心化,服务自治。服务可以使用不同的语言、不同的存储技术

2)  分布式

 

 

 分布式的困难

  1. 远程调用
  2. 服务发现
  3. 负载均衡
  4. 服务容错
  5. 配置管理
  6. 服务监控
  7. 链路追踪
  8. 日志管理
  9. 任务调度
  10. ......

分布式的解决

  1. SpringBoot + SpringCloud

 

 

 3)  云原生

原生应用如何上云。 Cloud Native

上云的困难:

  1. 服务自愈
  2. 弹性伸缩
  3. 服务隔离
  4. 自动化部署
  5. 灰度发布
  6. 流量治理
  7. ......

 上云的解决:

 

 

 1.4 如何学习SpringBoot

1)  官网文档架构

 

 

   查看版本新特性:https://github.com/spring-projects/spring-boot/wiki#release-notes

2 SpringBoot2入门

2.1 系统配置

    • Java 8 & 兼容java14 .
    • Maven 3.3+
    • idea 2019.1.2
 1 <mirrors>
 2       <mirror>
 3         <id>nexus-aliyun</id>
 4         <mirrorOf>central</mirrorOf>
 5         <name>Nexus aliyun</name>
 6         <url>http://maven.aliyun.com/nexus/content/groups/public</url>
 7       </mirror>
 8   </mirrors>
 9  
10   <profiles>
11          <profile>
12               <id>jdk-1.8</id>
13               <activation>
14                 <activeByDefault>true</activeByDefault>
15                 <jdk>1.8</jdk>
16               </activation>
17               <properties>
18                 <maven.compiler.source>1.8</maven.compiler.source>
19                 <maven.compiler.target>1.8</maven.compiler.target>
20                 <maven.compiler.compilerVersion>1.8</maven.compiler.compilerVersion>
21               </properties>
22          </profile>
23   </profiles>
maven配置

2.2 HelloWorld

需求:浏览发送/hello请求,响应 Hello,Spring Boot 2

 

1)  创建maven工程

 

  2)  引入依赖

 1     <parent>
 2         <groupId>org.springframework.boot</groupId>
 3         <artifactId>spring-boot-starter-parent</artifactId>
 4         <version>2.3.4.RELEASE</version>
 5     </parent>
 6 
 7 
 8     <dependencies>
 9         <dependency>
10             <groupId>org.springframework.boot</groupId>
11             <artifactId>spring-boot-starter-web</artifactId>
12         </dependency>
13 
14     </dependencies>
pom.xml

3)  创建主程序

 1 /**
 2  * 主程序类
 3  * @SpringBootApplication:这是一个SpringBoot应用
 4  */
 5 @SpringBootApplication
 6 public class MainApplication {
 7 
 8     public static void main(String[] args) {
 9         SpringApplication.run(MainApplication.class,args);
10     }
11 }
MainApplication

4)  编写业务

 1 @RestController
 2 public class HelloController {
 3 
 4 
 5     @RequestMapping("/hello")
 6     public String handle01(){
 7         return "Hello, Spring Boot 2!";
 8     }
 9 
10 
11 }
HelloController

5)  测试

直接运行主程序main方法

其他:

更改配置(application.properties)

      server.port=8888

打包部署:把项目打成jar包,直接在目标服务器执行即可。

     <build>

        <plugins>

            <plugin>

                <groupId>org.springframework.boot</groupId>

                <artifactId>spring-boot-maven-plugin</artifactId>

            </plugin>

        </plugins>

    </build>
pom.xml

 

注意点:  取消掉cmd的快速编辑模式

3 自动配置原理

3.1 SpringBoot特点

3.1.1 依赖管理

 1 <--依赖管理   --> 
 2 <parent>
 3         <groupId>org.springframework.boot</groupId>
 4         <artifactId>spring-boot-starter-parent</artifactId>
 5         <version>2.3.4.RELEASE</version>
 6 </parent>
 7 
 8 <-- 他的父项目 -->
 9  <parent>
10     <groupId>org.springframework.boot</groupId>
11     <artifactId>spring-boot-dependencies</artifactId>
12     <version>2.3.4.RELEASE</version>
13   </parent>
14 
15 <--几乎声明了所有开发中常用的依赖的版本号,自动版本仲裁机制 -->
16 
17 <--1、见到很多 spring-boot-starter-* : *就某种场景
18     2、只要引入starter,这个场景的所有常规需要的依赖我们都自动引入
19     3、SpringBoot所有支持的场景
20     https://docs.spring.io/spring-boot/docs/current/reference/html/using-spring-boot.html#using-boot-starter
21     4、见到的  *-spring-boot-starter: 第三方为我们提供的简化开发的场景启动器。
22     5、所有场景启动器最底层的依赖 -->
23 <dependency>
24   <groupId>org.springframework.boot</groupId>
25   <artifactId>spring-boot-starter</artifactId>
26   <version>2.3.4.RELEASE</version>
27   <scope>compile</scope>
28 </dependency>    
依赖管理
无需关注版本号,自动版本仲裁
  1、引入依赖默认都可以不写版本   2、引入非版本仲裁的jar,要写版本号。

可以修改默认版本号:
  1、查看spring-boot-dependencies里面规定当前依赖的版本 用的 key。
  2、在当前项目里面重写配置
      <properties>
          <mysql.version>5.1.43</mysql.version>
      </properties>

3.1.2 自动配置

    • 自动配好Tomcat
      • 引入Tomcat依赖。
      • 配置Tomcat
        1     <dependency>
        2        <groupId>org.springframework.boot</groupId>
        3        <artifactId>spring-boot-starter-tomcat</artifactId>
        4        <version>2.3.4.RELEASE</version>
        5        <scope>compile</scope>
        6     </dependency>
        tomcat
    • 自动配好SpringMVC
      • 引入SpringMVC全套组件
      • 自动配好SpringMVC常用组件(功能)
    • 自动配好Web常见功能,如:字符编码问题
      • SpringBoot帮我们配置好了所有web开发的常见场景
    • 默认的包结构
      • 主程序所在包及其下面的所有子包里面的组件都会被默认扫描进来
      • 无需以前的包扫描配置
      • 想要改变扫描路径,@SpringBootApplication(scanBasePackages="com.atguigu")
        • 或者@ComponentScan 指定扫描路径
          1 @SpringBootApplication
          2 
          3 等同于
          4 
          5 @SpringBootConfiguration
          6 
          7 @EnableAutoConfiguration
          8 
          9 @ComponentScan("com.atguigu.boot")
          注解
    • 各种配置拥有默认值
      • 默认配置最终都是映射到某个类上,如:MultipartProperties
      • 配置文件的值最终会绑定每个类上,这个类会在容器中创建对象
    • 按需加载所有自动配置项
      • 非常多的starter
      • 引入了哪些场景这个场景的自动配置才会开启
      • SpringBoot所有的自动配置功能都在 spring-boot-autoconfigure 包里面

3.2 容器功能

3.2.1 组件添加

1)@Configuration

基本使用:Full模式与Lite模式

最佳实战:

配置 类组件之间无依赖关系,用Lite模式加速容器启动过程,减少判断

配置类组件之间有依赖关系,方法会被调用得到之前单实例组件,用Full模式

  1 #############################Configuration使用示例######################################################
  2 
  3 /**
  4 
  5  * 1、配置类里面使用@Bean标注在方法上给容器注册组件,默认也是单实例的
  6 
  7  * 2、配置类本身也是组件
  8 
  9  * 3、proxyBeanMethods:代理bean的方法
 10 
 11  *      Full(proxyBeanMethods = true)、【保证每个@Bean方法被调用多少次返回的组件都是单实例的】
 12 
 13  *      Lite(proxyBeanMethods = false)【每个@Bean方法被调用多少次返回的组件都是新创建的】
 14 
 15  *      组件依赖必须使用Full模式默认。其他默认是否Lite模式
 16 
 17  *
 18 
 19  *
 20 
 21  *
 22 
 23  */
 24 
 25 @Configuration(proxyBeanMethods = false) //告诉SpringBoot这是一个配置类 == 配置文件
 26 
 27 public class MyConfig {
 28 
 29 
 30     /**
 31 
 32      * Full:外部无论对配置类中的这个组件注册方法调用多少次获取的都是之前注册容器中的单实例对象
 33 
 34      * @return
 35 
 36      */
 37 
 38     @Bean //给容器中添加组件。以方法名作为组件的id。返回类型就是组件类型。返回的值,就是组件在容器中的实例
 39 
 40     public User user01(){
 41 
 42         User zhangsan = new User("zhangsan", 18);
 43 
 44         //user组件依赖了Pet组件
 45 
 46         zhangsan.setPet(tomcatPet());
 47 
 48         return zhangsan;
 49 
 50     }
 51 
 52 
 53     @Bean("tom")
 54 
 55     public Pet tomcatPet(){
 56 
 57         return new Pet("tomcat");
 58 
 59     }
 60 
 61 }
 62 
 63 
 64 
 65 ################################@Configuration测试代码如下########################################
 66 
 67 @SpringBootConfiguration
 68 
 69 @EnableAutoConfiguration
 70 
 71 @ComponentScan("com.atguigu.boot")
 72 
 73 public class MainApplication {
 74 
 75 
 76     public static void main(String[] args) {
 77 
 78         //1、返回我们IOC容器
 79 
 80         ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);
 81 
 82 
 83         //2、查看容器里面的组件
 84 
 85         String[] names = run.getBeanDefinitionNames();
 86 
 87         for (String name : names) {
 88 
 89             System.out.println(name);
 90 
 91         }
 92 
 93 
 94         //3、从容器中获取组件
 95 
 96 
 97         Pet tom01 = run.getBean("tom", Pet.class);
 98 
 99 
100         Pet tom02 = run.getBean("tom", Pet.class);
101 
102 
103         System.out.println("组件:"+(tom01 == tom02));
104 
105 
106 
107         //4、com.atguigu.boot.config.MyConfig$$EnhancerBySpringCGLIB$$51f1e1ca@1654a892
108 
109         MyConfig bean = run.getBean(MyConfig.class);
110 
111         System.out.println(bean);
112 
113 
114         //如果@Configuration(proxyBeanMethods = true)代理对象调用方法。SpringBoot总会检查这个组件是否在容器中有。
115 
116         //保持组件单实例
117 
118         User user = bean.user01();
119 
120         User user1 = bean.user01();
121 
122         System.out.println(user == user1);
123 
124 
125 
126         User user01 = run.getBean("user01", User.class);
127 
128         Pet tom = run.getBean("tom", Pet.class);
129 
130 
131         System.out.println("用户的宠物:"+(user01.getPet() == tom));
132 
133 
134 
135 
136     }
137 
138 }
@Configuration

2)@Bean、@Component、@Controller、@Service、@Repository

通过@Bean注解可以使该类注册到容器

1     @Bean
2     public User user(){
3         return new User("zhangsan", 18);
4     }
5 
6     @Bean("tom")
7     public Pet pet(){
8         return new Pet("tom");
9     }
@Bean

#############################Configuration使用示例###################################################### /** * 1、配置类里面使用@Bean标注在方法上给容器注册组件,默认也是单实例的 * 2、配置类本身也是组件 * 3、proxyBeanMethods:代理bean的方法 * Full(proxyBeanMethods = true)、【保证每个@Bean方法被调用多少次返回的组件都是单实例的】 * Lite(proxyBeanMethods = false)【每个@Bean方法被调用多少次返回的组件都是新创建的】 * 组件依赖必须使用Full模式默认。其他默认是否Lite模式 * * * */ @Configuration(proxyBeanMethods = false) //告诉SpringBoot这是一个配置类 == 配置文件 public class MyConfig { /** * Full:外部无论对配置类中的这个组件注册方法调用多少次获取的都是之前注册容器中的单实例对象 * @return */ @Bean //给容器中添加组件。以方法名作为组件的id。返回类型就是组件类型。返回的值,就是组件在容器中的实例 public User user01(){ User zhangsan = new User("zhangsan", 18); //user组件依赖了Pet组件 zhangsan.setPet(tomcatPet()); return zhangsan; } @Bean("tom") public Pet tomcatPet(){ return new Pet("tomcat"); } } ################################@Configuration测试代码如下######################################## @SpringBootConfiguration @EnableAutoConfiguration @ComponentScan("com.atguigu.boot") public class MainApplication { public static void main(String[] args) { //1、返回我们IOC容器 ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args); //2、查看容器里面的组件 String[] names = run.getBeanDefinitionNames(); for (String name : names) { System.out.println(name); } //3、从容器中获取组件 Pet tom01 = run.getBean("tom", Pet.class); Pet tom02 = run.getBean("tom", Pet.class); System.out.println("组件:"+(tom01 == tom02)); //4、com.atguigu.boot.config.MyConfig$$EnhancerBySpringCGLIB$$51f1e1ca@1654a892 MyConfig bean = run.getBean(MyConfig.class); System.out.println(bean); //如果@Configuration(proxyBeanMethods = true)代理对象调用方法。SpringBoot总会检查这个组件是否在容器中有。 //保持组件单实例 User user = bean.user01(); User user1 = bean.user01(); System.out.println(user == user1); User user01 = run.getBean("user01", User.class); Pet tom = run.getBean("tom", Pet.class); System.out.println("用户的宠物:"+(user01.getPet() == tom)); } }

  通过@Component、@Controller、@Service、@Repository注解可以使该类注册到容器

1 @Controller
2 public class HelloCoontroleer {
3 
4     @RequestMapping("/hello")
5     public String handler() {
6         return "forward:success";
7     }
8 }
@Controller

3)@ComponentScan、@Import

  当这个组件(普通类:未被注解)不在容器中,可以通过@Import使该组件注册到容器

 1 // @Import({User.class, DBHelper.class})
 2 //    给容器中自动创建出这两个类型的组件、默认组件的名字就是全类名
 3 
 4 @Import({User.class, DBHelper.class})
 5 
 6 @Configuration(proxyBeanMethods = false) //告诉SpringBoot这是一个配置类 == 配置文件
 7 
 8 public class MyConfig {
 9 
10 }
@Import
 1 @Import({User.class,ImportSelectorTest.class})
 2 public class MyConfig {
 3 
 4 }
 5 
 6 public class ImportSelectorTest implements ImportSelector {
 7     @Override
 8     public String[] selectImports(AnnotationMetadata annotationMetadata) {
 9         // 可以把color注册到容器
10         return new String[]{"com.atguigu.boot.bean.Color"};
11     }
12 }
ImportSelector接口
 1 @Import({ImportSelectorTest.class,ImportBeanDefinitionRegistrarTest .class})
 2 public class MyConfig {
 3 }
 4 
 5 public class ImportBeanDefinitionRegistrarTest implements ImportBeanDefinitionRegistrar {
 6     @Override
 7     public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry, BeanNameGenerator importBeanNameGenerator) {
 8         registry.registerBeanDefinition("rainBow", new RootBeanDefinition(RainBow.class));
 9     }
10 }
ImportBeanDefinitionRegistrar 接口

@Import 高级用法: https://www.bilibili.com/video/BV1gW411W7wy?p=8

4) @SpringBootApplication

 1 @SpringBootApplication
 2 public class MainApplication {
 3     public static void main(String[] args) {
 4         ConfigurableApplicationContext applicationContext = SpringApplication.run(MainApplication.class, args);
 5         String[] names = applicationContext.getBeanDefinitionNames();
 6         for (String name : names) {
 7 
 8             if (name.startsWith("u")){
 9                 System.out.println(name);
10             }
11 
12         }
13     }
14 }
@SpringBootApplication

5)@Conditional

条件装配:满足Conditional指定的条件,则进行组件注入

 

 1 =====================测试条件装配==========================
 2 
 3 @Configuration(proxyBeanMethods = false) //告诉SpringBoot这是一个配置类 == 配置文件
 4 
 5 //@ConditionalOnBean(name = "tom")
 6 
 7 @ConditionalOnMissingBean(name = "tom")
 8 
 9 public class MyConfig {
10 
11 
12 
13     /**
14 
15      * Full:外部无论对配置类中的这个组件注册方法调用多少次获取的都是之前注册容器中的单实例对象
16 
17      * @return
18 
19      */
20 
21 
22     @Bean //给容器中添加组件。以方法名作为组件的id。返回类型就是组件类型。返回的值,就是组件在容器中的实例
23 
24     public User user01(){
25 
26         User zhangsan = new User("zhangsan", 18);
27 
28         //user组件依赖了Pet组件
29 
30         zhangsan.setPet(tomcatPet());
31 
32         return zhangsan;
33 
34     }
35 
36 
37     @Bean("tom22")
38 
39     public Pet tomcatPet(){
40 
41         return new Pet("tomcat");
42 
43     }
44 
45 }
46 
47 
48 public static void main(String[] args) {
49 
50         //1、返回我们IOC容器
51 
52         ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);
53 
54 
55         //2、查看容器里面的组件
56 
57         String[] names = run.getBeanDefinitionNames();
58 
59         for (String name : names) {
60 
61             System.out.println(name);
62 
63         }
64 
65 
66         boolean tom = run.containsBean("tom");
67 
68         System.out.println("容器中Tom组件:"+tom);
69 
70 
71         boolean user01 = run.containsBean("user01");
72 
73         System.out.println("容器中user01组件:"+user01);
74 
75 
76         boolean tom22 = run.containsBean("tom22");
77 
78         System.out.println("容器中tom22组件:"+tom22);
79 
80 
81     }
@Conditional

3.2.2 原生配置文件引入

1)  @ImportResource

 1 ======================beans.xml=========================
 2 
 3 <?xml version="1.0" encoding="UTF-8"?>
 4 
 5 <beans xmlns="http://www.springframework.org/schema/beans"
 6 
 7        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 8 
 9        xmlns:context="http://www.springframework.org/schema/context"
10 
11        xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
12 
13 
14     <bean id="haha" class="com.atguigu.boot.bean.User">
15 
16         <property name="name" value="zhangsan"></property>
17 
18         <property name="age" value="18"></property>
19 
20     </bean>
21 
22 
23     <bean id="hehe" class="com.atguigu.boot.bean.Pet">
24 
25         <property name="name" value="tomcat"></property>
26 
27     </bean>
28 
29 </beans>
beans.xml
1 @ImportResource("classpath:beans.xml")
2 public class MyConfig {}
3 
4 ======================测试=================
5         boolean haha = run.containsBean("haha");
6         boolean hehe = run.containsBean("hehe");
7         System.out.println("haha:"+haha);//true
8         System.out.println("hehe:"+hehe);//true
@ImportResource

3.2.3 配置绑定

如何使用Java读取到properties文件中的内容,并且把它封装到JavaBean中,以供随时使用;

 1 public class getProperties {
 2 
 3      public static void main(String[] args) throws FileNotFoundException, IOException {
 4 
 5          Properties pps = new Properties();
 6 
 7          pps.load(new FileInputStream("a.properties"));
 8 
 9          Enumeration enum1 = pps.propertyNames();//得到配置文件的名字
10 
11          while(enum1.hasMoreElements()) {
12 
13              String strKey = (String) enum1.nextElement();
14 
15              String strValue = pps.getProperty(strKey);
16 
17              System.out.println(strKey + "=" + strValue);
18 
19              //封装到JavaBean。
20 
21          }
22 
23      }
24 
25  }
Properties

1)  @Component + @ConfigurationProperties

 1 /**
 2  * 只有在容器中的组件,才会拥有SpringBoot提供的强大功能
 3  */
 4 @Component
 5 @ConfigurationProperties(prefix = "mycar")
 6 public class Car {
 7 
 8     private String brand;
 9     private Integer price;
10 
11     public String getBrand() {
12         return brand;
13     }
14 
15     public void setBrand(String brand) {
16         this.brand = brand;
17     }
18 
19     public Integer getPrice() {
20         return price;
21     }
22 
23     public void setPrice(Integer price) {
24         this.price = price;
25     }
26 
27     @Override
28     public String toString() {
29         return "Car{" +
30                 "brand='" + brand + '\'' +
31                 ", price=" + price +
32                 '}';
33     }
34 }
@ConfigurationProperties

2)  @EnableConfigurationProperties + @ConfigurationProperties

1 @EnableConfigurationProperties(Car.class)
2 
3 //1、开启Car配置绑定功能
4 
5 //2、把这个Car这个组件自动注册到容器中
6 
7 public class MyConfig {
8 
9 }
@EnableConfigurationProperties

3.3 自动配置原理

3.3.1 引导加载自动配置类

1 @SpringBootConfiguration
2 
3 @EnableAutoConfiguration
4 
5 @ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
6 
7         @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
8 
9 public @interface SpringBootApplication{}
@SpringBootApplication

1)  @SpringBootConfiguration

  • @Configuration。代表当前是一个配置类

2)  @ComponentScan

  • 指定扫描哪些,Spring注解;

3)  @EnableAutoConfiguration

1 @AutoConfigurationPackage
2 
3 @Import(AutoConfigurationImportSelector.class)
4 
5 public @interface EnableAutoConfiguration {}
@EnableAutoConfiguration
  • @AutoConfigurationPackage:自动配置包?指定了默认的包规则
1 @Import(AutoConfigurationPackages.Registrar.class)  //给容器中导入一个组件
2 
3 public @interface AutoConfigurationPackage {}
4 
5 
6 //利用Registrar给容器中导入一系列组件
7 
8 //将指定的一个包下的所有组件导入进来?MainApplication 所在包下。
@AutoConfigurationPackage
 1 static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
 2 
 3         @Override
 4         public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
 5             register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0]));
 6         }
 7 
 8         @Override
 9         public Set<Object> determineImports(AnnotationMetadata metadata) {
10             return Collections.singleton(new PackageImports(metadata));
11         }
12 
13     }
Registrar
  • @Import(AutoConfigurationImportSelector.class)
 1 1、利用getAutoConfigurationEntry(annotationMetadata);给容器中批量导入一些组件
 2 
 3 2、调用List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes)获取到所有需要导入到容器中的配置类
 4 
 5 3、利用工厂加载 Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader);得到所有的组件
 6 
 7 4、从META-INF/spring.factories位置来加载一个文件。
 8 
 9     默认扫描我们当前系统里面所有META-INF/spring.factories位置的文件
10 
11     spring-boot-autoconfigure-2.3.4.RELEASE.jar包里面也有META-INF/spring.factories
@Import
 1 public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
 2         ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
 3 
 4 @Override
 5     public String[] selectImports(AnnotationMetadata annotationMetadata) {
 6         if (!isEnabled(annotationMetadata)) {
 7             return NO_IMPORTS;
 8         }
 9         AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
10         return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
11     }
12 
13 }
AutoConfigurationImportSelector
 1 protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
 2         if (!isEnabled(annotationMetadata)) {
 3             return EMPTY_ENTRY;
 4         }
 5         AnnotationAttributes attributes = getAttributes(annotationMetadata);
 6         List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
 7         configurations = removeDuplicates(configurations);
 8         Set<String> exclusions = getExclusions(annotationMetadata, attributes);
 9         checkExcludedClasses(configurations, exclusions);
10         configurations.removeAll(exclusions);
11         configurations = getConfigurationClassFilter().filter(configurations);
12         fireAutoConfigurationImportEvents(configurations, exclusions);
13         return new AutoConfigurationEntry(configurations, exclusions);
14     }
getAutoConfigurationEntry
 1 protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
 2         List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
 3                 getBeanClassLoader());
 4         Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you "
 5                 + "are using a custom packaging, make sure that file is correct.");
 6         return configurations;
 7     }
 8 
 9 
10 public final class SpringFactoriesLoader {
11 
12 public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
13         String factoryTypeName = factoryType.getName();
14         return (List)loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
15     }
16 
17     private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
18         MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader);
19         if (result != null) {
20             return result;
21         } else {
22             try {
23                 Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
24                 LinkedMultiValueMap result = new LinkedMultiValueMap();
25 
26                 while(urls.hasMoreElements()) {
27                     URL url = (URL)urls.nextElement();
28                     UrlResource resource = new UrlResource(url);
29                     Properties properties = PropertiesLoaderUtils.loadProperties(resource);
30                     Iterator var6 = properties.entrySet().iterator();
31 
32                     while(var6.hasNext()) {
33                         Entry<?, ?> entry = (Entry)var6.next();
34                         String factoryTypeName = ((String)entry.getKey()).trim();
35                         String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
36                         int var10 = var9.length;
37 
38                         for(int var11 = 0; var11 < var10; ++var11) {
39                             String factoryImplementationName = var9[var11];
40                             result.add(factoryTypeName, factoryImplementationName.trim());
41                         }
42                     }
43                 }
44 
45                 cache.put(classLoader, result);
46                 return result;
47             } catch (IOException var13) {
48                 throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13);
49             }
50         }
51     }
52 }
configurations

 

 

  1 文件里面写死了spring-boot一启动就要给容器中加载的所有配置类
  2 
  3 spring-boot-autoconfigure-2.3.4.RELEASE.jar/META-INF/spring.factories
  4 
  5 # Auto Configure
  6 
  7 org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  8 
  9 org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
 10 
 11 org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
 12 
 13 org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
 14 
 15 org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
 16 
 17 org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
 18 
 19 org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\
 20 
 21 org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\
 22 
 23 org.springframework.boot.autoconfigure.context.LifecycleAutoConfiguration,\
 24 
 25 org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration,\
 26 
 27 org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration,\
 28 
 29 org.springframework.boot.autoconfigure.couchbase.CouchbaseAutoConfiguration,\
 30 
 31 org.springframework.boot.autoconfigure.dao.PersistenceExceptionTranslationAutoConfiguration,\
 32 
 33 org.springframework.boot.autoconfigure.data.cassandra.CassandraDataAutoConfiguration,\
 34 
 35 org.springframework.boot.autoconfigure.data.cassandra.CassandraReactiveDataAutoConfiguration,\
 36 
 37 org.springframework.boot.autoconfigure.data.cassandra.CassandraReactiveRepositoriesAutoConfiguration,\
 38 
 39 org.springframework.boot.autoconfigure.data.cassandra.CassandraRepositoriesAutoConfiguration,\
 40 
 41 org.springframework.boot.autoconfigure.data.couchbase.CouchbaseDataAutoConfiguration,\
 42 
 43 org.springframework.boot.autoconfigure.data.couchbase.CouchbaseReactiveDataAutoConfiguration,\
 44 
 45 org.springframework.boot.autoconfigure.data.couchbase.CouchbaseReactiveRepositoriesAutoConfiguration,\
 46 
 47 org.springframework.boot.autoconfigure.data.couchbase.CouchbaseRepositoriesAutoConfiguration,\
 48 
 49 org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchDataAutoConfiguration,\
 50 
 51 org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchRepositoriesAutoConfiguration,\
 52 
 53 org.springframework.boot.autoconfigure.data.elasticsearch.ReactiveElasticsearchRepositoriesAutoConfiguration,\
 54 
 55 org.springframework.boot.autoconfigure.data.elasticsearch.ReactiveElasticsearchRestClientAutoConfiguration,\
 56 
 57 org.springframework.boot.autoconfigure.data.jdbc.JdbcRepositoriesAutoConfiguration,\
 58 
 59 org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration,\
 60 
 61 org.springframework.boot.autoconfigure.data.ldap.LdapRepositoriesAutoConfiguration,\
 62 
 63 org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration,\
 64 
 65 org.springframework.boot.autoconfigure.data.mongo.MongoReactiveDataAutoConfiguration,\
 66 
 67 org.springframework.boot.autoconfigure.data.mongo.MongoReactiveRepositoriesAutoConfiguration,\
 68 
 69 org.springframework.boot.autoconfigure.data.mongo.MongoRepositoriesAutoConfiguration,\
 70 
 71 org.springframework.boot.autoconfigure.data.neo4j.Neo4jDataAutoConfiguration,\
 72 
 73 org.springframework.boot.autoconfigure.data.neo4j.Neo4jRepositoriesAutoConfiguration,\
 74 
 75 org.springframework.boot.autoconfigure.data.solr.SolrRepositoriesAutoConfiguration,\
 76 
 77 org.springframework.boot.autoconfigure.data.r2dbc.R2dbcDataAutoConfiguration,\
 78 
 79 org.springframework.boot.autoconfigure.data.r2dbc.R2dbcRepositoriesAutoConfiguration,\
 80 
 81 org.springframework.boot.autoconfigure.data.r2dbc.R2dbcTransactionManagerAutoConfiguration,\
 82 
 83 org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration,\
 84 
 85 org.springframework.boot.autoconfigure.data.redis.RedisReactiveAutoConfiguration,\
 86 
 87 org.springframework.boot.autoconfigure.data.redis.RedisRepositoriesAutoConfiguration,\
 88 
 89 org.springframework.boot.autoconfigure.data.rest.RepositoryRestMvcAutoConfiguration,\
 90 
 91 org.springframework.boot.autoconfigure.data.web.SpringDataWebAutoConfiguration,\
 92 
 93 org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchRestClientAutoConfiguration,\
 94 
 95 org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration,\
 96 
 97 org.springframework.boot.autoconfigure.freemarker.FreeMarkerAutoConfiguration,\
 98 
 99 org.springframework.boot.autoconfigure.groovy.template.GroovyTemplateAutoConfiguration,\
100 
101 org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration,\
102 
103 org.springframework.boot.autoconfigure.h2.H2ConsoleAutoConfiguration,\
104 
105 org.springframework.boot.autoconfigure.hateoas.HypermediaAutoConfiguration,\
106 
107 org.springframework.boot.autoconfigure.hazelcast.HazelcastAutoConfiguration,\
108 
109 org.springframework.boot.autoconfigure.hazelcast.HazelcastJpaDependencyAutoConfiguration,\
110 
111 org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration,\
112 
113 org.springframework.boot.autoconfigure.http.codec.CodecsAutoConfiguration,\
114 
115 org.springframework.boot.autoconfigure.influx.InfluxDbAutoConfiguration,\
116 
117 org.springframework.boot.autoconfigure.info.ProjectInfoAutoConfiguration,\
118 
119 org.springframework.boot.autoconfigure.integration.IntegrationAutoConfiguration,\
120 
121 org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration,\
122 
123 org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\
124 
125 org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration,\
126 
127 org.springframework.boot.autoconfigure.jdbc.JndiDataSourceAutoConfiguration,\
128 
129 org.springframework.boot.autoconfigure.jdbc.XADataSourceAutoConfiguration,\
130 
131 org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration,\
132 
133 org.springframework.boot.autoconfigure.jms.JmsAutoConfiguration,\
134 
135 org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration,\
136 
137 org.springframework.boot.autoconfigure.jms.JndiConnectionFactoryAutoConfiguration,\
138 
139 org.springframework.boot.autoconfigure.jms.activemq.ActiveMQAutoConfiguration,\
140 
141 org.springframework.boot.autoconfigure.jms.artemis.ArtemisAutoConfiguration,\
142 
143 org.springframework.boot.autoconfigure.jersey.JerseyAutoConfiguration,\
144 
145 org.springframework.boot.autoconfigure.jooq.JooqAutoConfiguration,\
146 
147 org.springframework.boot.autoconfigure.jsonb.JsonbAutoConfiguration,\
148 
149 org.springframework.boot.autoconfigure.kafka.KafkaAutoConfiguration,\
150 
151 org.springframework.boot.autoconfigure.availability.ApplicationAvailabilityAutoConfiguration,\
152 
153 org.springframework.boot.autoconfigure.ldap.embedded.EmbeddedLdapAutoConfiguration,\
154 
155 org.springframework.boot.autoconfigure.ldap.LdapAutoConfiguration,\
156 
157 org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration,\
158 
159 org.springframework.boot.autoconfigure.mail.MailSenderAutoConfiguration,\
160 
161 org.springframework.boot.autoconfigure.mail.MailSenderValidatorAutoConfiguration,\
162 
163 org.springframework.boot.autoconfigure.mongo.embedded.EmbeddedMongoAutoConfiguration,\
164 
165 org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration,\
166 
167 org.springframework.boot.autoconfigure.mongo.MongoReactiveAutoConfiguration,\
168 
169 org.springframework.boot.autoconfigure.mustache.MustacheAutoConfiguration,\
170 
171 org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration,\
172 
173 org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration,\
174 
175 org.springframework.boot.autoconfigure.r2dbc.R2dbcAutoConfiguration,\
176 
177 org.springframework.boot.autoconfigure.rsocket.RSocketMessagingAutoConfiguration,\
178 
179 org.springframework.boot.autoconfigure.rsocket.RSocketRequesterAutoConfiguration,\
180 
181 org.springframework.boot.autoconfigure.rsocket.RSocketServerAutoConfiguration,\
182 
183 org.springframework.boot.autoconfigure.rsocket.RSocketStrategiesAutoConfiguration,\
184 
185 org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration,\
186 
187 org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration,\
188 
189 org.springframework.boot.autoconfigure.security.servlet.SecurityFilterAutoConfiguration,\
190 
191 org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration,\
192 
193 org.springframework.boot.autoconfigure.security.reactive.ReactiveUserDetailsServiceAutoConfiguration,\
194 
195 org.springframework.boot.autoconfigure.security.rsocket.RSocketSecurityAutoConfiguration,\
196 
197 org.springframework.boot.autoconfigure.security.saml2.Saml2RelyingPartyAutoConfiguration,\
198 
199 org.springframework.boot.autoconfigure.sendgrid.SendGridAutoConfiguration,\
200 
201 org.springframework.boot.autoconfigure.session.SessionAutoConfiguration,\
202 
203 org.springframework.boot.autoconfigure.security.oauth2.client.servlet.OAuth2ClientAutoConfiguration,\
204 
205 org.springframework.boot.autoconfigure.security.oauth2.client.reactive.ReactiveOAuth2ClientAutoConfiguration,\
206 
207 org.springframework.boot.autoconfigure.security.oauth2.resource.servlet.OAuth2ResourceServerAutoConfiguration,\
208 
209 org.springframework.boot.autoconfigure.security.oauth2.resource.reactive.ReactiveOAuth2ResourceServerAutoConfiguration,\
210 
211 org.springframework.boot.autoconfigure.solr.SolrAutoConfiguration,\
212 
213 org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration,\
214 
215 org.springframework.boot.autoconfigure.task.TaskSchedulingAutoConfiguration,\
216 
217 org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration,\
218 
219 org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration,\
220 
221 org.springframework.boot.autoconfigure.transaction.jta.JtaAutoConfiguration,\
222 
223 org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration,\
224 
225 org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration,\
226 
227 org.springframework.boot.autoconfigure.web.embedded.EmbeddedWebServerFactoryCustomizerAutoConfiguration,\
228 
229 org.springframework.boot.autoconfigure.web.reactive.HttpHandlerAutoConfiguration,\
230 
231 org.springframework.boot.autoconfigure.web.reactive.ReactiveWebServerFactoryAutoConfiguration,\
232 
233 org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration,\
234 
235 org.springframework.boot.autoconfigure.web.reactive.error.ErrorWebFluxAutoConfiguration,\
236 
237 org.springframework.boot.autoconfigure.web.reactive.function.client.ClientHttpConnectorAutoConfiguration,\
238 
239 org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientAutoConfiguration,\
240 
241 org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration,\
242 
243 org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration,\
244 
245 org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration,\
246 
247 org.springframework.boot.autoconfigure.web.servlet.HttpEncodingAutoConfiguration,\
248 
249 org.springframework.boot.autoconfigure.web.servlet.MultipartAutoConfiguration,\
250 
251 org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration,\
252 
253 org.springframework.boot.autoconfigure.websocket.reactive.WebSocketReactiveAutoConfiguration,\
254 
255 org.springframework.boot.autoconfigure.websocket.servlet.WebSocketServletAutoConfiguration,\
256 
257 org.springframework.boot.autoconfigure.websocket.servlet.WebSocketMessagingAutoConfiguration,\
258 
259 org.springframework.boot.autoconfigure.webservices.WebServicesAutoConfiguration,\
260 
261 org.springframework.boot.autoconfigure.webservices.client.WebServiceTemplateAutoConfiguration
Auto Configure

3.3.2 按需开启自动配置项

虽然我们127个场景的所有自动配置启动的时候默认全部加载。xxxxAutoConfiguration
按照条件装配规则(@Conditional),最终会按需配置。

3.3.3 修改默认配置

 1         @Bean
 2         @ConditionalOnBean(MultipartResolver.class)  //容器中有这个类型组件
 3         @ConditionalOnMissingBean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME) //容器中没有这个名字 multipartResolver 的组件
 4         public MultipartResolver multipartResolver(MultipartResolver resolver) {
 5             //给@Bean标注的方法传入了对象参数,这个参数的值就会从容器中找。
 6             //SpringMVC multipartResolver。防止有些用户配置的文件上传解析器不符合规范
 7             // Detect if the user has created a MultipartResolver but named it incorrectly
 8             return resolver;
 9         }
10 给容器中加入了文件上传解析器;
View Code

SpringBoot默认会在底层配好所有的组件。但是如果用户自己配置了以用户的优先

总结:

  • SpringBoot先加载所有的自动配置类  xxxxxAutoConfiguration
  • 每个自动配置类按照条件进行生效,默认都会绑定配置文件指定的值。xxxxProperties里面拿。xxxProperties和配置文件进行了绑定
  • 生效的配置类就会给容器中装配很多组件
  • 只要容器中有这些组件,相当于这些功能就有了
  • 定制化配置
  • 用户直接自己@Bean替换底层的组件
  • 用户去看这个组件是获取的配置文件什么值就去修改。

xxxxxAutoConfiguration ---> 组件  ---> xxxxProperties里面拿值  ----> application.properties

3.3.4 最佳实践

    • 引入场景依赖
    • 查看自动配置了哪些(选做)
      • 自己分析,引入场景对应的自动配置一般都生效了
      • 配置文件中debug=true开启自动配置报告。Negative(不生效)\Positive(生效)
    • 是否需要修改
      • 参照文档修改配置项
      • 自定义加入或者替换组件
        • @Bean、@Component。。。
      • 自定义器  XXXXXCustomizer

3.4 开发小技巧

3.4.1 Lombok

 1 1.  
 2       <dependency>
 3 
 4             <groupId>org.projectlombok</groupId>
 5 
 6             <artifactId>lombok</artifactId>
 7 
 8         </dependency>
 9 
10 
11 
12 2. idea中搜索安装lombok插件
Lombok
 1 ===============================简化JavaBean开发===================================
 2 
 3 @NoArgsConstructor
 4 
 5 //@AllArgsConstructor
 6 
 7 @Data
 8 
 9 @ToString
10 
11 @EqualsAndHashCode
12 
13 public class User {
14 
15 
16     private String name;
17 
18     private Integer age;
19 
20 
21     private Pet pet;
22 
23 
24     public User(String name,Integer age){
25 
26         this.name = name;
27 
28         this.age = age;
29 
30     }
31 
32 
33 
34 }
简化JavaBean开发
 1 @Slf4j
 2 
 3 @RestController
 4 
 5 public class HelloController {
 6 
 7     @RequestMapping("/hello")
 8 
 9     public String handle01(@RequestParam("name") String name){
10 
11         
12 
13         log.info("请求进来了....");
14 
15         
16 
17         return "Hello, Spring Boot 2!"+"你好:"+name;
18 
19     }
20 
21 }
简化日志开发

3.4.2 dev-tools

1         <dependency>
2 
3             <groupId>org.springframework.boot</groupId>
4 
5             <artifactId>spring-boot-devtools</artifactId>
6 
7             <optional>true</optional>
8 
9         </dependency>
dev-tools

项目或者页面修改以后:Ctrl+F9;

3.4.3 Spring Initailizr(项目初始化向导)

1)  选择我们需要的开发场景

 

 2)  自动依赖引入

3)  自动创建项目结构

 

 

 4)  自动编写好主配置类

 第二章:SpringBoot2核心技术-核心功能

 

 

4 配置文件

4.1 文件类型

4.1.1 properties

同以前的properties用法

4.1.2 yaml

1)  简介

YAML 是 "YAML Ain't Markup Language"(YAML 不是一种标记语言)的递归缩写。在开发的这种语言时,YAML 的意思其实是:"Yet Another Markup Language"(仍是一种标记语言)。

非常适合用来做以数据为中心的配置文件

2)  基本语法

  • key: value;kv之间有空格
  • 大小写敏感
  • 使用缩进表示层级关系
  • 缩进不允许使用tab,只允许空格
  • 缩进的空格数不重要,只要相同层级的元素左对齐即可
  • '#'表示注释
  • 字符串无需加引号,如果要加,''与""表示字符串内容 会被 转义/不转义

3)  数据类型

  • 字面量:单个的、不可再分的值。date、boolean、string、number、null
      k: v
  • 对象:键值对的集合。map、hash、set、object
      行内写法:  k: {k1:v1,k2:v2,k3:v3}

      #或

      k: 

        k1: v1

        k2: v2

        k3: v3
  • 数组:一组按次序排列的值。array、list、queue
      行内写法:  k: [v1,v2,v3]

      #或者

      k:

       - v1

       - v2

       - v3

 

 4)  示例

 1 @Data
 2 
 3 public class Person {
 4 
 5     
 6 
 7     private String userName;
 8 
 9     private Boolean boss;
10 
11     private Date birth;
12 
13     private Integer age;
14 
15     private Pet pet;
16 
17     private String[] interests;
18 
19     private List<String> animal;
20 
21     private Map<String, Object> score;
22 
23     private Set<Double> salarys;
24 
25     private Map<String, List<Pet>> allPets;
26 
27 }
28 
29 
30 @Data
31 
32 public class Pet {
33 
34     private String name;
35 
36     private Double weight;
37 
38 }
Person
 1 # yaml表示以上对象
 2 person:
 3   userName: zhangsan
 4   boss: false
 5   birth: 2019/12/12 20:12:33
 6   age: 18
 7   pet: 
 8     name: tomcat
 9     weight: 23.4
10   interests: [篮球,游泳]
11   animal: 
12     - jerry
13     - mario
14   score:
15     english: 
16       first: 30
17       second: 40
18       third: 50
19     math: [131,140,148]
20     chinese: {first: 128,second: 136}
21   salarys: [3999,4999.98,5999.99]
22   allPets:
23     sick:
24       - {name: tom}
25       - {name: jerry,weight: 47}
26     health: [{name: mario,weight: 47}]
person.yaml

5 Web开发

 

 

 

5.1 springmvc自动配置概览

Spring Boot provides auto-configuration for Spring MVC that works well with most applications.(大多场景我们都无需自定义配置)

The auto-configuration adds the following features on top of Spring’s defaults:

 

  • Inclusion of ContentNegotiatingViewResolver and BeanNameViewResolver beans.
    • 内容协商视图解析器和BeanName视图解析器
  • Support for serving static resources, including support for WebJars (covered later in this document)).
    • 静态资源(包括webjars)
  • Automatic registration of Converter, GenericConverter, and Formatter beans.
    • 自动注册 Converter,GenericConverter,Formatter
  • Support for HttpMessageConverters (covered later in this document).
    • 支持 HttpMessageConverters (后来我们配合内容协商理解原理)
  • Automatic registration of MessageCodesResolver (covered later in this document).
    • 自动注册 MessageCodesResolver (国际化用)
  • Static index.html support.
    • 静态index.html 页支持
  • Custom Favicon support (covered later in this document).
    • 自定义 Favicon  
  • Automatic use of a ConfigurableWebBindingInitializer bean (covered later in this document).
    • 自动使用 ConfigurableWebBindingInitializer ,(DataBinder负责将请求数据绑定到JavaBean上)

 

1 If you want to keep those Spring Boot MVC customizations and make more MVC customizations (interceptors, formatters, view controllers, and other features), you can add your own @Configuration class of type WebMvcConfigurer but without @EnableWebMvc.
2 不用@EnableWebMvc注解。使用 @Configuration + WebMvcConfigurer 自定义规则

 

1 If you want to provide custom instances of RequestMappingHandlerMapping, RequestMappingHandlerAdapter, or ExceptionHandlerExceptionResolver, and still keep the Spring Boot MVC customizations, you can declare a bean of type WebMvcRegistrations and use it to provide custom instances of those components.
2 声明 WebMvcRegistrations 改变默认底层组件
1 If you want to take complete control of Spring MVC, you can add your own @Configuration annotated with @EnableWebMvc, or alternatively add your own @Configuration-annotated DelegatingWebMvcConfiguration as described in the Javadoc of @EnableWebMvc.
2 使用 @EnableWebMvc+@Configuration+DelegatingWebMvcConfiguration 全面接管SpringMVC

https://www.yuque.com/atguigu/springboot/vgzmgh#UnPqMhttps://www.yuque.com/atguigu/springboot/vgzmgh#UnPqM2、简单功能分析2

 

5.2、简单功能分析

5.2.1 静态资源访问

1)  静态资源目录  

只要静态资源放在类路径下: called /static (or /public or /resources or /META-INF/resources

访问 : 当前项目根路径/ + 静态资源名

原理: 静态映射/**。

请求进来,先去找Controller看能不能处理。不能处理的所有请求又都交给静态资源处理器。静态资源也找不到则响应404页面

 

改变默认的静态资源路径

1   spring:
2     mvc:
3       static-path-pattern: /res/**
4   
5     resources:
6       static-locations: [classpath:/haha/]

2)  静态资源访问前缀

默认无前缀

1     当前项目 + static-path-pattern + 静态资源名 = 静态资源文件夹下找
2     spring:
3 
4       mvc:
5 
6         static-path-pattern: /res/**

3)  webjar

自动映射 /webjars/**

https://www.webjars.org/

  <dependency>

      <groupId>org.webjars</groupId>

      <artifactId>jquery</artifactId>

      <version>3.5.1</version>

  </dependency>

访问地址:http://localhost:8080/webjars/jquery/3.5.1/jquery.js   后面地址要按照依赖里面的包路径

5.2.2 欢迎页支持 

  • 静态资源路径下  index.html
    • 可以配置静态资源路径
    • 但是不可以配置静态资源的访问前缀。否则导致 index.html不能被默认访问
  • controller能处理/index
 1 spring:
 2 
 3 #  mvc:
 4 
 5 #    static-path-pattern: /res/**   这个会导致welcome page功能失效
 6 
 7 
 8   resources:
 9 
10     static-locations: [classpath:/haha/]
static-locations

5.2.3 自定义 Favicon 

 favicon.ico 放在静态资源目录下即可。配置静态资源的访问前缀会造成失败。

 5.2.4 静态资源配置原理

  •  SpringBoot启动默认加载  xxxAutoConfiguration 类(自动配置类)
  • SpringMVC功能的自动配置类 WebMvcAutoConfiguration,生效
1 @Configuration(proxyBeanMethods = false)
2 @ConditionalOnWebApplication(type = Type.SERVLET)
3 @ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
4 @ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
5 @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
6 @AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,
7         ValidationAutoConfiguration.class })
8 public class WebMvcAutoConfiguration {}
WebMvcAutoConfiguration
  • 给容器中配了什么
1     @Configuration(proxyBeanMethods = false)
2     @Import(EnableWebMvcConfiguration.class)
3     @EnableConfigurationProperties({ WebMvcProperties.class, ResourceProperties.class })
4     @Order(0)
5     public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer {}
WebMvcAutoConfigurationAdapter
  • 配置文件的相关属性和xxx进行了绑定。

 WebMvcProperties==spring.mvc、ResourceProperties==spring.resources

 1)  配置类只有一个有参构造器

 1     //有参构造器所有参数的值都会从容器中确定
 2 //ResourceProperties resourceProperties;获取和spring.resources绑定的所有的值的对象
 3 //WebMvcProperties mvcProperties 获取和spring.mvc绑定的所有的值的对象
 4 //ListableBeanFactory beanFactory Spring的beanFactory
 5 //HttpMessageConverters 找到所有的HttpMessageConverters
 6 //ResourceHandlerRegistrationCustomizer 找到 资源处理器的自定义器。=========
 7 //DispatcherServletPath  
 8 //ServletRegistrationBean   给应用注册Servlet、Filter....
 9     public WebMvcAutoConfigurationAdapter(ResourceProperties resourceProperties, WebMvcProperties mvcProperties,
10                 ListableBeanFactory beanFactory, ObjectProvider<HttpMessageConverters> messageConvertersProvider,
11                 ObjectProvider<ResourceHandlerRegistrationCustomizer> resourceHandlerRegistrationCustomizerProvider,
12                 ObjectProvider<DispatcherServletPath> dispatcherServletPath,
13                 ObjectProvider<ServletRegistrationBean<?>> servletRegistrations) {
14             this.resourceProperties = resourceProperties;
15             this.mvcProperties = mvcProperties;
16             this.beanFactory = beanFactory;
17             this.messageConvertersProvider = messageConvertersProvider;
18             this.resourceHandlerRegistrationCustomizer = resourceHandlerRegistrationCustomizerProvider.getIfAvailable();
19             this.dispatcherServletPath = dispatcherServletPath;
20             this.servletRegistrations = servletRegistrations;
21         }
WebMvcAutoConfigurationAdapter

2)  资源处理的默认规则

 1 @Override
 2         public void addResourceHandlers(ResourceHandlerRegistry registry) {
 3             if (!this.resourceProperties.isAddMappings()) {
 4                 logger.debug("Default resource handling disabled");
 5                 return;
 6             }
 7             Duration cachePeriod = this.resourceProperties.getCache().getPeriod();
 8             CacheControl cacheControl = this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl();
 9             //webjars的规则
10             if (!registry.hasMappingForPattern("/webjars/**")) {
11                 customizeResourceHandlerRegistration(registry.addResourceHandler("/webjars/**")
12                         .addResourceLocations("classpath:/META-INF/resources/webjars/")
13                         .setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl));
14             }
15             
16             //
17             String staticPathPattern = this.mvcProperties.getStaticPathPattern();
18             if (!registry.hasMappingForPattern(staticPathPattern)) {
19                 customizeResourceHandlerRegistration(registry.addResourceHandler(staticPathPattern)
20                         .addResourceLocations(getResourceLocations(this.resourceProperties.getStaticLocations()))
21                         .setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl));
22             }
23         }
addResourceHandlers
1 spring:
2 
3   resources:
4 
5     add-mappings: false   禁用所有静态资源规则
resources
 1 @ConfigurationProperties(prefix = "spring.resources", ignoreUnknownFields = false)
 2 public class ResourceProperties {
 3 
 4     private static final String[] CLASSPATH_RESOURCE_LOCATIONS = { "classpath:/META-INF/resources/",
 5             "classpath:/resources/", "classpath:/static/", "classpath:/public/" };
 6 
 7     /**
 8      * Locations of static resources. Defaults to classpath:[/META-INF/resources/,
 9      * /resources/, /static/, /public/].
10      */
11     private String[] staticLocations = CLASSPATH_RESOURCE_LOCATIONS;
staticLocations

3)  欢迎页的处理规则

 1     HandlerMapping:处理器映射。保存了每一个Handler能处理哪些请求。    
 2 
 3     @Bean
 4         public WelcomePageHandlerMapping welcomePageHandlerMapping(ApplicationContext applicationContext,
 5                 FormattingConversionService mvcConversionService, ResourceUrlProvider mvcResourceUrlProvider) {
 6             WelcomePageHandlerMapping welcomePageHandlerMapping = new WelcomePageHandlerMapping(
 7                     new TemplateAvailabilityProviders(applicationContext), applicationContext, getWelcomePage(),
 8                     this.mvcProperties.getStaticPathPattern());
 9             welcomePageHandlerMapping.setInterceptors(getInterceptors(mvcConversionService, mvcResourceUrlProvider));
10             welcomePageHandlerMapping.setCorsConfigurations(getCorsConfigurations());
11             return welcomePageHandlerMapping;
12         }
13 
14     WelcomePageHandlerMapping(TemplateAvailabilityProviders templateAvailabilityProviders,
15             ApplicationContext applicationContext, Optional<Resource> welcomePage, String staticPathPattern) {
16         if (welcomePage.isPresent() && "/**".equals(staticPathPattern)) {
17             //要用欢迎页功能,必须是/**
18             logger.info("Adding welcome page: " + welcomePage.get());
19             setRootViewName("forward:index.html");
20         }
21         else if (welcomeTemplateExists(templateAvailabilityProviders, applicationContext)) {
22             // 调用Controller  /index
23             logger.info("Adding welcome page template: index");
24             setRootViewName("index");
25         }
26     }
WelcomePageHandlerMapping

5.3 请求参数处理

 5.3.1 请求映射

1)  rest使用与原理

  • @xxxMapping;
  • Rest风格支持(使用HTTP请求方式动词来表示对资源的操作
    • 以前:/getUser   获取用户     /deleteUser 删除用户    /editUser  修改用户       /saveUser 保存用户
    • 现在: /user    GET-获取用户    DELETE-删除用户     PUT-修改用户      POST-保存用户
    • 核心Filter;HiddenHttpMethodFilter
      • 用法: 表单method=post,隐藏域 _method=put
      • SpringBoot中手动开启
    • 扩展:如何把_method 这个名字换成我们自己喜欢的。
 1     @RequestMapping(value = "/user",method = RequestMethod.GET)
 2     public String getUser(){
 3         return "GET-张三";
 4     }
 5 
 6     @RequestMapping(value = "/user",method = RequestMethod.POST)
 7     public String saveUser(){
 8         return "POST-张三";
 9     }
10 
11 
12     @RequestMapping(value = "/user",method = RequestMethod.PUT)
13     public String putUser(){
14         return "PUT-张三";
15     }
16 
17     @RequestMapping(value = "/user",method = RequestMethod.DELETE)
18     public String deleteUser(){
19         return "DELETE-张三";
20     }
21 
22 
23     @Bean
24     @ConditionalOnMissingBean(HiddenHttpMethodFilter.class)
25     @ConditionalOnProperty(prefix = "spring.mvc.hiddenmethod.filter", name = "enabled", matchIfMissing = false)
26     public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
27         return new OrderedHiddenHttpMethodFilter();
28     }
29 
30 
31 //自定义filter
32     @Bean
33     public HiddenHttpMethodFilter hiddenHttpMethodFilter(){
34         HiddenHttpMethodFilter methodFilter = new HiddenHttpMethodFilter();
35         methodFilter.setMethodParam("_m");
36         return methodFilter;
37     }
/user

Rest原理(表单提交要使用REST的时候)

  • 表单提交会带上_method=PUT
  • 请求过来被HiddenHttpMethodFilter拦截
    • 请求是否正常,并且是POST
      • 获取到_method的值。
      • 兼容以下请求;PUT.DELETE.PATCH
      • 原生request(post),包装模式requesWrapper重写了getMethod方法,返回的是传入的值。
      • 过滤器链放行的时候用wrapper。以后的方法调用getMethod是调用requesWrapper的。

2)  请求映射原理

 1 protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
 2         HttpServletRequest processedRequest = request;
 3         HandlerExecutionChain mappedHandler = null;
 4         boolean multipartRequestParsed = false;
 5 
 6         WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
 7 
 8         try {
 9             ModelAndView mv = null;
10             Exception dispatchException = null;
11 
12             try {
13                 processedRequest = checkMultipart(request);
14                 multipartRequestParsed = (processedRequest != request);
15 
16                 // 找到当前请求使用哪个Handler(Controller的方法)处理
17                 mappedHandler = getHandler(processedRequest);
18                 
19                 //HandlerMapping:处理器映射。/xxx->>xxxx
doDispatch
 1 @Nullable
 2     protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
 3         if (this.handlerMappings != null) {
 4             for (HandlerMapping mapping : this.handlerMappings) {
 5                 HandlerExecutionChain handler = mapping.getHandler(request);
 6                 if (handler != null) {
 7                     return handler;
 8                 }
 9             }
10         }
11         return null;
12     }
getHandler

 

 RequestMappingHandlerMapping:保存了所有@RequestMapping 和handler的映射规则。

 

 1 public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport
 2         implements HandlerMapping, Ordered, BeanNameAware {   
 3         @Override
 4     @Nullable
 5     public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
 6         Object handler = getHandlerInternal(request);
 7         if (handler == null) {
 8             handler = getDefaultHandler();
 9         }
10         if (handler == null) {
11             return null;
12         }
13         // Bean name or resolved handler?
14         if (handler instanceof String) {
15             String handlerName = (String) handler;
16             handler = obtainApplicationContext().getBean(handlerName);
17         }
18 
19         HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
20 
21         if (logger.isTraceEnabled()) {
22             logger.trace("Mapped to " + handler);
23         }
24         else if (logger.isDebugEnabled() && !request.getDispatcherType().equals(DispatcherType.ASYNC)) {
25             logger.debug("Mapped to " + executionChain.getHandler());
26         }
27 
28         if (hasCorsConfigurationSource(handler) || CorsUtils.isPreFlightRequest(request)) {
29             CorsConfiguration config = (this.corsConfigurationSource != null ? this.corsConfigurationSource.getCorsConfiguration(request) : null);
30             CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
31             config = (config != null ? config.combine(handlerConfig) : handlerConfig);
32             executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
33         }
34 
35         return executionChain;
36     }
37 }
38  
getHandler
 1 public abstract class RequestMappingInfoHandlerMapping extends AbstractHandlerMethodMapping<RequestMappingInfo> {
 2 @Override
 3     protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
 4         request.removeAttribute(PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
 5         try {
 6             return super.getHandlerInternal(request);
 7         }
 8         finally {
 9             ProducesRequestCondition.clearMediaTypesAttribute(request);
10         }
11     }
12 }
getHandlerInternal
 1 public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMapping implements InitializingBean {
 2 @Override
 3     protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
 4         String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
 5         request.setAttribute(LOOKUP_PATH, lookupPath);
 6         this.mappingRegistry.acquireReadLock();
 7         try {
 8             HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
 9             return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
10         }
11         finally {
12             this.mappingRegistry.releaseReadLock();
13         }
14     }
15 }
getHandlerInternal
 1 public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMapping implements InitializingBean {
 2         @Nullable
 3     protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
 4         List<Match> matches = new ArrayList<>();
 5         List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
 6         if (directPathMatches != null) {
 7             addMatchingMappings(directPathMatches, matches, request);
 8         }
 9         if (matches.isEmpty()) {
10             // No choice but to go through all mappings...
11             addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);
12         }
13 
14         if (!matches.isEmpty()) {
15             Match bestMatch = matches.get(0);
16             if (matches.size() > 1) {
17                 Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
18                 matches.sort(comparator);
19                 bestMatch = matches.get(0);
20                 if (logger.isTraceEnabled()) {
21                     logger.trace(matches.size() + " matching mappings: " + matches);
22                 }
23                 if (CorsUtils.isPreFlightRequest(request)) {
24                     return PREFLIGHT_AMBIGUOUS_MATCH;
25                 }
26                 Match secondBestMatch = matches.get(1);
27                 if (comparator.compare(bestMatch, secondBestMatch) == 0) {
28                     Method m1 = bestMatch.handlerMethod.getMethod();
29                     Method m2 = secondBestMatch.handlerMethod.getMethod();
30                     String uri = request.getRequestURI();
31                     throw new IllegalStateException(
32                             "Ambiguous handler methods mapped for '" + uri + "': {" + m1 + ", " + m2 + "}");
33                 }
34             }
35             request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.handlerMethod);
36             handleMatch(bestMatch.mapping, lookupPath, request);
37             return bestMatch.handlerMethod;
38         }
39         else {
40             return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request);
41         }
42     }
43 
44 private void addMatchingMappings(Collection<T> mappings, List<Match> matches, HttpServletRequest request) {
45         for (T mapping : mappings) {
46             T match = getMatchingMapping(mapping, request);
47             if (match != null) {
48                 matches.add(new Match(match, this.mappingRegistry.getMappings().get(mapping)));
49             }
50         }
51     }
52 } 
lookupHandlerMethod

 

所有的请求映射都在HandlerMapping中。

  • SpringBoot自动配置欢迎页的 WelcomePageHandlerMapping 。访问 /能访问到index.html;
  • SpringBoot自动配置了默认 的 RequestMappingHandlerMapping
  • 请求进来,挨个尝试所有的HandlerMapping看是否有请求信息。
    • 如果有就找到这个请求对应的handler
    • 如果没有就是下一个 HandlerMapping
  • 我们需要一些自定义的映射处理,我们也可以自己给容器中放HandlerMapping。自定义 HandlerMapping

5.3.2 普通参数与基本注解

1) 注解:@PathVariable、@RequestHeader、@ModelAttribute、@RequestParam、@MatrixVariable、@CookieValue、@RequestBody 

 1 @RestController
 2 public class ParameterTestController {
 3 
 4 
 5     //  car/2/owner/zhangsan
 6     @GetMapping("/car/{id}/owner/{username}")
 7     public Map<String,Object> getCar(@PathVariable("id") Integer id,
 8                                      @PathVariable("username") String name,
 9                                      @PathVariable Map<String,String> pv,
10                                      @RequestHeader("User-Agent") String userAgent,
11                                      @RequestHeader Map<String,String> header,
12                                      @RequestParam("age") Integer age,
13                                      @RequestParam("inters") List<String> inters,
14                                      @RequestParam Map<String,String> params,
15                                      @CookieValue("_ga") String _ga,
16                                      @CookieValue("_ga") Cookie cookie){
17 
18 
19         Map<String,Object> map = new HashMap<>();
20 
21 //        map.put("id",id);
22 //        map.put("name",name);
23 //        map.put("pv",pv);
24 //        map.put("userAgent",userAgent);
25 //        map.put("headers",header);
26         map.put("age",age);
27         map.put("inters",inters);
28         map.put("params",params);
29         map.put("_ga",_ga);
30         System.out.println(cookie.getName()+"===>"+cookie.getValue());
31         return map;
32     }
33 
34 
35     @PostMapping("/save")
36     public Map postMethod(@RequestBody String content){
37         Map<String,Object> map = new HashMap<>();
38         map.put("content",content);
39         return map;
40     }
41 
42 
43     //1、语法: 请求路径:/cars/sell;low=34;brand=byd,audi,yd
44     //2、SpringBoot默认是禁用了矩阵变量的功能
45     //      手动开启:原理。对于路径的处理。UrlPathHelper进行解析。
46     //              removeSemicolonContent(移除分号内容)支持矩阵变量的
47     //3、矩阵变量必须有url路径变量才能被解析
48     @GetMapping("/cars/{path}")
49     public Map carsSell(@MatrixVariable("low") Integer low,
50                         @MatrixVariable("brand") List<String> brand,
51                         @PathVariable("path") String path){
52         Map<String,Object> map = new HashMap<>();
53 
54         map.put("low",low);
55         map.put("brand",brand);
56         map.put("path",path);
57         return map;
58     }
59 
60     // /boss/1;age=20/2;age=10
61 
62     @GetMapping("/boss/{bossId}/{empId}")
63     public Map boss(@MatrixVariable(value = "age",pathVar = "bossId") Integer bossAge,
64                     @MatrixVariable(value = "age",pathVar = "empId") Integer empAge){
65         Map<String,Object> map = new HashMap<>();
66 
67         map.put("bossAge",bossAge);
68         map.put("empAge",empAge);
69         return map;
70 
71     }
72 
73 }
注解

2) Servlet API: WebRequest、ServletRequest、MultipartRequest、 HttpSession、javax.servlet.http.PushBuilder、Principal、InputStream、Reader、HttpMethod、Locale、TimeZone、ZoneId

ServletRequestMethodArgumentResolver  以上的部分参数

 1         @Override
 2     public boolean supportsParameter(MethodParameter parameter) {
 3         Class<?> paramType = parameter.getParameterType();
 4         return (WebRequest.class.isAssignableFrom(paramType) ||
 5                 ServletRequest.class.isAssignableFrom(paramType) ||
 6                 MultipartRequest.class.isAssignableFrom(paramType) ||
 7                 HttpSession.class.isAssignableFrom(paramType) ||
 8                 (pushBuilder != null && pushBuilder.isAssignableFrom(paramType)) ||
 9                 Principal.class.isAssignableFrom(paramType) ||
10                 InputStream.class.isAssignableFrom(paramType) ||
11                 Reader.class.isAssignableFrom(paramType) ||
12                 HttpMethod.class == paramType ||
13                 Locale.class == paramType ||
14                 TimeZone.class == paramType ||
15                 ZoneId.class == paramType);
16     }    
supportsParameter

3)  复杂参数:

MapModel(map、model里面的数据会被放在request的请求域  request.setAttribute)、RedirectAttributes( 重定向携带数据)ServletResponse(response)

Errors/BindingResult、SessionStatus、UriComponentsBuilder、ServletUriComponentsBuilder

Map、Model类型的参数,会返回 mavContainer.getModel();---> BindingAwareModelMap 是Model 也是Map。mavContainer.getModel(); 获取到值的

 1 public class MapMethodProcessor implements HandlerMethodArgumentResolver, HandlerMethodReturnValueHandler {
 2     @Nullable
 3     public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
 4         Assert.state(mavContainer != null, "ModelAndViewContainer is required for model exposure");
 5         return mavContainer.getModel();
 6     }
 7 }
 8 
 9 
10 public class ModelAndViewContainer {
11     private final ModelMap defaultModel = new BindingAwareModelMap();
12     @Nullable
13     private ModelMap redirectModel;
14 
15     private boolean useDefaultModel() {
16         return !this.redirectModelScenario || this.redirectModel == null && !this.ignoreDefaultModelOnRedirect;
17     }
18 
19     public ModelMap getModel() {
20         if (this.useDefaultModel()) {
21             return this.defaultModel;
22         } else {
23             if (this.redirectModel == null) {
24                 this.redirectModel = new ModelMap();
25             }
26 
27             return this.redirectModel;
28         }
29     }
30 }
map
1 public class ModelMethodProcessor implements HandlerMethodArgumentResolver, HandlerMethodReturnValueHandler {
2 
3     @Nullable
4     public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
5         Assert.state(mavContainer != null, "ModelAndViewContainer is required for model exposure");
6         return mavContainer.getModel();
7     }
8 }
model
 1 public class HandlerMethodReturnValueHandlerComposite implements HandlerMethodReturnValueHandler {
 2 public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
 3         HandlerMethodReturnValueHandler handler = this.selectHandler(returnValue, returnType);
 4         if (handler == null) {
 5             throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName());
 6         } else {
 7             handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
 8         }
 9     }
10 }
11 
12 
13 public class ViewNameMethodReturnValueHandler implements HandlerMethodReturnValueHandler {
14     @Override
15     public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
16             ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
17 
18         if (returnValue instanceof CharSequence) {
19             String viewName = returnValue.toString();
20             mavContainer.setViewName(viewName);
21             if (isRedirectViewName(viewName)) {
22                 mavContainer.setRedirectModelScenario(true);
23             }
24         }
25         else if (returnValue != null) {
26             // should not happen
27             throw new UnsupportedOperationException("Unexpected return type: " +
28                     returnType.getParameterType().getName() + " in method: " + returnType.getMethod());
29         }
30     }
31 }
32 
33 public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter
34         implements BeanFactoryAware, InitializingBean {
35         @Nullable
36     protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
37             HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
38     
39     ....
40     getModelAndView(mavContainer, modelFactory, webRequest);
41     }
42     @Nullable
43     private ModelAndView getModelAndView(ModelAndViewContainer mavContainer,
44             ModelFactory modelFactory, NativeWebRequest webRequest) throws Exception {
45 
46         modelFactory.updateModel(webRequest, mavContainer);
47         if (mavContainer.isRequestHandled()) {
48             return null;
49         }
50         ModelMap model = mavContainer.getModel();
51         ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, mavContainer.getStatus());
52         if (!mavContainer.isViewReference()) {
53             mav.setView((View) mavContainer.getView());
54         }
55         if (model instanceof RedirectAttributes) {
56             Map<String, ?> flashAttributes = ((RedirectAttributes) model).getFlashAttributes();
57             HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
58             if (request != null) {
59                 RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes);
60             }
61         }
62         return mav;
63     }
64 }
65 
66 public final class ModelFactory {
67 public void updateModel(NativeWebRequest request, ModelAndViewContainer container) throws Exception {
68         ModelMap defaultModel = container.getDefaultModel();
69         if (container.getSessionStatus().isComplete()) {
70             this.sessionAttributesHandler.cleanupAttributes(request);
71         } else {
72             this.sessionAttributesHandler.storeAttributes(request, defaultModel);
73         }
74 
75         if (!container.isRequestHandled() && container.getModel() == defaultModel) {
76             this.updateBindingResult(request, defaultModel);
77         }
78 
79     }
80 
81     private void updateBindingResult(NativeWebRequest request, ModelMap model) throws Exception {
82         List<String> keyNames = new ArrayList(model.keySet());
83         Iterator var4 = keyNames.iterator();
84 
85         while(var4.hasNext()) {
86             String name = (String)var4.next();
87             Object value = model.get(name);
88             if (value != null && this.isBindingCandidate(name, value)) {
89                 String bindingResultKey = BindingResult.MODEL_KEY_PREFIX + name;
90                 if (!model.containsAttribute(bindingResultKey)) {
91                     WebDataBinder dataBinder = this.dataBinderFactory.createBinder(request, value, name);
92                     model.put(bindingResultKey, dataBinder.getBindingResult());
93                 }
94             }
95         }
96 
97     }
98 }
99     
handleReturnValue
  1 public class DispatcherServlet extends FrameworkServlet {
  2 
  3 private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
  4             @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
  5             @Nullable Exception exception) throws Exception {
  6 
  7         boolean errorView = false;
  8 
  9         if (exception != null) {
 10             if (exception instanceof ModelAndViewDefiningException) {
 11                 logger.debug("ModelAndViewDefiningException encountered", exception);
 12                 mv = ((ModelAndViewDefiningException) exception).getModelAndView();
 13             }
 14             else {
 15                 Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
 16                 mv = processHandlerException(request, response, handler, exception);
 17                 errorView = (mv != null);
 18             }
 19         }
 20 
 21         // Did the handler return a view to render?
 22         if (mv != null && !mv.wasCleared()) {
 23             render(mv, request, response);
 24             if (errorView) {
 25                 WebUtils.clearErrorRequestAttributes(request);
 26             }
 27         }
 28         else {
 29             if (logger.isTraceEnabled()) {
 30                 logger.trace("No view rendering, null ModelAndView returned.");
 31             }
 32         }
 33 
 34         if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
 35             // Concurrent handling started during a forward
 36             return;
 37         }
 38 
 39         if (mappedHandler != null) {
 40             // Exception (if any) is already handled..
 41             mappedHandler.triggerAfterCompletion(request, response, null);
 42         }
 43     }
 44 
 45 protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
 46         // Determine locale for request and apply it to the response.
 47         Locale locale =
 48                 (this.localeResolver != null ? this.localeResolver.resolveLocale(request) : request.getLocale());
 49         response.setLocale(locale);
 50 
 51         View view;
 52         String viewName = mv.getViewName();
 53         if (viewName != null) {
 54             // We need to resolve the view name.
 55             view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
 56             if (view == null) {
 57                 throw new ServletException("Could not resolve view with name '" + mv.getViewName() +
 58                         "' in servlet with name '" + getServletName() + "'");
 59             }
 60         }
 61         else {
 62             // No need to lookup: the ModelAndView object contains the actual View object.
 63             view = mv.getView();
 64             if (view == null) {
 65                 throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a " +
 66                         "View object in servlet with name '" + getServletName() + "'");
 67             }
 68         }
 69 
 70         // Delegate to the View object for rendering.
 71         if (logger.isTraceEnabled()) {
 72             logger.trace("Rendering view [" + view + "] ");
 73         }
 74         try {
 75             if (mv.getStatus() != null) {
 76                 response.setStatus(mv.getStatus().value());
 77             }
 78             view.render(mv.getModelInternal(), request, response);
 79         }
 80         catch (Exception ex) {
 81             if (logger.isDebugEnabled()) {
 82                 logger.debug("Error rendering view [" + view + "]", ex);
 83             }
 84             throw ex;
 85         }
 86     }
 87 
 88    @Nullable
 89     protected View resolveViewName(String viewName, @Nullable Map<String, Object> model,
 90             Locale locale, HttpServletRequest request) throws Exception {
 91 
 92         if (this.viewResolvers != null) {
 93             for (ViewResolver viewResolver : this.viewResolvers) {
 94                 View view = viewResolver.resolveViewName(viewName, locale);
 95                 if (view != null) {
 96                     return view;
 97                 }
 98             }
 99         }
100         return null;
101     }
102 }
DispatcherServlet
 1 public class ContentNegotiatingViewResolver extends WebApplicationObjectSupport
 2         implements ViewResolver, Ordered, InitializingBean {
 3         @Override
 4     @Nullable
 5     public View resolveViewName(String viewName, Locale locale) throws Exception {
 6         RequestAttributes attrs = RequestContextHolder.getRequestAttributes();
 7         Assert.state(attrs instanceof ServletRequestAttributes, "No current ServletRequestAttributes");
 8         List<MediaType> requestedMediaTypes = getMediaTypes(((ServletRequestAttributes) attrs).getRequest());
 9         if (requestedMediaTypes != null) {
10             List<View> candidateViews = getCandidateViews(viewName, locale, requestedMediaTypes);
11             View bestView = getBestView(candidateViews, requestedMediaTypes, attrs);
12             if (bestView != null) {
13                 return bestView;
14             }
15         }
16 
17         String mediaTypeInfo = logger.isDebugEnabled() && requestedMediaTypes != null ?
18                 " given " + requestedMediaTypes.toString() : "";
19 
20         if (this.useNotAcceptableStatusCode) {
21             if (logger.isDebugEnabled()) {
22                 logger.debug("Using 406 NOT_ACCEPTABLE" + mediaTypeInfo);
23             }
24             return NOT_ACCEPTABLE_VIEW;
25         }
26         else {
27             logger.debug("View remains unresolved" + mediaTypeInfo);
28             return null;
29         }
30     }
31 }
resolveViewName
 1 protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
 2     ...
 3     view.render(mv.getModelInternal(), request, response);
 4 }
 5 
 6 
 7 public abstract class AbstractView extends WebApplicationObjectSupport implements View, BeanNameAware {
 8         @Override
 9     public void render(@Nullable Map<String, ?> model, HttpServletRequest request,
10             HttpServletResponse response) throws Exception {
11 
12         if (logger.isDebugEnabled()) {
13             logger.debug("View " + formatViewName() +
14                     ", model " + (model != null ? model : Collections.emptyMap()) +
15                     (this.staticAttributes.isEmpty() ? "" : ", static attributes " + this.staticAttributes));
16         }
17 
18         Map<String, Object> mergedModel = createMergedOutputModel(model, request, response);
19         prepareResponse(request, response);
20         renderMergedOutputModel(mergedModel, getRequestToExpose(request), response);
21     }
22 
23     protected Map<String, Object> createMergedOutputModel(@Nullable Map<String, ?> model,
24             HttpServletRequest request, HttpServletResponse response) {
25 
26         @SuppressWarnings("unchecked")
27         Map<String, Object> pathVars = (this.exposePathVariables ?
28                 (Map<String, Object>) request.getAttribute(View.PATH_VARIABLES) : null);
29 
30         // Consolidate static and dynamic model attributes.
31         int size = this.staticAttributes.size();
32         size += (model != null ? model.size() : 0);
33         size += (pathVars != null ? pathVars.size() : 0);
34 
35         Map<String, Object> mergedModel = new LinkedHashMap<>(size);
36         mergedModel.putAll(this.staticAttributes);
37         if (pathVars != null) {
38             mergedModel.putAll(pathVars);
39         }
40         if (model != null) {
41             mergedModel.putAll(model);
42         }
43 
44         // Expose RequestContext?
45         if (this.requestContextAttribute != null) {
46             mergedModel.put(this.requestContextAttribute, createRequestContext(request, response, mergedModel));
47         }
48 
49         return mergedModel;
50     }
51 
52     protected abstract void renderMergedOutputModel(
53             Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception;
54 
55 
56     /**
57      * Expose the model objects in the given map as request attributes.
58      * Names will be taken from the model Map.
59      * This method is suitable for all resources reachable by {@link javax.servlet.RequestDispatcher}.
60      * @param model a Map of model objects to expose
61      * @param request current HTTP request
62      */
63     protected void exposeModelAsRequestAttributes(Map<String, Object> model,
64             HttpServletRequest request) throws Exception {
65 
66         model.forEach((name, value) -> {
67             if (value != null) {
68                 request.setAttribute(name, value);
69             }
70             else {
71                 request.removeAttribute(name);
72             }
73         });
74     }
75 }
view.render
 1 public class InternalResourceView extends AbstractUrlBasedView {
 2     @Override
 3     protected void renderMergedOutputModel(
 4             Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
 5 
 6         // Expose the model object as request attributes.
 7         exposeModelAsRequestAttributes(model, request);
 8 
 9         // Expose helpers as request attributes, if any.
10         exposeHelpers(request);
11 
12         // Determine the path for the request dispatcher.
13         String dispatcherPath = prepareForRendering(request, response);
14 
15         // Obtain a RequestDispatcher for the target resource (typically a JSP).
16         RequestDispatcher rd = getRequestDispatcher(request, dispatcherPath);
17         if (rd == null) {
18             throw new ServletException("Could not get RequestDispatcher for [" + getUrl() +
19                     "]: Check that the corresponding file exists within your web application archive!");
20         }
21 
22         // If already included or response already committed, perform include, else forward.
23         if (useInclude(request, response)) {
24             response.setContentType(getContentType());
25             if (logger.isDebugEnabled()) {
26                 logger.debug("Including [" + getUrl() + "]");
27             }
28             rd.include(request, response);
29         }
30 
31         else {
32             // Note: The forwarded resource is supposed to determine the content type itself.
33             if (logger.isDebugEnabled()) {
34                 logger.debug("Forwarding to [" + getUrl() + "]");
35             }
36             rd.forward(request, response);
37         }
38     }
39 }
40 public abstract class AbstractView extends WebApplicationObjectSupport implements View, BeanNameAware {
41         protected void exposeModelAsRequestAttributes(Map<String, Object> model,
42             HttpServletRequest request) throws Exception {
43 
44         model.forEach((name, value) -> {
45             if (value != null) {
46                 request.setAttribute(name, value);
47             }
48             else {
49                 request.removeAttribute(name);
50             }
51         });
52     }
53 }    
renderMergedOutputModel

 

 

 

4)  自定义对象参数: 

POJO封装过程:  ServletModelAttributeMethodProcessor

 WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);

WebDataBinder :web数据绑定器,将请求参数的值绑定到指定的JavaBean里面

WebDataBinder 利用它里面的 Converters 将请求数据转成指定的数据类型。再次封装到JavaBean中

 GenericConversionService:在设置每一个值的时候,找它里面的所有converter那个可以将这个数据类型(request带来参数的字符串)转换到指定的类型(JavaBean -- Integer)

byte -- > file

 

 1 /**
 2  *     姓名: <input name="userName"/> <br/>
 3  *     年龄: <input name="age"/> <br/>
 4  *     生日: <input name="birth"/> <br/>
 5  *     宠物姓名:<input name="pet.name"/><br/>
 6  *     宠物年龄:<input name="pet.age"/>
 7  */
 8 @Data
 9 public class Person {
10     
11     private String userName;
12     private Integer age;
13     private Date birth;
14     private Pet pet;
15     
16 }
17 
18 @Data
19 public class Pet {
20 
21     private String name;
22     private String age;
23 
24 }
25 
26 result
person
 1 public class ModelAttributeMethodProcessor implements HandlerMethodArgumentResolver, HandlerMethodReturnValueHandler {
 2     public boolean supportsParameter(MethodParameter parameter) {
 3         return parameter.hasParameterAnnotation(ModelAttribute.class) || this.annotationNotRequired && !BeanUtils.isSimpleProperty(parameter.getParameterType());
 4     }
 5 }
 6 
 7 public abstract class BeanUtils {
 8     public static boolean isSimpleProperty(Class<?> type) {
 9         Assert.notNull(type, "'type' must not be null");
10         return isSimpleValueType(type) || type.isArray() && isSimpleValueType(type.getComponentType());
11     }
12 
13     public static boolean isSimpleValueType(Class<?> type) {
14         return Void.class != type && Void.TYPE != type && (ClassUtils.isPrimitiveOrWrapper(type) || Enum.class.isAssignableFrom(type) || CharSequence.class.isAssignableFrom(type) || Number.class.isAssignableFrom(type) || Date.class.isAssignableFrom(type) || Temporal.class.isAssignableFrom(type) || URI.class == type || URL.class == type || Locale.class == type || Class.class == type);
15     }
16 }
isSimpleProperty
  1 public class ModelAttributeMethodProcessor implements HandlerMethodArgumentResolver, HandlerMethodReturnValueHandler {
  2 
  3    @Nullable
  4     public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
  5         Assert.state(mavContainer != null, "ModelAttributeMethodProcessor requires ModelAndViewContainer");
  6         Assert.state(binderFactory != null, "ModelAttributeMethodProcessor requires WebDataBinderFactory");
  7         String name = ModelFactory.getNameForParameter(parameter);
  8         ModelAttribute ann = (ModelAttribute)parameter.getParameterAnnotation(ModelAttribute.class);
  9         if (ann != null) {
 10             mavContainer.setBinding(name, ann.binding());
 11         }
 12 
 13         Object attribute = null;
 14         BindingResult bindingResult = null;
 15         if (mavContainer.containsAttribute(name)) {
 16             attribute = mavContainer.getModel().get(name);
 17         } else {
 18             try {
 19                 attribute = this.createAttribute(name, parameter, binderFactory, webRequest);
 20             } catch (BindException var10) {
 21                 if (this.isBindExceptionRequired(parameter)) {
 22                     throw var10;
 23                 }
 24 
 25                 if (parameter.getParameterType() == Optional.class) {
 26                     attribute = Optional.empty();
 27                 }
 28 
 29                 bindingResult = var10.getBindingResult();
 30             }
 31         }
 32 
 33         if (bindingResult == null) {
 34             WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);
 35             if (binder.getTarget() != null) {
 36                 if (!mavContainer.isBindingDisabled(name)) {
 37                     this.bindRequestParameters(binder, webRequest);
 38                 }
 39 
 40                 this.validateIfApplicable(binder, parameter);
 41                 if (binder.getBindingResult().hasErrors() && this.isBindExceptionRequired(binder, parameter)) {
 42                     throw new BindException(binder.getBindingResult());
 43                 }
 44             }
 45 
 46             if (!parameter.getParameterType().isInstance(attribute)) {
 47                 attribute = binder.convertIfNecessary(binder.getTarget(), parameter.getParameterType(), parameter);
 48             }
 49 
 50             bindingResult = binder.getBindingResult();
 51         }
 52 
 53         Map<String, Object> bindingResultModel = bindingResult.getModel();
 54         mavContainer.removeAttributes(bindingResultModel);
 55         mavContainer.addAllAttributes(bindingResultModel);
 56         return attribute;
 57     }
 58 
 59     protected Object createAttribute(String attributeName, MethodParameter parameter, WebDataBinderFactory binderFactory, NativeWebRequest webRequest) throws Exception {
 60         MethodParameter nestedParameter = parameter.nestedIfOptional();
 61         Class<?> clazz = nestedParameter.getNestedParameterType();
 62         Constructor<?> ctor = BeanUtils.findPrimaryConstructor(clazz);
 63         if (ctor == null) {
 64             Constructor<?>[] ctors = clazz.getConstructors();
 65             if (ctors.length == 1) {
 66                 ctor = ctors[0];
 67             } else {
 68                 try {
 69                     ctor = clazz.getDeclaredConstructor();
 70                 } catch (NoSuchMethodException var10) {
 71                     throw new IllegalStateException("No primary or default constructor found for " + clazz, var10);
 72                 }
 73             }
 74         }
 75 
 76         Object attribute = this.constructAttribute(ctor, attributeName, parameter, binderFactory, webRequest);
 77         if (parameter != nestedParameter) {
 78             attribute = Optional.of(attribute);
 79         }
 80 
 81         return attribute;
 82     }
 83 }
 84 
 85 public class ModelAttributeMethodProcessor implements HandlerMethodArgumentResolver, HandlerMethodReturnValueHandler {
 86 protected void bindRequestParameters(WebDataBinder binder, NativeWebRequest request) {
 87         ((WebRequestDataBinder)binder).bind(request);
 88     }
 89 }
 90 
 91 public class WebRequestDataBinder extends WebDataBinder {
 92 public void bind(WebRequest request) {
 93         MutablePropertyValues mpvs = new MutablePropertyValues(request.getParameterMap());
 94         if (request instanceof NativeWebRequest) {
 95             MultipartRequest multipartRequest = (MultipartRequest)((NativeWebRequest)request).getNativeRequest(MultipartRequest.class);
 96             if (multipartRequest != null) {
 97                 this.bindMultipart(multipartRequest.getMultiFileMap(), mpvs);
 98             } else if (this.isMultipartRequest(request)) {
 99                 HttpServletRequest servletRequest = (HttpServletRequest)((NativeWebRequest)request).getNativeRequest(HttpServletRequest.class);
100                 if (servletRequest != null) {
101                     this.bindParts(servletRequest, mpvs);
102                 }
103             }
104         }
105 
106         this.doBind(mpvs);
107     }
108 }
109 
110 public class WebDataBinder extends DataBinder {
111     protected void doBind(MutablePropertyValues mpvs) {
112         this.checkFieldDefaults(mpvs);
113         this.checkFieldMarkers(mpvs);
114         super.doBind(mpvs);
115     }
116 }
117 
118 public class DataBinder implements PropertyEditorRegistry, TypeConverter {
119     protected void doBind(MutablePropertyValues mpvs) {
120         this.checkAllowedFields(mpvs);
121         this.checkRequiredFields(mpvs);
122         this.applyPropertyValues(mpvs);
123     }
124 
125     protected void applyPropertyValues(MutablePropertyValues mpvs) {
126         try {
127             this.getPropertyAccessor().setPropertyValues(mpvs, this.isIgnoreUnknownFields(), this.isIgnoreInvalidFields());
128         } catch (PropertyBatchUpdateException var7) {
129             PropertyAccessException[] var3 = var7.getPropertyAccessExceptions();
130             int var4 = var3.length;
131 
132             for(int var5 = 0; var5 < var4; ++var5) {
133                 PropertyAccessException pae = var3[var5];
134                 this.getBindingErrorProcessor().processPropertyAccessException(pae, this.getInternalBindingResult());
135             }
136         }
137 
138     }
139 }
140 
141 public abstract class AbstractPropertyAccessor extends TypeConverterSupport implements ConfigurablePropertyAccessor {
142     public void setPropertyValues(PropertyValues pvs, boolean ignoreUnknown, boolean ignoreInvalid) throws BeansException {
143         List<PropertyAccessException> propertyAccessExceptions = null;
144         List<PropertyValue> propertyValues = pvs instanceof MutablePropertyValues ? ((MutablePropertyValues)pvs).getPropertyValueList() : Arrays.asList(pvs.getPropertyValues());
145         Iterator var6 = propertyValues.iterator();
146 
147         while(var6.hasNext()) {
148             PropertyValue pv = (PropertyValue)var6.next();
149 
150             try {
151                 this.setPropertyValue(pv);
152             } catch (NotWritablePropertyException var9) {
153                 if (!ignoreUnknown) {
154                     throw var9;
155                 }
156             } catch (NullValueInNestedPathException var10) {
157                 if (!ignoreInvalid) {
158                     throw var10;
159                 }
160             } catch (PropertyAccessException var11) {
161                 if (propertyAccessExceptions == null) {
162                     propertyAccessExceptions = new ArrayList();
163                 }
164 
165                 propertyAccessExceptions.add(var11);
166             }
167         }
168 
169         if (propertyAccessExceptions != null) {
170             PropertyAccessException[] paeArray = (PropertyAccessException[])propertyAccessExceptions.toArray(new PropertyAccessException[0]);
171             throw new PropertyBatchUpdateException(paeArray);
172         }
173     }
174 }
175 
176 public abstract class AbstractNestablePropertyAccessor extends AbstractPropertyAccessor {
177     public void setPropertyValue(PropertyValue pv) throws BeansException {
178         AbstractNestablePropertyAccessor.PropertyTokenHolder tokens = (AbstractNestablePropertyAccessor.PropertyTokenHolder)pv.resolvedTokens;
179         if (tokens == null) {
180             String propertyName = pv.getName();
181 
182             AbstractNestablePropertyAccessor nestedPa;
183             try {
184                 nestedPa = this.getPropertyAccessorForPropertyPath(propertyName);
185             } catch (NotReadablePropertyException var6) {
186                 throw new NotWritablePropertyException(this.getRootClass(), this.nestedPath + propertyName, "Nested property in path '" + propertyName + "' does not exist", var6);
187             }
188 
189             tokens = this.getPropertyNameTokens(this.getFinalPath(nestedPa, propertyName));
190             if (nestedPa == this) {
191                 pv.getOriginalPropertyValue().resolvedTokens = tokens;
192             }
193 
194             nestedPa.setPropertyValue(tokens, pv);
195         } else {
196             this.setPropertyValue(tokens, pv);
197         }
198 
199     }
200 
201     protected void setPropertyValue(AbstractNestablePropertyAccessor.PropertyTokenHolder tokens, PropertyValue pv) throws BeansException {
202         if (tokens.keys != null) {
203             this.processKeyedProperty(tokens, pv);
204         } else {
205             this.processLocalProperty(tokens, pv);
206         }
207 
208     }
209 
210     private void processLocalProperty(AbstractNestablePropertyAccessor.PropertyTokenHolder tokens, PropertyValue pv) {
211         AbstractNestablePropertyAccessor.PropertyHandler ph = this.getLocalPropertyHandler(tokens.actualName);
212         if (ph != null && ph.isWritable()) {
213             Object oldValue = null;
214 
215             PropertyChangeEvent propertyChangeEvent;
216             try {
217                 Object originalValue = pv.getValue();
218                 Object valueToApply = originalValue;
219                 if (!Boolean.FALSE.equals(pv.conversionNecessary)) {
220                     if (pv.isConverted()) {
221                         valueToApply = pv.getConvertedValue();
222                     } else {
223                         if (this.isExtractOldValueForEditor() && ph.isReadable()) {
224                             try {
225                                 oldValue = ph.getValue();
226                             } catch (Exception var8) {
227                                 Exception ex = var8;
228                                 if (var8 instanceof PrivilegedActionException) {
229                                     ex = ((PrivilegedActionException)var8).getException();
230                                 }
231 
232                                 if (logger.isDebugEnabled()) {
233                                     logger.debug("Could not read previous value of property '" + this.nestedPath + tokens.canonicalName + "'", ex);
234                                 }
235                             }
236                         }
237 
238                         valueToApply = this.convertForProperty(tokens.canonicalName, oldValue, originalValue, ph.toTypeDescriptor());
239                     }
240 
241                     pv.getOriginalPropertyValue().conversionNecessary = valueToApply != originalValue;
242                 }
243 
244                 ph.setValue(valueToApply);
245             } catch (TypeMismatchException var9) {
246                 throw var9;
247             } catch (InvocationTargetException var10) {
248                 propertyChangeEvent = new PropertyChangeEvent(this.getRootInstance(), this.nestedPath + tokens.canonicalName, oldValue, pv.getValue());
249                 if (var10.getTargetException() instanceof ClassCastException) {
250                     throw new TypeMismatchException(propertyChangeEvent, ph.getPropertyType(), var10.getTargetException());
251                 } else {
252                     Throwable cause = var10.getTargetException();
253                     if (cause instanceof UndeclaredThrowableException) {
254                         cause = cause.getCause();
255                     }
256 
257                     throw new MethodInvocationException(propertyChangeEvent, cause);
258                 }
259             } catch (Exception var11) {
260                 propertyChangeEvent = new PropertyChangeEvent(this.getRootInstance(), this.nestedPath + tokens.canonicalName, oldValue, pv.getValue());
261                 throw new MethodInvocationException(propertyChangeEvent, var11);
262             }
263         } else if (pv.isOptional()) {
264             if (logger.isDebugEnabled()) {
265                 logger.debug("Ignoring optional value for property '" + tokens.actualName + "' - property not found on bean class [" + this.getRootClass().getName() + "]");
266             }
267 
268         } else {
269             throw this.createNotWritablePropertyException(tokens.canonicalName);
270         }
271     }
272 
273     @Nullable
274     protected Object convertForProperty(String propertyName, @Nullable Object oldValue, @Nullable Object newValue, TypeDescriptor td) throws TypeMismatchException {
275         return this.convertIfNecessary(propertyName, oldValue, newValue, td.getType(), td);
276     }
277 
278     @Nullable
279     private Object convertIfNecessary(@Nullable String propertyName, @Nullable Object oldValue, @Nullable Object newValue, @Nullable Class<?> requiredType, @Nullable TypeDescriptor td) throws TypeMismatchException {
280         Assert.state(this.typeConverterDelegate != null, "No TypeConverterDelegate");
281 
282         PropertyChangeEvent pce;
283         try {
284             return this.typeConverterDelegate.convertIfNecessary(propertyName, oldValue, newValue, requiredType, td);
285         } catch (IllegalStateException | ConverterNotFoundException var8) {
286             pce = new PropertyChangeEvent(this.getRootInstance(), this.nestedPath + propertyName, oldValue, newValue);
287             throw new ConversionNotSupportedException(pce, requiredType, var8);
288         } catch (IllegalArgumentException | ConversionException var9) {
289             pce = new PropertyChangeEvent(this.getRootInstance(), this.nestedPath + propertyName, oldValue, newValue);
290             throw new TypeMismatchException(pce, requiredType, var9);
291         }
292     }
293 }
294 
295 class TypeConverterDelegate {
296 @Nullable
297     public <T> T convertIfNecessary(@Nullable String propertyName, @Nullable Object oldValue, @Nullable Object newValue, @Nullable Class<T> requiredType, @Nullable TypeDescriptor typeDescriptor) throws IllegalArgumentException {
298         PropertyEditor editor = this.propertyEditorRegistry.findCustomEditor(requiredType, propertyName);
299         ConversionFailedException conversionAttemptEx = null;
300         ConversionService conversionService = this.propertyEditorRegistry.getConversionService();
301         if (editor == null && conversionService != null && newValue != null && typeDescriptor != null) {
302             TypeDescriptor sourceTypeDesc = TypeDescriptor.forObject(newValue);
303             if (conversionService.canConvert(sourceTypeDesc, typeDescriptor)) {
304                 try {
305                     return conversionService.convert(newValue, sourceTypeDesc, typeDescriptor);
306                 } catch (ConversionFailedException var14) {
307                     conversionAttemptEx = var14;
308                 }
309             }
310         }
311 
312         Object convertedValue = newValue;
313         if (editor != null || requiredType != null && !ClassUtils.isAssignableValue(requiredType, newValue)) {
314             if (typeDescriptor != null && requiredType != null && Collection.class.isAssignableFrom(requiredType) && newValue instanceof String) {
315                 TypeDescriptor elementTypeDesc = typeDescriptor.getElementTypeDescriptor();
316                 if (elementTypeDesc != null) {
317                     Class<?> elementType = elementTypeDesc.getType();
318                     if (Class.class == elementType || Enum.class.isAssignableFrom(elementType)) {
319                         convertedValue = StringUtils.commaDelimitedListToStringArray((String)newValue);
320                     }
321                 }
322             }
323 
324             if (editor == null) {
325                 editor = this.findDefaultEditor(requiredType);
326             }
327 
328             convertedValue = this.doConvertValue(oldValue, convertedValue, requiredType, editor);
329         }
330 
331         boolean standardConversion = false;
332         if (requiredType != null) {
333             if (convertedValue != null) {
334                 if (Object.class == requiredType) {
335                     return convertedValue;
336                 }
337 
338                 if (requiredType.isArray()) {
339                     if (convertedValue instanceof String && Enum.class.isAssignableFrom(requiredType.getComponentType())) {
340                         convertedValue = StringUtils.commaDelimitedListToStringArray((String)convertedValue);
341                     }
342 
343                     return this.convertToTypedArray(convertedValue, propertyName, requiredType.getComponentType());
344                 }
345 
346                 if (convertedValue instanceof Collection) {
347                     convertedValue = this.convertToTypedCollection((Collection)convertedValue, propertyName, requiredType, typeDescriptor);
348                     standardConversion = true;
349                 } else if (convertedValue instanceof Map) {
350                     convertedValue = this.convertToTypedMap((Map)convertedValue, propertyName, requiredType, typeDescriptor);
351                     standardConversion = true;
352                 }
353 
354                 if (convertedValue.getClass().isArray() && Array.getLength(convertedValue) == 1) {
355                     convertedValue = Array.get(convertedValue, 0);
356                     standardConversion = true;
357                 }
358 
359                 if (String.class == requiredType && ClassUtils.isPrimitiveOrWrapper(convertedValue.getClass())) {
360                     return convertedValue.toString();
361                 }
362 
363                 if (convertedValue instanceof String && !requiredType.isInstance(convertedValue)) {
364                     if (conversionAttemptEx == null && !requiredType.isInterface() && !requiredType.isEnum()) {
365                         try {
366                             Constructor<T> strCtor = requiredType.getConstructor(String.class);
367                             return BeanUtils.instantiateClass(strCtor, new Object[]{convertedValue});
368                         } catch (NoSuchMethodException var12) {
369                             if (logger.isTraceEnabled()) {
370                                 logger.trace("No String constructor found on type [" + requiredType.getName() + "]", var12);
371                             }
372                         } catch (Exception var13) {
373                             if (logger.isDebugEnabled()) {
374                                 logger.debug("Construction via String failed for type [" + requiredType.getName() + "]", var13);
375                             }
376                         }
377                     }
378 
379                     String trimmedValue = ((String)convertedValue).trim();
380                     if (requiredType.isEnum() && trimmedValue.isEmpty()) {
381                         return null;
382                     }
383 
384                     convertedValue = this.attemptToConvertStringToEnum(requiredType, trimmedValue, convertedValue);
385                     standardConversion = true;
386                 } else if (convertedValue instanceof Number && Number.class.isAssignableFrom(requiredType)) {
387                     convertedValue = NumberUtils.convertNumberToTargetClass((Number)convertedValue, requiredType);
388                     standardConversion = true;
389                 }
390             } else if (requiredType == Optional.class) {
391                 convertedValue = Optional.empty();
392             }
393 
394             if (!ClassUtils.isAssignableValue(requiredType, convertedValue)) {
395                 if (conversionAttemptEx != null) {
396                     throw conversionAttemptEx;
397                 }
398 
399                 if (conversionService != null && typeDescriptor != null) {
400                     TypeDescriptor sourceTypeDesc = TypeDescriptor.forObject(newValue);
401                     if (conversionService.canConvert(sourceTypeDesc, typeDescriptor)) {
402                         return conversionService.convert(newValue, sourceTypeDesc, typeDescriptor);
403                     }
404                 }
405 
406                 StringBuilder msg = new StringBuilder();
407                 msg.append("Cannot convert value of type '").append(ClassUtils.getDescriptiveType(newValue));
408                 msg.append("' to required type '").append(ClassUtils.getQualifiedName(requiredType)).append("'");
409                 if (propertyName != null) {
410                     msg.append(" for property '").append(propertyName).append("'");
411                 }
412 
413                 if (editor != null) {
414                     msg.append(": PropertyEditor [").append(editor.getClass().getName()).append("] returned inappropriate value of type '").append(ClassUtils.getDescriptiveType(convertedValue)).append("'");
415                     throw new IllegalArgumentException(msg.toString());
416                 }
417 
418                 msg.append(": no matching editors or conversion strategy found");
419                 throw new IllegalStateException(msg.toString());
420             }
421         }
422 
423         if (conversionAttemptEx != null) {
424             if (editor == null && !standardConversion && requiredType != null && Object.class != requiredType) {
425                 throw conversionAttemptEx;
426             }
427 
428             logger.debug("Original ConversionService attempt failed - ignored since PropertyEditor based conversion eventually succeeded", conversionAttemptEx);
429         }
430 
431         return convertedValue;
432     }
433 }
434 
435 public class GenericConversionService implements ConfigurableConversionService {
436 public boolean canConvert(@Nullable TypeDescriptor sourceType, TypeDescriptor targetType) {
437         Assert.notNull(targetType, "Target type to convert to cannot be null");
438         if (sourceType == null) {
439             return true;
440         } else {
441             GenericConverter converter = this.getConverter(sourceType, targetType);
442             return converter != null;
443         }
444     }
445 
446     @Nullable
447     protected GenericConverter getConverter(TypeDescriptor sourceType, TypeDescriptor targetType) {
448         GenericConversionService.ConverterCacheKey key = new GenericConversionService.ConverterCacheKey(sourceType, targetType);
449         GenericConverter converter = (GenericConverter)this.converterCache.get(key);
450         if (converter != null) {
451             return converter != NO_MATCH ? converter : null;
452         } else {
453             converter = this.converters.find(sourceType, targetType);
454             if (converter == null) {
455                 converter = this.getDefaultConverter(sourceType, targetType);
456             }
457 
458             if (converter != null) {
459                 this.converterCache.put(key, converter);
460                 return converter;
461             } else {
462                 this.converterCache.put(key, NO_MATCH);
463                 return null;
464             }
465         }
466     }
467 
468     @Nullable
469         public GenericConverter find(TypeDescriptor sourceType, TypeDescriptor targetType) {
470             List<Class<?>> sourceCandidates = this.getClassHierarchy(sourceType.getType());
471             List<Class<?>> targetCandidates = this.getClassHierarchy(targetType.getType());
472             Iterator var5 = sourceCandidates.iterator();
473 
474             while(var5.hasNext()) {
475                 Class<?> sourceCandidate = (Class)var5.next();
476                 Iterator var7 = targetCandidates.iterator();
477 
478                 while(var7.hasNext()) {
479                     Class<?> targetCandidate = (Class)var7.next();
480                     ConvertiblePair convertiblePair = new ConvertiblePair(sourceCandidate, targetCandidate);
481                     GenericConverter converter = this.getRegisteredConverter(sourceType, targetType, convertiblePair);
482                     if (converter != null) {
483                         return converter;
484                     }
485                 }
486             }
487 
488             return null;
489         }
490 }
View Code

 

 

 

 

未来我们可以给WebDataBinder里面放自己的Converter;

private static final class StringToNumber<T extends Number> implements Converter<String, T>

 

自定义 Converter

 1     //1、WebMvcConfigurer定制化SpringMVC的功能
 2     @Bean
 3     public WebMvcConfigurer webMvcConfigurer(){
 4         return new WebMvcConfigurer() {
 5             @Override
 6             public void configurePathMatch(PathMatchConfigurer configurer) {
 7                 UrlPathHelper urlPathHelper = new UrlPathHelper();
 8                 // 不移除;后面的内容。矩阵变量功能就可以生效
 9                 urlPathHelper.setRemoveSemicolonContent(false);
10                 configurer.setUrlPathHelper(urlPathHelper);
11             }
12 
13             @Override
14             public void addFormatters(FormatterRegistry registry) {
15                 registry.addConverter(new Converter<String, Pet>() {
16 
17                     @Override
18                     public Pet convert(String source) {
19                         // 啊猫,3
20                         if(!StringUtils.isEmpty(source)){
21                             Pet pet = new Pet();
22                             String[] split = source.split(",");
23                             pet.setName(split[0]);
24                             pet.setAge(Integer.parseInt(split[1]));
25                             return pet;
26                         }
27                         return null;
28                     }
29                 });
30             }
31         };
32     }
Converter

5.3.4 参数处理原理

  • HandlerMapping中找到能处理请求的Handler(Controller.method())
  • 为当前Handler 找一个适配器 HandlerAdapter; RequestMappingHandlerAdapter
  • 适配器执行目标方法并确定方法参数的每一个值

1)  HandlerAdapter 

 1 protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
 2         if (this.handlerAdapters != null) {
 3             for (HandlerAdapter adapter : this.handlerAdapters) {
 4                 if (adapter.supports(handler)) {
 5                     return adapter;
 6                 }
 7             }
 8         }
 9         throw new ServletException("No adapter for handler [" + handler +
10                 "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
11     }
getHandlerAdapter

 2)  执行目标方法

// Actually invoke the handler.
//DispatcherServlet -- doDispatch
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
1 public abstract class AbstractHandlerMethodAdapter extends WebContentGenerator implements HandlerAdapter, Ordered {      
2         @Override
3     @Nullable
4     public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
5             throws Exception {
6 
7         return handleInternal(request, response, (HandlerMethod) handler);
8     }  
9 }  
handle
 1 public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter
 2         implements BeanFactoryAware, InitializingBean {
 3         @Override
 4     protected ModelAndView handleInternal(HttpServletRequest request,
 5             HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
 6 
 7         ModelAndView mav;
 8         checkRequest(request);
 9 
10         // Execute invokeHandlerMethod in synchronized block if required.
11         if (this.synchronizeOnSession) {
12             HttpSession session = request.getSession(false);
13             if (session != null) {
14                 Object mutex = WebUtils.getSessionMutex(session);
15                 synchronized (mutex) {
16                     mav = invokeHandlerMethod(request, response, handlerMethod);
17                 }
18             }
19             else {
20                 // No HttpSession available -> no mutex necessary
21                 mav = invokeHandlerMethod(request, response, handlerMethod);
22             }
23         }
24         else {
25             // No synchronization on session demanded at all...
26             mav = invokeHandlerMethod(request, response, handlerMethod);
27         }
28 
29         if (!response.containsHeader(HEADER_CACHE_CONTROL)) {
30             if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
31                 applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
32             }
33             else {
34                 prepareResponse(response);
35             }
36         }
37 
38         return mav;
39     }
40 
41     @Nullable
42     protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
43             HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
44 
45         ServletWebRequest webRequest = new ServletWebRequest(request, response);
46         try {
47             WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
48             ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);
49 
50             ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
51             if (this.argumentResolvers != null) {
52                 invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
53             }
54             if (this.returnValueHandlers != null) {
55                 invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
56             }
57             invocableMethod.setDataBinderFactory(binderFactory);
58             invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
59 
60             ModelAndViewContainer mavContainer = new ModelAndViewContainer();
61             mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
62             modelFactory.initModel(webRequest, mavContainer, invocableMethod);
63             mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);
64 
65             AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);
66             asyncWebRequest.setTimeout(this.asyncRequestTimeout);
67 
68             WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
69             asyncManager.setTaskExecutor(this.taskExecutor);
70             asyncManager.setAsyncWebRequest(asyncWebRequest);
71             asyncManager.registerCallableInterceptors(this.callableInterceptors);
72             asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors);
73 
74             if (asyncManager.hasConcurrentResult()) {
75                 Object result = asyncManager.getConcurrentResult();
76                 mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0];
77                 asyncManager.clearConcurrentResult();
78                 LogFormatUtils.traceDebug(logger, traceOn -> {
79                     String formatted = LogFormatUtils.formatValue(result, !traceOn);
80                     return "Resume with async result [" + formatted + "]";
81                 });
82                 invocableMethod = invocableMethod.wrapConcurrentResult(result);
83             }
84 
85             invocableMethod.invokeAndHandle(webRequest, mavContainer);
86             if (asyncManager.isConcurrentHandlingStarted()) {
87                 return null;
88             }
89 
90             return getModelAndView(mavContainer, modelFactory, webRequest);
91         }
92         finally {
93             webRequest.requestCompleted();
94         }
95     }
96 }    
handleInternal-->invokeHandlerMethod
 1 public class ServletInvocableHandlerMethod extends InvocableHandlerMethod {
 2 
 3 public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
 4             Object... providedArgs) throws Exception {
 5 
 6         Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
 7         setResponseStatus(webRequest);
 8 
 9         if (returnValue == null) {
10             if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) {
11                 disableContentCachingIfNecessary(webRequest);
12                 mavContainer.setRequestHandled(true);
13                 return;
14             }
15         }
16         else if (StringUtils.hasText(getResponseStatusReason())) {
17             mavContainer.setRequestHandled(true);
18             return;
19         }
20 
21         mavContainer.setRequestHandled(false);
22         Assert.state(this.returnValueHandlers != null, "No return value handlers");
23         try {
24             this.returnValueHandlers.handleReturnValue(
25                     returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
26         }
27         catch (Exception ex) {
28             if (logger.isTraceEnabled()) {
29                 logger.trace(formatErrorForReturnValue(returnValue), ex);
30             }
31             throw ex;
32         }
33     }
34 }
invokeAndHandle
 1 public class InvocableHandlerMethod extends HandlerMethod {
 2     @Nullable
 3     public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
 4         Object[] args = this.getMethodArgumentValues(request, mavContainer, providedArgs);
 5         if (this.logger.isTraceEnabled()) {
 6             this.logger.trace("Arguments: " + Arrays.toString(args));
 7         }
 8 
 9         return this.doInvoke(args);
10     }
11 
12    protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
13         MethodParameter[] parameters = this.getMethodParameters();
14         if (ObjectUtils.isEmpty(parameters)) {
15             return EMPTY_ARGS;
16         } else {
17             Object[] args = new Object[parameters.length];
18 
19             for(int i = 0; i < parameters.length; ++i) {
20                 MethodParameter parameter = parameters[i];
21                 parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
22                 args[i] = findProvidedArgument(parameter, providedArgs);
23                 if (args[i] == null) {
24                     if (!this.resolvers.supportsParameter(parameter)) {
25                         throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));
26                     }
27 
28                     try {
29                         args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
30                     } catch (Exception var10) {
31                         if (this.logger.isDebugEnabled()) {
32                             String exMsg = var10.getMessage();
33                             if (exMsg != null && !exMsg.contains(parameter.getExecutable().toGenericString())) {
34                                 this.logger.debug(formatArgumentError(parameter, exMsg));
35                             }
36                         }
37 
38                         throw var10;
39                     }
40                 }
41             }
42 
43             return args;
44         }
45     } 
46 }
invokeForRequest-->getMethodArgumentValues
 1 public class HandlerMethodArgumentResolverComposite implements HandlerMethodArgumentResolver {
 2     public boolean supportsParameter(MethodParameter parameter) {
 3         return this.getArgumentResolver(parameter) != null;
 4     }
 5 
 6     @Nullable
 7     private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
 8         HandlerMethodArgumentResolver result = (HandlerMethodArgumentResolver)this.argumentResolverCache.get(parameter);
 9         if (result == null) {
10             Iterator var3 = this.argumentResolvers.iterator();
11 
12             while(var3.hasNext()) {
13                 HandlerMethodArgumentResolver resolver = (HandlerMethodArgumentResolver)var3.next();
14                 if (resolver.supportsParameter(parameter)) {
15                     result = resolver;
16                     this.argumentResolverCache.put(parameter, resolver);
17                     break;
18                 }
19             }
20         }
21 
22         return result;
23     }
24 }
supportsParameter-->getArgumentResolver
 1 public class HandlerMethodArgumentResolverComposite implements HandlerMethodArgumentResolver {
 2     @Nullable
 3     public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
 4         HandlerMethodArgumentResolver resolver = this.getArgumentResolver(parameter);
 5         if (resolver == null) {
 6             throw new IllegalArgumentException("Unsupported parameter type [" + parameter.getParameterType().getName() + "]. supportsParameter should be called first.");
 7         } else {
 8             return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
 9         }
10     }
11 
12     @Nullable
13     private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
14         HandlerMethodArgumentResolver result = (HandlerMethodArgumentResolver)this.argumentResolverCache.get(parameter);
15         if (result == null) {
16             Iterator var3 = this.argumentResolvers.iterator();
17 
18             while(var3.hasNext()) {
19                 HandlerMethodArgumentResolver resolver = (HandlerMethodArgumentResolver)var3.next();
20                 if (resolver.supportsParameter(parameter)) {
21                     result = resolver;
22                     this.argumentResolverCache.put(parameter, resolver);
23                     break;
24                 }
25             }
26         }
27 
28         return result;
29     }
30 }
resolveArgument-->getArgumentResolver
 1 public abstract class AbstractNamedValueMethodArgumentResolver implements HandlerMethodArgumentResolver {
 2     @Nullable
 3     public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
 4         AbstractNamedValueMethodArgumentResolver.NamedValueInfo namedValueInfo = this.getNamedValueInfo(parameter);
 5         MethodParameter nestedParameter = parameter.nestedIfOptional();
 6         Object resolvedName = this.resolveStringValue(namedValueInfo.name);
 7         if (resolvedName == null) {
 8             throw new IllegalArgumentException("Specified name must not resolve to null: [" + namedValueInfo.name + "]");
 9         } else {
10             Object arg = this.resolveName(resolvedName.toString(), nestedParameter, webRequest);
11             if (arg == null) {
12                 if (namedValueInfo.defaultValue != null) {
13                     arg = this.resolveStringValue(namedValueInfo.defaultValue);
14                 } else if (namedValueInfo.required && !nestedParameter.isOptional()) {
15                     this.handleMissingValue(namedValueInfo.name, nestedParameter, webRequest);
16                 }
17 
18                 arg = this.handleNullValue(namedValueInfo.name, arg, nestedParameter.getNestedParameterType());
19             } else if ("".equals(arg) && namedValueInfo.defaultValue != null) {
20                 arg = this.resolveStringValue(namedValueInfo.defaultValue);
21             }
22 
23             if (binderFactory != null) {
24                 WebDataBinder binder = binderFactory.createBinder(webRequest, (Object)null, namedValueInfo.name);
25 
26                 try {
27                     arg = binder.convertIfNecessary(arg, parameter.getParameterType(), parameter);
28                 } catch (ConversionNotSupportedException var11) {
29                     throw new MethodArgumentConversionNotSupportedException(arg, var11.getRequiredType(), namedValueInfo.name, parameter, var11.getCause());
30                 } catch (TypeMismatchException var12) {
31                     throw new MethodArgumentTypeMismatchException(arg, var12.getRequiredType(), namedValueInfo.name, parameter, var12.getCause());
32                 }
33             }
34 
35             this.handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest);
36             return arg;
37         }
38     }
39 }
resolveArgument

3)  参数解析器-HandlerMethodArgumentResolver

确定将要执行的目标方法的每一个参数的值是什么;

SpringMVC目标方法能写多少种参数类型。取决于参数解析器。

 

 

   4)  返回值处理器

  5) 目标方法执行完成 

将所有的数据都放在 ModelAndViewContainer;包含要去的页面地址View。还包含Model数据。

 

 6)  处理派发结果

processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);

 

renderMergedOutputModel(mergedModel, getRequestToExpose(request), response);

 

exposeModelAsRequestAttributes(model, request);

5.4 数据响应与内容协商

5.4.1 响应JSON

1)  jackson.jar+@ResponseBody

给前端自动返回json数据;

 1         <dependency>
 2 
 3             <groupId>org.springframework.boot</groupId>
 4 
 5             <artifactId>spring-boot-starter-web</artifactId>
 6 
 7         </dependency>
 8 
 9 web场景自动引入了json场景
10 
11     <dependency>
12 
13           <groupId>org.springframework.boot</groupId>
14 
15           <artifactId>spring-boot-starter-json</artifactId>
16 
17           <version>2.3.4.RELEASE</version>
18 
19           <scope>compile</scope>
20 
21     </dependency>
22     
23 
24             <dependency>
25             <groupId>com.fasterxml.jackson.core</groupId>
26             <artifactId>jackson-databind</artifactId>
27             <version>2.11.4</version>
28         </dependency>
29         <dependency>
30             <groupId>com.fasterxml.jackson.datatype</groupId>
31             <artifactId>jackson-datatype-jdk8</artifactId>
32             <version>2.11.4</version>
33         </dependency>
34         <dependency>
35             <groupId>com.fasterxml.jackson.datatype</groupId>
36             <artifactId>jackson-datatype-jsr310</artifactId>
37             <version>2.11.4</version>
38         </dependency>
jackson

 2)  返回值解析器

 

  3) 返回值解析器原理 

 

 1、返回值处理器判断是否支持这种类型返回值 supportsReturnType

2、返回值处理器调用 handleReturnValue 进行处理

3、RequestResponseBodyMethodProcessor 可以处理返回值标了@ResponseBody 注解的。

1.  利用 MessageConverters 进行处理 将数据写为json

1、内容协商(浏览器默认会以请求头的方式告诉服务器他能接受什么样的内容类型)

2、服务器最终根据自己自身的能力,决定服务器能生产出什么样内容类型的数据,

3、SpringMVC会挨个遍历所有容器底层的 HttpMessageConverter ,看谁能处理?

1、得到MappingJackson2HttpMessageConverter可以将对象写为json

2、利用MappingJackson2HttpMessageConverter将对象转为json再写出去。

 1 public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter
 2         implements BeanFactoryAware, InitializingBean {
 3     protected ModelAndView invokeHandlerMethod(){
 4         ...
 5         invocableMethod.invokeAndHandle(webRequest, mavContainer);
 6     }   
 7 } 
 8 
 9 public class ServletInvocableHandlerMethod extends InvocableHandlerMethod {
10     public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
11             Object... providedArgs) throws Exception {
12         Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
13         ......
14         this.returnValueHandlers.handleReturnValue(
15                     returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
16     }
17 }
18 
19 public class HandlerMethodReturnValueHandlerComposite implements HandlerMethodReturnValueHandler {
20     public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
21         HandlerMethodReturnValueHandler handler = this.selectHandler(returnValue, returnType);
22         if (handler == null) {
23             throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName());
24         } else {
25             handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
26         }
27     }
28 
29     @Nullable
30     private HandlerMethodReturnValueHandler selectHandler(@Nullable Object value, MethodParameter returnType) {
31         boolean isAsyncValue = this.isAsyncReturnValue(value, returnType);
32         Iterator var4 = this.returnValueHandlers.iterator();
33 
34         HandlerMethodReturnValueHandler handler;
35         do {
36             do {
37                 if (!var4.hasNext()) {
38                     return null;
39                 }
40 
41                 handler = (HandlerMethodReturnValueHandler)var4.next();
42             } while(isAsyncValue && !(handler instanceof AsyncHandlerMethodReturnValueHandler));
43         } while(!handler.supportsReturnType(returnType));
44 
45         return handler;
46     }
47 }
48 
49 public class RequestResponseBodyMethodProcessor extends AbstractMessageConverterMethodProcessor {
50         @Override
51     public boolean supportsReturnType(MethodParameter returnType) {
52         return (AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResponseBody.class) ||
53                 returnType.hasMethodAnnotation(ResponseBody.class));
54     }
55     @Override
56     public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
57             ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
58             throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
59 
60         mavContainer.setRequestHandled(true);
61         ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
62         ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);
63 
64         // Try even with null return value. ResponseBodyAdvice could get involved.
65         writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
66     }
67 }
writeWithMessageConverters
  1 public abstract class AbstractMessageConverterMethodProcessor extends AbstractMessageConverterMethodArgumentResolver
  2         implements HandlerMethodReturnValueHandler {
  3     protected <T> void writeWithMessageConverters(@Nullable T value, MethodParameter returnType,
  4             ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)
  5             throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
  6 
  7         Object body;
  8         Class<?> valueType;
  9         Type targetType;
 10 
 11         if (value instanceof CharSequence) {
 12             body = value.toString();
 13             valueType = String.class;
 14             targetType = String.class;
 15         }
 16         else {
 17             body = value;
 18             valueType = getReturnValueType(body, returnType);
 19             targetType = GenericTypeResolver.resolveType(getGenericType(returnType), returnType.getContainingClass());
 20         }
 21 
 22         if (isResourceType(value, returnType)) {
 23             outputMessage.getHeaders().set(HttpHeaders.ACCEPT_RANGES, "bytes");
 24             if (value != null && inputMessage.getHeaders().getFirst(HttpHeaders.RANGE) != null &&
 25                     outputMessage.getServletResponse().getStatus() == 200) {
 26                 Resource resource = (Resource) value;
 27                 try {
 28                     List<HttpRange> httpRanges = inputMessage.getHeaders().getRange();
 29                     outputMessage.getServletResponse().setStatus(HttpStatus.PARTIAL_CONTENT.value());
 30                     body = HttpRange.toResourceRegions(httpRanges, resource);
 31                     valueType = body.getClass();
 32                     targetType = RESOURCE_REGION_LIST_TYPE;
 33                 }
 34                 catch (IllegalArgumentException ex) {
 35                     outputMessage.getHeaders().set(HttpHeaders.CONTENT_RANGE, "bytes */" + resource.contentLength());
 36                     outputMessage.getServletResponse().setStatus(HttpStatus.REQUESTED_RANGE_NOT_SATISFIABLE.value());
 37                 }
 38             }
 39         }
 40 
 41         MediaType selectedMediaType = null;
 42         MediaType contentType = outputMessage.getHeaders().getContentType();
 43         boolean isContentTypePreset = contentType != null && contentType.isConcrete();
 44         if (isContentTypePreset) {
 45             if (logger.isDebugEnabled()) {
 46                 logger.debug("Found 'Content-Type:" + contentType + "' in response");
 47             }
 48             selectedMediaType = contentType;
 49         }
 50         else {
 51             HttpServletRequest request = inputMessage.getServletRequest();
 52             List<MediaType> acceptableTypes = getAcceptableMediaTypes(request);
 53             List<MediaType> producibleTypes = getProducibleMediaTypes(request, valueType, targetType);
 54 
 55             if (body != null && producibleTypes.isEmpty()) {
 56                 throw new HttpMessageNotWritableException(
 57                         "No converter found for return value of type: " + valueType);
 58             }
 59             List<MediaType> mediaTypesToUse = new ArrayList<>();
 60             for (MediaType requestedType : acceptableTypes) {
 61                 for (MediaType producibleType : producibleTypes) {
 62                     if (requestedType.isCompatibleWith(producibleType)) {
 63                         mediaTypesToUse.add(getMostSpecificMediaType(requestedType, producibleType));
 64                     }
 65                 }
 66             }
 67             if (mediaTypesToUse.isEmpty()) {
 68                 if (body != null) {
 69                     throw new HttpMediaTypeNotAcceptableException(producibleTypes);
 70                 }
 71                 if (logger.isDebugEnabled()) {
 72                     logger.debug("No match for " + acceptableTypes + ", supported: " + producibleTypes);
 73                 }
 74                 return;
 75             }
 76 
 77             MediaType.sortBySpecificityAndQuality(mediaTypesToUse);
 78 
 79             for (MediaType mediaType : mediaTypesToUse) {
 80                 if (mediaType.isConcrete()) {
 81                     selectedMediaType = mediaType;
 82                     break;
 83                 }
 84                 else if (mediaType.isPresentIn(ALL_APPLICATION_MEDIA_TYPES)) {
 85                     selectedMediaType = MediaType.APPLICATION_OCTET_STREAM;
 86                     break;
 87                 }
 88             }
 89 
 90             if (logger.isDebugEnabled()) {
 91                 logger.debug("Using '" + selectedMediaType + "', given " +
 92                         acceptableTypes + " and supported " + producibleTypes);
 93             }
 94         }
 95 
 96         if (selectedMediaType != null) {
 97             selectedMediaType = selectedMediaType.removeQualityValue();
 98             for (HttpMessageConverter<?> converter : this.messageConverters) {
 99                 GenericHttpMessageConverter genericConverter = (converter instanceof GenericHttpMessageConverter ?
100                         (GenericHttpMessageConverter<?>) converter : null);
101                 if (genericConverter != null ?
102                         ((GenericHttpMessageConverter) converter).canWrite(targetType, valueType, selectedMediaType) :
103                         converter.canWrite(valueType, selectedMediaType)) {
104                     body = getAdvice().beforeBodyWrite(body, returnType, selectedMediaType,
105                             (Class<? extends HttpMessageConverter<?>>) converter.getClass(),
106                             inputMessage, outputMessage);
107                     if (body != null) {
108                         Object theBody = body;
109                         LogFormatUtils.traceDebug(logger, traceOn ->
110                                 "Writing [" + LogFormatUtils.formatValue(theBody, !traceOn) + "]");
111                         addContentDispositionHeader(inputMessage, outputMessage);
112                         if (genericConverter != null) {
113                             genericConverter.write(body, targetType, selectedMediaType, outputMessage);
114                         }
115                         else {
116                             ((HttpMessageConverter) converter).write(body, selectedMediaType, outputMessage);
117                         }
118                     }
119                     else {
120                         if (logger.isDebugEnabled()) {
121                             logger.debug("Nothing to write: null body");
122                         }
123                     }
124                     return;
125                 }
126             }
127         }
128 
129         if (body != null) {
130             Set<MediaType> producibleMediaTypes =
131                     (Set<MediaType>) inputMessage.getServletRequest()
132                             .getAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
133 
134             if (isContentTypePreset || !CollectionUtils.isEmpty(producibleMediaTypes)) {
135                 throw new HttpMessageNotWritableException(
136                         "No converter for [" + valueType + "] with preset Content-Type '" + contentType + "'");
137             }
138             throw new HttpMediaTypeNotAcceptableException(this.allSupportedMediaTypes);
139         }
140     }
141 }
142 
143 public abstract class AbstractJackson2HttpMessageConverter extends AbstractGenericHttpMessageConverter<Object>
144     public boolean canRead(Type type, @Nullable Class<?> contextClass, @Nullable MediaType mediaType) {
145         if (!this.canRead(mediaType)) {
146             return false;
147         } else {
148             JavaType javaType = this.getJavaType(type, contextClass);
149             AtomicReference<Throwable> causeRef = new AtomicReference();
150             if (this.objectMapper.canDeserialize(javaType, causeRef)) {
151                 return true;
152             } else {
153                 this.logWarningIfNecessary(javaType, (Throwable)causeRef.get());
154                 return false;
155             }
156         }
157     }
158 
159     protected void writeInternal(Object object, @Nullable Type type, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
160         MediaType contentType = outputMessage.getHeaders().getContentType();
161         JsonEncoding encoding = this.getJsonEncoding(contentType);
162         JsonGenerator generator = this.objectMapper.getFactory().createGenerator(outputMessage.getBody(), encoding);
163 
164         try {
165             this.writePrefix(generator, object);
166             Object value = object;
167             Class<?> serializationView = null;
168             FilterProvider filters = null;
169             JavaType javaType = null;
170             if (object instanceof MappingJacksonValue) {
171                 MappingJacksonValue container = (MappingJacksonValue)object;
172                 value = container.getValue();
173                 serializationView = container.getSerializationView();
174                 filters = container.getFilters();
175             }
176 
177             if (type != null && TypeUtils.isAssignable(type, value.getClass())) {
178                 javaType = this.getJavaType(type, (Class)null);
179             }
180 
181             ObjectWriter objectWriter = serializationView != null ? this.objectMapper.writerWithView(serializationView) : this.objectMapper.writer();
182             if (filters != null) {
183                 objectWriter = objectWriter.with(filters);
184             }
185 
186             if (javaType != null && javaType.isContainerType()) {
187                 objectWriter = objectWriter.forType(javaType);
188             }
189 
190             SerializationConfig config = objectWriter.getConfig();
191             if (contentType != null && contentType.isCompatibleWith(MediaType.TEXT_EVENT_STREAM) && config.isEnabled(SerializationFeature.INDENT_OUTPUT)) {
192                 objectWriter = objectWriter.with(this.ssePrettyPrinter);
193             }
194 
195             objectWriter.writeValue(generator, value);
196             this.writeSuffix(generator, object);
197             generator.flush();
198         } catch (InvalidDefinitionException var13) {
199             throw new HttpMessageConversionException("Type definition error: " + var13.getType(), var13);
200         } catch (JsonProcessingException var14) {
201             throw new HttpMessageNotWritableException("Could not write JSON: " + var14.getOriginalMessage(), var14);
202         }
203     }
204 }
205 
206 public abstract class AbstractHttpMessageConverter<T> implements HttpMessageConverter<T> {
207     protected boolean canWrite(@Nullable MediaType mediaType) {
208         if (mediaType != null && !MediaType.ALL.equalsTypeAndSubtype(mediaType)) {
209             Iterator var2 = this.getSupportedMediaTypes().iterator();
210 
211             MediaType supportedMediaType;
212             do {
213                 if (!var2.hasNext()) {
214                     return false;
215                 }
216 
217                 supportedMediaType = (MediaType)var2.next();
218             } while(!supportedMediaType.isCompatibleWith(mediaType));
219 
220             return true;
221         } else {
222             return true;
223         }
224     }
225 }
226 
227 public abstract class AbstractGenericHttpMessageConverter<T> extends AbstractHttpMessageConverter<T> implements GenericHttpMessageConverter<T> {
228 public boolean canWrite(@Nullable Type type, Class<?> clazz, @Nullable MediaType mediaType) {
229         return this.canWrite(clazz, mediaType);
230     }
231 
232     public final void write(T t, @Nullable Type type, @Nullable MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
233         HttpHeaders headers = outputMessage.getHeaders();
234         this.addDefaultHeaders(headers, t, contentType);
235         if (outputMessage instanceof StreamingHttpOutputMessage) {
236             StreamingHttpOutputMessage streamingOutputMessage = (StreamingHttpOutputMessage)outputMessage;
237             streamingOutputMessage.setBody((outputStream) -> {
238                 this.writeInternal(t, type, new HttpOutputMessage() {
239                     public OutputStream getBody() {
240                         return outputStream;
241                     }
242 
243                     public HttpHeaders getHeaders() {
244                         return headers;
245                     }
246                 });
247             });
248         } else {
249             this.writeInternal(t, type, outputMessage);
250             outputMessage.getBody().flush();
251         }
252 
253     }
254 }
255 
256 public class MappingJackson2HttpMessageConverter extends AbstractJackson2HttpMessageConverter {
257     protected void writePrefix(JsonGenerator generator, Object object) throws IOException {
258         if (this.jsonPrefix != null) {
259             generator.writeRaw(this.jsonPrefix);
260         }
261 
262     }
263 }
HttpMessageConverter

 

  

 

 最终 MappingJackson2HttpMessageConverter  把对象转为JSON(利用底层的jackson的objectMapper转换的)

 

 

 5.4.2内容协商

根据客户端接收能力不同,返回不同媒体类型的数据。

1)  引入xml依赖

1  <dependency>
2 
3             <groupId>com.fasterxml.jackson.dataformat</groupId>
4 
5             <artifactId>jackson-dataformat-xml</artifactId>
6 
7 </dependency>
xml

2)  postman分别测试返回json和xml

只需要改变请求头中Accept字段。Http协议中规定的,告诉服务器本客户端可以接收的数据类型。

 

 3)  开启浏览器参数方式内容协商功能

为了方便内容协商,开启基于请求参数的内容协商功能。

spring:

    contentnegotiation:

      favor-parameter: true  #开启请求参数内容协商模式

发请求: http://localhost:8080/test/person?format=json

 http://localhost:8080/test/person?format=xml

1、Parameter策略优先确定是要返回json数据(获取请求头中的format的值)

2、最终进行内容协商返回给客户端json即可。

 

 4)  内容协商原理

1、判断当前响应头中是否已经有确定的媒体类型。MediaType

2、获取客户端(PostMan、浏览器)支持接收的内容类型。(获取客户端Accept请求头字段)【application/xml】

  • contentNegotiationManager 内容协商管理器 默认使用基于请求头的策略
  • HeaderContentNegotiationStrategy  确定客户端可以接收的内容类型

3、遍历循环所有当前系统的 MessageConverter,看谁支持操作这个对象(Person)

4、找到支持操作Person的converter,把converter支持的媒体类型统计出来。

5、客户端需要【application/xml】。服务端能力【10种、json、xml】

6、进行内容协商的最佳匹配媒体类型

7、用 支持 将对象转为 最佳匹配媒体类型 的converter。调用它进行转化

  1 public abstract class AbstractMessageConverterMethodProcessor extends AbstractMessageConverterMethodArgumentResolver
  2         implements HandlerMethodReturnValueHandler {
  3 protected <T> void writeWithMessageConverters(@Nullable T value, MethodParameter returnType,
  4             ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)
  5             throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
  6 
  7     MediaType selectedMediaType = null;
  8         MediaType contentType = outputMessage.getHeaders().getContentType();
  9         boolean isContentTypePreset = contentType != null && contentType.isConcrete();
 10         if (isContentTypePreset) {
 11             if (logger.isDebugEnabled()) {
 12                 logger.debug("Found 'Content-Type:" + contentType + "' in response");
 13             }
 14             selectedMediaType = contentType;
 15         }
 16         else {
 17             HttpServletRequest request = inputMessage.getServletRequest();
 18             List<MediaType> acceptableTypes = getAcceptableMediaTypes(request);
 19             List<MediaType> producibleTypes = getProducibleMediaTypes(request, valueType, targetType);
 20 
 21             if (body != null && producibleTypes.isEmpty()) {
 22                 throw new HttpMessageNotWritableException(
 23                         "No converter found for return value of type: " + valueType);
 24             }
 25             List<MediaType> mediaTypesToUse = new ArrayList<>();
 26             for (MediaType requestedType : acceptableTypes) {
 27                 for (MediaType producibleType : producibleTypes) {
 28                     if (requestedType.isCompatibleWith(producibleType)) {
 29                         mediaTypesToUse.add(getMostSpecificMediaType(requestedType, producibleType));
 30                     }
 31                 }
 32             }
 33             if (mediaTypesToUse.isEmpty()) {
 34                 if (body != null) {
 35                     throw new HttpMediaTypeNotAcceptableException(producibleTypes);
 36                 }
 37                 if (logger.isDebugEnabled()) {
 38                     logger.debug("No match for " + acceptableTypes + ", supported: " + producibleTypes);
 39                 }
 40                 return;
 41             }
 42 
 43             MediaType.sortBySpecificityAndQuality(mediaTypesToUse);
 44 
 45             for (MediaType mediaType : mediaTypesToUse) {
 46                 if (mediaType.isConcrete()) {
 47                     selectedMediaType = mediaType;
 48                     break;
 49                 }
 50                 else if (mediaType.isPresentIn(ALL_APPLICATION_MEDIA_TYPES)) {
 51                     selectedMediaType = MediaType.APPLICATION_OCTET_STREAM;
 52                     break;
 53                 }
 54             }
 55 
 56             if (logger.isDebugEnabled()) {
 57                 logger.debug("Using '" + selectedMediaType + "', given " +
 58                         acceptableTypes + " and supported " + producibleTypes);
 59             }
 60         }
 61 
 62         if (selectedMediaType != null) {
 63             selectedMediaType = selectedMediaType.removeQualityValue();
 64             for (HttpMessageConverter<?> converter : this.messageConverters) {
 65                 GenericHttpMessageConverter genericConverter = (converter instanceof GenericHttpMessageConverter ?
 66                         (GenericHttpMessageConverter<?>) converter : null);
 67                 if (genericConverter != null ?
 68                         ((GenericHttpMessageConverter) converter).canWrite(targetType, valueType, selectedMediaType) :
 69                         converter.canWrite(valueType, selectedMediaType)) {
 70                     body = getAdvice().beforeBodyWrite(body, returnType, selectedMediaType,
 71                             (Class<? extends HttpMessageConverter<?>>) converter.getClass(),
 72                             inputMessage, outputMessage);
 73                     if (body != null) {
 74                         Object theBody = body;
 75                         LogFormatUtils.traceDebug(logger, traceOn ->
 76                                 "Writing [" + LogFormatUtils.formatValue(theBody, !traceOn) + "]");
 77                         addContentDispositionHeader(inputMessage, outputMessage);
 78                         if (genericConverter != null) {
 79                             genericConverter.write(body, targetType, selectedMediaType, outputMessage);
 80                         }
 81                         else {
 82                             ((HttpMessageConverter) converter).write(body, selectedMediaType, outputMessage);
 83                         }
 84                     }
 85                     else {
 86                         if (logger.isDebugEnabled()) {
 87                             logger.debug("Nothing to write: null body");
 88                         }
 89                     }
 90                     return;
 91                 }
 92             }
 93         }
 94 
 95         if (body != null) {
 96             Set<MediaType> producibleMediaTypes =
 97                     (Set<MediaType>) inputMessage.getServletRequest()
 98                             .getAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
 99 
100             if (isContentTypePreset || !CollectionUtils.isEmpty(producibleMediaTypes)) {
101                 throw new HttpMessageNotWritableException(
102                         "No converter for [" + valueType + "] with preset Content-Type '" + contentType + "'");
103             }
104             throw new HttpMediaTypeNotAcceptableException(this.allSupportedMediaTypes);
105         }
106     }
107    
108      private List<MediaType> getAcceptableMediaTypes(HttpServletRequest request)
109             throws HttpMediaTypeNotAcceptableException {
110 
111         return this.contentNegotiationManager.resolveMediaTypes(new ServletWebRequest(request));
112     }
113 
114     protected List<MediaType> getProducibleMediaTypes(
115             HttpServletRequest request, Class<?> valueClass, @Nullable Type targetType) {
116 
117         Set<MediaType> mediaTypes =
118                 (Set<MediaType>) request.getAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
119         if (!CollectionUtils.isEmpty(mediaTypes)) {
120             return new ArrayList<>(mediaTypes);
121         }
122         else if (!this.allSupportedMediaTypes.isEmpty()) {
123             List<MediaType> result = new ArrayList<>();
124             for (HttpMessageConverter<?> converter : this.messageConverters) {
125                 if (converter instanceof GenericHttpMessageConverter && targetType != null) {
126                     if (((GenericHttpMessageConverter<?>) converter).canWrite(targetType, valueClass, null)) {
127                         result.addAll(converter.getSupportedMediaTypes());
128                     }
129                 }
130                 else if (converter.canWrite(valueClass, null)) {
131                     result.addAll(converter.getSupportedMediaTypes());
132                 }
133             }
134             return result;
135         }
136         else {
137             return Collections.singletonList(MediaType.ALL);
138         }
139     }
140 }
141 
142 public class ContentNegotiationManager implements ContentNegotiationStrategy, MediaTypeFileExtensionResolver {
143     public List<MediaType> resolveMediaTypes(NativeWebRequest request) throws HttpMediaTypeNotAcceptableException {
144         Iterator var2 = this.strategies.iterator();
145 
146         List mediaTypes;
147         do {
148             if (!var2.hasNext()) {
149                 return MEDIA_TYPE_ALL_LIST;
150             }
151 
152             ContentNegotiationStrategy strategy = (ContentNegotiationStrategy)var2.next();
153             mediaTypes = strategy.resolveMediaTypes(request);
154         } while(mediaTypes.equals(MEDIA_TYPE_ALL_LIST));
155 
156         return mediaTypes;
157     }
158 }
159 
160 public class HeaderContentNegotiationStrategy implements ContentNegotiationStrategy {
161     public List<MediaType> resolveMediaTypes(NativeWebRequest request) throws HttpMediaTypeNotAcceptableException {
162         String[] headerValueArray = request.getHeaderValues("Accept");
163         if (headerValueArray == null) {
164             return MEDIA_TYPE_ALL_LIST;
165         } else {
166             List headerValues = Arrays.asList(headerValueArray);
167 
168             try {
169                 List<MediaType> mediaTypes = MediaType.parseMediaTypes(headerValues);
170                 MediaType.sortBySpecificityAndQuality(mediaTypes);
171                 return !CollectionUtils.isEmpty(mediaTypes) ? mediaTypes : MEDIA_TYPE_ALL_LIST;
172             } catch (InvalidMediaTypeException var5) {
173                 throw new HttpMediaTypeNotAcceptableException("Could not parse 'Accept' header " + headerValues + ": " + var5.getMessage());
174             }
175         }
176     }
177 }
writeWithMessageConverters

 

 

 5)  自定义 MessageConverter

实现多协议数据兼容。json、xml、x-guigu

0、@ResponseBody 响应数据出去 调用 RequestResponseBodyMethodProcessor 处理

1、Processor 处理方法返回值。通过 MessageConverter 处理

2、所有 MessageConverter 合起来可以支持各种媒体类型数据的操作(读、写)

3、内容协商找到最终的 messageConverter

 1 public class GuiguMessageConverter implements HttpMessageConverter<Person> {
 2     @Override
 3     public boolean canRead(Class<?> aClass, MediaType mediaType) {
 4         return false;
 5     }
 6 
 7     @Override
 8     public boolean canWrite(Class<?> aClass, MediaType mediaType) {
 9         return aClass.isAssignableFrom(Person.class);
10     }
11 
12     @Override
13     public List<MediaType> getSupportedMediaTypes() {
14         return MediaType.parseMediaTypes("application/x-guigu");
15     }
16 
17     @Override
18     public Person read(Class<? extends Person> aClass, HttpInputMessage httpInputMessage) throws IOException, HttpMessageNotReadableException {
19         return null;
20     }
21 
22     @Override
23     public void write(Person person, MediaType mediaType, HttpOutputMessage httpOutputMessage) throws IOException, HttpMessageNotWritableException {
24         String data = person.getUsername() + ";" + person.getAge() + ";" + person.getBirth();
25         httpOutputMessage.getBody().write(data.getBytes());
26     }
27 }
GuiguMessageConverter
1 spring:
2   resources:
3     add-mappings: true
4   mvc:
5     hiddenmethod:
6       filter:
7         enabled: true
8     contentnegotiation:
9       favor-parameter: true
contentnegotiation
 1     @Bean
 2     public WebMvcConfigurer webMvcConfigurer() {
 3         WebMvcConfigurer webMvcConfigurer = new WebMvcConfigurer(){
 4             @Override
 5             public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
 6                 Map<String, MediaType> mediaTypeHashMap = new HashMap<>();
 7                 mediaTypeHashMap.put("json",MediaType.APPLICATION_JSON);
 8                 mediaTypeHashMap.put("xml",MediaType.APPLICATION_XML);
 9                 mediaTypeHashMap.put("gg",MediaType.parseMediaType("application/x-guigu"));
10 
11                 ParameterContentNegotiationStrategy parameterStrategy = new ParameterContentNegotiationStrategy(mediaTypeHashMap);
12                 HeaderContentNegotiationStrategy headerStrategy = new HeaderContentNegotiationStrategy();
13                 configurer.strategies(Arrays.asList(parameterStrategy,headerStrategy));
14             }
15 
16             @Override
17             public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
18                 converters.add(new GuiguMessageConverter());
19             }
20         };
21 
22         return webMvcConfigurer;
23     }
WebMvcConfigurer

 5.5 视图解析与模板引擎

视图解析:SpringBoot默认不支持 JSP,需要引入第三方模板引擎技术实现页面渲染。 

5.5.1 视图解析

  • 1、目标方法处理的过程中,所有数据都会被放在 ModelAndViewContainer 里面。包括数据和视图地址
  • 2、方法的参数是一个自定义类型对象(从请求参数中确定的),把他重新放在 ModelAndViewContainer
  • 3、任何目标方法执行完成以后都会返回 ModelAndView(数据和视图地址)。
  • 4、processDispatchResult  处理派发结果(页面改如何响应)
  • 1、render(mv, request, response); 进行页面渲染逻辑
  • 1、根据方法的String返回值得到 View 对象【定义了页面的渲染逻辑】
  • 1、所有的视图解析器尝试是否能根据当前返回值得到View对象
  • 2、得到了 redirect:/main.html --> Thymeleaf new RedirectView()
  • 3、ContentNegotiationViewResolver 里面包含了下面所有的视图解析器,内部还是利用下面所有视图解析器得到视图对象。
  • 4、view.render(mv.getModelInternal(), request, response);   视图对象调用自定义的render进行页面渲染工作
  • RedirectView 如何渲染【重定向到一个页面】
  • 1、获取目标url地址
  • 2、response.sendRedirect(encodedURL);

 

视图解析:

  • 返回值以 forward: 开始: new InternalResourceView(forwardUrl); -->  转发request.getRequestDispatcher(path).forward(request, response);
  • 返回值以 redirect: 开始: new RedirectView() --》 render就是重定向
  • 返回值是普通字符串: new ThymeleafView()---> 
 1 public class DispatcherServlet extends FrameworkServlet {
 2    protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { 
 3         mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
 4          this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);
 5     }
 6 }
 7 
 8 public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter implements BeanFactoryAware, InitializingBean {
 9     protected ModelAndView handleInternal(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
10         mav = this.invokeHandlerMethod(request, response, handlerMethod);
11     }
12     protected ModelAndView invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
13         invocableMethod.invokeAndHandle(webRequest, mavContainer, new Object[0]);
14         var15 = this.getModelAndView(mavContainer, modelFactory, webRequest);
15     }
16 
17      private ModelAndView getModelAndView(ModelAndViewContainer mavContainer, ModelFactory modelFactory, NativeWebRequest webRequest) throws Exception {
18         ModelMap model = mavContainer.getModel();
19         ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, mavContainer.getStatus());
20         if (model instanceof RedirectAttributes) {
21                 Map<String, ?> flashAttributes = ((RedirectAttributes)model).getFlashAttributes();
22                 HttpServletRequest request = (HttpServletRequest)webRequest.getNativeRequest(HttpServletRequest.class);
23                 if (request != null) {
24                     RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes);
25                 }
26             }
27     }
28 }
29 
30 public class ServletInvocableHandlerMethod extends InvocableHandlerMethod {
31     public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
32 
33     this.returnValueHandlers.handleReturnValue(returnValue, this.getReturnValueType(returnValue), mavContainer, webRequest);
34     }
35 }
36 
37 public class HandlerMethodReturnValueHandlerComposite implements HandlerMethodReturnValueHandler {
38    public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
39         HandlerMethodReturnValueHandler handler = this.selectHandler(returnValue, returnType);
40        
41           handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
42         
43     }
44 
45     private HandlerMethodReturnValueHandler selectHandler(@Nullable Object value, MethodParameter returnType) {
46         boolean isAsyncValue = this.isAsyncReturnValue(value, returnType);
47         Iterator var4 = this.returnValueHandlers.iterator();
48 
49         HandlerMethodReturnValueHandler handler;
50         do {
51             do {
52                 if (!var4.hasNext()) {
53                     return null;
54                 }
55 
56                 handler = (HandlerMethodReturnValueHandler)var4.next();
57             } while(isAsyncValue && !(handler instanceof AsyncHandlerMethodReturnValueHandler));
58         } while(!handler.supportsReturnType(returnType));
59 
60         return handler;
61     }
62 }
63 
64 public class ViewNameMethodReturnValueHandler implements HandlerMethodReturnValueHandler {
65     public boolean supportsReturnType(MethodParameter returnType) {
66         Class<?> paramType = returnType.getParameterType();
67         return Void.TYPE == paramType || CharSequence.class.isAssignableFrom(paramType);
68     }
69 
70     public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
71         if (returnValue instanceof CharSequence) {
72             String viewName = returnValue.toString();
73             mavContainer.setViewName(viewName);
74             if (this.isRedirectViewName(viewName)) {
75                 mavContainer.setRedirectModelScenario(true);
76             }
77         } else if (returnValue != null) {
78             throw new UnsupportedOperationException("Unexpected return type: " + returnType.getParameterType().getName() + " in method: " + returnType.getMethod());
79         }
80 
81     }
82 
83     protected boolean isRedirectViewName(String viewName) {
84         return PatternMatchUtils.simpleMatch(this.redirectPatterns, viewName) || viewName.startsWith("redirect:");
85     }
86 }    
ha.handle
 1 public class DispatcherServlet extends FrameworkServlet {
 2    protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { 
 3         mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
 4          this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);
 5     }
 6 
 7     private void processDispatchResult(HttpServletRequest request, HttpServletResponse response, @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv, @Nullable Exception exception) throws Exception {
 8         this.render(mv, request, response);
 9     }
10     
11     protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
12         String viewName = mv.getViewName();
13         view = this.resolveViewName(viewName, mv.getModelInternal(), locale, request);
14         view.render(mv.getModelInternal(), request, response);
15     }
16 
17     protected View resolveViewName(String viewName, @Nullable Map<String, Object> model, Locale locale, HttpServletRequest request) throws Exception {
18         if (this.viewResolvers != null) {
19             Iterator var5 = this.viewResolvers.iterator();
20 
21             while(var5.hasNext()) {
22                 ViewResolver viewResolver = (ViewResolver)var5.next();
23                 View view = viewResolver.resolveViewName(viewName, locale);
24                 if (view != null) {
25                     return view;
26                 }
27             }
28         }
29 
30         return null;
31     }
32 }
33 
34 public class ContentNegotiatingViewResolver extends WebApplicationObjectSupport implements ViewResolver, Ordered, InitializingBean {
35     public View resolveViewName(String viewName, Locale locale) throws Exception {
36         List<View> candidateViews = this.getCandidateViews(viewName, locale, requestedMediaTypes);
37     }
38 
39     private List<View> getCandidateViews(String viewName, Locale locale, List<MediaType> requestedMediaTypes) throws Exception {
40         View view = viewResolver.resolveViewName(viewName, locale);
41     }
42 }
43 
44 public abstract class AbstractView extends WebApplicationObjectSupport implements View, BeanNameAware {
45     public void render(@Nullable Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
46         Map<String, Object> mergedModel = this.createMergedOutputModel(model, request, response);
47         this.prepareResponse(request, response);
48         this.renderMergedOutputModel(mergedModel, this.getRequestToExpose(request), response);
49     }
50 }
51 
52 public class RedirectView extends AbstractUrlBasedView implements SmartView {
53     protected void renderMergedOutputModel(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws IOException {
54         String targetUrl = this.createTargetUrl(model, request);
55         targetUrl = this.updateTargetUrl(targetUrl, model, request, response);
56         RequestContextUtils.saveOutputFlashMap(targetUrl, request, response);
57         this.sendRedirect(request, response, targetUrl, this.http10Compatible);
58     }
59 
60     protected void sendRedirect(HttpServletRequest request, HttpServletResponse response, String targetUrl, boolean http10Compatible) throws IOException {
61         response.sendRedirect(encodedURL);
62     }
63 }
64     
processDispatchResult
 1 public class ThymeleafViewResolver extends AbstractCachingViewResolver implements Ordered {
 2     protected View createView(String viewName, Locale locale) throws Exception {
 3         return this.loadView(viewName, locale);
 4     }
 5 
 6     protected View loadView(String viewName, Locale locale) throws Exception {    AutowireCapableBeanFactory beanFactory = this.getApplicationContext().getAutowireCapableBeanFactory();
 7         boolean viewBeanExists = beanFactory.containsBean(viewName);
 8         Class<?> viewBeanType = viewBeanExists ? beanFactory.getType(viewName) : null;
 9         AbstractThymeleafView view;
10 
11         view = (AbstractThymeleafView)beanFactory.configureBean(viewInstance, viewName);
12     
13         view.setTemplateEngine(this.getTemplateEngine());
14         view.setStaticVariables(this.getStaticVariables());
15     }
16 }    
17 
18 public class ThymeleafView extends AbstractThymeleafView {
19     public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
20         this.renderFragment(this.markupSelectors, model, request, response);
21     }
22 
23     protected void renderFragment(Set<String> markupSelectorsToRender, Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
24         ServletContext servletContext = this.getServletContext();
25         String viewTemplateName = this.getTemplateName();
26         ISpringTemplateEngine viewTemplateEngine = this.getTemplateEngine();
27     Map<String, Object> templateStaticVariables = this.getStaticVariables();
28             if (templateStaticVariables != null) {
29                 mergedModel.putAll(templateStaticVariables);
30             }
31 if (!viewTemplateName.contains("::")) {
32                 templateName = viewTemplateName;
33                 markupSelectors = null;
34             } else {
35                 SpringRequestUtils.checkViewNameNotInRequest(viewTemplateName, request);
36                 IStandardExpressionParser parser = StandardExpressions.getExpressionParser(configuration);
37 
38                 FragmentExpression fragmentExpression;
39                 try {
40                     fragmentExpression = (FragmentExpression)parser.parseExpression(context, "~{" + viewTemplateName + "}");
41                 } catch (TemplateProcessingException var25) {
42                     throw new IllegalArgumentException("Invalid template name specification: '" + viewTemplateName + "'");
43                 }
44 
45                 ExecutedFragmentExpression fragment = FragmentExpression.createExecutedFragmentExpression(context, fragmentExpression);
46                 templateName = FragmentExpression.resolveTemplateName(fragment);
47                 markupSelectors = FragmentExpression.resolveFragments(fragment);
48                 Map<String, Object> nameFragmentParameters = fragment.getFragmentParameters();
49                 if (nameFragmentParameters != null) {
50                     if (fragment.hasSyntheticParameters()) {
51                         throw new IllegalArgumentException("Parameters in a view specification must be named (non-synthetic): '" + viewTemplateName + "'");
52                     }
53 
54                     context.setVariables(nameFragmentParameters);
55                 }
56             }
57     viewTemplateEngine.process(templateName, processMarkupSelectors, context, (Writer)templateWriter);
58     }
59 }
60 
61 public class TemplateEngine implements ITemplateEngine {
62     public final void process(TemplateSpec templateSpec, IContext context, Writer writer) {
63         TemplateManager templateManager = this.configuration.getTemplateManager();
64          templateManager.parseAndProcess(templateSpec, context, writer);
65         writer.flush();
66     }
67 }    
ThymeleafViewResolver

 

自定义视图解析器+自定义视图;

 

 

 

 

 

 

 

 5.5.2 模板引擎-Thymeleaf

1)  thymeleaf简介

Thymeleaf is a modern server-side Java template engine for both web and standalone environments, capable of processing HTML, XML, JavaScript, CSS and even plain text.

现代化、服务端Java模板引擎

2)  基本语法 

1. 表达式 

   表达式名字    语法      用途

   变量取值     ${...}     获取请求域、session域、对象等值

   选择变量     *{...}        获取上下文对象值

   消息       #{...}     获取国际化等值

   链接         @{...}      生成链接

   片段表达式    ~{...}     jsp:include 作用,引入公共页面片段

 2. 字面量

文本值: 'one text' , 'Another one!' ,…

数字: 0 , 34 , 3.0 , 12.3 ,…

布尔值: true , false

空值: null

变量: one,two,.... 变量不能有空格

3. 文本操作

字符串拼接: +

变量替换: |The name is ${name}|

4. 数学运算

运算符: + , - , * , / , %

5. 布尔运算

运算符:  and , or

一元运算: ! , not

6. 比较运算

比较: > , < , >= , <= ( gt , lt , ge , le )等式: == , != ( eq , ne )

7. 条件运算

If-then: (if) ? (then)

If-then-else: (if) ? (then) : (else)

Default: (value) ?: (defaultvalue)

 

 8. 特殊操作

无操作: _

 

3)  设置属性值-th:attr

设置单个值

 1 <form action="subscribe.html" th:attr="action=@{/subscribe}">
 2 
 3   <fieldset>
 4 
 5     <input type="text" name="email" />
 6 
 7     <input type="submit" value="Subscribe!" th:attr="value=#{subscribe.submit}"/>
 8 
 9   </fieldset>
10 
11 </form>
单值

设置多个值

1 <img src="../../images/gtvglogo.png"  th:attr="src=@{/images/gtvglogo.png},title=#{logo},alt=#{logo}" />
多值

以上两个的代替写法 th:xxxx

1 <input type="submit" value="Subscribe!" th:value="#{subscribe.submit}"/>
2 <form action="subscribe.html" th:action="@{/subscribe}">
th:xxxx

 

4)  迭代

 1 <tr th:each="prod : ${prods}">
 2 
 3         <td th:text="${prod.name}">Onions</td>
 4         <td th:text="${prod.price}">2.41</td>
 5         <td th:text="${prod.inStock}? #{true} : #{false}">yes</td>
 6 
 7 </tr>
 8 
 9 <tr th:each="prod,iterStat : ${prods}" th:class="${iterStat.odd}? 'odd'">
10 
11   <td th:text="${prod.name}">Onions</td>
12   <td th:text="${prod.price}">2.41</td>
13   <td th:text="${prod.inStock}? #{true} : #{false}">yes</td>
14 
15 </tr>
迭代

 

5)  条件运算

1 <a href="comments.html"
2 th:href="@{/product/comments(prodId=${prod.id})}"
3 th:if="${not #lists.isEmpty(prod.comments)}">view</a>
4 
5 <div th:switch="${user.role}">
6   <p th:case="'admin'">User is an administrator</p>
7   <p th:case="#{roles.manager}">User is a manager</p>
8   <p th:case="*">User is some other thing</p>
9 </div>
条件

6)  属性优先级

 

 

 

 5.5.3 thymeleaf使用

1)  引入Starter

1         <dependency>
2 
3             <groupId>org.springframework.boot</groupId>
4 
5             <artifactId>spring-boot-starter-thymeleaf</artifactId>
6 
7         </dependency>
thymeleaf

 

2)  自动配置好了thymeleaf

1 @Configuration(proxyBeanMethods = false)
2 @EnableConfigurationProperties(ThymeleafProperties.class)
3 @ConditionalOnClass({ TemplateMode.class, SpringTemplateEngine.class })
4 @AutoConfigureAfter({ WebMvcAutoConfiguration.class, WebFluxAutoConfiguration.class })
5 public class ThymeleafAutoConfiguration { }
ThymeleafAutoConfiguration

自动配好的策略

  • 1、所有thymeleaf的配置值都在 ThymeleafProperties
  • 2、配置好了 SpringTemplateEngine
  • 3、配好了 ThymeleafViewResolver
  • 4、我们只需要直接开发页面
public static final String DEFAULT_PREFIX = "classpath:/templates/";
    public static final String DEFAULT_SUFFIX = ".html";  //xxx.html

3)  页面开发

 1 <!DOCTYPE html>
 2 <html lang="en" xmlns:th="http://www.thymeleaf.org">
 3 <head>
 4     <meta charset="UTF-8">
 5     <title>Title</title>
 6 </head>
 7 <body>
 8 <h1 th:text="${msg}">哈哈</h1>
 9 <h2>
10     <a href="www.atguigu.com" th:href="${link}">去百度</a>  <br/>
11     <a href="www.atguigu.com" th:href="@{link}">去百度2</a>
12 </h2>
13 </body>
14 </html>
index.html

5.5.4  构建后台管理系统

1)  项目创建

thymeleaf、web-starter、devtools、lombok

2)  静态资源处理

自动配置好,我们只需要把所有静态资源放到 static 文件夹下

3)  路径构建

th:action="@{/login}"

4)  模板抽取

th:insert/replace/include

 

5)  页面跳转

 1     @PostMapping("/login")
 2     public String main(User user, HttpSession session, Model model){
 3 
 4         if(StringUtils.hasLength(user.getUserName()) && "123456".equals(user.getPassword())){
 5             //把登陆成功的用户保存起来
 6             session.setAttribute("loginUser",user);
 7             //登录成功重定向到main.html;  重定向防止表单重复提交
 8             return "redirect:/main.html";
 9         }else {
10             model.addAttribute("msg","账号密码错误");
11             //回到登录页面
12             return "login";
13         }
14 
15     }
controller

6)  数据渲染

 1     @GetMapping("/dynamic_table")
 2     public String dynamic_table(Model model){
 3         //表格内容的遍历
 4         List<User> users = Arrays.asList(new User("zhangsan", "123456"),
 5                 new User("lisi", "123444"),
 6                 new User("haha", "aaaaa"),
 7                 new User("hehe ", "aaddd"));
 8         model.addAttribute("users",users);
 9 
10         return "table/dynamic_table";
11     }
dynamic_tablede
 1         <table class="display table table-bordered" id="hidden-table-info">
 2         <thead>
 3         <tr>
 4             <th>#</th>
 5             <th>用户名</th>
 6             <th>密码</th>
 7         </tr>
 8         </thead>
 9         <tbody>
10         <tr class="gradeX" th:each="user,stats:${users}">
11             <td th:text="${stats.count}">Trident</td>
12             <td th:text="${user.userName}">Internet</td>
13             <td >[[${user.password}]]</td>
14         </tr>
15         </tbody>
16         </table>
table

 

5.6  拦截器

 5.6.1 HandlerInterceptor 接口

 1 /**
 2  * 登录检查
 3  * 1、配置好拦截器要拦截哪些请求
 4  * 2、把这些配置放在容器中
 5  */
 6 @Slf4j
 7 public class LoginInterceptor implements HandlerInterceptor {
 8 
 9     /**
10      * 目标方法执行之前
11      * @param request
12      * @param response
13      * @param handler
14      * @return
15      * @throws Exception
16      */
17     @Override
18     public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
19 
20         String requestURI = request.getRequestURI();
21         log.info("preHandle拦截的请求路径是{}",requestURI);
22 
23         //登录检查逻辑
24         HttpSession session = request.getSession();
25 
26         Object loginUser = session.getAttribute("loginUser");
27 
28         if(loginUser != null){
29             //放行
30             return true;
31         }
32 
33         //拦截住。未登录。跳转到登录页
34         request.setAttribute("msg","请先登录");
35 //        re.sendRedirect("/");
36         request.getRequestDispatcher("/").forward(request,response);
37         return false;
38     }
39 
40     /**
41      * 目标方法执行完成以后
42      * @param request
43      * @param response
44      * @param handler
45      * @param modelAndView
46      * @throws Exception
47      */
48     @Override
49     public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
50         log.info("postHandle执行{}",modelAndView);
51     }
52 
53     /**
54      * 页面渲染以后
55      * @param request
56      * @param response
57      * @param handler
58      * @param ex
59      * @throws Exception
60      */
61     @Override
62     public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
63         log.info("afterCompletion执行异常{}",ex);
64     }
65 }
LoginInterceptor

5.6.2 配置拦截器

 1 /**
 2  * 1、编写一个拦截器实现HandlerInterceptor接口
 3  * 2、拦截器注册到容器中(实现WebMvcConfigurer的addInterceptors)
 4  * 3、指定拦截规则【如果是拦截所有,静态资源也会被拦截】
 5  */
 6 @Configuration
 7 public class AdminWebConfig implements WebMvcConfigurer {
 8 
 9     @Override
10     public void addInterceptors(InterceptorRegistry registry) {
11         registry.addInterceptor(new LoginInterceptor())
12                 .addPathPatterns("/**")  //所有请求都被拦截包括静态资源
13                 .excludePathPatterns("/","/login","/css/**","/fonts/**","/images/**","/js/**"); //放行的请求
14     }
15 }
addInterceptors

5.6.3 拦截器原理

1、根据当前请求,找到HandlerExecutionChain【可以处理请求的handler以及handler的所有 拦截器】

2、先来顺序执行 所有拦截器的 preHandle方法

    • 1、如果当前拦截器prehandler返回为true。则执行下一个拦截器的preHandle
    • 2、如果当前拦截器返回为false。直接    倒序执行所有已经执行了的拦截器的  afterCompletion;

3、如果任何一个拦截器返回false。直接跳出不执行目标方法

4、所有拦截器都返回True。执行目标方法

5、倒序执行所有拦截器的postHandle方法。

6、前面的步骤有任何异常都会直接倒序触发已经执行拦截器的 afterCompletion

7、页面成功渲染完成以后,也会倒序触发 afterCompletion

 1 public class DispatcherServlet extends FrameworkServlet {
 2     protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
 3     if (!mappedHandler.applyPreHandle(processedRequest, response)) {return;}
 4     
 5     mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
 6     mappedHandler.applyPostHandle(processedRequest, response, mv);
 7 
 8     catch (Exception ex) {
 9             triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
10         }
11     mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
12     }
13 
14     private void processDispatchResult(){
15         render(mv, request, response);
16         if (mappedHandler != null) {
17             // Exception (if any) is already handled..
18             mappedHandler.triggerAfterCompletion(request, response, null);
19         }
20     }
21 }
22 
23 public class HandlerExecutionChain {
24     boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
25         HandlerInterceptor[] interceptors = getInterceptors();
26         if (!ObjectUtils.isEmpty(interceptors)) {
27             for (int i = 0; i < interceptors.length; i++) {
28                 HandlerInterceptor interceptor = interceptors[i];
29                 if (!interceptor.preHandle(request, response, this.handler)) {
30                     triggerAfterCompletion(request, response, null);
31                     return false;
32                 }
33                 this.interceptorIndex = i;
34             }
35         }
36         return true;
37     }
38 
39     void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv)
40             throws Exception {
41 
42         HandlerInterceptor[] interceptors = getInterceptors();
43         if (!ObjectUtils.isEmpty(interceptors)) {
44             for (int i = interceptors.length - 1; i >= 0; i--) {
45                 HandlerInterceptor interceptor = interceptors[i];
46                 interceptor.postHandle(request, response, this.handler, mv);
47             }
48         }
49     }
50 
51    void applyAfterConcurrentHandlingStarted(HttpServletRequest request, HttpServletResponse response) {
52         HandlerInterceptor[] interceptors = getInterceptors();
53         if (!ObjectUtils.isEmpty(interceptors)) {
54             for (int i = interceptors.length - 1; i >= 0; i--) {
55                 HandlerInterceptor interceptor = interceptors[i];
56                 if (interceptor instanceof AsyncHandlerInterceptor) {
57                     try {
58                         AsyncHandlerInterceptor asyncInterceptor = (AsyncHandlerInterceptor) interceptor;
59                         asyncInterceptor.afterConcurrentHandlingStarted(request, response, this.handler);
60                     }
61                     catch (Throwable ex) {
62                         if (logger.isErrorEnabled()) {
63                             logger.error("Interceptor [" + interceptor + "] failed in afterConcurrentHandlingStarted", ex);
64                         }
65                     }
66                 }
67             }
68         }
69     } 
70 }
View Code

 

 

 

5.7 文件上传

5.7.1 页面表单

<form method="post" action="/upload" enctype="multipart/form-data">
    <input type="file" name="file"><br>
    <input type="submit" value="提交">
</form>
from

5.7.2 文件上传

 1     /**
 2      * MultipartFile 自动封装上传过来的文件
 3      * @param email
 4      * @param username
 5      * @param headerImg
 6      * @param photos
 7      * @return
 8      */
 9     @PostMapping("/upload")
10     public String upload(@RequestParam("email") String email,
11                          @RequestParam("username") String username,
12                          @RequestPart("headerImg") MultipartFile headerImg,
13                          @RequestPart("photos") MultipartFile[] photos) throws IOException {
14 
15         log.info("上传的信息:email={},username={},headerImg={},photos={}",
16                 email,username,headerImg.getSize(),photos.length);
17 
18         if(!headerImg.isEmpty()){
19             //保存到文件服务器,OSS服务器
20             String originalFilename = headerImg.getOriginalFilename();
21             headerImg.transferTo(new File("H:\\cache\\"+originalFilename));
22         }
23 
24         if(photos.length > 0){
25             for (MultipartFile photo : photos) {
26                 if(!photo.isEmpty()){
27                     String originalFilename = photo.getOriginalFilename();
28                     photo.transferTo(new File("H:\\cache\\"+originalFilename));
29                 }
30             }
31         }
32 
33 
34         return "main";
35     }
MultipartFile

5.7.3 配置原理

文件上传自动配置类-MultipartAutoConfiguration-MultipartProperties

    • 自动配置好了 StandardServletMultipartResolver   【文件上传解析器】
    • 原理步骤
      • 1、请求进来使用文件上传解析器判断(isMultipart)并封装(resolveMultipart,返回MultipartHttpServletRequest)文件上传请求
      • 2、参数解析器来解析请求中的文件内容封装成MultipartFile
      • 3、将request中文件信息封装为一个Map;MultiValueMap<String, MultipartFile>;FileCopyUtils。实现文件流的拷贝

 

  1 public class DispatcherServlet extends FrameworkServlet {
  2    protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
  3         processedRequest = checkMultipart(request);
  4     } 
  5 
  6     protected HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException {
  7         if (this.multipartResolver != null && this.multipartResolver.isMultipart(request)) {
  8             if (WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class) != null) {
  9                 if (request.getDispatcherType().equals(DispatcherType.REQUEST)) {
 10                     logger.trace("Request already resolved to MultipartHttpServletRequest, e.g. by MultipartFilter");
 11                 }
 12             }
 13             else if (hasMultipartException(request)) {
 14                 logger.debug("Multipart resolution previously failed for current request - " +
 15                         "skipping re-resolution for undisturbed error rendering");
 16             }
 17             else {
 18                 try {
 19                     return this.multipartResolver.resolveMultipart(request);
 20                 }
 21                 catch (MultipartException ex) {
 22                     if (request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) != null) {
 23                         logger.debug("Multipart resolution failed for error dispatch", ex);
 24                         // Keep processing error dispatch with regular request handle below
 25                     }
 26                     else {
 27                         throw ex;
 28                     }
 29                 }
 30             }
 31         }
 32         // If not returned before: return original request.
 33         return request;
 34     }
 35 }
 36 
 37 public class StandardServletMultipartResolver implements MultipartResolver {
 38     public boolean isMultipart(HttpServletRequest request) {
 39         return StringUtils.startsWithIgnoreCase(request.getContentType(), "multipart/");
 40     }
 41 
 42     public MultipartHttpServletRequest resolveMultipart(HttpServletRequest request) throws MultipartException {
 43         return new StandardMultipartHttpServletRequest(request, this.resolveLazily);
 44     }
 45 }
 46 
 47 public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter
 48         implements BeanFactoryAware, InitializingBean {
 49     protected ModelAndView handleInternal(HttpServletRequest request,
 50             HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
 51         mav = invokeHandlerMethod(request, response, handlerMethod);
 52     }
 53 
 54     protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
 55             HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
 56        invocableMethod.invokeAndHandle(webRequest, mavContainer); 
 57     }
 58 }
 59 
 60 public class ServletInvocableHandlerMethod extends InvocableHandlerMethod {
 61     public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
 62             Object... providedArgs) throws Exception {
 63         Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
 64     }
 65 
 66     protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
 67 
 68     }
 69 }
 70 
 71 public class InvocableHandlerMethod extends HandlerMethod {
 72     public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
 73         Object[] args = this.getMethodArgumentValues(request, mavContainer, providedArgs);
 74         if (this.logger.isTraceEnabled()) {
 75             this.logger.trace("Arguments: " + Arrays.toString(args));
 76         }
 77 
 78         return this.doInvoke(args);
 79     }
 80 
 81     protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
 82         if (!this.resolvers.supportsParameter(parameter)) {}
 83         args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
 84     }
 85 }
 86 
 87 public class HandlerMethodArgumentResolverComposite implements HandlerMethodArgumentResolver {
 88     private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
 89     HandlerMethodArgumentResolver result = (HandlerMethodArgumentResolver)this.argumentResolverCache.get(parameter);
 90         if (result == null) {
 91             Iterator var3 = this.argumentResolvers.iterator();
 92 
 93             while(var3.hasNext()) {
 94                 HandlerMethodArgumentResolver resolver = (HandlerMethodArgumentResolver)var3.next();
 95                 if (resolver.supportsParameter(parameter)) {
 96                     result = resolver;
 97                     this.argumentResolverCache.put(parameter, resolver);
 98                     break;
 99                 }
100             }
101         }
102 
103           return result;
104     }
105 
106     public Object resolveArgument(){
107         return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
108     }
109 }
110 
111 public class RequestPartMethodArgumentResolver extends AbstractMessageConverterMethodArgumentResolver {
112     public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
113             NativeWebRequest request, @Nullable WebDataBinderFactory binderFactory) throws Exception {
114             String name = getPartName(parameter, requestPart);
115         parameter = parameter.nestedIfOptional();
116             Object mpArg = MultipartResolutionDelegate.resolveMultipartArgument(name, parameter, servletRequest);
117     }
118 }
119 
120 public final class MultipartResolutionDelegate {
121     public static Object resolveMultipartArgument(String name, MethodParameter parameter, HttpServletRequest request) throws Exception {
122          return multipartRequest != null ? ((MultipartHttpServletRequest)multipartRequest).getFile(name) : null;
123 
124         return multipartRequest != null ? ((MultipartHttpServletRequest)multipartRequest).getFiles(name) : null;
125     }
126 }
127 
128 public abstract class AbstractMultipartHttpServletRequest extends HttpServletRequestWrapper implements MultipartHttpServletRequest {
129     public MultipartFile getFile(String name) {
130         return (MultipartFile)this.getMultipartFiles().getFirst(name);
131     }
132 
133     public List<MultipartFile> getFiles(String name) {
134         List<MultipartFile> multipartFiles = (List)this.getMultipartFiles().get(name);
135         return multipartFiles != null ? multipartFiles : Collections.emptyList();
136     }
137 
138     protected MultiValueMap<String, MultipartFile> getMultipartFiles() {
139         if (this.multipartFiles == null) {
140             this.initializeMultipart();
141         }
142 
143         return this.multipartFiles;
144     }
145 }
MultipartFiles

 

 

 

5.8 异常处理

5.8.1 默认规则

  • 默认情况下,Spring Boot提供/error处理所有错误的映射
  • 对于机器客户端,它将生成JSON响应,其中包含错误,HTTP状态和异常消息的详细信息。对于浏览器客户端,响应一个“ whitelabel”错误视图,以HTML格式呈现相同的数据
  • 要对其进行自定义,添加View解析为error
  • 要完全替换默认行为,可以实现 ErrorController 并注册该类型的Bean定义,或添加ErrorAttributes类型的组件以使用现有机制但替换其内容。
  • error/下的4xx,5xx页面会被自动解析;

 

 

 

 

 

5.8.2 定制错误处理逻辑

  • 自定义错误页
    • error/404.html   error/5xx.html;有精确的错误状态码页面就匹配精确,没有就找 4xx.html;如果都没有就触发白页
  • @ControllerAdvice+@ExceptionHandler处理全局异常;底层是 ExceptionHandlerExceptionResolver 支持的
  • @ResponseStatus+自定义异常 ;底层是 ResponseStatusExceptionResolver ,把responsestatus注解的信息底层调用 response.sendError(statusCode, resolvedReason);tomcat发送的/error
  • Spring底层的异常,如 参数类型转换异常;DefaultHandlerExceptionResolver 处理框架底层的异常。
    • response.sendError(HttpServletResponse.SC_BAD_REQUEST, ex.getMessage());
  • 自定义实现 HandlerExceptionResolver 处理异常;可以作为默认的全局异常处理规则
  • ErrorViewResolver  实现自定义处理异常;
    • response.sendError 。error请求就会转给controller
    • 你的异常没有任何人能处理。tomcat底层 response.sendError。error请求就会转给controller
    • basicErrorController 要去的页面地址是 ErrorViewResolver  ;

 

1 @ControllerAdvice
2 public class GlobalExceptionHandler {
3     @ExceptionHandler({ArithmeticException.class,NullPointerException.class})
4     public String handlerArithException(Exception e){
5         System.out.println("异常是:" + e);
6         return "login";
7     }
8 }
全局异常
1 @ResponseStatus(value = HttpStatus.FORBIDDEN, reason = "用户太多")
2 public class UserTooManyException extends RuntimeException{
3     public UserTooManyException(){}
4     public UserTooManyException(String msg){
5         super(msg);
6     }
7 }
自定义异常
 1 @Order(Ordered.HIGHEST_PRECEDENCE)
 2 @Component
 3 public class CustomerHandlerExceptionResolver implements HandlerExceptionResolver {
 4     @Override
 5     public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
 6         try {
 7             response.sendError(555, "自定义错误");
 8         } catch (IOException e) {
 9             e.printStackTrace();
10         }
11         return new ModelAndView();
12     }
13 }
自定义错误视图解析器

 

 

 

 

5.8.3 异常处理自动配置原理

  • ErrorMvcAutoConfiguration  自动配置异常处理规则
    • 容器中的组件:类型:DefaultErrorAttributes -> id:errorAttributes
      • public class DefaultErrorAttributes implements ErrorAttributes, HandlerExceptionResolver
      • DefaultErrorAttributes:定义错误页面中可以包含哪些数据。
    • 容器中的组件:类型:BasicErrorController --> id:basicErrorController(json+白页 适配响应)
      • 处理默认 /error 路径的请求;页面响应 new ModelAndView("error", model);
      • 容器中有组件 View->id是error;(响应默认错误页)
      • 容器中放组件 BeanNameViewResolver(视图解析器);按照返回的视图名作为组件的id去容器中找View对象。
    • 容器中的组件:类型:DefaultErrorViewResolver -> id:conventionErrorViewResolver
      • 如果发生错误,会以HTTP的状态码 作为视图页地址(viewName),找到真正的页面
      • error/404、5xx.html

 

 

 

 

 

如果想要返回页面;就会找error视图【StaticView】。(默认是一个白页);出去json;错误页

 

 

 

5.8.4 异常处理步骤流程

1、执行目标方法,目标方法运行期间有任何异常都会被catch、而且标志当前请求结束;并且用 dispatchException

2、进入视图解析流程(页面渲染?)

processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);

3、mv = processHandlerException;处理handler发生的异常,处理完成返回ModelAndView;

  • 1、遍历所有的 handlerExceptionResolvers,看谁能处理当前异常【HandlerExceptionResolver处理器异常解析器
  • 2、系统默认的  异常解析器;
    • 1、DefaultErrorAttributes先来处理异常。把异常信息保存到rrequest域,并且返回null;
    • 2、默认没有任何人能处理异常,所以异常会被抛出
      • 1、如果没有任何人能处理最终底层就会发送 /error 请求。会被底层的BasicErrorController处理
      • 2、解析错误视图;遍历所有的  ErrorViewResolver  看谁能解析。
      • 3、默认的 DefaultErrorViewResolver ,作用是把响应状态码作为错误页的地址,error/500.html
      • 4、模板引擎最终响应这个页面 error/500.html

 

 1 public class DispatcherServlet extends FrameworkServlet {
 2     protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
 3        catch (Exception ex) {
 4                 dispatchException = ex;
 5             }
 6             catch (Throwable err) {
 7                 // As of 4.3, we're processing Errors thrown from handler methods as well,
 8                 // making them available for @ExceptionHandler methods and other scenarios.
 9                 dispatchException = new NestedServletException("Handler dispatch failed", err);
10             }
11         processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); 
12 
13                 catch (Exception ex) {
14             triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
15         }
16         catch (Throwable err) {
17             triggerAfterCompletion(processedRequest, response, mappedHandler,
18                     new NestedServletException("Handler processing failed", err));
19         }
20     }
21 
22     private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
23             @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
24             @Nullable Exception exception) throws Exception {
25         if (exception != null) {
26             if (exception instanceof ModelAndViewDefiningException) {
27                 logger.debug("ModelAndViewDefiningException encountered", exception);
28                 mv = ((ModelAndViewDefiningException) exception).getModelAndView();
29             }
30             else {
31                 Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
32                 mv = processHandlerException(request, response, handler, exception);
33                 errorView = (mv != null);
34             }
35         }
36     }
37 
38     protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,
39             @Nullable Object handler, Exception ex) throws Exception {
40         ModelAndView exMv = null;
41     if (this.handlerExceptionResolvers != null) {
42             for (HandlerExceptionResolver resolver : this.handlerExceptionResolvers) {
43                 exMv = resolver.resolveException(request, response, handler, ex);
44                 if (exMv != null) {
45                     break;
46                 }
47             }
48         }
49         throw ex;
50     }
51 }
52 
53 public class DefaultErrorAttributes implements ErrorAttributes, HandlerExceptionResolver, Ordered {
54     public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
55         this.storeErrorAttributes(request, ex);
56         return null;
57     }
58 
59     private void storeErrorAttributes(HttpServletRequest request, Exception ex) {
60         request.setAttribute(ERROR_ATTRIBUTE, ex);
61     }
62 }
正常请求
 1 public class DispatcherServlet extends FrameworkServlet {
 2     protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
 3 
 4     }
 5 }
 6 
 7 public class BasicErrorController extends AbstractErrorController {
 8     @RequestMapping(produces = MediaType.TEXT_HTML_VALUE)
 9     public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
10         HttpStatus status = getStatus(request);
11         Map<String, Object> model = Collections
12                 .unmodifiableMap(getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.TEXT_HTML)));
13         response.setStatus(status.value());
14         ModelAndView modelAndView = resolveErrorView(request, response, status, model);
15         return (modelAndView != null) ? modelAndView : new ModelAndView("error", model);
16     }
17 }
18 
19 public abstract class AbstractErrorController implements ErrorController {
20    protected ModelAndView resolveErrorView(HttpServletRequest request, HttpServletResponse response, HttpStatus status,
21             Map<String, Object> model) {
22         for (ErrorViewResolver resolver : this.errorViewResolvers) {
23             ModelAndView modelAndView = resolver.resolveErrorView(request, status, model);
24             if (modelAndView != null) {
25                 return modelAndView;
26             }
27         }
28         return null;
29     } 
30 }
31 
32 public class DefaultErrorViewResolver implements ErrorViewResolver, Ordered {
33     @Override
34     public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) {
35         ModelAndView modelAndView = resolve(String.valueOf(status.value()), model);
36         if (modelAndView == null && SERIES_VIEWS.containsKey(status.series())) {
37             modelAndView = resolve(SERIES_VIEWS.get(status.series()), model);
38         }
39         return modelAndView;
40     }
41 
42     private ModelAndView resolve(String viewName, Map<String, Object> model) {
43         String errorViewName = "error/" + viewName;
44         TemplateAvailabilityProvider provider = this.templateAvailabilityProviders.getProvider(errorViewName,
45                 this.applicationContext);
46         if (provider != null) {
47             return new ModelAndView(errorViewName, model);
48         }
49         return resolveResource(errorViewName, model);
50     }
51 
52     private ModelAndView resolveResource(String viewName, Map<String, Object> model) {
53         for (String location : this.resourceProperties.getStaticLocations()) {
54             try {
55                 Resource resource = this.applicationContext.getResource(location);
56                 resource = resource.createRelative(viewName + ".html");
57                 if (resource.exists()) {
58                     return new ModelAndView(new HtmlResourceView(resource), model);
59                 }
60             }
61             catch (Exception ex) {
62             }
63         }
64         return null;
65     }
66 }
/error请求

 

 

 

 5.9 Web原生组件注入(Servlet、Filter、Listener)

5.9.1 使用Servlet API

  • @ServletComponentScan(basePackages = "com.atguigu.admin") :指定原生Servlet组件都放在那里
  • @WebServlet(urlPatterns = "/my"):效果:直接响应,没有经过Spring的拦截器?
  • @WebFilter(urlPatterns={"/css/*","/images/*"})
  • @WebListener
 1 @WebServlet(urlPatterns = "/xx")
 2 public class MyServlet extends HttpServlet {
 3     @Override
 4     protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
 5         resp.getWriter().write("66666");
 6     }
 7 }
 8 
 9 @WebFilter(urlPatterns = "/css/*")
10 public class MyFilter implements Filter {
11     @Override
12     public void init(FilterConfig filterConfig) throws ServletException {
13 
14     }
15 
16     @Override
17     public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
18         filterChain.doFilter(servletRequest,servletResponse);
19     }
20 
21     @Override
22     public void destroy() {
23 
24     }
25 }
26 
27 @WebListener
28 public class MyServletContextListener implements ServletContextListener {
29     @Override
30     public void contextInitialized(ServletContextEvent sce) {
31 
32     }
33 
34     @Override
35     public void contextDestroyed(ServletContextEvent sce) {
36 
37     }
38 }
View Code

 

扩展:DispatchServlet 如何注册进来

    • 容器中自动配置了  DispatcherServlet  属性绑定到 WebMvcProperties;对应的配置文件配置项是 spring.mvc。
    • 通过 ServletRegistrationBean<DispatcherServlet> 把 DispatcherServlet  配置进来。
    • 默认映射的是 / 路径。

 

 5.9.2 使用RegistrationBean

ServletRegistrationBean

FilterRegistrationBean

ServletListenerRegistrationBean

 1 @Configuration
 2 public class MyRegistConfig {
 3 
 4     @Bean
 5     public ServletRegistrationBean myServlet(){
 6         MyServlet myServlet = new MyServlet();
 7 
 8         return new ServletRegistrationBean(myServlet,"/my","/my02");
 9     }
10 
11 
12     @Bean
13     public FilterRegistrationBean myFilter(){
14 
15         MyFilter myFilter = new MyFilter();
16 //        return new FilterRegistrationBean(myFilter,myServlet());
17         FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(myFilter);
18         filterRegistrationBean.setUrlPatterns(Arrays.asList("/my","/css/*"));
19         return filterRegistrationBean;
20     }
21 
22     @Bean
23     public ServletListenerRegistrationBean myListener(){
24         MySwervletContextListener mySwervletContextListener = new MySwervletContextListener();
25         return new ServletListenerRegistrationBean(mySwervletContextListener);
26     }
27 }
View Code

5.10 嵌入式Servlet容器

5.10.1 切换嵌入式Servlet容器

  • 默认支持的webServer
    • Tomcat, Jetty, or Undertow
    • ServletWebServerApplicationContext 容器启动寻找ServletWebServerFactory 并引导创建服务器
  • 切换服务器

 

 

 

 1 <dependency>
 2 
 3     <groupId>org.springframework.boot</groupId>
 4 
 5     <artifactId>spring-boot-starter-web</artifactId>
 6 
 7     <exclusions>
 8 
 9         <exclusion>
10 
11             <groupId>org.springframework.boot</groupId>
12 
13             <artifactId>spring-boot-starter-tomcat</artifactId>
14 
15         </exclusion>
16 
17     </exclusions>
18 
19 </dependency>
tomcat
    • 原理
      • SpringBoot应用启动发现当前是Web应用。web场景包-导入tomcat
      • web应用会创建一个web版的ioc容器 ServletWebServerApplicationContext 
      • ServletWebServerApplicationContext  启动的时候寻找 ServletWebServerFactory(Servlet 的web服务器工厂---> Servlet 的web服务器)  
      • SpringBoot底层默认有很多的WebServer工厂;TomcatServletWebServerFactory, JettyServletWebServerFactory, or UndertowServletWebServerFactory
      • 底层直接会有一个自动配置类。ServletWebServerFactoryAutoConfiguration
      • ServletWebServerFactoryAutoConfiguration导入了ServletWebServerFactoryConfiguration(配置类)
      • ServletWebServerFactoryConfiguration 配置类 根据动态判断系统中到底导入了那个Web服务器的包。(默认是web-starter导入tomcat包),容器中就有 TomcatServletWebServerFactory
      • TomcatServletWebServerFactory 创建出Tomcat服务器并启动;TomcatWebServer 的构造器拥有初始化方法initialize---this.tomcat.start();
      • 内嵌服务器,就是手动把启动服务器的代码调用(tomcat核心jar包存在)

 5.10.2 定制Servlet容器

  • 实现 WebServerFactoryCustomizer<ConfigurableServletWebServerFactory>
    • 把配置文件的值和ServletWebServerFactory 进行绑定
  • 修改配置文件 server.xxx
  • 直接自定义 ConfigurableServletWebServerFactory

 

xxxxxCustomizer:定制化器,可以改变xxxx的默认规则

 1 import org.springframework.boot.web.server.WebServerFactoryCustomizer;
 2 import org.springframework.boot.web.servlet.server.ConfigurableServletWebServerFactory;
 3 import org.springframework.stereotype.Component;
 4 
 5 @Component
 6 public class CustomizationBean implements WebServerFactoryCustomizer<ConfigurableServletWebServerFactory> {
 7 
 8     @Override
 9     public void customize(ConfigurableServletWebServerFactory server) {
10         server.setPort(9000);
11     }
12 
13 }
CustomizationBean

5.11 定制化原理

5.11.1 定制化的常见方式

  • 修改配置文件;
  • xxxxxCustomizer;
  • 编写自定义的配置类   xxxConfiguration;+ @Bean替换、增加容器中默认组件;视图解析器
  • Web应用 编写一个配置类实现 WebMvcConfigurer 即可定制化web功能;+ @Bean给容器中再扩展一些组件
  • @EnableWebMvc + WebMvcConfigurer —— @Bean  可以全面接管SpringMVC,所有规则全部自己重新配置; 实现定制和扩展功能
    • 1、WebMvcAutoConfiguration  默认的SpringMVC的自动配置功能类。静态资源、欢迎页.....
    • 2、一旦使用 @EnableWebMvc 、。会 @Import(DelegatingWebMvcConfiguration.class)
    • 3、DelegatingWebMvcConfiguration 的 作用,只保证SpringMVC最基本的使用
      • 把所有系统中的 WebMvcConfigurer 拿过来。所有功能的定制都是这些 WebMvcConfigurer  合起来一起生效
      • 自动配置了一些非常底层的组件。RequestMappingHandlerMapping、这些组件依赖的组件都是从容器中获取
      • public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport
    • 4、WebMvcAutoConfiguration 里面的配置要能生效 必须  @ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
    • 5、@EnableWebMvc  导致了 WebMvcAutoConfiguration  没有生效。

 

5.11.2 原理分析套路

场景starter - xxxxAutoConfiguration - 导入xxx组件 - 绑定xxxProperties -- 绑定配置文件项

6 数据访问

6.1 SQL

6.1.1 数据源的自动配置-HikariDataSource

1)  数据源的自动配置-HikariDataSource

导入JDBC场景

1         <dependency>
2 
3             <groupId>org.springframework.boot</groupId>
4 
5             <artifactId>spring-boot-starter-data-jdbc</artifactId>
6 
7         </dependency>
jdbc导包

 

 

数据库驱动?

为什么导入JDBC场景,官方不导入驱动?官方不知道我们接下要操作什么数据库。

数据库版本和驱动版本对应

 1 默认版本:<mysql.version>8.0.22</mysql.version>
 2 
 3         <dependency>
 4             <groupId>mysql</groupId>
 5             <artifactId>mysql-connector-java</artifactId>
 6 <!--            <version>5.1.49</version>-->
 7         </dependency>
 8 想要修改版本
 9 1、直接依赖引入具体版本(maven的就近依赖原则)
10 2、重新声明版本(maven的属性的就近优先原则)
11     <properties>
12         <java.version>1.8</java.version>
13         <mysql.version>5.1.49</mysql.version>
14     </properties>
mysql.version

2)  分析自动配置

自动配置的类

  • DataSourceAutoConfiguration : 数据源的自动配置
    • 修改数据源相关的配置:spring.datasource
    • 数据库连接池的配置,是自己容器中没有DataSource才自动配置的
    • 底层配置好的连接池是:HikariDataSource
  • DataSourceTransactionManagerAutoConfiguration: 事务管理器的自动配置
  • JdbcTemplateAutoConfiguration: JdbcTemplate的自动配置,可以来对数据库进行crud
    • 可以修改这个配置项@ConfigurationProperties(prefix = "spring.jdbc") 来修改JdbcTemplate
    • @Bean@Primary    JdbcTemplate;容器中有这个组件
  • JndiDataSourceAutoConfiguration: jndi的自动配置
  • XADataSourceAutoConfiguration: 分布式事务相关的
1     @Configuration(proxyBeanMethods = false)
2     @Conditional(PooledDataSourceCondition.class)
3     @ConditionalOnMissingBean({ DataSource.class, XADataSource.class })
4     @Import({ DataSourceConfiguration.Hikari.class, DataSourceConfiguration.Tomcat.class,
5             DataSourceConfiguration.Dbcp2.class, DataSourceConfiguration.OracleUcp.class,
6             DataSourceConfiguration.Generic.class, DataSourceJmxConfiguration.class })
7     protected static class PooledDataSourceConfiguration
PooledDataSourceConfiguration

3)  修改配置项

1 spring:
2   datasource:
3     url: jdbc:mysql://localhost:3306/db_account
4     username: root
5     password: 123456
6     driver-class-name: com.mysql.jdbc.Driver
datasource

4)  测试

 1 @Slf4j
 2 @SpringBootTest
 3 class Boot05WebAdminApplicationTests {
 4 
 5     @Autowired
 6     JdbcTemplate jdbcTemplate;
 7 
 8 
 9     @Test
10     void contextLoads() {
11 
12 //        jdbcTemplate.queryForObject("select * from account_tbl")
13 //        jdbcTemplate.queryForList("select * from account_tbl",)
14         Long aLong = jdbcTemplate.queryForObject("select count(*) from account_tbl", Long.class);
15         log.info("记录总数:{}",aLong);
16     }
17 
18 }
contextLoads

6.1.2 使用Druid数据源

1)  druid官方github地址

https://github.com/alibaba/druid

整合第三方技术的两种方式

  • 自定义
  • 找starter

 

2)   自定义方式

  • 创建数据源
 1         <dependency>
 2             <groupId>com.alibaba</groupId>
 3             <artifactId>druid</artifactId>
 4             <version>1.1.17</version>
 5         </dependency>
 6 
 7 <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
 8         destroy-method="close">
 9         <property name="url" value="${jdbc.url}" />
10         <property name="username" value="${jdbc.username}" />
11         <property name="password" value="${jdbc.password}" />
12         <property name="maxActive" value="20" />
13         <property name="initialSize" value="1" />
14         <property name="maxWait" value="60000" />
15         <property name="minIdle" value="1" />
16         <property name="timeBetweenEvictionRunsMillis" value="60000" />
17         <property name="minEvictableIdleTimeMillis" value="300000" />
18         <property name="testWhileIdle" value="true" />
19         <property name="testOnBorrow" value="false" />
20         <property name="testOnReturn" value="false" />
21         <property name="poolPreparedStatements" value="true" />
22         <property name="maxOpenPreparedStatements" value="20" />
druid
  • StatViewServlet
  • StatViewServlet的用途包括:
    • 提供监控信息展示的html页面
    • 提供监控信息的JSON API
1     <servlet>
2         <servlet-name>DruidStatView</servlet-name>
3         <servlet-class>com.alibaba.druid.support.http.StatViewServlet</servlet-class>
4     </servlet>
5     <servlet-mapping>
6         <servlet-name>DruidStatView</servlet-name>
7         <url-pattern>/druid/*</url-pattern>
8     </servlet-mapping>
View Code
 1 @ConfigurationProperties("spring.datasource")
 2     @Bean
 3     public DataSource dataSource() throws SQLException {
 4         DruidDataSource source = new DruidDataSource();
 5         source.setFilters("sata,wall");
 6         return source;
 7     }
 8 
 9     @Bean
10     public ServletRegistrationBean statViewServlet(){
11         StatViewServlet statViewServlet = new StatViewServlet();
12         ServletRegistrationBean<StatViewServlet> registrationBean = new ServletRegistrationBean<>(statViewServlet,"/druid/*");
13         registrationBean.addInitParameter("loginUsername","admin");
14         registrationBean.addInitParameter("loginPassword","122054");
15         registrationBean.addInitParameter("allow","122054");
16         return registrationBean;
17     }
18 
19     @Bean
20     public FilterRegistrationBean webStatFilter(){
21         WebStatFilter webStatFilter = new WebStatFilter();
22         FilterRegistrationBean<WebStatFilter> filterRegistrationBean = new FilterRegistrationBean<>(webStatFilter);
23         filterRegistrationBean.setUrlPatterns(Arrays.asList("/*"));
24         filterRegistrationBean.addInitParameter("exclusions","*.js,*.gif,*.png,*.ico,/druid/*");
25         return filterRegistrationBean;
26     }
StatViewServlet
  • StatFilter

用于统计监控信息;如SQL监控、URI监控

        需要给数据源中配置如下属性;可以允许多个filter,多个用,分割;如:

        <property name="filters" value="stat,slf4j" />

 

    系统中所有filter:

      别名        filter类名

      default        com.alibaba.druid.filter.stat.StatFilter

      stat          com.alibaba.druid.filter.stat.StatFilter

      mergeStat       com.alibaba.druid.filter.stat.MergeStatFilter

      encoding       com.alibaba.druid.filter.encoding.EncodingConvertFilter

      log4j         com.alibaba.druid.filter.logging.Log4jFilter

      log4j2         com.alibaba.druid.filter.logging.Log4j2Filter

      slf4j         com.alibaba.druid.filter.logging.Slf4jLogFilter

      commonlogging    com.alibaba.druid.filter.logging.CommonsLogFilter

 

慢SQL记录配置: 

1 <bean id="stat-filter" class="com.alibaba.druid.filter.stat.StatFilter">
2     <property name="slowSqlMillis" value="10000" />
3     <property name="logSlowSql" value="true" />
4 </bean>
5 
6 使用 slowSqlMillis 定义慢SQL的时长
View Code

3)   使用官方Starter方式

  • 引入druid-starter
1         <dependency>
2             <groupId>com.alibaba</groupId>
3             <artifactId>druid-spring-boot-starter</artifactId>
4             <version>1.1.17</version>
5         </dependency>
druid导包
  • 分析自动配置
    • 扩展配置项 spring.datasource.druid
    • DruidSpringAopConfiguration.class,   监控SpringBean的;配置项:spring.datasource.druid.aop-patterns
    • DruidStatViewServletConfiguration.class, 监控页的配置:spring.datasource.druid.stat-view-servlet;默认开启
    • DruidWebStatFilterConfiguration.class, web监控配置;spring.datasource.druid.web-stat-filter;默认开启
    • DruidFilterConfiguration.class}) 所有Druid自己filter的配置
1     private static final String FILTER_STAT_PREFIX = "spring.datasource.druid.filter.stat";
2     private static final String FILTER_CONFIG_PREFIX = "spring.datasource.druid.filter.config";
3     private static final String FILTER_ENCODING_PREFIX = "spring.datasource.druid.filter.encoding";
4     private static final String FILTER_SLF4J_PREFIX = "spring.datasource.druid.filter.slf4j";
5     private static final String FILTER_LOG4J_PREFIX = "spring.datasource.druid.filter.log4j";
6     private static final String FILTER_LOG4J2_PREFIX = "spring.datasource.druid.filter.log4j2";
7     private static final String FILTER_COMMONS_LOG_PREFIX = "spring.datasource.druid.filter.commons-log";
8     private static final String FILTER_WALL_PREFIX = "spring.datasource.druid.filter.wall";
View Code
  • 配置示例
 1 spring:
 2   datasource:
 3     url: jdbc:mysql://localhost:3306/db_account
 4     username: root
 5     password: 123456
 6     driver-class-name: com.mysql.jdbc.Driver
 7 
 8     druid:
 9       aop-patterns: com.atguigu.admin.*  #监控SpringBean
10       filters: stat,wall     # 底层开启功能,stat(sql监控),wall(防火墙)
11 
12       stat-view-servlet:   # 配置监控页功能
13         enabled: true
14         login-username: admin
15         login-password: admin
16         resetEnable: false
17 
18       web-stat-filter:  # 监控web
19         enabled: true
20         urlPattern: /*
21         exclusions: '*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*'
22 
23 
24       filter:
25         stat:    # 对上面filters里面的stat的详细配置
26           slow-sql-millis: 1000
27           logSlowSql: true
28           enabled: true
29         wall:
30           enabled: true
31           config:
32             drop-table-allow: false
配置

 

SpringBoot配置示例https://github.com/alibaba/druid/tree/master/druid-spring-boot-starter

配置项列表:https://github.com/alibaba/druid/wiki/DruidDataSource%E9%85%8D%E7%BD%AE%E5%B1%9E%E6%80%A7%E5%88%97%E8%A1%A8

6.1.3 整合mybatis

https://github.com/mybatis

starter

SpringBoot官方的Starterspring-boot-starter-*

第三方的: *-spring-boot-starter

1         <dependency>
2             <groupId>org.mybatis.spring.boot</groupId>
3             <artifactId>mybatis-spring-boot-starter</artifactId>
4             <version>2.1.4</version>
5         </dependency>
mybatis导包

 

1)  配置模式

  • 全局配置文件
  • SqlSessionFactory: 自动配置好了
  • SqlSession:自动配置了 SqlSessionTemplate 组合了SqlSession
  • @Import(AutoConfiguredMapperScannerRegistrar.class);
  • Mapper: 只要我们写的操作MyBatis的接口标准了 @Mapper 就会被自动扫描进来
1 @EnableConfigurationProperties(MybatisProperties.class) : MyBatis配置项绑定类。
2 @AutoConfigureAfter({ DataSourceAutoConfiguration.class, MybatisLanguageDriverAutoConfiguration.class })
3 public class MybatisAutoConfiguration{}
4 
5 @ConfigurationProperties(prefix = "mybatis")
6 public class MybatisProperties
View Code
  • 可以修改配置文件中 mybatis 开始的所有;
 1 # 配置mybatis规则
 2 mybatis:
 3   config-location: classpath:mybatis/mybatis-config.xml  #全局配置文件位置
 4   mapper-locations: classpath:mybatis/mapper/*.xml  #sql映射文件位置
 5   
 6 Mapper接口--->绑定Xml
 7 <?xml version="1.0" encoding="UTF-8" ?>
 8 <!DOCTYPE mapper
 9         PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
10         "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
11 <mapper namespace="com.atguigu.admin.mapper.AccountMapper">
12 <!--    public Account getAcct(Long id); -->
13     <select id="getAcct" resultType="com.atguigu.admin.bean.Account">
14         select * from  account_tbl where  id=#{id}
15     </select>
16 </mapper>
配置mybatis规则
  • 配置 private Configuration configuration; mybatis.configuration下面的所有,就是相当于改mybatis全局配置文件中的值
1 # 配置mybatis规则
2 mybatis:
3 #  config-location: classpath:mybatis/mybatis-config.xml
4   mapper-locations: classpath:mybatis/mapper/*.xml
5   configuration:
6     map-underscore-to-camel-case: true
7     
8  可以不写全局;配置文件,所有全局配置文件的配置都放在configuration配置项中即可
View Code

 

  • 导入mybatis官方starter
  • 编写mapper接口。标准@Mapper注解
  • 编写sql映射文件并绑定mapper接口
  • 在application.yaml中指定Mapper配置文件的位置,以及指定全局配置文件的信息 (建议;配置在mybatis.configuration

2)  注解模式

1 @Mapper
2 public interface CityMapper {
3 
4     @Select("select * from city where id=#{id}")
5     public City getById(Long id);
6 
7     public void insert(City city);
8 
9 }
注解

3)  混合模式

1 @Mapper
2 public interface CityMapper {
3 
4     @Select("select * from city where id=#{id}")
5     public City getById(Long id);
6 
7     public void insert(City city);
8 
9 }
View Code

4)  最佳实战

  • 引入mybatis-starter
  • 配置application.yaml中,指定mapper-location位置即可
  • 编写Mapper接口并标注@Mapper注解
  • 简单方法直接注解方式
  • 复杂方法编写mapper.xml进行绑定映射
  • @MapperScan("com.atguigu.admin.mapper") 简化,其他的接口就可以不用标注@Mapper注解

 

6.1.4 整合mybatis-plus完成crud

1)  什么是MyBatis-Plus

MyBatis-Plus(简称 MP)是一个 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。

mybatis plus 官网

建议安装 MybatisX 插件

2)  整合MyBatis-Plus

优点:

只需要我们的Mapper继承 BaseMapper 就可以拥有crud能力

 

自动配置:

  • MybatisPlusAutoConfiguration 配置类,MybatisPlusProperties 配置项绑定。mybatis-plus:xxx 就是对mybatis-plus的定制
  • SqlSessionFactory 自动配置好。底层是容器中默认的数据源
  • mapperLocations 自动配置好的。有默认值。classpath*:/mapper/**/*.xml;任意包的类路径下的所有mapper文件夹下任意路径下的所有xml都是sql映射文件。  建议以后sql映射文件,放在 mapper下
  • 容器中也自动配置好了 SqlSessionTemplate
  • @Mapper 标注的接口也会被自动扫描;建议直接 @MapperScan("com.atguigu.admin.mapper") 批量扫描就行

 

3)  CRUD功能

 1     @GetMapping("/user/delete/{id}")
 2     public String deleteUser(@PathVariable("id") Long id,
 3                              @RequestParam(value = "pn",defaultValue = "1")Integer pn,
 4                              RedirectAttributes ra){
 5 
 6         userService.removeById(id);
 7 
 8         ra.addAttribute("pn",pn);
 9         return "redirect:/dynamic_table";
10     }
11 
12 
13     @GetMapping("/dynamic_table")
14     public String dynamic_table(@RequestParam(value="pn",defaultValue = "1") Integer pn,Model model){
15         //表格内容的遍历
16 //        response.sendError
17 //     List<User> users = Arrays.asList(new User("zhangsan", "123456"),
18 //                new User("lisi", "123444"),
19 //                new User("haha", "aaaaa"),
20 //                new User("hehe ", "aaddd"));
21 //        model.addAttribute("users",users);
22 //
23 //        if(users.size()>3){
24 //            throw new UserTooManyException();
25 //        }
26         //从数据库中查出user表中的用户进行展示
27 
28         //构造分页参数
29         Page<User> page = new Page<>(pn, 2);
30         //调用page进行分页
31         Page<User> userPage = userService.page(page, null);
32 
33 
34 //        userPage.getRecords()
35 //        userPage.getCurrent()
36 //        userPage.getPages()
37 
38 
39         model.addAttribute("users",userPage);
40 
41         return "table/dynamic_table";
42     }
controller
1 @Service
2 public class UserServiceImpl extends ServiceImpl<UserMapper,User> implements UserService {
3 
4 
5 }
6 
7 public interface UserService extends IService<User> {
8 
9 }
Service
1 @Mapper
2 public interface UserMapper  extends BaseMapper<User> {
3 }
mapper
 1 @Data
 2 @ToString
 3 @TableName("user") // 当使用的类名与表名不一致时使用,参数:表名
 4 public class User {
 5     @TableField(exist = false)
 6     private String userName;
 7     @TableField(exist = false)
 8     private String password;
 9 
10     private Long id;
11     private String name;
12     private Integer age;
13     private String email;
14 }
user
 1 @MapperScan("com.baomidou.cloud.service.*.mapper*")
 2 public class MybatisPlusConfig {
 3 
 4     // 旧版
 5     @Bean
 6     public PaginationInterceptor paginationInterceptor() {
 7         PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
 8         // 设置请求的页面大于最大页后操作, true调回到首页,false 继续请求  默认false
 9         // paginationInterceptor.setOverflow(false);
10         // 设置最大单页限制数量,默认 500 条,-1 不受限制
11         // paginationInterceptor.setLimit(500);
12         // 开启 count 的 join 优化,只针对部分 left join
13         paginationInterceptor.setCountSqlParser(new JsqlParserCountOptimize(true));
14         return paginationInterceptor;
15     }
16 
17     // 最新版
18     @Bean
19     public MybatisPlusInterceptor mybatisPlusInterceptor() {
20         MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
21         interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.H2));
22         return interceptor;
23     }
24 
25 }
分页插件

6.2 NoSQL

Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。 它支持多种类型的数据结构,如 字符串(strings)散列(hashes)列表(lists)集合(sets)有序集合(sorted sets) 与范围查询, bitmapshyperloglogs地理空间(geospatial) 索引半径查询。 Redis 内置了 复制(replication)LUA脚本(Lua scripting)LRU驱动事件(LRU eviction)事务(transactions) 和不同级别的 磁盘持久化(persistence), 并通过 Redis哨兵(Sentinel)和自动 分区(Cluster)提供高可用性(high availability)。

6.2.1 Redis自动配置

自动配置:

  • RedisAutoConfiguration 自动配置类。RedisProperties 属性类 --> spring.redis.xxx是对redis的配置
  • 连接工厂是准备好的。LettuceConnectionConfiguration、JedisConnectionConfiguration
  • 自动注入了RedisTemplate<Object, Object> : xxxTemplate;
  • 自动注入了StringRedisTemplate;k:v都是String
  • key:value
  • 底层只要我们使用 StringRedisTemplate、RedisTemplate就可以操作redis

redis环境搭建:

1、阿里云按量付费redis。经典网络

2、申请redis的公网连接地址

3、修改白名单  允许0.0.0.0/0 访问

1         <dependency>
2             <groupId>org.springframework.boot</groupId>
3             <artifactId>spring-boot-starter-data-redis</artifactId>
4         </dependency>
redis导包

 

6.2.2 RedisTemplate与Lettuce

1     @Test
2     void testRedis(){
3         ValueOperations<String, String> operations = redisTemplate.opsForValue();
4 
5         operations.set("hello","world");
6 
7         String hello = operations.get("hello");
8         System.out.println(hello);
9     }
View Code

6.2.3 切换至客户端jedis

 1         <dependency>
 2             <groupId>org.springframework.boot</groupId>
 3             <artifactId>spring-boot-starter-data-redis</artifactId>
 4         </dependency>
 5 
 6 <!--        导入jedis-->
 7         <dependency>
 8             <groupId>redis.clients</groupId>
 9             <artifactId>jedis</artifactId>
10         </dependency>
导包
1 spring:
2   redis:
3       host: r-bp1nc7reqesxisgxpipd.redis.rds.aliyuncs.com
4       port: 6379
5       password: lfy:Lfy123456
6       client-type: jedis
7       jedis:
8         pool:
9           max-active: 10
配置

 

7 单元测试

7.1 JUnit5 的变化

Spring Boot 2.2.0 版本开始引入 JUnit 5 作为单元测试默认库

作为最新版本的JUnit框架,JUnit5与之前版本的Junit框架有很大的不同。由三个不同子项目的几个不同模块组成。

JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage

 

  • JUnit Platform: Junit Platform是在JVM上启动测试框架的基础,不仅支持Junit自制的测试引擎,其他测试引擎也都可以接入。
  • JUnit Jupiter: JUnit Jupiter提供了JUnit5的新的编程模型,是JUnit5新特性的核心。内部 包含了一个测试引擎,用于在Junit Platform上运行。
  • JUnit Vintage: 由于JUint已经发展多年,为了照顾老的项目,JUnit Vintage提供了兼容JUnit4.x,Junit3.x的测试引擎。

注意:

SpringBoot 2.4 以上版本移除了默认对 Vintage 的依赖。如果需要兼容junit4需要自行引入(不能使用junit4的功能 @Test

JUnit 5’s Vintage Engine Removed from spring-boot-starter-test,如果需要继续兼容junit4需要自行引入vintage

 1 <dependency>
 2     <groupId>org.junit.vintage</groupId>
 3     <artifactId>junit-vintage-engine</artifactId>
 4     <scope>test</scope>
 5     <exclusions>
 6         <exclusion>
 7             <groupId>org.hamcrest</groupId>
 8             <artifactId>hamcrest-core</artifactId>
 9         </exclusion>
10     </exclusions>
11 </dependency>
导包

 

 

 

1 <dependency>
2   <groupId>org.springframework.boot</groupId>
3   <artifactId>spring-boot-starter-test</artifactId>
4   <scope>test</scope>
5 </dependency>
test
1 @SpringBootTest
2 class Boot05WebAdminApplicationTests {
3 
4 
5     @Test
6     void contextLoads() {
7 
8     }
9 }
@Test

以前:

  • @SpringBootTest + @RunWith(SpringTest.class)

SpringBoot整合Junit以后:

  • 编写测试方法:@Test标注(注意需要使用junit5版本的注解)
  • Junit类具有Spring的功能,@Autowired、比如 @Transactional 标注测试方法,测试完成后自动回滚

7.2 JUnit5常用注解

JUnit5的注解与JUnit4的注解有所变化

https://junit.org/junit5/docs/current/user-guide/#writing-tests-annotations

    • @Test :表示方法是测试方法。但是与JUnit4的@Test不同,他的职责非常单一不能声明任何属性,拓展的测试将会由Jupiter提供额外测试
    • @ParameterizedTest :表示方法是参数化测试,下方会有详细介绍
    • @RepeatedTest :表示方法可重复执行,下方会有详细介绍
    • @DisplayName :为测试类或者测试方法设置展示名称
    • @BeforeEach :表示在每个单元测试之前执行
    • @AfterEach :表示在每个单元测试之后执行
    • @BeforeAll :表示在所有单元测试之前执行
    • @AfterAll :表示在所有单元测试之后执行
    • @Tag :表示单元测试类别,类似于JUnit4中的@Categories
    • @Disabled :表示测试类或测试方法不执行,类似于JUnit4中的@Ignore
    • @Timeout :表示测试方法运行如果超过了指定时间将会返回错误
    • @ExtendWith :为测试类或测试方法提供扩展类引用
 1 import org.junit.jupiter.api.Test; //注意这里使用的是jupiter的Test注解!!
 2 
 3 
 4 public class TestDemo {
 5 
 6   @Test
 7   @DisplayName("第一次测试")
 8   public void firstTest() {
 9       System.out.println("hello world");
10   }
@DisplayName

7.3 断言

断言(assertions)是测试方法中的核心部分,用来对测试需要满足的条件进行验证。这些断言方法都是 org.junit.jupiter.api.Assertions 的静态方法。JUnit 5 内置的断言可以分成如下几个类别:

  • 检查业务逻辑返回的数据是否合理。
  • 所有的测试运行结束以后,会有一个详细的测试报告;

7.3.1 简单断言

用来对单个值进行简单的验证。如:

1 方法                            说明
2 assertEquals        判断两个对象或两个原始类型是否相等
3 assertNotEquals      判断两个对象或两个原始类型是否不相等
4 assertSame         判断两个对象引用是否指向同一个对象
5 assertNotSame       判断两个对象引用是否指向不同的对象
6 assertTrue           判断给定的布尔值是否为 true
7 assertFalse          判断给定的布尔值是否为 false
8 assertNull           判断给定的对象引用是否为 null
9 assertNotNull       判断给定的对象引用是否不为 null                        
 1 @Test
 2 @DisplayName("simple assertion")
 3 public void simple() {
 4      assertEquals(3, 1 + 2, "simple math");
 5      assertNotEquals(3, 1 + 1);
 6 
 7      assertNotSame(new Object(), new Object());
 8      Object obj = new Object();
 9      assertSame(obj, obj);
10 
11      assertFalse(1 > 2);
12      assertTrue(1 < 2);
13 
14      assertNull(null);
15      assertNotNull(new Object());
16 }
简单断言  

7.3.2 数组断言

1 @Test
2 @DisplayName("array assertion")
3 public void array() {
4  assertArrayEquals(new int[]{1, 2}, new int[] {1, 2});
5 }
array assertion

7.3.3 组合断言

assertAll 方法接受多个 org.junit.jupiter.api.Executable 函数式接口的实例作为要验证的断言,可以通过 lambda 表达式很容易的提供这些断言

1 @Test
2 @DisplayName("assert all")
3 public void all() {
4  assertAll("Math",
5     () -> assertEquals(2, 1 + 1),
6     () -> assertTrue(1 > 0)
7  );
8 }
组合断言

7.3.4 异常断言

在JUnit4时期,想要测试方法的异常情况时,需要用@Rule注解的ExpectedException变量还是比较麻烦的。而JUnit5提供了一种新的断言方式Assertions.assertThrows() ,配合函数式编程就可以进行使用。

1 @Test
2 @DisplayName("异常测试")
3 public void exceptionTest() {
4     ArithmeticException exception = Assertions.assertThrows(
5            //扔出断言异常
6             ArithmeticException.class, () -> System.out.println(1 % 0));
7 
8 }
异常测试

7.3.5 超时断言

Junit5还提供了Assertions.assertTimeout() 为测试方法设置了超时时间

 1 @Test
 2 
 3 @DisplayName("超时测试")
 4 
 5 public void timeoutTest() {
 6 
 7     //如果测试方法时间超过1s将会异常
 8 
 9     Assertions.assertTimeout(Duration.ofMillis(1000), () -> Thread.sleep(500));
10 
11 }
超时测试

7.3.6 快速断言

通过 fail 方法直接使得测试失败

@Test
@DisplayName("fail")
public void shouldFail() {
 fail("This should fail");
}
fail

7.4 前置条件

JUnit 5 中的前置条件(assumptions【假设】)类似于断言,不同之处在于不满足的断言会使得测试方法失败,而不满足的前置条件只会使得测试方法的执行终止。前置条件可以看成是测试方法执行的前提,当该前提不满足时,就没有继续执行的必要。

assumeTrue 和 assumFalse 确保给定的条件为 true 或 false,不满足条件会使得测试执行终止。assumingThat 的参数是表示条件的布尔值和对应的 Executable 接口的实现对象。只有条件满足时,Executable 对象才会被执行;当条件不满足时,测试执行并不会终止。

 1 @DisplayName("前置条件")
 2 public class AssumptionsTest {
 3  private final String environment = "DEV";
 4  
 5  @Test
 6  @DisplayName("simple")
 7  public void simpleAssume() {
 8     assumeTrue(Objects.equals(this.environment, "DEV"));
 9     assumeFalse(() -> Objects.equals(this.environment, "PROD"));
10  }
11  
12  @Test
13  @DisplayName("assume then do")
14  public void assumeThenDo() {
15     assumingThat(
16        Objects.equals(this.environment, "DEV"),
17        () -> System.out.println("In DEV")
18     );
19  }
20 }
前置条件

7.5 嵌套测试

JUnit 5 可以通过 Java 中的内部类和@Nested 注解实现嵌套测试,从而可以更好的把相关的测试方法组织在一起。在内部类中可以使用@BeforeEach 和@AfterEach 注解,而且嵌套的层次没有限制。

 1 @DisplayName("A stack")
 2 class TestingAStackDemo {
 3 
 4     Stack<Object> stack;
 5 
 6     @Test
 7     @DisplayName("is instantiated with new Stack()")
 8     void isInstantiatedWithNew() {
 9         new Stack<>();
10     }
11 
12     @Nested
13     @DisplayName("when new")
14     class WhenNew {
15 
16         @BeforeEach
17         void createNewStack() {
18             stack = new Stack<>();
19         }
20 
21         @Test
22         @DisplayName("is empty")
23         void isEmpty() {
24             assertTrue(stack.isEmpty());
25         }
26 
27         @Test
28         @DisplayName("throws EmptyStackException when popped")
29         void throwsExceptionWhenPopped() {
30             assertThrows(EmptyStackException.class, stack::pop);
31         }
32 
33         @Test
34         @DisplayName("throws EmptyStackException when peeked")
35         void throwsExceptionWhenPeeked() {
36             assertThrows(EmptyStackException.class, stack::peek);
37         }
38 
39         @Nested
40         @DisplayName("after pushing an element")
41         class AfterPushing {
42 
43             String anElement = "an element";
44 
45             @BeforeEach
46             void pushAnElement() {
47                 stack.push(anElement);
48             }
49 
50             @Test
51             @DisplayName("it is no longer empty")
52             void isNotEmpty() {
53                 assertFalse(stack.isEmpty());
54             }
55 
56             @Test
57             @DisplayName("returns the element when popped and is empty")
58             void returnElementWhenPopped() {
59                 assertEquals(anElement, stack.pop());
60                 assertTrue(stack.isEmpty());
61             }
62 
63             @Test
64             @DisplayName("returns the element when peeked but remains not empty")
65             void returnElementWhenPeeked() {
66                 assertEquals(anElement, stack.peek());
67                 assertFalse(stack.isEmpty());
68             }
69         }
70     }
71 }
A stack

7.6 参数化测试

参数化测试是JUnit5很重要的一个新特性,它使得用不同的参数多次运行测试成为了可能,也为我们的单元测试带来许多便利。

利用@ValueSource等注解,指定入参,我们将可以使用不同的参数进行多次单元测试,而不需要每新增一个参数就新增一个单元测试,省去了很多冗余代码。

  • @ValueSource: 为参数化测试指定入参来源,支持八大基础类以及String类型,Class类型
  • @NullSource: 表示为参数化测试提供一个null的入参
  • @EnumSource: 表示为参数化测试提供一个枚举入参
  • @CsvFileSource:表示读取指定CSV文件内容作为参数化测试入参
  • @MethodSource:表示读取指定方法的返回值作为参数化测试入参(注意方法返回需要是一个流)

当然如果参数化测试仅仅只能做到指定普通的入参还达不到让我觉得惊艳的地步。让我真正感到他的强大之处的地方在于他可以支持外部的各类入参。如:CSV,YML,JSON 文件甚至方法的返回值也可以作为入参。只需要去实现ArgumentsProvider接口,任何外部文件都可以作为它的入参。

 1 @ParameterizedTest
 2 @ValueSource(strings = {"one", "two", "three"})
 3 @DisplayName("参数化测试1")
 4 public void parameterizedTest1(String string) {
 5     System.out.println(string);
 6     Assertions.assertTrue(StringUtils.isNotBlank(string));
 7 }
 8 
 9 
10 @ParameterizedTest
11 @MethodSource("method")    //指定方法名
12 @DisplayName("方法来源参数")
13 public void testWithExplicitLocalMethodSource(String name) {
14     System.out.println(name);
15     Assertions.assertNotNull(name);
16 }
17 
18 static Stream<String> method() {
19     return Stream.of("apple", "banana");
20 }
参数化测试

7.7 迁移指南

在进行迁移的时候需要注意如下的变化:

    • 注解在 org.junit.jupiter.api 包中,断言在 org.junit.jupiter.api.Assertions 类中,前置条件在 org.junit.jupiter.api.Assumptions 类中。
    • 把@Before 和@After 替换成@BeforeEach 和@AfterEach。
    • 把@BeforeClass 和@AfterClass 替换成@BeforeAll 和@AfterAll。
    • 把@Ignore 替换成@Disabled。
    • 把@Category 替换成@Tag。
    • 把@RunWith、@Rule 和@ClassRule 替换成@ExtendWith。

8 指标监控

8.1SpringBoot Actuator

8.1.1 简介

未来每一个微服务在云上部署以后,我们都需要对其进行监控、追踪、审计、控制等。SpringBoot就抽取了Actuator场景,使得我们每个微服务快速引用即可获得生产级别的应用监控、审计等功能。

1         <dependency>
2             <groupId>org.springframework.boot</groupId>
3             <artifactId>spring-boot-starter-actuator</artifactId>
4         </dependency>
导包

8.1.2 1.x与2.x的不同

 

8.1.3 如何使用

1 management:
2   endpoints:
3     enabled-by-default: true #暴露所有端点信息
4     web:
5       exposure:
6         include: '*'  #以web方式暴露
配置
  • 测试
  1. http://localhost:8080/actuator/beans
  2. http://localhost:8080/actuator/configprops
  3. http://localhost:8080/actuator/metrics
  4. http://localhost:8080/actuator/metrics/jvm.gc.pause
  5. http://localhost:8080/actuator/endpointName/detailPath

 

8.1.4 可视化

https://github.com/codecentric/spring-boot-admin

微服务端:

 

1         <dependency>
2             <groupId>de.codecentric</groupId>
3             <artifactId>spring-boot-admin-starter-client</artifactId>
4             <version>2.3.1</version>
5         </dependency>
导包

 

 

 

 1 spring:
 2   boot:
 3     admin:
 4       client:
 5         url:http://localhost:8888
 6         instance:
 7           prefer-ip:true
 8   application:
 9     name: fpringboot-admin
10 management:
11   endpoints:
12     enabled-by-default: true
13     web:
14       exposure:
15         include: '*'
16 
17   endpoint:
18     health:
19       show-details: always
配置

 

服务端:

 

1         <dependency>
2             <groupId>de.codecentric</groupId>
3             <artifactId>spring-boot-admin-starter-server</artifactId>
4             <version>2.3.1</version>
5         </dependency>
导包

 

1 server:
2   port: 8888
端口
1 @EnableAdminServer
2 @SpringBootApplication
3 public class SpringbootAdminApplication {
4 
5     public static void main(String[] args) {
6         SpringApplication.run(SpringbootAdminApplication.class, args);
7     }
8 
9 }
@EnableAdminServer 

 

8.2 Actuator Endpoint

8.2.1 最常用的端点

ID	                    描述

auditevents        暴露当前应用程序的审核事件信息。需要一个AuditEventRepository组件。

beans           显示应用程序中所有Spring Bean的完整列表。

caches          暴露可用的缓存。

conditions       显示自动配置的所有条件信息,包括匹配或不匹配的原因。

configprops       显示所有@ConfigurationProperties。

env            暴露Spring的属性ConfigurableEnvironment

flyway          显示已应用的所有Flyway数据库迁移。需要一个或多个Flyway组件。

health         显示应用程序运行状况信息。

httptrace        显示HTTP跟踪信息(默认情况下,最近100个HTTP请求-响应)。需要一个HttpTraceRepository组件。

info          显示应用程序信息。

integrationgraph   显示Spring integrationgraph 。需要依赖spring-integration-core。

loggers        显示和修改应用程序中日志的配置。

liquibase       显示已应用的所有Liquibase数据库迁移。需要一个或多个Liquibase组件。

metrics         显示当前应用程序的“指标”信息。

mappings         显示所有@RequestMapping路径列表。

scheduledtasks    显示应用程序中的计划任务。

sessions         允许从Spring Session支持的会话存储中检索和删除用户会话。需要使用Spring Session的基于Servlet的Web应用程序。

shutdown         使应用程序正常关闭。默认禁用。

startup         显示由ApplicationStartup收集的启动步骤数据。需要使用SpringApplication进行配置BufferingApplicationStartup。

threaddump       执行线程转储。

 

如果您的应用程序是Web应用程序(Spring MVC,Spring WebFlux或Jersey),则可以使用以下附加端点:

ID	            描述

heapdump        返回hprof堆转储文件。

jolokia        通过HTTP暴露JMX bean(需要引入Jolokia,不适用于WebFlux)。需要引入依赖jolokia-core。

logfile        返回日志文件的内容(如果已设置logging.file.name或logging.file.path属性)。支持使用HTTPRange标头来检索部分日志文件的内容。

prometheus      以Prometheus服务器可以抓取的格式公开指标。需要依赖micrometer-registry-prometheus。

 

最常用的Endpoint

    • Health:监控状况
    • Metrics:运行时指标
    • Loggers:日志记录

8.2.2 Health Endpoint

健康检查端点,我们一般用于在云平台,平台会定时的检查应用的健康状况,我们就需要Health Endpoint可以为平台返回当前应用的一系列组件健康状况的集合。

重要的几点:

    • health endpoint返回的结果,应该是一系列健康检查后的一个汇总报告
    • 很多的健康检查默认已经自动配置好了,比如:数据库、redis等
    • 可以很容易的添加自定义的健康检查机制
1 management:
2   endpoints:
3     web:
4       exposure:
5         include: '*'
6   endpoint:
7     health:
8       show-details: always
配置health

 

8.2.3 Metrics Endpoint

提供详细的、层级的、空间指标信息,这些信息可以被pull(主动推送)或者push(被动获取)方式得到;

    • 通过Metrics对接多种监控系统
    • 简化核心Metrics开发
    • 添加自定义Metrics或者扩展已有Metrics
    • 查看具体的:localhost:8080/actuator/metrics/jvm.gc.pause

8.2.4 管理Endpoints

1)  开启与禁用Endpoints

  • 默认所有的Endpoint除过shutdown都是开启的。
  • 需要开启或者禁用某个Endpoint。配置模式为  management.endpoint.<endpointName>.enabled = true
  • 或者禁用所有的Endpoint然后手动开启指定的Endpoin
management:
  endpoint:
    beans:
      enabled: true


management:
  endpoints:
    enabled-by-default: false
  endpoint:
    beans:
      enabled: true
    health:
      enabled: true
management

2)  暴露Endpoints

  • 支持的暴露方式
    • HTTP:默认只暴露healthinfo Endpoint
    • JMX:默认暴露所有Endpoint
    • 除过health和info,剩下的Endpoint都应该进行保护访问。如果引入SpringSecurity,则会默认配置安全访问规则
ID JMX Web

auditevents

Yes

No

beans

Yes

No

caches

Yes

No

conditions

Yes

No

configprops

Yes

No

env

Yes

No

flyway

Yes

No

health

Yes

Yes

heapdump

N/A

No

httptrace

Yes

No

info

Yes

Yes

integrationgraph

Yes

No

jolokia

N/A

No

logfile

N/A

No

loggers

Yes

No

liquibase

Yes

No

metrics

Yes

No

mappings

Yes

No

prometheus

N/A

No

scheduledtasks

Yes

No

sessions

Yes

No

shutdown

Yes

No

startup

Yes

No

threaddump

Yes

No

https://www.yuque.com/atguigu/springboot/sgpvgn?inner=PAtkA

 

8.3 定制 Endpoint

8.3.1 定制 Health 信息

import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.stereotype.Component;

@Component
public class MyHealthIndicator implements HealthIndicator {

    @Override
    public Health health() {
        int errorCode = check(); // perform some specific health check
        if (errorCode != 0) {
            return Health.down().withDetail("Error Code", errorCode).build();
        }
        return Health.up().build();
    }

}

构建Health
Health build = Health.down()
                .withDetail("msg", "error service")
                .withDetail("code", "500")
                .withException(new RuntimeException())
                .build();
构建Health
1 management:
2     health:
3       enabled: true
4       show-details: always #总是显示详细信息。可显示每个模块的状态信息
配置
 1 @Component
 2 public class MyComHealthIndicator extends AbstractHealthIndicator {
 3 
 4     /**
 5      * 真实的检查方法
 6      * @param builder
 7      * @throws Exception
 8      */
 9     @Override
10     protected void doHealthCheck(Health.Builder builder) throws Exception {
11         //mongodb。  获取连接进行测试
12         Map<String,Object> map = new HashMap<>();
13         // 检查完成
14         if(1 == 2){
15 //            builder.up(); //健康
16             builder.status(Status.UP);
17             map.put("count",1);
18             map.put("ms",100);
19         }else {
20 //            builder.down();
21             builder.status(Status.OUT_OF_SERVICE);
22             map.put("err","连接超时");
23             map.put("ms",3000);
24         }
25 
26 
27         builder.withDetail("code",100)
28                 .withDetails(map);
29 
30     }
31 }
AbstractHealthIndicator

8.3.2 定制info信息

1)  编写配置文件

1 info:
2   appName: boot-admin
3   version: 2.0.1
4   mavenProjectName: @project.artifactId@  #使用@@可以获取maven的pom文件值
5   mavenProjectVersion: @project.version@
info配置

2)  编写实现InfoContributor

http://localhost:8080/actuator/info 会输出以上方式返回的所有info信息

 1 import java.util.Collections;
 2 
 3 import org.springframework.boot.actuate.info.Info;
 4 import org.springframework.boot.actuate.info.InfoContributor;
 5 import org.springframework.stereotype.Component;
 6 
 7 @Component
 8 public class ExampleInfoContributor implements InfoContributor {
 9 
10     @Override
11     public void contribute(Info.Builder builder) {
12         builder.withDetail("example",
13                 Collections.singletonMap("key", "value"));
14     }
15 
16 }
InfoContributo

8.3.3 定制Metrics信息

1)  SpringBoot支持自动适配的Metrics

  • JVM metrics, report utilization of:
    • Various memory and buffer pools
    • Statistics related to garbage collection
    • Threads utilization
    • Number of classes loaded/unloaded
  • CPU metrics
  • File descriptor metrics
  • Kafka consumer and producer metrics
  • Log4j2 metrics: record the number of events logged to Log4j2 at each level
  • Logback metrics: record the number of events logged to Logback at each level
  • Uptime metrics: report a gauge for uptime and a fixed gauge representing the application’s absolute start time
  • Tomcat metrics (server.tomcat.mbeanregistry.enabled must be set to true for all Tomcat metrics to be registered)
  • Spring Integration metrics

2)  增加定制Metrics

 1 class MyService{
 2     Counter counter;
 3     public MyService(MeterRegistry meterRegistry){
 4          counter = meterRegistry.counter("myservice.method.running.counter");
 5     }
 6 
 7     public void hello() {
 8         counter.increment();
 9     }
10 }
11 
12 
13 //也可以使用下面的方式
14 @Bean
15 MeterBinder queueSize(Queue queue) {
16     return (registry) -> Gauge.builder("queueSize", queue::size).register(registry);
17 }
Metrics

8.3.4 定制Endpoint端点

场景:开发ReadinessEndpoint来管理程序是否就绪,或者LivenessEndpoint来管理程序是否存活;

当然,这个也可以直接使用 https://docs.spring.io/spring-boot/docs/current/reference/html/production-ready-features.html#production-ready-kubernetes-probes

 1 @Component
 2 @Endpoint(id = "container")
 3 public class DockerEndpoint {
 4 
 5 
 6     @ReadOperation
 7     public Map getDockerInfo(){
 8         return Collections.singletonMap("info","docker started...");
 9     }
10 
11     @WriteOperation
12     private void restartDocker(){
13         System.out.println("docker restarted....");
14     }
15 
16 }
定制Endpoint

9 原理解析

9.1 Profile功能

9.1.1 application-profile功能

  • 默认配置文件  application.yaml;任何时候都会加载
  • 指定环境配置文件  application-{env}.yaml
  • 激活指定环境
    • 配置文件激活:spring.profiles.active=test  (test即为env)
    • 命令行激活:java -jar xxx.jar --spring.profiles.active=prod  --person.name=haha
      • 修改配置文件的任意值,命令行优先
  • 默认配置与环境配置同时生效
  • 同名配置项,profile配置优先

9.1.2 @Profile条件装配功能

1 @Configuration(proxyBeanMethods = false)
2 @Profile("production")
3 public class ProductionConfiguration {
4 
5     // ...
6 
7 }
@Profile

9.1.3 profile分组

1 spring.profiles.group.production[0]=proddb
2 spring.profiles.group.production[1]=prodmq
3 
4 使用:--spring.profiles.active=production  激活
group

9.2 外部化配置

https://docs.spring.io/spring-boot/docs/current/reference/html/spring-boot-features.html#boot-features-external-config

    1. Default properties (specified by setting SpringApplication.setDefaultProperties).
    2. @PropertySource annotations on your @Configuration classes. Please note that such property sources are not added to the Environment until the application context is being refreshed. This is too late to configure certain properties such as logging.* and spring.main.* which are read before refresh begins.
    3. Config data (such as application.properties files)
    4. A RandomValuePropertySource that has properties only in random.*.
    5. OS environment variables.
    6. Java System properties (System.getProperties()).
    7. JNDI attributes from java:comp/env.
    8. ServletContext init parameters.
    9. ServletConfig init parameters.
    10. Properties from SPRING_APPLICATION_JSON (inline JSON embedded in an environment variable or system property).
    11. Command line arguments.
    12. properties attribute on your tests. Available on @SpringBootTest and the test annotations for testing a particular slice of your application.
    13. @TestPropertySource annotations on your tests.
    14. Devtools global settings properties in the $HOME/.config/spring-boot directory when devtools is active.

9.2.1 外部配置源

常用:Java属性文件YAML文件环境变量命令行参数

9.2.2 配置文件查找位置

(1) classpath 根路

(2) classpath 根路径下config目录

(3) jar包当前目录

(4) jar包当前目录的config目录

(5) /config子目录的直接子目录

9.2.3 配置文件加载顺序

  1.  当前jar包内部的application.properties和application.yml
  2.  当前jar包内部的application-{profile}.properties 和 application-{profile}.yml
  3.  引用的外部jar包的application.properties和application.yml
  4.  引用的外部jar包的application-{profile}.properties 和 application-{profile}.yml

9.2.4 指定环境优先、外部优先

9.3 自定义starter

9.3.1 starter启动原理

  • starter-pom引入 autoconfigurer 包
  • autoconfigure包中配置使用 META-INF/spring.factoriesEnableAutoConfiguration 的值,使得项目启动加载指定的自动配置类
  • 编写自动配置类 xxxAutoConfiguration -> xxxxProperties
    • @Configuration
    • @Conditional
    • @EnableConfigurationProperties
    • @Bean

引入starter --- xxxAutoConfiguration --- 容器中放入组件 ---- 绑定xxxProperties ---- 配置项

 

9.3.2 自定义starter

atguigu-hello-spring-boot-starter(启动器)

1     <dependencies>
2         <dependency>
3             <groupId>com.atguigu</groupId>
4             <artifactId>atguigu-hello-spring-boot-starter-autoconfigure</artifactId>
5             <version>0.0.1-SNAPSHOT</version>
6         </dependency>
7     </dependencies>
导包

atguigu-hello-spring-boot-starter-autoconfigure(自动配置包)

 1 @Configuration
 2 @EnableConfigurationProperties(HelloProperties.class)
 3 public class HelloServiceAutoConfiguration {
 4     @ConditionalOnMissingBean(HelloService.class)
 5     @Bean
 6     public HelloService helloService(){
 7         HelloService helloService = new HelloService();
 8         return helloService;
 9     }
10 }
11 
12 @Component
13 @ConfigurationProperties("atguigu.hello")
14 public class HelloProperties {
15     private String prefix;
16     private String suffix;
17 
18     public String getPrefix() {
19         return prefix;
20     }
21 
22     public void setPrefix(String prefix) {
23         this.prefix = prefix;
24     }
25 
26     public String getSuffix() {
27         return suffix;
28     }
29 
30     public void setSuffix(String suffix) {
31         this.suffix = suffix;
32     }
33 }
34 
35 public class HelloService {
36     @Autowired
37     HelloProperties helloProperties;
38     public String sayHello(String name){
39 
40         String suffix = helloProperties.getSuffix();
41         String prefix = helloProperties.getPrefix();
42 
43         return prefix +"-"+ name + "-"+suffix;
44     }
45 }
View Code
1 # Auto Configure
2 org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.atguigu.boot.auto.HelloServiceAutoConfiguration
META-INF/spring.factories

9.4 SpringBoot原理

Spring原理【Spring注解】、SpringMVC原理、自动配置原理、SpringBoot原理

9.4.1 SpringBoot启动过程

  • SpringApplication
    • 保存一些信息。
    • 判定当前应用的类型。ClassUtils。Servlet
    • bootstrappers:初始启动引导器(List<Bootstrapper>):去spring.factories文件中找 org.springframework.boot.Bootstrapper
    • ApplicationContextInitializer;去spring.factoriesApplicationContextInitializer
      • List<ApplicationContextInitializer<?>> initializers
    • ApplicationListener  ;应用监听器。spring.factoriesApplicationListener
      • List<ApplicationListener<?>> listeners
  • 运行 SpringApplication
    • StopWatch
    • 记录应用的启动时间
    • 创建引导上下文(Context环境)createBootstrapContext()
      • 获取到所有之前的 bootstrappers 挨个执行 intitialize() 来完成对引导启动器上下文环境设置
    • 让当前应用进入headless模式。java.awt.headless
    • 获取所有 RunListener(运行监听器)【为了方便所有Listener进行事件感知】
      • getSpringFactoriesInstances 去spring.factoriesSpringApplicationRunListener.
    • 遍历 SpringApplicationRunListener 调用 starting 方法;
      • 相当于通知所有感兴趣系统正在启动过程的人,项目正在 starting。
    • 保存命令行参数;ApplicationArguments
    • 准备环境 prepareEnvironment();
      • 返回或者创建基础环境信息对象。StandardServletEnvironment
      • 配置环境信息对象。
        • 读取所有的配置源的配置属性值。
      • 绑定环境信息
      • 监听器调用 listener.environmentPrepared();通知所有的监听器当前环境准备完成
    • 创建IOC容器(createApplicationContext())
      • 根据项目类型(Servlet)创建容器,
      • 当前会创建 AnnotationConfigServletWebServerApplicationContext
    • 准备ApplicationContext IOC容器的基本信息  prepareContext()
      • 保存环境信息
      • IOC容器的后置处理流程。
      • 应用初始化器;applyInitializers;
        • 遍历所有的 ApplicationContextInitializer 。调用 initialize.。来对ioc容器进行初始化扩展功能
        • 遍历所有的 listener 调用 contextPrepared。EventPublishRunListenr;通知所有的监听器contextPrepared
      • 所有的监听器 调用 contextLoaded。通知所有的监听器 contextLoaded;
    • 刷新IOC容器。refreshContext
      • 创建容器中的所有组件(Spring注解)
    • 容器刷新完成后工作?afterRefresh
    • 所有监听 器 调用 listeners.started(context); 通知所有的监听器 started
    • 调用所有runners;callRunners()
      • 获取容器中的 ApplicationRunner
      • 获取容器中的  CommandLineRunner
      • 合并所有runner并且按照@Order进行排序
      • 遍历所有的runner。调用 run 方法
    • 如果以上有异常,
      • 调用Listener 的 failed
    • 调用所有监听器的 running 方法  listeners.running(context); 通知所有的监听器 running
    • running如果有问题。继续通知 failed 。调用所有 Listener 的 failed;通知所有的监听器 failed
1 public interface Bootstrapper {
2 
3     /**
4      * Initialize the given {@link BootstrapRegistry} with any required registrations.
5      * @param registry the registry to initialize
6      */
7     void intitialize(BootstrapRegistry registry);
8 
9 }
Bootstrapper

 

 

 

 1 @FunctionalInterface
 2 public interface ApplicationRunner {
 3 
 4     /**
 5      * Callback used to run the bean.
 6      * @param args incoming application arguments
 7      * @throws Exception on error
 8      */
 9     void run(ApplicationArguments args) throws Exception;
10 
11 }
ApplicationRunner
 1 @FunctionalInterface
 2 public interface CommandLineRunner {
 3 
 4     /**
 5      * Callback used to run the bean.
 6      * @param args incoming main method arguments
 7      * @throws Exception on error
 8      */
 9     void run(String... args) throws Exception;
10 
11 }
CommandLineRunner

9.4.2 Application Events and Listeners

https://docs.spring.io/spring-boot/docs/current/reference/html/spring-boot-features.html#boot-features-application-events-and-listeners

ApplicationContextInitializer

ApplicationListener

SpringApplicationRunListener

9.4.3 ApplicationRunner 与 CommandLineRunner

 

 

 
 
 
 
 
 
 
 
posted @ 2022-12-14 15:39  咘網鶵釁  阅读(80)  评论(0编辑  收藏  举报