欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Springboot笔记(1-30)

程序员文章站 2024-01-20 16:12:58
Springboot笔记52 错误处理,Springboot默认错误处理机制1、默认规则53 错误处理 - 【源码分析】底层组件功能分析52 错误处理,Springboot默认错误处理机制Springboot错误处理官方文档1、默认规则默认情况下,Springboot将提供 /error处理所有错误的映射。对于机器客户端,它将生成JSON响应,其中包含错误,HTTP状态和异常消息的详细信息。对于浏览器客户端,响应一个错误视图,以HTML格式呈现相同的数据。自定义错误页面。在static或...

1-4 Springboot相关介绍

1.Springboot官网
2.Springboot官方文档
3.尚硅谷文档地址
4.本文参考的Springboot笔记(上)
5.本文参考的Springboot笔记(下)

5 Springboot入门

系统要求

  • Java 8
  • Maven 3.3+
  • idea 2019.1.2

Maven配置文件

在maven的setting.xml文件中修改如下配置

<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd">
          ...
<mirror>
	<id>nexus-aliyun</id>
	<mirrorOf>*,!jeecg,!jeecg-snapshots</mirrorOf>
	<name>Nexus aliyun </name>
	<url>https://maven.aliyun.com/repository/public </url>
</mirror>          

引入依赖

在maven项目中创建一个Springboot的module,依赖自行添加即可。

创建主程序

在创建Springboot项目时,该主程序已经自行创建。本文引入的Springboot的版本为2.3.4。

@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
   
}

编写业务

@RestController
public class HelloController {

    @RequestMapping("/hello")
    public String handle01(){
        return "Hello, Spring Boot 2!";
    }
}

RestServices插件测试

Springboot笔记(1-30)

配置及打包

application.properties文件或application.yml文件中配置
application.properties中的配置为:

server.port=8080

application.yml中配置为:

server:
  port: 8080

在pom文件中添加如下插件

<build>
	<plugins>
		<plugin>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-maven-plugin</artifactId>
		</plugin>
	</plugins>
</build>

Springboot笔记(1-30)

  • 先点击clean,再点击package,即可打包为jar包。
  • 打包好的jar包被生成在helloworld程项目的target文件夹内。
  • 用cmd运行java -jar 01-helloworld-0.0.1-SNAPSHOT.jar,既可以运行helloworld工程项目。
  • 将jar包直接在目标服务器执行即可。
    Springboot笔记(1-30)

6 基础入门-SpringBoot-依赖管理特性

  • 依赖管理
    	依赖管理
        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>2.3.4.RELEASE</version>
            <relativePath/> <!-- lookup parent from repository -->
        </parent>
    
    	他的父项目
    	<parent>
    	    <groupId>org.springframework.boot</groupId>
    	    <artifactId>spring-boot-dependencies</artifactId>
    	    <version>2.3.4.RELEASE</version>
      	</parent>
    	几乎声明了所有开发中常用的依赖的版本号,自动版本仲裁机制,可以在其中修改版本号
    	<properties>
    	    <activemq.version>5.15.13</activemq.version>
    	    <antlr2.version>2.7.7</antlr2.version>
    	   
    	 	......
    	    <versions-maven-plugin.version>2.7</versions-maven-plugin.version>
    	    <webjars-hal-browser.version>3325375</webjars-hal-browser.version>
    	    <webjars-locator-core.version>0.45</webjars-locator-core.version>
    	    <wsdl4j.version>1.6.3</wsdl4j.version>
    	    <xml-maven-plugin.version>1.0.2</xml-maven-plugin.version>
    	    <xmlunit2.version>2.7.0</xmlunit2.version>
      	</properties>
    
  • starter场景启动器
    最基本的依赖
        <dependency>
    	      <groupId>org.springframework.boot</groupId>
    	      <artifactId>spring-boot-starter</artifactId>
    	      <version>2.3.4.RELEASE</version>
    	      <scope>compile</scope>
        </dependency>
    

7 基础入门-SpringBoot-自动配置特性

  • 自动配好Tomcat

    • 引入Tomcat依赖。
    • 配置Tomcat
      <dependency>
        	<groupId>org.springframework.boot</groupId>
        	<artifactId>spring-boot-starter-tomcat</artifactId>
       	<version>2.3.4.RELEASE</version>
        	<scope>compile</scope>
      </dependency>
      
  • 自动配好SpringMVC

    • 引入SpringMVC全套组件
    • 自动配好SpringMVC常用组件(功能)
  • 自动配好Web常见功能,如:字符编码问题

    • SpringBoot帮我们配置好了所有web开发的常见场景
      @SpringBootApplication
      public class Application {
          public static void main(String[] args) {
              ConfigurableApplicationContext run = SpringApplication.run(Application.class, args);
              String[] names = run.getBeanDefinitionNames();
              for (String name : names) {
                  System.out.println(name);
              }
          }
      }
      

    以上可查看web开发常见场景。

  • 默认的包结构

    • 主程序所在包及其下面的所有子包里面的组件都会被默认扫描进来
    • 无需以前的包扫描配置
    • 想要改变扫描路径,可指定扫描路径
      @SpringBootApplication
      等同于
      @SpringBootConfiguration
      @EnableAutoConfiguration
      @ComponentScan(basePackages = "com.qxd")
      
  • 各种配置拥有默认值

    • 默认配置最终都是映射到某个类上,如:MultipartProperties。
    • 配置文件的值最终会绑定每个类上,这个类会在容器中创建对象
  • 按需加载所有自动配置项

    • 非常多的starter
    • 引入了哪些场景这个场景的自动配置才会开启
    • SpringBoot所有的自动配置功能都在 spring-boot-autoconfigure 包里面。

8 底层注解-@Configuration详解

  • 基本使用

  • Full模式与Lite模式

  • 示例

    /**
     * 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组件,@Configuration(proxyBeanMethods = true) 组件依赖成立
            zhangsan.setPet(tomcatPet());
            return zhangsan;
        }
    
        @Bean("tom")
        public Pet tomcatPet(){
            return new Pet("tomcat");
        }
    }
    
  • @Configuration测试代码如下:

    //@SpringBootApplication
    @SpringBootConfiguration
    @EnableAutoConfiguration
    @ComponentScan(basePackages = "com.qxd")
    public class Application {
    
        public static void main(String[] args) {
    
            // 1. 返回IOC容器
            ConfigurableApplicationContext run = SpringApplication.run(Application.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.qxd.config.MyConfig@ae3691
            MyConfig bean = run.getBean(MyConfig.class);
            System.out.println(bean);
    
            //如果@Configuration(proxyBeanMethods = true)代理对象调用方法。SpringBoot总会检查这个组件是否在容器中有。
            //保持组件单实例
            User user = bean.user01();
            User user1 = bean.user01();
            System.out.println(user == user1);
    
            User user01 = run.getBean("user01", User.class);
            Pet tom = run.getBean("tom", Pet.class);
    
            System.out.println("用户的宠物:"+(user01.getPet() == tom));
    
        }
    
    }
    
  • 经验总结

    • 配置容器组件之间不存在依赖关系,使用Lite模式。保证容器的启动速度。
    • 配置容器组件之间存在依赖关系,使用Full模式。保证容器中依赖的组件就是该容器中注册的组件。

9 底层注解-@Import导入组件

  • @Bean、@Component、@Controller、@Service、@Repository与之前用法相同。
  • @Import写在容器中的组件上
  • @Import({User.class, DBHelper.class})给容器中自动创建出这两个类型的组件、默认组件的名字就是全类名。
    @Configuration(proxyBeanMethods = true) //告诉SpringBoot这是一个配置类 == 配置文件
    @Import({User.class, DBHelper.class})
    public class MyConfig {
    		......
        }
    }
    
  • 在主程序中进行测试
    @SpringBootConfiguration
    @EnableAutoConfiguration
    @ComponentScan(basePackages = "com.qxd")
    public class Application {
    
        public static void main(String[] args) {
    
            // 1. 返回IOC容器
            ConfigurableApplicationContext run = SpringApplication.run(Application.class, args);
            //5、获取组件
            String[] beanNamesForType = run.getBeanNamesForType(User.class);
    
            for (String s : beanNamesForType) {
                System.out.println(s);
            }
    
            DBHelper dbHelper = run.getBean(DBHelper.class);
            System.out.println(dbHelper);
        }
    
    }
    

10 底层注解-@Conditional条件装配

  • 条件装配:满足Conditional指定的条件,则进行组件注入
  • @ConditionalOnMissingBean: 当没有容器时,配置类生效
@Configuration(proxyBeanMethods = true) //告诉SpringBoot这是一个配置类 == 配置文件
@Import({User.class, DBHelper.class})
//@ConditionalOnBean(name = "tom") //当容器中存在tom组件时,容器才生效,否则不生效
@ConditionalOnMissingBean(name = "tom") //当容器中没有tom组件时,容器才生效
public class MyConfig {

    /**
     * Full:外部无论对配置类中的这个组件注册方法调用多少次获取的都是之前注册容器中的单实例对象
     * @return
     */
    @Bean //给容器中添加组件。以方法名作为组件的id。返回类型就是组件类型。返回的值,就是组件在容器中的实例

    public User user01(){
        User zhangsan = new User("zhangsan", 18);
        //user组件依赖了Pet组件,@Configuration(proxyBeanMethods = true) 组件依赖成立
        zhangsan.setPet(tomcatPet());
        return zhangsan;
    }

    @Bean("tom22")
    public Pet tomcatPet(){
        return new Pet("tomcat");
    }

}

......
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(basePackages = "com.qxd")
public class Application {

    public static void main(String[] args) {

        //1、返回我们IOC容器
        ConfigurableApplicationContext run = SpringApplication.run(Application.class, args);

        //2、查看容器里面的组件
        String[] names = run.getBeanDefinitionNames();
        for (String name : names) {
            System.out.println(name);
        }

        boolean tom = run.containsBean("tom");
        System.out.println("容器中Tom组件:"+tom);

        boolean user01 = run.containsBean("user01");
        System.out.println("容器中user01组件:"+user01);

        boolean tom22 = run.containsBean("tom22");
        System.out.println("容器中tom22组件:"+tom22);
    }

}

11 底层注解-@ImportResource导入Spring配置文件

  • 在springboot中导入bean的xml配置文件,使用@ImportResource注解。
    bean.xml文件:
    Springboot笔记(1-30)

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xmlns:context="http://www.springframework.org/schema/context"
          xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
    
       <bean id="haha" class="com.qxd.bean.User">
           <property name="name" value="zhangsan"></property>
           <property name="age" value="18"></property>
       </bean>
    
       <bean id="hehe" class="com.qxd.bean.Pet">
           <property name="name" value="tomcat"></property>
       </bean>
    </beans>
    
  • 配置类:

    @ImportResource("classpath:beans.xml")
    public class MyConfig2 {
    ...
    }
    ***********************************************************
    public static void main(String[] args) {
    
        //1、返回我们IOC容器
        ConfigurableApplicationContext run = SpringApplication.run(Application.class, args);
    
        boolean haha = run.containsBean("haha");
        boolean hehe = run.containsBean("hehe");
        System.out.println("haha:"+haha);//true
        System.out.println("hehe:"+hehe);//true
    }
    
  • 注: classpath 等价于 main/java + main/resources + 第三方jar包的根目录

12 底层注解-@ConfigurationProperties配置绑定

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

  • 传统

    	public class getProperties {
         public static void main(String[] args) throws FileNotFoundException, IOException {
             Properties pps = new Properties();
             pps.load(new FileInputStream("a.properties"));
             Enumeration enum1 = pps.propertyNames();//得到配置文件的名字
             while(enum1.hasMoreElements()) {
                 String strKey = (String) enum1.nextElement();
                 String strValue = pps.getProperty(strKey);
                 System.out.println(strKey + "=" + strValue);
                 //封装到JavaBean。
             }
         }
     }
    
  • Springboot中

    • @Component+@ConfigurationProperties
      只有在容器中的组件,才会拥有SpringBoot提供的强大功能。

      @ToString
      @Data
      @Component
      @ConfigurationProperties(prefix = "mycar")
      public class Car {
      
          private String brand;
          private Integer price;
      
      
      }
      

      在配置文件中

      mycar.brand = dazhong
      mycar.price = 1000
      
    • @EnableConfigurationProperties(在配置类上标注,开启属性配置功能) + @ConfigurationProperties

      1 开启Car配置绑定功能
      2 把这个Car这个组件自动注册到容器中

      @EnableConfigurationProperties(Car.class)
      public class MyConfig2 {
      ...
      }
      *************************************
      @ToString
      @Data
      @ConfigurationProperties(prefix = "mycar")
      public class Car {
          private String brand;
          private Integer price;
      }
      

13 自动配置【源码分析】-自动包规则原理

springboot启动类

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
      SpringApplication.run(Application.class, args);
    }
}

分析@SpringBootApplication这个注解

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
......
}

重点分析@SpringBootConfiguration@EnableAutoConfiguration@ComponentScan

  • @SpringBootConfiguration

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Configuration
    public @interface SpringBootConfiguration {
    	@AliasFor(annotation = Configuration.class)
    	boolean proxyBeanMethods() default true;
    }
    

    @Configuration说明该类是Springboot的配置类。

  • @EnableAutoConfiguration

    	@Target(ElementType.TYPE)
    	@Retention(RetentionPolicy.RUNTIME)
    	@Documented
    	@Inherited
    	@AutoConfigurationPackage
    	@Import(AutoConfigurationImportSelector.class)
    	public @interface EnableAutoConfiguration {
    	......
    	}
    

    重点分析@AutoConfigurationPackage@Import(AutoConfigurationImportSelector.class)

  • @AutoConfigurationPackage
    自动配置包

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Inherited
    @Import(AutoConfigurationPackages.Registrar.class)
    public @interface AutoConfigurationPackage {
    ...}
    

    1 利用Registrar给容器中导入一系列组件。
    2 将指定的一个包下的所有组件导入进MainApplication所在包下。

14 自动配置【源码分析】-初始加载自动配置类

  • @Import(AutoConfigurationImportSelector.class)
    1、利用getAutoConfigurationEntry(annotationMetadata);给容器中批量导入一些组件

    	public String[] selectImports(AnnotationMetadata annotationMetadata) {
    	if (!isEnabled(annotationMetadata)) {
    		return NO_IMPORTS;
    	}
    	AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
    	return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
    }
    

    2、调用List configurations = getCandidateConfigurations(annotationMetadata, attributes)获取到所有需要导入到容器中的配置类。
    Springboot笔记(1-30)
    3、利用工厂加载 Map<String, List> loadSpringFactories(@Nullable ClassLoader classLoader);得到所有的组件。

    private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
    ......
    }
    

    4、从META-INF/spring.factories位置来加载一个文件。
    1 默认扫描我们当前系统里面所有META-INF/spring.factories位置的文件
    2 spring-boot-autoconfigure-2.3.4.RELEASE.jar包里面也有META-INF/spring.factories

Springboot笔记(1-30)
3 按需加载
AopAutoConfiguration为例

Springboot笔记(1-30)
@ConditionalOnClass(Advice.class)爆红,说明该类不存在,按照Springboot按需配置原理,该组件不会被装配。

15 自动配置【源码分析】-自动配置流程

DispatcherServletAutoConfiguration的内部类DispatcherServletConfiguration为例子:

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

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

16 最佳实践-SpringBoot应用如何编写

17 最佳实践-Lombok简化开发

18 最佳实践-dev-tools

页面修改后Ctrl+F9。

19 最佳实践-Spring Initailizr

20 配置文件-yaml的用法

  • 数据类型
    • 字面量:单个的、不可再分的值。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
      ```yml
      行内写法: k: [v1,v2,v3]
      #或者
      k:
      - v1
      - v2
      - v3

  • 基本语法示例:
    • 对象:

      @Data
      public class Person {
          private String userName;
          private Boolean boss;
          private Date birth;
          private Integer age;
          private Pet pet;
          private String[] interests;
          private List<String> animal;
          private Map<String, Object> score;
          private Set<Double> salarys;
          private Map<String, List<Pet>> allPets;
      }
      
      @Data
      public class Pet {
          private String name;
          private Double weight;
      }
      
    • yml:

      person:
      #  单引号会将 \n作为字符串输出   双引号会将\n 作为换行输出
      #  双引号不会转义,单引号会转义
        boss: true
        birth: 2019/12/9
        age: 18
      #  interests: [篮球,足球]
        interests:
          - 篮球
          - 足球
          - 18
        animal: [阿猫,阿狗]
      #  score:
      #    english: 80
      #    math: 90
        score: {english:80,math:90}
        salarys:
          - 9999.98
          - 9999.99
        pet:
          name: 阿狗
          weight: 99.99
        allPets:
          sick:
            - {name: 阿狗,weight: 99.99}
            - name: 阿猫
              weight: 88.88
            - name: 阿虫
              weight: 77.77
          health:
            - {name: 阿花,weight: 199.99}
            - {name: 阿明,weight: 199.99}
        user-name: zhangsan
      

21 配置文件-自定义类绑定的配置提示

如需提示需要添加以下依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-configuration-processor</artifactId>
    <optional>true</optional>
</dependency>

<!-- 下面插件作用是工程打包时,不将spring-boot-configuration-processor打进包内,让其只在编码的时候有用 -->
<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <configuration>
                <excludes>
                    <exclude>
                        <groupId>org.springframework.boot</groupId>
                        <artifactId>spring-boot-configuration-processor</artifactId>
                    </exclude>
                </excludes>
            </configuration>
        </plugin>
    </plugins>
</build>

22 web场景-web开发简介

23 web场景-静态资源规则与定制化

  • 静态资源目录
    只要静态资源放在类路径下: called /static (or /public or /resources or /META-INF/resources
    访问 : 当前项目根路径/ + 静态资源名

    原理: 静态映射/**。

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

    spring:
      resources:
        static-locations: [classpath:/haha/]
    

    静态资源访问前缀

    spring:
      mvc:
        static-path-pattern: /res/**
    

    当前项目 + static-path-pattern + 静态资源名 = 静态资源文件夹下找

    webjar:
    添加jquery

    <dependency>
        <groupId>org.webjars</groupId>
        <artifactId>jquery</artifactId>
        <version>3.5.1</version>
    </dependency>
    

    访问路径为: http://localhost:8080/webjars/jquery/3.5.1/jquery.js
    Springboot笔记(1-30)

24 web场景-welcome与favicon功能

官方欢迎页文档

欢迎页支持

  • 静态资源路径下 index.html
    • 可以配置静态资源路径
    • 但是不可以配置静态资源的访问前缀。否则导致 index.html不能被默认访问
      spring:
      #  mvc:
      #    static-path-pattern: /res/** #会导致欢迎页失效
        resources:
          static-locations: [classpath:/haha/]
      
  • controller能处理/index。

自定义 Favicon

放在静态资源目录即可

25 web场景-【源码分析】-静态资源原理

  • SpringBoot启动默认加载 xxxAutoConfiguration 类(自动配置类)

  • SpringMVC功能的自动配置类 WebMvcAutoConfiguration,生效

    @Configuration(proxyBeanMethods = false)
    @ConditionalOnWebApplication(type = Type.SERVLET)
    @ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
    @ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
    @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
    @AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,
            ValidationAutoConfiguration.class })
    public class WebMvcAutoConfiguration {
    ......
    }
    
  • 给容器中配了什么。
    配置文件属性绑定
    WebMvcProperties —spring.mvc
    ResourceProperties — spring.resources

    @Configuration(proxyBeanMethods = false)
    @Import(EnableWebMvcConfiguration.class)
    @EnableConfigurationProperties({ WebMvcProperties.class, ResourceProperties.class })
    @Order(0)
    public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer {
    ......
    }
    

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

//有参构造器所有参数的值都会从容器中确定
//ResourceProperties resourceProperties;获取和spring.resources绑定的所有的值的对象
//WebMvcProperties mvcProperties 获取和spring.mvc绑定的所有的值的对象
//ListableBeanFactory beanFactory Spring的beanFactory
//HttpMessageConverters 找到所有的HttpMessageConverters
//ResourceHandlerRegistrationCustomizer 找到 资源处理器的自定义器。=========
//DispatcherServletPath  
//ServletRegistrationBean   给应用注册Servlet、Filter....
public WebMvcAutoConfigurationAdapter(ResourceProperties resourceProperties, WebMvcProperties mvcProperties,
				ListableBeanFactory beanFactory, ObjectProvider<HttpMessageConverters> messageConvertersProvider,
				ObjectProvider<ResourceHandlerRegistrationCustomizer> resourceHandlerRegistrationCustomizerProvider,
				ObjectProvider<DispatcherServletPath> dispatcherServletPath,
				ObjectProvider<ServletRegistrationBean<?>> servletRegistrations) {
			this.resourceProperties = resourceProperties;
			this.mvcProperties = mvcProperties;
			this.beanFactory = beanFactory;
			this.messageConvertersProvider = messageConvertersProvider;
			this.resourceHandlerRegistrationCustomizer = resourceHandlerRegistrationCustomizerProvider.getIfAvailable();
			this.dispatcherServletPath = dispatcherServletPath;
			this.servletRegistrations = servletRegistrations;
	}

资源处理的默认规则

	@Override
	public void addResourceHandlers(ResourceHandlerRegistry registry) {
		if (!this.resourceProperties.isAddMappings()) {
			logger.debug("Default resource handling disabled");
			return;
		}
		Duration cachePeriod = this.resourceProperties.getCache().getPeriod();
		CacheControl cacheControl = this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl();
		if (!registry.hasMappingForPattern("/webjars/**")) {
			customizeResourceHandlerRegistration(registry.addResourceHandler("/webjars/**")
					.addResourceLocations("classpath:/META-INF/resources/webjars/")
					.setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl));
		}
		String staticPathPattern = this.mvcProperties.getStaticPathPattern();
		if (!registry.hasMappingForPattern(staticPathPattern)) {
			customizeResourceHandlerRegistration(registry.addResourceHandler(staticPathPattern)
					.addResourceLocations(getResourceLocations(this.resourceProperties.getStaticLocations()))
					.setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl));
		}
	}

根据上述代码,我们可以同过配置禁止所有静态资源规则。
Springboot笔记(1-30)

spring:
  resources:
    add-mappings: false #禁用所有静态资源规则

静态资源规则:

@ConfigurationProperties(prefix = "spring.resources", ignoreUnknownFields = false)
public class ResourceProperties {
	private static final String[] CLASSPATH_RESOURCE_LOCATIONS = { "classpath:/META-INF/resources/",
				"classpath:/resources/", "classpath:/static/", "classpath:/public/" };
				......
	private String[] staticLocations = CLASSPATH_RESOURCE_LOCATIONS;
}

欢迎页的处理规则

@Bean
public WelcomePageHandlerMapping welcomePageHandlerMapping(ApplicationContext applicationContext,
		FormattingConversionService mvcConversionService, ResourceUrlProvider mvcResourceUrlProvider) {
	WelcomePageHandlerMapping welcomePageHandlerMapping = new WelcomePageHandlerMapping(
			new TemplateAvailabilityProviders(applicationContext), applicationContext, getWelcomePage(),
			this.mvcProperties.getStaticPathPattern());
	welcomePageHandlerMapping.setInterceptors(getInterceptors(mvcConversionService, mvcResourceUrlProvider));
	welcomePageHandlerMapping.setCorsConfigurations(getCorsConfigurations());
	return welcomePageHandlerMapping;
}

WelcomePageHandlerMapping的构造方法如下:

WelcomePageHandlerMapping(TemplateAvailabilityProviders templateAvailabilityProviders,
		ApplicationContext applicationContext, Optional<Resource> welcomePage, String staticPathPattern) {
		//要用欢迎页功能,必须是/**
	if (welcomePage.isPresent() && "/**".equals(staticPathPattern)) {
		logger.info("Adding welcome page: " + welcomePage.get());
		setRootViewName("forward:index.html");
	}
	else if (welcomeTemplateExists(templateAvailabilityProviders, applicationContext)) {
	 	// 调用Controller  /index
		logger.info("Adding welcome page template: index");
		setRootViewName("index");
	}
}

favicon

失效原因与浏览器有关

26 请求处理-【源码分析】-Rest映射及源码解析

请求映射

  • @xxxMapping;

    • 以前:
      /getUser 获取用户
      /deleteUser 删除用户
      /editUser 修改用户
      /saveUser 保存用户
    • 现在:
      /user
      GET-获取用户
      DELETE-删除用户
      PUT-修改用户
      POST-保存用户
  • 核心Filter;HiddenHttpMethodFilter

  • 用法: 表单method=post,隐藏域 _method=put

  • SpringBoot中手动开启

  • 扩展:如何把_method 这个名字换成我们自己喜欢的。

spring:
  mvc:
    hiddenmethod:
      filter:
        enabled: true  #开启表单的rest功能

Controller层

  @RequestMapping(value = "/user",method = RequestMethod.GET)
    @GetMapping("/user")
    public String getUser(){

        return "GET-张三";
    }

	//    @RequestMapping(value = "/user",method = RequestMethod.POST)
    @PostMapping("/user")
    public String saveUser(){
        return "POST-张三";
    }


	//    @RequestMapping(value = "/user",method = RequestMethod.PUT)
    @PutMapping("/user")
    public String putUser(){

        return "PUT-张三";
    }

    @DeleteMapping("/user")
	//    @RequestMapping(value = "/user",method = RequestMethod.DELETE)
    public String deleteUser(){
        return "DELETE-张三";
    }
**********************************************************************
	@Bean
	@ConditionalOnMissingBean(HiddenHttpMethodFilter.class)
	@ConditionalOnProperty(prefix = "spring.mvc.hiddenmethod.filter", name = "enabled", matchIfMissing = false)
	public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
		return new OrderedHiddenHttpMethodFilter();
	}

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

  • 表单提交会带上_method=PUT
  • 请求过来被HiddenHttpMethodFilter拦截
    • 请求是否正常,并且是POST
      • 获取到_method的值

      • 兼容以下请求:PUT, DELETE, PATCH

      • 原生request(post),包装模式requesWrapper重写了getMethod方法,返回的是传入的值

      • 过滤器链放行的时候用wrapper。以后的方法调用getMethod是调用requesWrapper的。

        @Override
        protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
        		throws ServletException, IOException {
        
        	HttpServletRequest requestToUse = request;
        
        	//请求为post请求并请求正常
        	if ("POST".equals(request.getMethod()) && request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) == null) {
        		String paramValue = request.getParameter(this.methodParam);
        		if (StringUtils.hasLength(paramValue)) {
        			//传入参数大小写无所谓
        			String method = paramValue.toUpperCase(Locale.ENGLISH);
        			//PUT,DELETE,PATCH
        			if (ALLOWED_METHODS.contains(method)) {
        				// 包装模式
        				requestToUse = new HttpMethodRequestWrapper(request, method);
        			}
        		}
        	}
        
        	filterChain.doFilter(requestToUse, response);
        }
        ******************************************************************************************************
        	private static class HttpMethodRequestWrapper extends HttpServletRequestWrapper {
        
        	private final String method;
        
        	public HttpMethodRequestWrapper(HttpServletRequest request, String method) {
        		super(request);
        		this.method = method;
        	}
        
        	@Override
        	public String getMethod() {
        		return this.method;
        	}
        }
        
  • Rest使用客户端工具,
    • 如PostMan直接发送Put、delete等方式请求,无需Filter。

27 请求处理-【源码分析】-怎么改变默认的_method

重写上述代码POST请求处理规则

	@Bean
	@ConditionalOnMissingBean(HiddenHttpMethodFilter.class)
	@ConditionalOnProperty(prefix = "spring.mvc.hiddenmethod.filter", name = "enabled", matchIfMissing = false)
	public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
		return new OrderedHiddenHttpMethodFilter();
	}

@ConditionalOnMissingBean(HiddenHttpMethodFilter.class)表示容器中没有HiddenHttpMethodFilter类型组件时OrderedHiddenHttpMethodFilter组件才生效,可以仿照springboot中HiddenHttpMethodFilter的写法在配置类中加入该组件。

@Configuration
public class MyConfig {

    @Bean
    public HiddenHttpMethodFilter hiddenHttpMethodFilter(){
        HiddenHttpMethodFilter filter = new HiddenHttpMethodFilter();
        filter.setMethodParam("_m");
        return filter;
    }
}

在配置类中加入自定义组件,springboot默认规则失效。

28 请求处理-【源码分析】-请求映射原理

Springboot笔记(1-30)

org.springframework.web.servlet.DispatcherServlet开始分析—> doDispatch()

  • doService

    @Override
    	protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
    		......
    
    		try {
    			doDispatch(request, response);
    		}
    		finally {
    			if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
    				// Restore the original attribute snapshot, in case of an include.
    				if (attributesSnapshot != null) {
    					restoreAttributesAfterInclude(request, attributesSnapshot);
    				}
    			}
    		}
    	}
    
  • doDispatch()分发请求与响应

    	protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    	HttpServletRequest processedRequest = request;
    	HandlerExecutionChain mappedHandler = null;
    	boolean multipartRequestParsed = false;
    
    	WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
    
    	try {
    		ModelAndView mv = null;
    		Exception dispatchException = null;
    
    		try {
    			processedRequest = checkMultipart(request);
    			multipartRequestParsed = (processedRequest != request);
    
    			// Determine handler for the current request.
    			// 找到当前请求使用哪个Handler(Controller的方法)处理
    			mappedHandler = getHandler(processedRequest);
    			if (mappedHandler == null) {
    				noHandlerFound(processedRequest, response);
    				return;
    			}
    			//HandlerMapping:处理器映射。/xxx->>xxxx
    			// Determine handler adapter for the current request.
    			HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
    
    			// Process last-modified header, if supported by the handler.
    			String method = request.getMethod();
    			boolean isGet = "GET".equals(method);
    			if (isGet || "HEAD".equals(method)) {
    				long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
    				if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
    					return;
    				}
    			}
    
    			if (!mappedHandler.applyPreHandle(processedRequest, response)) {
    				return;
    			}
    
    			// Actually invoke the handler.
    			mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
    
    			if (asyncManager.isConcurrentHandlingStarted()) {
    				return;
    			}
    
    			applyDefaultViewName(processedRequest, mv);
    			mappedHandler.applyPostHandle(processedRequest, response, mv);
    		}
    		catch (Exception ex) {
    			dispatchException = ex;
    		}
    		catch (Throwable err) {
    			// As of 4.3, we're processing Errors thrown from handler methods as well,
    			// making them available for @ExceptionHandler methods and other scenarios.
    			dispatchException = new NestedServletException("Handler dispatch failed", err);
    		}
    		processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
    	}
    	catch (Exception ex) {
    		triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
    	}
    	catch (Throwable err) {
    		triggerAfterCompletion(processedRequest, response, mappedHandler,
    				new NestedServletException("Handler processing failed", err));
    	}
    	finally {
    		if (asyncManager.isConcurrentHandlingStarted()) {
    			// Instead of postHandle and afterCompletion
    			if (mappedHandler != null) {
    				mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
    			}
    		}
    		else {
    			// Clean up any resources used by a multipart request.
    			if (multipartRequestParsed) {
    				cleanupMultipart(processedRequest);
    			}
    		}
    	}
    }
    
  • 分析getHandler()

    @Nullable
    	protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
    		if (this.handlerMappings != null) {
    			for (HandlerMapping mapping : this.handlerMappings) {
    				HandlerExecutionChain handler = mapping.getHandler(request);
    				if (handler != null) {
    					return handler;
    				}
    			}
    		}
    		return null;
    	}
    

    Springboot笔记(1-30)

    其中,保存了所有@RequestMapping 和handler的映射规则。

Springboot笔记(1-30)
所有的请求映射都在HandlerMapping中。

  • SpringBoot自动配置欢迎页的 WelcomePageHandlerMapping 。访问 /能访问到index.html;

  • SpringBoot自动配置了默认 的 RequestMappingHandlerMapping

  • `请求进来,挨个尝试所有的HandlerMapping看是否有请求信息

    • 如果有就找到这个请求对应的handler
    • 如果没有就是下一个 HandlerMapping
  • 我们需要一些自定义的映射处理,我们也可以自己给容器中放HandlerMapping。自定义 HandlerMapping

  • IDEA快捷键:

    • Ctrl + Alt + U : 以UML的类图展现类有哪些继承类,派生类以及实现哪些接口。
    • Crtl + Alt + Shift + U : 同上,区别在于上条快捷键结果在新页展现,而本条快捷键结果在弹窗展现。
    • Ctrl + H : 以树形方式展现类层次结构图。

29 请求处理-常用参数注解使用

  • 常用注解:

    • @PathVariable
    • @RequestHeader
    • @ModelAttribute
    • @RequestParam
    • @MatrixVariable
    • @CookieValue
    • @RequestBody
  • 举例

    @RestController
    public class ParameterTestController {
    
        /**
         * 数据绑定:页面提交的请求数据(GET、POST)都可以和对象属性进行绑定
         * @param person
         * @return
         */
        @PostMapping("/saveuser")
        public Person saveuser(Person person){
    
            return person;
        }
    
    
        //  car/2/owner/zhangsan
        @GetMapping("/car/{id}/owner/{username}")
        public Map<String,Object> getCar(@PathVariable("id") Integer id,
                                         @PathVariable("username") String name,
                                         @PathVariable Map<String,String> pv,
                                         @RequestHeader("User-Agent") String userAgent,
                                         @RequestHeader Map<String,String> header,
                                         @RequestParam("age") Integer age,
                                         @RequestParam("inters") List<String> inters,
                                         @RequestParam Map<String,String> params,
                                         @CookieValue("_ga") String _ga,
                                         @CookieValue("_ga") Cookie cookie){
            Map<String,Object> map = new HashMap<>();
    
            map.put("id",id);
            map.put("name",name);
            map.put("pv",pv);
            map.put("userAgent",userAgent);
            map.put("headers",header);
            map.put("age",age);
            map.put("inters",inters);
            map.put("params",params);
            map.put("_ga",_ga);
            System.out.println(cookie.getName()+"===>"+cookie.getValue());
            return map;
        }
        
        @PostMapping("/save")
        public Map postMethod(@RequestBody String content){
            Map<String,Object> map = new HashMap<>();
            map.put("content",content);
            return map;
        }
       }
    

30 请求处理-@RequestAttribute

  • 例子
    @Controller
    public class RequestController {
        @GetMapping("/goto")
        public String goToPage(HttpServletRequest request){
    
            request.setAttribute("msg","成功了...");
            request.setAttribute("code",200);
            return "forward:/success";  //转发到  /success请求
        }
    
    
        //java对象转为json格式的数据
        @ResponseBody
        @GetMapping("/success")
        public Map success(
                            //请求域属性
                            @RequestAttribute(value = "msg",required = false) String msg,
                            @RequestAttribute(value = "code",required = false)Integer code,
                            HttpServletRequest request){
            Object msg1 = request.getAttribute("msg");
    
            Map<String,Object> map = new HashMap<>();
            Object hello = request.getAttribute("hello");
            Object world = request.getAttribute("world");
            Object message = request.getAttribute("message");
    
            // 采用getAttribute方式
            map.put("reqMethod_msg",msg1);
            // 采用注解方式
            map.put("annotation_msg",msg);
            map.put("hello",hello);
            map.put("world",world);
            map.put("message",message);
            return map;
        }
    }
    

本文地址:https://blog.csdn.net/weixin_41709536/article/details/113858224

相关标签: springboot java