Springboot学习笔记(一)

SpringBoot笔记

一、Spring Boot HelloWorld

  1. 一个功能:浏览器发送hello请求,服务器接受请求并处理,响应Hello World字符串

  2. <properties>
       <java.version>1.8</java.version>
    </properties>
    
    <dependencies>
       <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-web</artifactId>
       </dependency>
    
       <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-test</artifactId>
          <scope>test</scope>
        
        //这个依赖是让测试类@SpringBootTest能够自己找到springboot主启动类,不用再加@Runwith注解了
        
        <exclusions>
             <exclusion>
                <groupId>org.junit.vintage</groupId>
                <artifactId>junit-vintage-engine</artifactId>
             </exclusion>
          </exclusions>
       </dependency>
    
    </dependencies>
    
    <build>
       <plugins>
          //将应用打包成jar包的插件
          <plugin>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-maven-plugin</artifactId>
          </plugin>
       </plugins>
    </build>
        
    //@SpringBootApplication: 标注这个类是一个springboot的应用: 启动类下的所有资源被导入
    //Spring Boot应用标注在某个类上说明这个类是SpringBoot的主配置类,SpringBoot 就应该运行这个类的main方法来启动SpringBoot应用
    @SpringBootApplication
    public class HelloworldApplication {
    
    	public static void main(String[] args) {
    		//将springboot应用启动
    		//SpringApplication类co
    		//run方法
    		SpringApplication.run(HelloworldApplication.class, args);
    	}
    
    }
    
    @RestController
    //注:这里的@RestController 就是指处理器方法不跳转页面而是直接返回结果(数据),
    //                             它的作用相当于@Controller + 处理器方法上加ResponseBody
    public class HelloController {
    
        @RequestMapping("/hello")
        public String Hello(){
            return "Hello!!!朱哥";
        }
    
    }
    

原理初探

  1. POM文件

    1. 父项目

      <parent>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-parent</artifactId>
         <version>2.3.3.RELEASE</version>
         <relativePath/> <!-- lookup parent from repository -->
      </parent>
          
          //它的父项目是
        
      <parent>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-dependencies</artifactId>
          <version>2.3.3.RELEASE</version>
      </parent>
          //他来真正管理Spring Boot应用里面所有的依赖版本
          
          //所以我们以后导入依赖默认是不需要写版本的(除非没在dependencies里面管理)
          
      <dependency>
      	<groupId>org.springframework.boot</groupId>
      	<artifactId>spring-boot-starter-web</artifactId>
      </dependency>
          
        //spring-boot-starter: springboot 场景启动器;帮我们导入了web模块正常运行所依赖的组件;
        //spring-boot 将所有的功能场景都抽取出来, 做成一个个的Staters(启动器), 只需要在项目里引入这些Starter相关的场景的所有依赖都会导入进来。
          
          
      
    2. 主程序类, 主入口类

      1. @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 {
        
      2. @SpringBootConfiguration:Spring Boot的配置类;
        标注在某个类上,表示这是一个Spring Boot的配置类;


        @Configuration:配置类上来标注这个注解;
        配置类 ----- 配置文件;配置类也是容器中的一个组件;@Component

  1. @EnableAutoConfiguration:开启自动配置功能;
    以前我们需要配置的东西,Spring Boot帮我们自动配置;

    @EnableAutoConfiguration告诉SpringBoot开启自 动配置功能;这样自动配置才能生效;
     
    ```java
    @AutoConfigurationPackage
    @Import(AutoConfigurationImportSelector.class)
    public @interface EnableAutoConfiguration {
          ```
    
    1. @AutoConfigurationPackage:自动配置包
      @Import(AutoConfigurationPackages.Registrar.class):
      Spring的底层注解@Import,给容器中导入一个组件,也就是 AutoConfigurationPackages.Registrar.class;

      @Import(AutoConfigurationPackages.Registrar.class)
      public @interface AutoConfigurationPackage {
      }
      

      将主配置类(@SpringBootApplication标注的类)的所在包及下面所有子包里面的所有组件扫描到Spring容器

      (注: 是将主程序类所在包里所有组件扫描进去,所以说那些controller包和service包一定要跟主类并列,而不是包含,否则可能出错,扫描不到容器里)

    2. @Import(AutoConfigurationImportSelector.class);给容器中导入组件

    AutoConfigurationImportSelector:导入哪些组件的选择器;
          将所有需要导入的组件以全类名的方式返回;这些组件就会被添加到容器中;
     
       会给容器中导入非常多的自动配置类(xxxAutoConfiguration);
     
          就是给容器中导入这个场景需要的所有组件, 并配置好这些组件;
    

    1. 有了自动配置类,免去了我们手动编写配置注入功能组件等的工作;
     SpringFactoriesLoader.loadFactoryNames(EnableAutoConfiguration.class,classLoader)
   ​		Spring Boot在启动的时候从类路径下的META-INF/spring.factories中获取EnableAutoConfiguration指定的值,

   ​		从源码不难看出加载类扫描的路径
   
   ~~~java
     ```java
     public final class SpringFactoriesLoader {
     
        /**
         * The location to look for factories.
         * <p>Can be present in multiple JAR files.
         */
        public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
   ~~~
   ```java
   将这些值作为自动配置类导入到容器中,自动配置类就生效,帮我们进行自动配置工作;
      
         J2EE的整体整合解决方案和自动配置都在spring-boot-autoconfigure-2.3.3.RELEASE.jar

二、配置文件(yml和properties)

1、基本介绍

  1. SpringBoot使用一个全局的配置文件,配置文件名是固定的;

    •application.properties

    •application.yml

  2. 配置文件的作用:修改SpringBoot自动配置的默认值;SpringBoot在底层都给我们自动配置好;

  3. 以前的配置文件;大多都使用的是 xxxx.xml文件;

​ YAML:以数据为中心,比json、xml等更适合做配置文件; 是一个标记语言

2、yml语法

  1. 基本语法

    1. k:(空格)v:表示一对键值对(空格必须有);
      以空格的缩进来控制层级关系;只要是左对齐的一列数据,都是同一个层级

      server:
        port: 8081
      
    2. 属性和值也是大小写敏感;

  2. 值的写法

    1. 字面量,普通的值(数字,字符串,布尔)

      k: v:字面直接来写;
      字符串默认不用加上单引号或者双引号;
      “”:双引号;不会转义字符串里面的特殊字符;特殊字符会作为本身想表示的意思

      name: “zhangsan \n lisi”:输出;zhangsan 换行 lisi
      ‘’:单引号;会转义特殊字符,特殊字符终只是一个普通的字符串数据

      name: ‘zhangsan \n lisi’:输出;zhangsan \n lisi

    2. 对象、Map(属性和值)(键值对)

      k: v:在下一行来写对象的属性和值的关系;注意缩进
      对象还是k: v的方式

      student:
        name: zhuge
        age: 20
      
    3. 数组(List、Set)

      用- 值表示数组中的一个元素

      pets:
       -cat
       -dog
       -pig
      

      行内写法

      petss: [cat,dog,pig]
      

3、配置文件的值注入

  1. 这个是application.yml文件里的内容
server:
  port: 8081

person:
  name: zhuge
  age: 20
  happy: false
  birth: 2020/8/24
  maps: {k1: v1,k2: v2}
  lists:
    - code
    - music
    - girl
  dog:
    name: ${person.hello:hello}_旺财
    age: 3

dog:
#  松散绑定
  first-name: 阿黄
  age: 3
  1. 这个是我们自己写的用于与配置文件进行绑定的javabean类(后面还有get、set和toString方法)
/**
 *  将配置文件中配置的每一个属性的值,映射到这个组件中
 *    @ConfigurationProperties:告诉SpringBoot将本类中的所有属性和配置文件中相关的配置进行绑定;
 *     prefix = "person":配置文件中哪个下面的所有属性进行一一映射
 *     
 *     
 *   只有这个组件是容器中的组件,才能容器提供的@ConfigurationProperties功能; 
 */
@Component
@ConfigurationProperties(prefix = "person")
public class Person {

//    @Email(message="邮箱格式错误")
    private String name;
    private Integer age;
    private Boolean happy;
    private Date birth;
    private Map<String,Object> maps;
    private List<Object> lists;
    private Dog dog;
  1. 我们可以导入配置文件处理器,以后编写配置就有提示
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-configuration-processor</artifactId>
    <optional>true</optional>
</dependency>
  1. @Value获取值和@ConfigurationProperties获取值比较

    1. 配置文件yml还是properties他们都能获取到值;
    2. 如果说,我们只是在某个业务逻辑中需要获取一下配置文件中的某项值,使用@Value;
    3. 如果说,我们专门编写了一个javaBean来和配置文件进行映射,我们就直接使用@ConfigurationProperties;
    4. 在这里插入图片描述
  2. 配置文件注入值数据校验

    1. @Component
      @ConfigurationProperties(prefix = "person")
      @Validated//数据校验
      public class Person {
      
          /**
           * <bean class="Person">
           *     <property name="lastName" value="字面量/${key}从环境变量、配置文件中获取值/# {SpEL}"></property>
           *   <bean/>
           */
      
          //Name必须是邮箱格式
          @Email(message="邮箱格式错误")
          //@Value("${person.name}")
          private String name;
          //@Value("#{11*2}")
          private Integer age;
          //@Value("true")
          private Boolean happy;
          private Date birth;
          private Map<String,Object> maps;
          private List<Object> lists;
          private Dog dog;
      
    2. 注: 2.3.0之后的版本需要导入相应的依赖才能进行数据校验

      <dependency>
          <groupId>org.hibernate.validator</groupId>
          <artifactId>hibernate-validator</artifactId>
          <version>6.0.17.Final</version>
          <scope>compile</scope>
      </dependency>
      
  3. @PropertySource

    1. @PropertySource(value = {"classpath:application.yml"})
      @Component
      @ConfigurationProperties(prefix = "dog")
      public class Dog {
      //    @Value("旺财")
          private String firstName;
      //    @Value("3")
          private Integer age;
      

      @PropertySource:加载指定的配置文件;

4、导入Spring配置文件

  1. 第一种方式: 配置类@Configuration------>Spring配置文件

    1. @ImportResource:导入Spring的配置文件,让配置文件里面的内容生效;

    2. Spring Boot里面没有Spring的配置文件,我们自己编写的配置文件,也不能自动识别;

    3. 想让Spring的配置文件生效,加载进来;@ImportResource标注在一个配置类

    4. @ImportResource(locations = {"classpath:beans.xml"})
      //导入Spring的配置文件让其生效
      
    5. Spring的配置文件

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    
        <bean id="helloService" class="com.zhuge.service.HelloService"></bean>
    </beans>
    
    package com.zhuge.service;
    
    /**
     * @author ZCH
     * @date 2020/9/24 0024 - 下午 4:03
     */
    public class HelloService {
    }
    
  2. 第二种方式: 使用@Bean给容器中添加组件

    1. @Configuration  //指明当前类是一个配置类;就是来替代之前的Spring配置文件
      public class MyMvcConfig implements WebMvcConfigurer {
          
          //将方法的返回值添加到容器中;容器中这个组件默认的id就是方法名 
          @Bean
          public LocaleResolver localeResolver(){
              return new MyLocaleResolver();
          }
      }
      

5、配置文件占位符

  1. 随机数

    ${random.value}、${random.int}、${random.long} ${random.int(10)}、${random.int[1024,65536]} 
    
  2. 占位符获取之前配置的值, 如果没有可以使用: 指定默认值 ${person.hello:hello} 默认是hello

    person:
      name: hello
      age: 20
      happy: false
      birth: 2020/8/24
      maps: {k1: v1,k2: v2}
      lists:
        - code
        - music
        - girl
      dog:
        name: ${person.hello:hello}_旺财
        age: 3
    

6、Profile

  1. 多Profile文件

    我们在主配置文件编写的时候,文件名可以是 application-{profile}.properties/yml
    默认使用application.properties的配置;

  2. server:
      port: 8081
    spring:
      profiles:
        active: dev     #激活指定profile
    
    ---
    server:
      port: 8082
    spring:
      profiles: dev
    ---
    server:
      port: 8083
    spring:
      profiles: test       #指定属于哪个环境
    
  3. 激活指定profile

    1. 在配置文件中指定 spring.profiles.active=dev

    2. 命令行:
      java -jar spring-boot-02-config-0.0.1-SNAPSHOT.jar --spring.profiles.active=dev;

      可以直接在测试的时候,配置传入命令行参数

    3. 虚拟机参数;

      -Dspring.profiles.active=dev

7、配置文件加载位置

  1. springboot 启动会扫描以下位置的application.properties或者application.yml文件作为Spring boot的默认配置文件

    (注: 这里classpath 指的是resources文件夹)

    1. –file:./config/ 代表读取jar包所在目录下的config文件
    2. –file:./ 代表读取jar包所在目录
    3. –classpath:/config/ 代表类路径下的config包下的文件
    4. –classpath:/ 代表类路径下的文件

    SpringBoot会从这四个位置全部加载主配置文件;互补配置;

  2. 我们还可以通过spring.config.location来改变默认的配置文件位置

  3. 项目打包好以后,我们可以使用命令行参数的形式,启动项目的时候来指定配置文件的新位置;指定配置文件和默 认加载的这些配置文件共同起作用形成互补配置;

  4. java -jar spring-boot-02-config-02-0.0.1-SNAPSHOT.jar --spring.config.location=G:/application.properties

8、自动配置原理(重要)

1、@SpringBootConfiguration

见原理初探

2、@EnableAutoConfiguration

  1. EnableAutoConfiguration源码

在这里插入图片描述

  1. SpringBoot启动的时候加载主配置类,开启了自动配置功能 @EnableAutoConfiguration

  2. 其内部有两个非常重要的注解:

    1. @AutoConfigurationPackage
    2. @Import(AutoConfigurationImportSelector.class)

3、@AutoConfigurationPackage

(自动配置包)

  1. AutoConfigurationPackage源码

    @Import(AutoConfigurationPackages.Registrar.class)
    public @interface AutoConfigurationPackage {
    

    Spring的底层注解@Import,给容器中导入一个组件,也就是 AutoConfigurationPackages.Registrar.class;

  2. Register类的内部

    static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
    
       @Override
       public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
          register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0]));
       }
    
    1. Register类是 AutoConfigurationPackage 下的一个静态内部类。 这个类的作用是给容器中导入组件

    2. 主要看 new PackageImports(metadata).getPackageNames(), 它是导入一个metadata(注释源信息)到PackageImports中,然后获取这个包的包名。 所以说这个实例对象获取的包名是什么呢?

    3. 在这里插入图片描述

      注: 要在debug模式下,选中new PackageImports(metadata)所在行作为断点

      当调用到register()时, 我们就会发现packageNames就是我们的主配置类所在包的包名

    4. 总结: @AutoConfigurationPackage 的作用就是将主配置类(@SpringBootApplication标注的类)的所在包及下面所有子包里面的所有组件扫描到Spring容器

4、@Import(AutoConfigurationImportSelector.class)

  1. 这个注解的作用是给容器中导入组件,该组件就是AutoConfigurationImportSelector:自动配置导入选择器,它可以帮我们选择要导入的组件

  2. 进入AutoConfigurationImportSelector的源码, 它与SpringBoot1.x 版本有一些区别, 2.x中有一个静态内部类

    在这里插入图片描述

    其大致的意思是 自动配置组 ,能够帮我们完成一系列的自动配置的操作。

  3. 重点回到该内部类下的**process()**方法

    在这里插入图片描述

    调用了 **getAutoConfigurationEntry()**方法, 该方法的作用就是告诉Spring容器需要导入什么组件, 并以 String[] 的形式返回全类名

  4. 从 标蓝的一行 可以看出, SpringBoot已经帮我们自动导入了 127 个组件, 从下图可以看出都是以全类名的形式返回。而且这些组件都是以 …AutoConfiguration 的形式命名, 也就是什么自动配置类

    在这里插入图片描述

    有了这些自动配置类, 我们就免去了手动配置注入功能组件的操作了。

  5. 那么为何能做到自动配置呢??(之前的AutoConfigurationImportSelector只是帮助我们选择要导入的组件,AutoConfigurationPackage是将组件扫描到Spring容器 )其实是 configurations 这个变量是由getCandidateConfigurations()方法(上图标蓝的上一行)得到的, 也就是获取候选的位置

    在这里插入图片描述

    这里调用了

    SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
          getBeanClassLoader());
    //传入了两个参数
    
    protected Class<?> getSpringFactoriesLoaderFactoryClass() {
    		return EnableAutoConfiguration.class;
    }
    
    protected ClassLoader getBeanClassLoader() {
    		return this.beanClassLoader;
    }
    

    于是便要知道loadFactoryNames()的作用

    public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
       String factoryTypeName = factoryType.getName();
       return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
    }
    

    在这里又调用了loadSpringFactories(classLoader)并将类加载器作为参数. (注: classloader的实参就是getBeanClassLoader())

    private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
       MultiValueMap<String, String> result = cache.get(classLoader);
       if (result != null) {
          return result;
       }
    
       try {
          Enumeration<URL> urls = (classLoader != null ?
                classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
                ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
          result = new LinkedMultiValueMap<>();
          while (urls.hasMoreElements()) {
             URL url = urls.nextElement();
             UrlResource resource = new UrlResource(url);
             Properties properties = PropertiesLoaderUtils.loadProperties(resource);
             for (Map.Entry<?, ?> entry : properties.entrySet()) {
                String factoryTypeName = ((String) entry.getKey()).trim();
                for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
                   result.add(factoryTypeName, factoryImplementationName.trim());
                }
             }
          }
          cache.put(classLoader, result);
          return result;
       }
       catch (IOException ex) {
          throw new IllegalArgumentException("Unable to load factories from location [" +
                FACTORIES_RESOURCE_LOCATION + "]", ex);
       }
    }
    

    首先是在 133 行,从类加载器中加载一个资源,资源路径为FACTORIES_RESOURCE_LOCATION其实就是

    META-INF/spring.factories

    将获取到的一个资源赋值给 Enumeration类型的变量urls, 如果该变量中有下一个元素,说明这里面又包含资源,那就将这个资源加载成 properties 配置文件,并转换成为键值对即 Map类型的数据 进行返回

    public final class SpringFactoriesLoader {
        
    public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
        ...
    }
    

    META-INF/spring.factories位置

    在这里插入图片描述

    总结:

    1. SpringBoot 在启动的时候就从类路径下的 META-INF/spring.factories 中获取EnableAutoConfiguration指定的值,并将这些值加载到自动配置类导入到容器中,自动配置类 就生效,帮助我们进行自动配置功能。 而这些自动配置类 全都在 spring-boot-autoconfigure-2.3.3.RELEASE.jar 该jar包之下

    2. 如果要看自动配置类配置了什么功能,就点进==…AutoConfiguration==的源码即可

    3. 每一个这样的==…AutoConfiguration==类都是容器中的一个组件, 都加入到容器中; 用他们来做自动配置;

    4. 举例(以HttpEncodingAutoConfiguration(Http编码自动配置)为例)

      org.springframework.boot.autoconfigure.web.servlet.HttpEncodingAutoConfiguration,\
      
      @Configuration(proxyBeanMethods = false) 
      @EnableConfigurationProperties(ServerProperties.class)
      ///启动指定类的 ConfigurationProperties功能;将配置文件中对应的值和HttpEncodingProperties绑定起来;并把 HttpEncodingProperties加入到ioc容器中 
      @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
      //Spring底层@Conditional注解),根据不同的条件,如果 满足指定的条件,整个配置类里面的配置就会生效;    判断当前应用是否是web应用,如果是,当前配置类生效 
      @ConditionalOnClass(CharacterEncodingFilter.class)
      //判断当前项目有没有这个类 CharacterEncodingFilter;SpringMVC中进行乱码解决的过滤器; 
      @ConditionalOnProperty(prefix = "server.servlet.encoding", value = "enabled", matchIfMissing = true)
      //判断配置文件中是否存在某个配置  spring.http.encoding.enabled;如果不存在,判断也是成立的 //即使我们配置文件中不配置pring.http.encoding.enabled=true,也是默认生效的; 
      public class HttpEncodingAutoConfiguration {
      
           //他已经和SpringBoot的配置文件映射了 
          
         private final Encoding properties;
      
          //只有一个有参构造器的情况下,参数的值就会从容器中拿 
         public HttpEncodingAutoConfiguration(ServerProperties properties) {
            this.properties = properties.getServlet().getEncoding();
         }
      
         @Bean //给容器中添加一个组件,这个组件的某些值需要从properties中获取
         @ConditionalOnMissingBean//判断容器没有这个组件?
         public CharacterEncodingFilter characterEncodingFilter() {
            CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
            filter.setEncoding(this.properties.getCharset().name());
            filter.setForceRequestEncoding(this.properties.shouldForce(Encoding.Type.REQUEST));
            filter.setForceResponseEncoding(this.properties.shouldForce(Encoding.Type.RESPONSE));
            return filter;
         }
      
         @Bean
         public LocaleCharsetMappingsCustomizer localeCharsetMappingsCustomizer() {
            return new LocaleCharsetMappingsCustomizer(this.properties);
         }
      
         static class LocaleCharsetMappingsCustomizer
               implements WebServerFactoryCustomizer<ConfigurableServletWebServerFactory>, Ordered {
      
            private final Encoding properties;
      
            LocaleCharsetMappingsCustomizer(Encoding properties) {
               this.properties = properties;
            }
      
            @Override
            public void customize(ConfigurableServletWebServerFactory factory) {
               if (this.properties.getMapping() != null) {
                  factory.setLocaleCharsetMappings(this.properties.getMapping());
               }
            }
      
            @Override
            public int getOrder() {
               return 0;
            }
      
         }
      
      }
      
    5. 所有在配置文件中能配置的属性都是在xxxxProperties类中封装着‘;配置文件能配置什么就可以参照某个功 能对应的这个属性类

      @ConfigurationProperties(   
          prefix = "spring.http.encoding"  //从配置文件中获取指定的值和bean的属性进行绑定 
      )
      public class HttpEncodingProperties {
          public static final Charset DEFAULT_CHARSET;
          ...
      }
      

9、@Conditional注解

  1. @Conditional派生注解(Spring注解版原生的@Conditional作用)
    作用:必须是@Conditional指定的条件成立,才给容器中添加组件,配置配里面的所有内容才生效;

    @Conditional扩展注解作用(判断是否满足当前指定条件)
    @ConditionalOnJava系统的java版本是否符合要求
    @ConditionalOnBean容器中存在指定Bean;
    @ConditionalOnMissingBean容器中不存在指定Bean;
    @ConditionalOnExpression满足SpEL表达式指定
    @ConditionalOnClass系统中有指定的类
    @ConditionalOnMissingClass系统中没有指定的类
    @ConditionalOnSingleCandidate容器中只有一个指定的Bean,或者这个Bean是首选Bean
    @ConditionalOnProperty系统中指定的属性是否有指定的值
    @ConditionalOnResource类路径下是否存在指定资源文件
    @ConditionalOnWebApplication当前是web环境
    @ConditionalOnNotWebApplication当前不是web环境
    @ConditionalOnJndiJNDI存在指定项
  2. 我们可以在配置文件中写上一项,debug我打成true

    debug=true

    默认是false,开启Springboot的debug模式,会非常好用,我们来运行一下,一旦debug模式进来以后呢,控制台就会告诉我们哪些用了

    哪些没用,自动配置报告

    #application.properties
    debug=true
    server.port=8081
        
    ============================
    CONDITIONS EVALUATION REPORT
    ============================
    
    
    Positive matches:
    -----------------
    
       AopAutoConfiguration matched:
          - @ConditionalOnProperty (spring.aop.auto=true) matched (OnPropertyCondition)
    
       AopAutoConfiguration.ClassProxyingConfiguration matched:
          - @ConditionalOnMissingClass did not find unwanted class 'org.aspectj.weaver.Advice' (OnClassCondition)
          - @ConditionalOnProperty (spring.aop.proxy-target-class=true) matched (OnPropertyCondition)
    
       DispatcherServletAutoConfiguration matched:
          - @ConditionalOnClass found required class 'org.springframework.web.servlet.DispatcherServlet' (OnClassCondition)
          - found 'session' scope (OnWebApplicationCondition)
    
       DispatcherServletAutoConfiguration.DispatcherServletConfiguration matched:
          - @ConditionalOnClass found required class 'javax.servlet.ServletRegistration' (OnClassCondition)
          - Default DispatcherServlet did not find dispatcher servlet beans (DispatcherServletAutoConfiguration.DefaultDispatcherServletCondition)
    
       DispatcherServletAutoConfiguration.DispatcherServletRegistrationConfiguration matched:
          - @ConditionalOnClass found required class 'javax.servlet.ServletRegistration' (OnClassCondition)
          - DispatcherServlet Registration did not find servlet registration bean (DispatcherServletAutoConfiguration.DispatcherServletRegistrationCondition)
    
       DispatcherServletAutoConfiguration.DispatcherServletRegistrationConfiguration#dispatcherServletRegistration matched:
          - @ConditionalOnBean (names: dispatcherServlet types: org.springframework.web.servlet.DispatcherServlet; SearchStrategy: all) found bean 'dispatcherServlet' (OnBeanCondition)
    
       EmbeddedWebServerFactoryCustomizerAutoConfiguration matched:
          - @ConditionalOnWebApplication (required) found 'session' scope (OnWebApplicationCondition)
    
       EmbeddedWebServerFactoryCustomizerAutoConfiguration.TomcatWebServerFactoryCustomizerConfiguration matched:
          - @ConditionalOnClass found required classes 'org.apache.catalina.startup.Tomcat', 'org.apache.coyote.UpgradeProtocol' (OnClassCondition)
    
       ErrorMvcAutoConfiguration matched:
          - @ConditionalOnClass found required classes 'javax.servlet.Servlet', 'org.springframework.web.servlet.DispatcherServlet' (OnClassCondition)
          - found 'session' scope (OnWebApplicationCondition)
    
       ErrorMvcAutoConfiguration#basicErrorController matched:
          - @ConditionalOnMissingBean (types: org.springframework.boot.web.servlet.error.ErrorController; SearchStrategy: current) did not find any beans (OnBeanCondition)
    
       ErrorMvcAutoConfiguration#errorAttributes matched:
          - @ConditionalOnMissingBean (types: org.springframework.boot.web.servlet.error.ErrorAttributes; SearchStrategy: current) did not find any beans (OnBeanCondition)
    
       ErrorMvcAutoConfiguration.DefaultErrorViewResolverConfiguration#conventionErrorViewResolver matched:
          - @ConditionalOnBean (types: org.springframework.web.servlet.DispatcherServlet; SearchStrategy: all) found bean 'dispatcherServlet'; @ConditionalOnMissingBean (types: org.springframework.boot.autoconfigure.web.servlet.error.ErrorViewResolver; SearchStrategy: all) did not find any beans (OnBeanCondition)
    ...
    

    这样我们就可以很方便的知道哪些自动配置类生效;(下面还有很多没打出来)

三、Web开发

1、基本介绍

  1. 创建SpringBoot应用,选中我们需要的模块;

  2. SpringBoot已经默认将这些场景配置好了,只需要在配置文件中指定少量配置就可以运行起来

  3. 自己编写业务代码;

  4. 自动配置原理?
    这个场景SpringBoot帮我们配置了什么?能不能修改?能修改哪些配置?能不能扩展?xxx

  5. xxxxAutoConfiguration:帮我们给容器中自动配置组件;
    
    xxxxProperties:配置类来封装配置文件的内容;
    

2、SpringBoot对静态资源的映射规则

@ConfigurationProperties(prefix = "spring.resources", ignoreUnknownFields = false)
public class ResourceProperties {
 ///可以设置和静态资源有关的参数,缓存时间等

   private static final String[] CLASSPATH_RESOURCE_LOCATIONS = { "classpath:/META-INF/resources/",
         "classpath:/resources/", "classpath:/static/", "classpath:/public/" };
    
   /**
   * Locations of static resources. Defaults to classpath:[/META-INF/resources/,
   * /resources/, /static/, /public/].
   */
   private String[] staticLocations = CLASSPATH_RESOURCE_LOCATIONS;//getStaticLocations下的位置
WebMvcAuotConfiguration类:
    
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
   if (!this.resourceProperties.isAddMappings()) {
      logger.debug("Default resource handling disabled");
      return;
   }
   Duration cachePeriod = this.resourceProperties.getCache().getPeriod(); //此处resourceProperties即是 
                                                                          //ResourceProperties类型的
   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));
   }              
                        //cachePeriod: 缓存时间等等可以通过上方ResourceProperties类来设置
    
   String staticPathPattern = this.mvcProperties.getStaticPathPattern();//静态资源的路径
    //静态资源文件夹映射
   if (!registry.hasMappingForPattern(staticPathPattern)) {
      customizeResourceHandlerRegistration(registry.addResourceHandler(staticPathPattern)
            .addResourceLocations(getResourceLocations(this.resourceProperties.getStaticLocations()))
                               //如果静态资源路径没有被映射,就去getStaticLocations下的位置寻找(见上一个代码块)
            .setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl));
   }
}

		//欢迎页的映射
		@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;
		}
  1. 第一种方式

    所有 /webjars/**(指的是这个访问路径) ,都去 classpath:/META-INF/resources/webjars/ 找资源;

    webjars:以jar包的方式引入静态资源(比如 jquery,css等等);

    //引入jquery‐webjar‐‐>在访问的时候只需要写webjars下面资源的名称即可
    <dependency> 
           <groupId>org.webjars</groupId> 
           <artifactId>jquery</artifactId>   
           <version>3.3.1</version> 
    </dependency>
    

    在这里插入图片描述

  2. 第二种方式

    @ConfigurationProperties(prefix = "spring.mvc")
    public class WebMvcProperties {
        ...
    /**
     * Path pattern used for static resources.
     */
    private String staticPathPattern = "/**";
        ...
    }
    

    “/**” 访问当前项目的任何资源,都去(静态资源的文件夹)找映射

    "classpath:/META‐INF/resources/",
    "classpath:/resources/",(注:这里是指在maven给定的resources下再建一个resources文件夹)
    "classpath:/static/", 
    "classpath:/public/" 
    "/":当前项目的根路径
    

    在这里插入图片描述

  3. 第三种方式

    欢迎页;静态资源文件夹下的所有index.html页面;被“/**”映射;

    localhost:8080/(也满足/**) 找index.html(需要放到静态资源的文件夹下)

3、模板引擎(thymeleaf)

  1. 导入坐标

    <!-- thymeleaf, 基于3.x开发,之前的版本还得在properties标签里改版本 -->
    <dependency>
       <groupId>org.thymeleaf</groupId>
       <artifactId>thymeleaf-spring5</artifactId>
    </dependency>
    <dependency>
       <groupId>org.thymeleaf.extras</groupId>
       <artifactId>thymeleaf-extras-java8time</artifactId>
    </dependency>
    

1、Thymeleaf使用&语法

@ConfigurationProperties(prefix = "spring.thymeleaf")
public class ThymeleafProperties {

   private static final Charset DEFAULT_ENCODING = StandardCharsets.UTF_8;

   public static final String DEFAULT_PREFIX = "classpath:/templates/";

   public static final String DEFAULT_SUFFIX = ".html";

只要我们把HTML页面==放在classpath:/templates/==下 , thymeleaf就能自动渲染;

  1. 导入thymeleaf的名称空间

    <html lang="en" xmlns:th="http://www.thymeleaf.org">
    

2、语法规则

  1. th:text ; 改变当前元素里面的文本内容 ;

    th: 任意html属性;来替换原生属性的值

    在这里插入图片描述

  2. 表达式

    补充:(OGNL):OGNL(Object-Graph Navigation Language)的全称是对象图导航语言,它是一种功能强大的开源表达式语言,比 EL(只能从域或内置对象中)表达式更强大,使用这种表达式语言,可以通过某种表达式语法,OGNL可以存取Java任意对象的任意属性,调用Java对象的方法,同时能够自动实现必要的类型转换。如果把表达式看作是一个带有语义的字符串,那么OGNL无疑成为了这个语义字符串与Java对象之间沟通的桥梁。

    Simple expressions: 
    	1.Variable Expressions: ${...} : 获取变量值 ; OGNL;
    		1)获取对象的属性、调用方法
    		2)使用内置的基本对象
    		#ctx : the context object. 
    		#vars: the context variables. 
    		#locale : the context locale. 
    		#request : (only in Web Contexts) the HttpServletRequest object. 
    		#response : (only in Web Contexts) the HttpServletResponse object. 
    		#session : (only in Web Contexts) the HttpSession object. 
    		#servletContext : (only in Web Contexts) the ServletContext object
    		
    #在thymeleaf官方文档里  You can read the full reference of these objects in Appendix A. 可以看用法
    
    		3)使用内置的工具对象
    #execInfo : information about the template being processed.
    #messages : methods for obtaining externalized messages inside variables expressions, in the same way as they would be obtained using
    #{…} syntax.
    #uris : methods for escaping parts of URLs/URIs
    #conversions : methods for executing the configured conversion service (if any). 
    #dates : methods for java.util.Date objects: formatting, component extraction, etc.
    #calendars : analogous to #dates , but for java.util.Calendar objects.
    #numbers : methods for formatting numeric objects. 
    #strings : methods for String objects: contains, startsWith, prepending/appending, etc. 
    #objects : methods for objects in general.
    #bools : methods for boolean evaluation. 
    #arrays : methods for arrays. #lists : methods for lists.
    #sets : methods for sets.
    #maps : methods for maps.
    #aggregates : methods for creating aggregates on arrays or collections.
    #ids : methods for dealing with id attributes that might be repeated (for example, as a result of an iteration).
    
    #在thymeleaf官方文档里  You can read the full reference of these objects in Appendix B. 可以看用法	
    
    	2.Selection Variable Expressions: *{...} :选择表达式:和${} 在功能上是一样的;
    	
    			补充: 配合 <div th:object="${session.user}"> (span标签里的 * 就相当于${session.user})
    		 <div th:object="${session.user}">   
             	<p>Name: <span th:text="*{firstName}">Sebastian</span>.</p>  
             	<p>Surname: <span th:text="*{lastName}">Pepper</span>.</p>  
             	<p>Nationality:  <span th:text="*{nationality}">Saturn</span>.</p> 
              </div>
              
    	3.Message Expressions: #{...} : 获取国际化信息
    	
    	4.Link URL Expressions: @{...} : 定义url
    	
    	 <!-- Will produce 'http://localhost:8080/gtvg/order/details?orderId=3' (plus rewriting) -->
         <a href="details.html"    th:href="@{http://localhost:8080/gtvg/order/details(orderId=${o.id})}">view</a>
        
    	 <!-- Will produce '/gtvg/order/details?orderId=3' (plus rewriting) -->
         <a href="details.html" th:href="@{/order/details(orderId=${o.id})}">view</a>
         #1.我们可以看出 用thymeleaf写url时可以把  http://localhost:8080/gtvg(项目名)省略掉,
         #2.本来地址中的请求参数?orderId=3 可以写为(orderId=${o.id})的形式
         #3.如果有多个参数,可以  (execId=${execId},execType='FAST')
    	 <!-- Will produce '/gtvg/order/3/details' (plus rewriting) --> 
    	 <a href="details.html" th:href="@{/order/{orderId}/details(orderId=${o.id})}">view</a>
    
    	
    	5.Fragment Expressions: ~{...} : 片段引用表达式
    	
    Literals(字面量)
    	Text literals: 'one text' , 'Another one!' ,… 
    	Number literals: 0 , 34 , 3.0 , 12.3 ,… 
    	Boolean literals: true , false
        Null literal: null 
        Literal tokens: one , sometext , main ,…
        
    Text operations(文本操作):
    	String concatenation: + 
    	Literal substitutions: |The name is ${name}|
    	
    Arithmetic operations (数学运算):
    	Binary operators: + , - , * , / , %
    	Minus sign (unary operator): -
    
    Boolean operations (布尔运算):
    	Binary operators: and , or 
    	Boolean negation (unary operator): ! , not
    	
    Comparisons and equality(比较运算):
    	Comparators: > , < , >= , <= ( gt , lt , ge , le )
        Equality operators: == , != ( eq , ne )
        
    Conditional operators (条件运算: 三元运算符):
    	If-then: (if) ? (then) 
    	If-then-else: (if) ? (then) : (else) 
    	Default: (value) ?: (defaultvalue)
    Special tokens:
    
    No-Operation: _
    All these features can be combined and nested:
    
    
  3. 小例子

    <!DOCTYPE html>
    <html lang="en" xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
    
        <h1> Hello !!! </h1>
        <!--th:text  将div里面的内容设置为-->
        <div id="div01" class="mydiv"  th:id="${hello}" th:class="${hello}" th:text="${hello}">这里显示欢迎信息</div>
        <!--th:属性名  将修改对应的属性值-->
        <hr/>
        <div th:text="${hello}"></div>
        <!--th:text 会转义特殊字符: 就是把本来的标签直接以字符串的形式输出来-->
        <div th:utext="${hello}"></div>
        <!--th:utext 不会转义, 就是说<h1>你不好</h1>中的<h1>会以html标签的效果展示出来-->
        <hr/>
    
        <!--th:each每次遍历都会生成当前这个标签-->
        <!--这里会生成四个 h4 标签-->
        <h4 th:text="${user}" th:each="user:${users}"></h4>
        <hr/>
        <h4>
            <!--这里会生成四个span标签-->
            <span th:each="user:${users}"> [[${user}]] </span>
            <!--直接在文本里写的话  [[...]] 相当于th:text    [(...)]相当于th:utext -->
        </h4>
    </body>
    </html>
    

    在这里插入图片描述

4、SpringMVC自动配置(重要)

1、Spring MVC auto-configuration

Spring Boot 自动配置好了SpringMVC

以下是SpringBoot对SpringMVC的默认配置:(WebMvcAutoConfiguration)

  • Inclusion of ContentNegotiatingViewResolver and BeanNameViewResolver beans. ()

    1. 自动配置了ViewResolver(视图解析器:根据方法的返回值得到视图对象(View),视图对象决定如何 渲染(转发?重定向?))

    2. ContentNegotiatingViewResolver:组合所有的视图解析器的;

    WebMvcAutoConfiguration类:
        
    @Bean
    @ConditionalOnBean(ViewResolver.class)
    @ConditionalOnMissingBean(name = "viewResolver", value = ContentNegotiatingViewResolver.class)
    //在没有名为"viewResolver",类型为ContentNegotiatingViewResolver的bean对象的情况下调用此方法
    public ContentNegotiatingViewResolver viewResolver(BeanFactory beanFactory) {
       ContentNegotiatingViewResolver resolver = new ContentNegotiatingViewResolver();
       resolver.setContentNegotiationManager(beanFactory.getBean(ContentNegotiationManager.class));
       // ContentNegotiatingViewResolver uses all the other view resolvers to locate
       // a view so it should have a high precedence
       resolver.setOrder(Ordered.HIGHEST_PRECEDENCE);
       return resolver;
    }
    
    public interface ViewResolver {
    
       /**
       视图解析器接口只有一个方法, 就是根据名称解析出视图信息(一个视图对象View)
        为了允许ViewResolver链接,ViewResolver应该
    	*如果未定义具有给定名称的视图,则返回{@code null}。
    	*一些ViewResolvers将总是尝试以给定的名称构建视图对象,就不返回{@code null}
        */
       @Nullable
       View resolveViewName(String viewName, Locale locale) throws Exception;
    
    }
    

    补充: 在IDEA中查看一个方法在哪里被调用了:

    1. 选中要查的方法名

    2. 快捷键==(Ctrl + G)==(在idea快捷键被改成了eclipse的情况下)

    3. 或者是右键, 选择 Find Usages

    ContentNegotiatingViewResolver类:  implements ViewResolver
    
    @Nullable
    public View resolveViewName(String viewName, Locale locale) throws Exception {
        RequestAttributes attrs = RequestContextHolder.getRequestAttributes();
        Assert.state(attrs instanceof ServletRequestAttributes, "No current ServletRequestAttributes");
        List<MediaType> requestedMediaTypes = this.getMediaTypes(((ServletRequestAttributes)attrs).getRequest());
        if (requestedMediaTypes != null) {
            List<View> candidateViews = this.getCandidateViews(viewName, locale, requestedMediaTypes);
            //获取候选的视图对象
            View bestView = this.getBestView(candidateViews, requestedMediaTypes, attrs);
            //选择一个最适合的视图对象
            if (bestView != null) {
                return bestView;
                //然后将最适合的视图对象返回
            }
        }
    
        String mediaTypeInfo = this.logger.isDebugEnabled() && requestedMediaTypes != null ? " given " + requestedMediaTypes.toString() : "";
        if (this.useNotAcceptableStatusCode) {
            if (this.logger.isDebugEnabled()) {
                this.logger.debug("Using 406 NOT_ACCEPTABLE" + mediaTypeInfo);
            }
    
            return NOT_ACCEPTABLE_VIEW;
        } else {
            this.logger.debug("View remains unresolved" + mediaTypeInfo);
            return null;
        }
    }
    
    private List<View> getCandidateViews(String viewName, Locale locale, List<MediaType> requestedMediaTypes) throws Exception {
        List<View> candidateViews = new ArrayList();
        if (this.viewResolvers != null) {
            Assert.state(this.contentNegotiationManager != null, "No ContentNegotiationManager set");
            Iterator var5 = this.viewResolvers.iterator();
            //使用迭代器获取所有的视图解析器对象
    
            while(var5.hasNext()) {
                ViewResolver viewResolver = (ViewResolver)var5.next();
                View view = viewResolver.resolveViewName(viewName, locale);
                if (view != null) {
                    candidateViews.add(view);
                }
    
                Iterator var8 = requestedMediaTypes.iterator();
    
                while(var8.hasNext()) {
                    MediaType requestedMediaType = (MediaType)var8.next();
                    List<String> extensions = this.contentNegotiationManager.resolveFileExtensions(requestedMediaType);
                    Iterator var11 = extensions.iterator();
    
                    while(var11.hasNext()) {
                        String extension = (String)var11.next();
                        String viewNameWithExtension = viewName + '.' + extension;
                        view = viewResolver.resolveViewName(viewNameWithExtension, locale);
                        if (view != null) {
                            candidateViews.add(view);
                        }
                    }
                }
            }
        }
    
        if (!CollectionUtils.isEmpty(this.defaultViews)) {
            candidateViews.addAll(this.defaultViews);
        }
    
        return candidateViews;//返回给List<View> candidateViews 作为候选视图解析器对象
    }
    
    1. 如何定制:我们可以自己给容器中添加一个视图解析器;自动的将其组合进来;

    说明

    1. 根据上面WebMvcAutoConfiguration类 以及 ContentNegotiatingViewResolver类

    2. 可以大致推断出如果我们自己定义了一个类实现ViewResolver接口

    3. 并且将该类型的对象添加到容器中(即用@Bean注解),就可以实现自己的视图解析器了。

    4. 由下方的代码可以知道,ContentNegotiatingViewResolver类中的initServletContext方法会通过BeanFactory工具 从容器中获取所有的视图解析器,自然也包括我们自己创建的

    @Override
    protected void initServletContext(ServletContext servletContext) {
       Collection<ViewResolver> matchingBeans =
             BeanFactoryUtils.beansOfTypeIncludingAncestors(obtainApplicationContext(), ViewResolver.class).values();
        //通过BeanFactory工具 从容器中获取所有的视图解析器
       if (this.viewResolvers == null) {
          this.viewResolvers = new ArrayList<>(matchingBeans.size());
          for (ViewResolver viewResolver : matchingBeans) {
             if (this != viewResolver) {
                this.viewResolvers.add(viewResolver);
                 //把获得的视图解析器放到
    	         //private List<ViewResolver> viewResolvers; 里了
             }
          }
       }
       else {
          for (int i = 0; i < this.viewResolvers.size(); i++) {
             ViewResolver vr = this.viewResolvers.get(i);
             if (matchingBeans.contains(vr)) {
                continue;
             }
             String name = vr.getClass().getName() + i;
             obtainApplicationContext().getAutowireCapableBeanFactory().initializeBean(vr, name);
          }
    
       }
       AnnotationAwareOrderComparator.sort(this.viewResolvers);
       this.cnmFactoryBean.setServletContext(servletContext);
    }
    
    1. 验证我们的想法
    @SpringBootApplication
    public class Springboot09TestthymeleafApplication {
    
       public static void main(String[] args) {
          SpringApplication.run(Springboot09TestthymeleafApplication.class, args);
       }
    
       public ViewResolver myViewResolver(){
          return new MyViewResolver();
       }
    
       public static class MyViewResolver implements ViewResolver{
    
          @Override
          public View resolveViewName(String s, Locale locale) throws Exception {
             return null;
          }
       }
    }
    

    于是,我们要通过调试来确认DispatcherServlet 中的 doDispatch()方法 (因为所有请求一进来都会来到该方法)中用到的视图解析器是否包含我们自己定义的那个

    在这里插入图片描述

    可以看到我们自己定义的视图解析器也被调用了!!!

  • Support for serving static resources, including support for WebJars (see below).静态资源文件夹路径 /webjars

  • Static index.html support. 静态首页访问

  • Custom Favicon support (see below). favicon.ico

  • 自动注册了 of Converter , GenericConverter , Formatter beans.

    1. Converter:转换器; public String hello(User user);类型转换使用Converter 把页面发来的文本类型转换为对应的类型)
    2. Formatter 格式化器;2017.12.17(从页面传来的===Date;先把字符串转为日期类型,还得按照传来的格式。
    @Bean
    @Override
    public FormattingConversionService mvcConversionService() {
       Format format = this.mvcProperties.getFormat();
        // 通过 private final WebMvcProperties mvcProperties。中的getFormat()方法 获取格式化信息
        
       WebConversionService conversionService = new WebConversionService(new DateTimeFormatters()
             .dateFormat(format.getDate()).timeFormat(format.getTime()).dateTimeFormat(format.getDateTime()));
       addFormatters(conversionService);
        //Create a new WebConversionService that configures formatters with the provided date, time, and date-time formats, or registers the default if no custom format is provided
        //WebConversionService是用来设置格式化器的
        
       return conversionService;
    }
    
    @ConfigurationProperties(prefix = "spring.mvc")
    public class WebMvcProperties {
        ...
       private final Format format = new Format();
       
       @Deprecated
        /**
        对@Deprecated 以及 @DeprecatedConfigurationProperty的补充说明:
        1.这个注解表明当前属性被弃用了
        2.弃用可以在代码中声明性地指定,方法是将@DeprecatedConfigurationProperty注释添加
                                        到暴露不赞成使用的属性的getter中(即被@Deprecated标注了的)
        3.例如下面的 spring.mvc.date‐format  被重命名为  spring.mvc.format.date                    
        */
       @DeprecatedConfigurationProperty(replacement = "spring.mvc.format.date")
        //在文件中配置日期格式化的规则
        //这个注解的意思就是说在properties文件中写日期格式的形式从过期的替换为spring.mvc.format.date
        //以前的是spring.mvc.date‐format
    	public String getDateFormat() {
    	return this.format.getDate();
    	}
        
        public Format getFormat() {
    		return this.format;//注: 这个方法就是上一个代码块this.mvcProperties.getFormat();处调用的
            //其实就是给WebMvcProperties中的format赋值
    	}
    
    1. 自己添加的格式化器转换器,我们只需要放在容器中即可
    @Override
    public void addFormatters(FormatterRegistry registry) {
       ApplicationConversionService.addBeans(registry, this.beanFactory);
        //addFormatters()通过调用ApplicationConversionService里的addBeans()来添加格式化器
        //参数为格式化器注册器  和  bean工厂(容器)
        //该方法由往上数第2个代码块中的addFormatters(conversionService);处调用
    }
    

    补充说明:

    1. 在IDEA快捷键为eclipse时,点击某个方法 按住 Ctrl + Alt + b
    2. 即可查看这个方法的具体实现方法
    public static void addBeans(FormatterRegistry registry, ListableBeanFactory beanFactory) {
        Set<Object> beans = new LinkedHashSet();//定义一个LinkedHashSet来存放组件(各种转换器)
        beans.addAll(beanFactory.getBeansOfType(GenericConverter.class).values());
        //该方法是由 StaticListableBeanFactory 类(实现了ListableBeanFactory接口)来实现的
        //用于获取所有实现了(。。。.class)的类并将它们添加到beans中去
        beans.addAll(beanFactory.getBeansOfType(Converter.class).values());
        beans.addAll(beanFactory.getBeansOfType(Printer.class).values());
        beans.addAll(beanFactory.getBeansOfType(Parser.class).values());
        Iterator var3 = beans.iterator();
    
        while(var3.hasNext()) {
            Object bean = var3.next();
            if (bean instanceof GenericConverter) {
                registry.addConverter((GenericConverter)bean);
            } else if (bean instanceof Converter) {
                registry.addConverter((Converter)bean);
            } else if (bean instanceof Formatter) {
                registry.addFormatter((Formatter)bean);
            } else if (bean instanceof Printer) {
                registry.addPrinter((Printer)bean);
            } else if (bean instanceof Parser) {
                registry.addParser((Parser)bean);
            }
            //如果各个类实例化了,就放到格式化器注册器中去
        }
    
    }
    
  • Support for HttpMessageConverters (see below).

    • HttpMessageConverter:SpringMVC用来转换Http请求和响应的;User—Json;
    • HttpMessageConverters 是从容器中确定;获取所有的HttpMessageConverter;
    • 自己给容器中添加HttpMessageConverter,只需要将自己的组件注册容器中
      (@Bean,@Component)
  • Automatic registration of MessageCodesResolver (see below).定义错误代码生成规则

  • Automatic use of a ConfigurableWebBindingInitializer bean (see below).
    我们可以配置一个ConfigurableWebBindingInitializer来替换默认的;(添加到容器)

    //初始化WebDataBinder;
    //请求数据=====JavaBean;
    

    org.springframework.boot.autoconfigure.web:web的所有自动场景;

    //If you want to keep Spring Boot MVC features, and you just want to add additional MVC configuration
    //(interceptors, formatters, view controllers etc.) you can add your own @Configuration class of type
    //WebMvcConfigurerAdapter , but without @EnableWebMvc . If you wish to provide custom instances of
    //RequestMappingHandlerMapping , RequestMappingHandlerAdapter or ExceptionHandlerExceptionResolver
    //you can declare a WebMvcRegistrationsAdapter instance providing such components.
    //If you want to take complete control of Spring MVC, you can add your own @Configuration annotated with
    //@EnableWebMvc 
    

2、扩展SpringMVC

 <mvc:view‐controller path="/hello" view‐name="success"/>
    <mvc:interceptors>
        <mvc:interceptor>
            <mvc:mapping path="/hello"/>
            <bean></bean>
        </mvc:interceptor>
    </mvc:interceptors>

  1. 编写一个配置类(@Configuration),是WebMvcConfigurer类型;不能标注@EnableWebMvc;

    (注:WebMvcConfigurerAdapter已经过时了,官方推荐用WebMvcConfigurer)

  2. 既保留了所有的自动配置,也能用我们扩展的配置;

//使用WebMvcConfigurer可以来扩展SpringMvc的功能
@Configuration
public class MyMvcConfig implements WebMvcConfigurer{
/**
 * 当我们不需要往页面里传入什么数据的时候,只需要定义一个ViewControllers就可以了
 * @param registry
 */
@Override
public void addViewControllers(ViewControllerRegistry registry) {
    //浏览器发送/zhuge请求 来到 index.html页面
    registry.addViewController("/zhuge").setViewName("index");
	}
}
  1. 原理:

    1. WebMvcAutoConfiguration是SpringMVC的自动配置类
    2. 在做其他自动配置时会导入; @Import(EnableWebMvcConfiguration.class) ,比如WebMvcAutoConfigurationAdapter
    @Configuration(proxyBeanMethods = false)
    public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
    
       private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite();//下个代码块是实现类
    
    //自动注入说明方法的参数便要从容器中获取,参数就是容器中所有的WebMvcConfigurer
       @Autowired(required = false)
       public void setConfigurers(List<WebMvcConfigurer> configurers) {
          if (!CollectionUtils.isEmpty(configurers)) {
             this.configurers.addWebMvcConfigurers(configurers);
          }
       }
        
    //举一个DelegatingWebMvcConfiguration中的例子
        @Override
    	protected void addViewControllers(ViewControllerRegistry registry) {
    		this.configurers.addViewControllers(registry);
    	}
    
    class WebMvcConfigurerComposite implements WebMvcConfigurer {
    
       private final List<WebMvcConfigurer> delegates = new ArrayList<>();
        
        @Override
    	public void addViewControllers(ViewControllerRegistry registry) {
    		for (WebMvcConfigurer delegate : this.delegates) {
                //从这里可以看出该方法把容器中所有WebMvcConfigurer都拿来
    			delegate.addViewControllers(registry);
                //然后再把这些Configurer的addViewControllers方法都调用一遍
    		}
    	}
        。。。
    
    1. 容器中所有的WebMvcConfigurer都会一起起作用;
    2. 我们的配置类也会被调用;
      效果:SpringMVC的自动配置和我们的扩展配置都会起作用;

3、全面接管SpringMvc

SpringBoot对SpringMVC的自动配置不需要了,所有都是我们自己配置;所有的SpringMVC的自动配置都失效了我们

需要在配置类中添加==@EnableWebMvc==即可;

补充说明:

  1. @see : 可以在注释中实现链接跳转
//使用WebMvcConfigurer可以来扩展SpringMvc的功能
@EnableWebMvc
@Configuration
public class MyMvcConfig implements WebMvcConfigurer{

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        //浏览器发送/zhuge请求 来到 index.html页面
        registry.addViewController("/zhuge").setViewName("index");
    }
}
/**
这个时候,我们连静态资源都访问不了了,因为SpringMVC的自动配置都失效了
*/

原理:

为什么有@EnableWebMvc自动配置就失效了?

  1. @EnableWebMvc的核心
@Import(DelegatingWebMvcConfiguration.class)
public @interface EnableWebMvc {
}
@Configuration(proxyBeanMethods = false)
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
  1. 回头看下WebMvcAutoConfiguration
@Configuration(
    proxyBeanMethods = false
)
@ConditionalOnWebApplication(
    type = Type.SERVLET
)
@ConditionalOnClass({Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class})
@ConditionalOnMissingBean({WebMvcConfigurationSupport.class})
//在容器中没有这个组件({WebMvcConfigurationSupport.class)的时候,这个自动配置类才生效
//然而当我们使用了@EnableWebMvc后,DelegatingWebMvcConfiguration类(WebMvcConfigurationSupport的子类在实例化时会先调用父类构造函数),所以,WebMvcAutoConfiguration就失效了
@AutoConfigureOrder(-2147483638)
@AutoConfigureAfter({DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class, ValidationAutoConfiguration.class})
public class WebMvcAutoConfiguration {
  1. @EnableWebMvc将WebMvcConfigurationSupport组件导入进来;
  2. 导入的WebMvcConfigurationSupport只是SpringMVC最基本的功能;

5、如果修改Springboot的默认配置

模式:

  1. SpringBoot在自动配置很多组件的时候,先看容器中有没有用户自己配置的(@Bean、@Component)如
    果有就用用户配置的,如果没有,才自动配置;如果有些组件可以有多个(ViewResolver)将用户配置的和自己默
    认的组合起来;
  2. 在SpringBoot中会有非常多的xxxConfigurer帮助我们进行扩展配置
  3. 在SpringBoot中会有很多的xxxCustomizer帮助我们进行定制配置

6、RestfulCRUD(实验)

1、默认访问登录页面

  1. 默认访问登录页面
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {

    }

    //所有的WebMvcConfigurer组件都会一起起作用
    //这里其实是采用的匿名内部类的方式,只要是WebMvcConfigurer类型的都会被使用
    @Bean
    public WebMvcConfigurer webMvcConfigurer(){
        WebMvcConfigurer configurer = new WebMvcConfigurer() {
            @Override
            public void addViewControllers(ViewControllerRegistry registry) {
                registry.addViewController("/").setViewName("login");
                registry.addViewController("index.html").setViewName("login");
            }
        };
        return configurer;
    }
}
  1. 首页 (login.html),
    1. 采用webjars导入的bootstrap
    2. 使用thymeleaf语法修改href和src
<html lang="en" xmlns:th="http://www.thymeleaf.org">
.........
<!-- Bootstrap core CSS -->
<link href="asserts/css/bootstrap.min.css" th:href="@{/webjars/bootstrap/4.5.2/css/bootstrap.min.css}" rel="stylesheet">
<!---->
<!-- Custom styles for this template -->
<link href="asserts/css/signin.css" th:href="@{/asserts/css/signin.css}" rel="stylesheet">
    .........
<img class="mb-4" th:src="@{/asserts/img/bootstrap-solid.svg}" src="asserts/img/bootstrap-solid.svg" alt="" width="72" height="72">
  1. 这样做的好处

    1. 当我们修改了项目访问的路径时
    server.servlet.context-path=/zhuge
    
    1. th:href 和 th:src会自动将添加的路径补到对应的属性值中去 (以下为网页源代码)
    <!-- Bootstrap core CSS -->
    <link href="/zhuge/webjars/bootstrap/4.5.2/css/bootstrap.min.css" rel="stylesheet">
    <!---->
    <!-- Custom styles for this template -->
    <link href="/zhuge/asserts/css/signin.css" rel="stylesheet">
    <img class="mb-4" src="/zhuge/asserts/img/bootstrap-solid.svg" alt="" width="72" height="72">
    

2、国际化

  1. 编写国际化文件
  2. 使用ResourceBundleMessageSource管理国际化资源文件(以前springmvc)
  3. 在页面使用fmt:message取出国际化内容(以前springmvc)

步骤:

  1. 编写国际化配置文件,抽取页面需要显示的国际化消息

在这里插入图片描述

  1. SpringBoot自动配置好了管理国际化资源文件的组件;
@Conditional(ResourceBundleCondition.class)
@EnableConfigurationProperties
public class MessageSourceAutoConfiguration {
    private static final Resource[] NO_RESOURCES = {};
    
     
    /**
 	* Comma‐separated list of basenames (essentially a fully‐qualified classpath    
 	* location), each following the ResourceBundle convention with relaxed support for    
 	* slash based locations. If it doesn't contain a package qualifier (such as    
 	* "org.mypackage"), it will be resolved from the classpath root.    
 	*/
    
    private String basename = "messages";//该属性是在MessageSourceAutoConProperties类下面的
    //我们的配置文件可以直接放在类路径下叫messages.properties;(但是我们是放在 i18n下的,所以需要自己指定一下)

	@Bean
	@ConfigurationProperties(prefix = "spring.messages")
	public MessageSourceProperties messageSourceProperties() {
		return new MessageSourceProperties();
	}

	@Bean
	public MessageSource messageSource(MessageSourceProperties properties) {
		ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
		if (StringUtils.hasText(properties.getBasename())) {
            //设置国际化资源文件的基础名(去掉语言国家代码的)
			messageSource.setBasenames(StringUtils
					.commaDelimitedListToStringArray(StringUtils.trimAllWhitespace(properties.getBasename())));
		}
		if (properties.getEncoding() != null) {
			messageSource.setDefaultEncoding(properties.getEncoding().name());
		}
		messageSource.setFallbackToSystemLocale(properties.isFallbackToSystemLocale());
		Duration cacheDuration = properties.getCacheDuration();
		if (cacheDuration != null) {
			messageSource.setCacheMillis(cacheDuration.toMillis());
		}
		messageSource.setAlwaysUseMessageFormat(properties.isAlwaysUseMessageFormat());
		messageSource.setUseCodeAsDefaultMessage(properties.isUseCodeAsDefaultMessage());
		return messageSource;
	}
    。。。
}
spring.messages.basename=i18n.login
#指定一下basename(国际化配置文件的路径)

  1. 去页面获取国际化的值;

    补充说明

    1. 在IDEA里,settings里的设置仅对当前项目生效,若要修改所有的,点other settings里的default settings
    2. 如果中文时有乱码,就在设置file encoding里把转化为ascll码给勾上
<body class="text-center">
   <form class="form-signin" action="dashboard.html">
      <img class="mb-4" th:src="@{/asserts/img/bootstrap-solid.svg}" src="asserts/img/bootstrap-solid.svg" alt="" width="72" height="72">
      <!---->
      <h1 class="h3 mb-3 font-weight-normal" th:text="#{login.tip}">Please sign in</h1>
      <label class="sr-only" th:text="#{login.username}">Username</label>
      <input type="text" class="form-control" placeholder="Username" th:placeholder="#{login.username}" required="" autofocus="">
      <label class="sr-only" th:text="#{login.password}">Password</label>
      <input type="password" class="form-control" placeholder="Password" th:placeholder="#{login.password}" required="">
      <div class="checkbox mb-3">
         <label>
         <input type="checkbox" value="remember-me" > [[#{login.remember}]]
            <!--注意:这里不能使用th:text="#{login.remember}"
               因为text是该标签体里面的内容,而input是用户在页面输入的内容,是字节数的,没有标签体
               所以我们要用行内写法
            -->
       </label>
      </div>
      <button class="btn btn-lg btn-primary btn-block" th:text="#{login.btn}" type="submit">Sign in</button>

效果:根据浏览器语言设置的信息切换了国际化;

原理:

国际化Locale(区域信息对象);LocaleResolver(获取区域信息对象);

@Bean
@ConditionalOnMissingBean//这个注解的意思是如果springmvc容器中有了LocaleResolver组件,该类就不会被使用
@ConditionalOnProperty(prefix = "spring.mvc", name = "locale")
public LocaleResolver localeResolver() {
   if (this.mvcProperties.getLocaleResolver() == WebMvcProperties.LocaleResolver.FIXED) {
       //这里指的是如果有设置好的LocaleResolver.FIXED,就用它
      return new FixedLocaleResolver(this.mvcProperties.getLocale());
   }
    //否则就用这个AcceptHeaderLocaleResolver的对象()
   AcceptHeaderLocaleResolver localeResolver = new AcceptHeaderLocaleResolver();
   localeResolver.setDefaultLocale(this.mvcProperties.getLocale());
   return localeResolver;
}
AcceptHeaderLocaleResolver类:

@Override
public Locale resolveLocale(HttpServletRequest request) {
   Locale defaultLocale = getDefaultLocale();
   if (defaultLocale != null && request.getHeader("Accept-Language") == null) {
       //默认的就是根据请求头带来的区域信息获取Locale进行国际化
      return defaultLocale;
   }
   Locale requestLocale = request.getLocale();
   List<Locale> supportedLocales = getSupportedLocales();
   if (supportedLocales.isEmpty() || supportedLocales.contains(requestLocale)) {
      return requestLocale;
   }
   Locale supportedLocale = findSupportedLocale(request, supportedLocales);
   if (supportedLocale != null) {
      return supportedLocale;
   }
   return (defaultLocale != null ? defaultLocale : requestLocale);
}

这里是网页中的信息

在这里插入图片描述

  1. 点击链接切换国际化(我们要实现自己的LocaleResolver组件)
/**
 * @author ZCH
 * @date 2020/10/2 0002 - 下午 9:19
 *
 * 可以在页面的链接上携带区域信息
 */
public class MyLocaleResolver implements LocaleResolver {


    @Override
    public Locale resolveLocale(HttpServletRequest request) {
        String l = request.getParameter("l");
        //从请求头中接收名为l的属性的值
        Locale locale = Locale.getDefault();
        //如果l没有值,最后就还是用浏览器默认的
        if (!StringUtils.isEmpty(l)){
            String[] split = l.split("_");
            //  “_“ 的左右两边分别是 语言代码 和 国家代码
            locale = new Locale(split[0],split[1]);
        }
        return locale;
    }

    @Override
    public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) {

    }
}

MyMvcConfig类:

  @Bean//将我们的区域解析器放到springmvc容器中去(注:不能直接在MyLocaleResolver类上加@Bean)
    public LocaleResolver localeResolver(){
        return new MyLocaleResolver();
    }

3、登录

开发期间模板引擎页面修改以后,要实时生效

  1. 禁用模板引擎的缓存
# 禁用缓存
spring.thymeleaf.cache=false
  1. 页面修改完成以后ctrl+f9:重新编译;
<p style="color: red" th:text="${msg}" th:if="${not #strings.isEmpty(msg)}"></p>
  1. 代码实现

补充说明

  1. 重复提交: 多次提交表单中的数据(这里只说一种情况,就是提交完表单后,不做其他操作直接刷新页面,会提交多次表单)
  2. 根本原因: Servlet处理完请求后, 直接转发到目标页面, 这样整个业务只发送了一次请求,点击刷新是会一直刷新之前的请求
  3. 解决办法: 不用转发到另一个页面, 采用重定向的方式跳转到目标页面
  4. 危害:
    1. 数据库可能会多次保存相同的数据
    2. 安全问题,如多次支付等
    3. 服务器性能受损
@Controller
public class LoginController {

    @PostMapping(value = "/user/login")
    public String login(@RequestParam("username") String username,
                        //@RequestParam("username") 用于与表单中name为username的input标签传来的参数对应
                        @RequestParam("password") String password,
                        Map<String,Object> map){
        if (!StringUtils.isEmpty(username) && "123456".equals(password)) {
            //登陆成功,防止表单重复提交,可以重定向到这个主页
            return "redirect:/main.html";
        }else {
            //登录失败
            map.put("msg","用户名或密码错误");
            return "login";
        }
    }

}

登陆错误消息的显示

<!--判断-->
<p style="color: red" th:text="${msg}" th:if="${not #strings.isEmpty(msg)}"></p>

4、拦截器

拦截器进行登录检查(没登录的不能访问后台)

public class LoginHandlerInterceptor implements HandlerInterceptor {

    //目标方法执行之前
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        Object user = request.getSession().getAttribute("loginUser");//没有进行过登录操作,处理器方法就没在session里添加"loginUser"(key),所以user就会为null
        if (user == null){
            //未登录, 返回登录页面
            request.setAttribute("msg","没有权限请先登录");
            request.getRequestDispatcher("/index.html").forward(request,response);
            return false;
        }else {
            //已登录,放行请求
            return true;
        }

    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {

    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {

    }
}

注册拦截器

@Bean
public WebMvcConfigurer webMvcConfigurer(){
    WebMvcConfigurer configurer = new WebMvcConfigurer() {
        @Override
        public void addViewControllers(ViewControllerRegistry registry) {
            registry.addViewController("/").setViewName("login");
            registry.addViewController("/index.html").setViewName("login");
            registry.addViewController("/main.html").setViewName("dashboard");
        }

        //注册拦截器
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            //静态资源: css,js
            //springboot已经做好了静态资源映射(但2.x又没了)
            registry.addInterceptor(new LoginHandlerInterceptor()).addPathPatterns("/**")
                                                //  "/**" 表示所有的地址都被拦截
                    .excludePathPatterns("/index.html","/","/user/login","/webjars/bootstrap/**","/asserts/**");
                                                //排除掉不用拦截的请求
            /*
            注: springboot2.x依赖的 spring 5.x版本, 针对资源的拦截器初始化时有区别,
            具体源码在WebMvcConfigurationSupport中,所以我们要手动把静态资源的请求路径添上
             */
        }
    };
    return configurer;
}

将登陆进去后的页面左上角改成当前用户的名字

 if (!StringUtils.isEmpty(username) && "123456".equals(password)) {
            //登陆成功,防止表单重复提交,可以重定向到这个主页
            session.setAttribute("loginUser",username);
            return "redirect:/main.html";

        }
//其余代码见登录中的 LoginController类
// session.setAttribute("loginUser",username);的作用是让后台左上角显示当前用户名
<a class="navbar-brand col-sm-3 col-md-2 mr-0" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">[[${session.loginUser}]]</a>
                                                <!--将登陆进去后的页面左上角改成当前用户的名字-->

5、实验要求

实验要求:

  1. RestfulCRUD:CRUD满足Rest风格;
    URI: /资源名称/资源标识 HTTP请求方式区分对资源CRUD操作

普通CRUD(uri来区分操作)RestfulCRUD
查询getEmpemp—GET
添加addEmp?xxxemp—POST
修改updateEmp?id=xxx&xxx=xxemp/{id}—PUT
删除deleteEmp?id=1emp/{id}—DELETE
  1. 实验的请求架构;
实验功能请求URI请求方式
查询所有员工empsGET
查询某个员工(来到修改页面)emp/1GET
来到添加页面empGET
添加员工empPOST
来到修改页面(查出员工进行信息回显)emp/1GET
修改员工empPUT
删除员工emp/1DELETE

6、CRUD-员工列表

  1. 在list页面也得加入thymeleaf的语法,并把资源引用改好路径(否则会没有样式)
<html lang="en" xmlns:th="http://www.thymeleaf.org">
    ..
 <!-- Bootstrap core CSS -->
		<link href="../../static/asserts/css/bootstrap.min.css" th:href="@{/webjars/bootstrap/4.5.2/css/bootstrap.min.css}" rel="stylesheet">

		<!-- Custom styles for this template -->
		<link href="../../static/asserts/css/dashboard.css" th:href="@{/asserts/css/dashboard.css}" rel="stylesheet">
    
 <!-- 因为我们修改了项目访问的路径(在8080/后面加了zhuge),所以原来的href肯定是访问不到静态资源的
     所以thymeleaf模板后,我们也不需要../../(返回上两层目录),直接可以从项目的根路径下寻找静态资源

我们不能再用../../, 否则在网页中会是  <link href="/zhuge/../../static/asserts/css/bootstrap.min.css" rel="stylesheet">   (这样没有意义,肯定还是访问不到静态资源的)  所以还是用上面代码里的方式比较好

   (拦截器里已经把静态资源对应的请求排除了)-->
  1. thymeleaf公共页面元素抽取
1、抽取公共片段
<div th:fragment="copy">
&copy; 2011 The Good Thymes Virtual Grocery
</div>

2、引入公共片段
<div th:insert="~{footer :: copy}"></div>
~{templatename::selector}:模板名::选择器
~{templatename::fragmentname}:模板名::片段名

3、默认效果:
insert的公共片段在div标签中
如果使用th:insert等属性进行引入,可以不用写~{}:
行内写法可以加上:[[~{}]];[(~{})];
  1. 三种引入公共片段的th属性:
    1. th:insert:将公共片段整个插入到声明引入的元素中
    2. th:replace:将声明引入的元素替换为公共片段
    3. th:include:将被引入的片段的内容包含进这个标签中
<footer th:fragment="copy">
&copy; 2011 The Good Thymes Virtual Grocery
</footer>
引入方式
<div th:insert="footer :: copy"></div>
<div th:replace="footer :: copy"></div>
<div th:include="footer :: copy"></div>
效果
<div>
    <footer>
    &copy; 2011 The Good Thymes Virtual Grocery
    </footer>
</div>
<footer>
&copy; 2011 The Good Thymes Virtual Grocery
</footer>
<div>
&copy; 2011 The Good Thymes Virtual Grocery
</div>
  1. 在dashboard.html中的公共部分代码
<nav class="navbar navbar-dark sticky-top bg-dark flex-md-nowrap p-0" th:fragment="topbar">
    											 <!--这里是要被公共提取的上边框th:fragment="topbar"-->
   <a class="navbar-brand col-sm-3 col-md-2 mr-0" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">[[${session.loginUser}]]</a>
                                                   <!--将登陆进去后的页面左上角改成当前用户的名字-->
   <input class="form-control form-control-dark w-100" type="text" placeholder="Search" aria-label="Search">
   <ul class="navbar-nav px-3">
      <li class="nav-item text-nowrap">
         <a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">Sign out</a>
      </li>
   </ul>
</nav>

<nav class="col-md-2 d-none d-md-block bg-light sidebar" id="sidebar">
          							<!--这里是要被公共提取的侧边框 id="sidebar"-->
  1. 在list.html中引入公共代码
<!--引入抽取的topbar-->
  <!--模板名: 会使用thymeleaf的前后缀配置规则进行解析-->
<div th:replace="~{dashboard::topbar}"></div>

<div class="container-fluid">
   <div class="row">
      <!--引入侧边栏-->
      <div th:replace="~{dashboard::#sidebar}"></div>
      <!--~{templatename::selector}:模板名::选择器-->
  1. 把公共部分的代码放入到一个文件夹(commons)下面

在这里插入图片描述

  1. 然后在list.html 和 dashboard.html页面进行引用
dashboard.html

<!--引入topbar-->
<div th:replace="commons/bar::topbar"></div>
<div class="container-fluid">
<div class="row">
<!--引入sidebar-->
 <div th:replace="commons/bar::#sidebar"></div>
       
list.html
       
<!--引入抽取的topbar-->
<!--模板名: 会使用thymeleaf的前后缀配置规则进行解析-->
<div th:replace="commons/bar::topbar"></div>

<div class="container-fluid">
<div class="row">
<!--引入侧边栏-->
<div th:replace="commons/bar::#sidebar"></div>
<!--~{templatename::selector}:模板名::选择器-->
  1. 引入片段的时候传入参数(来达成选择员工管理时高亮,Dashboard不亮,或者选择Dashboard时高亮,员工管理不亮):
list.html

<!--引入侧边栏-->
<div th:replace="commons/bar::#sidebar(activeUri='emps')"></div>
<!--~{templatename::selector}:模板名::选择器-->

dashboard.html

<!--引入sidebar-->
<div th:replace="commons/bar::#sidebar(activeUri='main.html')"></div>
 				
<!--它们在引入bar中的公共部分代码时,可以传入一个参数并给它赋值(然后在公共代码部分便可获取到对应的值并进行判断)-->

bar.html

<li class="nav-item">
 	<a class="nav-link active"
      th:class="${activeUri=='main.html'?'nav-link active':'nav-link'}"  href="#" th:href="@{/main.html}">
       
       <!--这里是用thymeleaf语法改变a标签的class属性值,
		如果接受的参数activeUri 是 main.html的话, 就将calss变成nav-link active(能够高亮),否则就没有active
-->
       
		<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-home">
			<path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"></path>
			<polyline points="9 22 9 12 15 12 15 22"></polyline>
		</svg>
 Dashboard <span class="sr-only">(current)</span>
	</a>
</li>

 <li class="nav-item">
      <a class="nav-link active" href="#"
       th:class="${activeUri=='emps'?'nav-link active':'nav-link'}" th:href="@{/emps}">
          
         <!--这里是用thymeleaf语法改变a标签的class属性值,
		如果接受的参数activeUri 是 emps的话, 就将calss变成nav-link active(能够高亮),否则就没有active
-->
        
             <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-users">
                     <path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path>
                     <circle cx="9" cy="7" r="4"></circle>
                     <path d="M23 21v-2a4 4 0 0 0-3-3.87"></path>
                     <path d="M16 3.13a4 4 0 0 1 0 7.75"></path>
             </svg>
员工管理
      </a>
</li>
    1. 将员工列表里的数据改为从请求域里的Map中获取(本来是假的数据–表格)
list.html

<main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4">
   <h2><button class="btn btn-sm btn-success">员工添加</button> </h2>
   <!--加入一个员工添加的按钮-->
   <div class="table-responsive">
      <table class="table table-striped table-sm">
         <thead>
            <tr>
               <th>#</th>
               <th>lastName</th>
               <th>email</th>
               <th>gender</th>
               <th>department</th>
               <th>birth</th>
               <th>操作</th>
            </tr>
         </thead>
         <tbody>
         <!--从请求域中Map中获取员工信息-->
         <tr th:each="emp:${emps}">
            <td th:text="${emp.id}"></td>
            <td>[[${emp.lastName}]]</td>
            <td th:text="${emp.email}"></td>
            <td th:text="${emp.gender}==1?'':''"></td>
            <td th:text="${emp.department.departmentName}"></td>
            <td th:text="${#dates.format(emp.birth,'yyyy-MM-dd HH:mm')}"></td>
            <!--用了thymeleaf工具类dates的格式化-->
            <td>
               <button class="btn btn-sm btn-primary">编辑</button>
               <button class="btn btn-sm btn-danger">删除</button>
            </td>
         </tr>
         </tbody>
      </table>
   </div>
</main>

​ 2. 处理器方法用于查询所有员工返回列表页面

//查询所有员工返回列表页面
@GetMapping("/emps")
public String list(Model model){
    Collection<Employee> employees = employeeDao.getAll();

    //放在请求域中
    model.addAttribute("emps",employees);
    //thymeleaf默认就会拼串
    //"classpath:/templates/xxxx.html"
    return "emp/list";

}

7、CRUD-员工添加

  1. 添加页面(add.html):其他部分代码与list.html里的一样,这里只是把显示员工的部分改为了一个表单
<main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4">
            <form>
                <div class="form-group">
                    <label>LastName</label>
                    <input type="text" class="form-control" placeholder="zhangsan">

                </div>
                <div class="form-group">
                    <label>Email</label>
                    <input type="email" class="form-control" placeholder="3272548251@qq.com">

                </div>
                <div class="form-group">
                    <label>Gender</label>
                    <div class="form-check form-check-inline">
                        <input type="radio" class="form-check-input" name="gender" value="1">
                        <label class="form-check-label"></label>
                    </div>
                    <div class="form-check form-check-inline">
                        <input type="radio" class="form-check-input" name="gender" value="0">
                        <label class="form-check-label"></label>
                    </div>
                </div>
                <div class="form-group">
                    <label>department</label>
                       <!--提交的是部门的id-->
                      <select class="form-control">
                          <option th:value="${dept.id}" th:each="dept:${depts}" th:text="${dept.departmentName}">1</option>
                           <!--使用th:each遍历来获取每个部门的名称, 注意提交时的value是部门的id-->
                      </select>
                </div>
                <div class="form-group">
                <label>Birth</label>
                <input type="text" class="form-control" placeholder="zhangsan">
            </div>
                <button type="submit" class="btn btn-primary">添加</button>
            </form>
        </main>
//来到员工添加页面
@GetMapping("/emp")
public String toAddPage(Model model){
    //来到添加页面,查出所有的部门,在页面显示(在表单中选择部门那一项会有下拉菜单里面显示的是部门名)
    Collection<Department> departments = departmentDao.getDepartments();
    model.addAttribute("depts",departments);
    return "emp/add";
}

//员工添加
//SpringMvc自动将请求参数和入参对象的属性进行一一绑定; 要求了请求参数的名字和javaBean入参的对象里面的属性名是一样的
@PostMapping("/emp")
public String addEmp(Employee employee){

    //来到员工列表页面

    System.out.println("保存的员工信息: "+employee);
    //保存员工
    employeeDao.save(employee);
    //redirect: 表示重定向到一个地址   “/”代表当前项目路径
    //forward: 表示转发到一个地址
    return "redirect:/emps";

}
  1. 添加员工时很容易发生的一个问题

    1. 提交的数据格式不对:生日:日期;
      2017-12-12;2017/12/12;2017.12.12;
    2. 日期的格式化;SpringMVC将页面提交的值需要转换为指定的类型;
      2017-12-12—Date; 类型转换,格式化;
      默认日期是按照 / 的方式;
    3. 其实这个问题在前面 Spring MVC auto-configuration 里有提到过
    DateTimeFormatters类:
    
    public DateTimeFormatters dateFormat(String pattern) {
       if (isIso(pattern)) {
          this.dateFormatter = DateTimeFormatter.ISO_LOCAL_DATE;
          this.datePattern = "yyyy-MM-dd"; //可以看到我们默认的日期格式
       }
       else {
          this.dateFormatter = formatter(pattern);
          this.datePattern = pattern;
       }
       return this;
    }
    
    WebMvcAutoConfiguration类:
    
    @Bean
    @Override
    public FormattingConversionService mvcConversionService() {
       Format format = this.mvcProperties.getFormat();//通过mvcProperties获取格式化信息,所以我们点到getFormat里去
       WebConversionService conversionService = new WebConversionService(new DateTimeFormatters()
             .dateFormat(format.getDate()).timeFormat(format.getTime()).dateTimeFormat(format.getDateTime()));
       addFormatters(conversionService);
       return conversionService;
    }
    
    WebMvcProperties类:
        
     @ConfigurationProperties(prefix = "spring.mvc")
    public class WebMvcProperties {
        
        private final Format format = new Format();
        。。。	
        
    	public Format getFormat() {
    		return this.format;
    	}
        。。。
         
    	public static class Format {
    
    		/**
    		 * Date format to use, for example `dd/MM/yyyy`.
    		 */
    		private String date;
        。。。
        }
        
        /*
        我们可以直接在springboot的配置文件里 用spring.mvc.(WebMvcProperties里的成员变量)
        来修改默认的配置
        */
    
    spring.mvc.format.date=yyyy-MM-dd
    

8、CRUD-员工修改

  1. 第十四行
<tbody>
<!--从请求域中Map中获取员工信息-->
<tr th:each="emp:${emps}">
   <td th:text="${emp.id}"></td>
   <td>[[${emp.lastName}]]</td>
   <td th:text="${emp.email}"></td>
   <td th:text="${emp.gender}==1?'':''"></td>
   <td th:text="${emp.department.departmentName}"></td>
   <td th:text="${#dates.format(emp.birth,'yyyy-MM-dd HH:mm')}"></td>
   <!--用了thymeleaf工具类dates的格式化-->
   <td>
      <a class="btn btn-sm btn-primary" th:href="@{/emp/}+${emp.id}">编辑</a>
       
       <!--在每一行员工数据的最右边添加修改链接, 并传入员工的id作为参数-->
       
      <a class="btn btn-sm btn-danger">删除</a>
   </td>
</tr>
</tbody>
  1. 写处理修改请求(回显)的处理器方法
//来到修改页面,查出当前员工, 在页面回显
@GetMapping("/emp/{id}")
public String toEditPage(Model model, @PathVariable("id") Integer id){
    Employee employee = employeeDao.get(id);
    model.addAttribute("emp",employee);

    //来到添加页面,查出所有的部门,在页面显示
    Collection<Department> departments = departmentDao.getDepartments();
    model.addAttribute("depts",departments);

    //回到修改页面(add是一个修改添加二合一的页面)
    return "emp/add";
}
  1. 因为是修改添加二合一的页面(add),所以要处理好如何判断到底是哪个

    ==${emp!=null}?==是判断的关键语句(三元运算)

<main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4">
            <!--需要区分是添加还是修改-->
            <form th:action="@{/emp}" method="post">
                <!--发送put请求修改员工数据-->
                <!--
                1、SpringMvc中配置HiddenHttpMethodFilter;(Springboot已经自动配置好了)
                2、页面创建一个post表单
                3、创建一个input项, name="_method";值就是我们指定的请求方式
                -->
                <input type="hidden" name="_method" value="put" th:if="${emp!=null}">
                
                <div class="form-group">
                    <label>LastName</label>
                    <input name="lastName" type="text" class="form-control" placeholder="zhangsan" th:value="${emp!=null}?${emp.lastName}">

                </div>
                <div class="form-group">
                    <label>Email</label>
                    <input name="email" type="email" class="form-control" placeholder="3272548251@qq.com" th:value="${emp!=null}?${emp.email}">

                </div>
                <div class="form-group">
                    <label>Gender</label>
                    <div class="form-check form-check-inline">
                        <input type="radio" class="form-check-input" name="gender" value="1" th:checked="${emp!=null}?${emp.gender==1}">
                        <label class="form-check-label"></label>
                    </div>
                    <div class="form-check form-check-inline">
                        <input type="radio" class="form-check-input" name="gender" value="0" th:checked="${emp!=null}?${emp.gender==0}">
                        <label class="form-check-label"></label>
                    </div>
                </div>
                <div class="form-group">
                    <label>department</label>
                       <!--提交的是部门的id-->
                      <select class="form-control" name="department.id">
                          <option th:selected="${emp!=null}?${dept.id == emp.id}" th:value="${dept.id}" th:each="dept:${depts}" th:text="${dept.departmentName}">1</option>
                      </select>
                </div>
                <div class="form-group">
                <label>Birth</label>
                <input name="birth" type="text" class="form-control" placeholder="zhangsan" th:value="${emp!=null}?${#dates.format(emp.birth,'yyyy-MM-dd HH:mm')}">
            </div>
                <button type="submit" class="btn btn-primary" th:text="${emp!=null}?'修改':'添加'">添加</button>
            </form>
  1. 写处理修改请求(保存)的处理器方法
//员工修改:需要提交员工id
@PutMapping("/emp")
public String updateEmployee(Employee employee){
    System.out.println("修改的员工数据:"+employee);
    employeeDao.save(employee);
    return "redirect:/emps";

}
<input type="hidden" name="_method" value="put" th:if="${emp!=null}">
<!--其余代码见上面第二个代码块-->

<input type="hidden" name="id" th:if="${emp!=null}" th:value="${emp.id}">
<!--这个标签用于修改时回显员工id-->

9、CRUD-员工删除

  1. 在list页面的删除按钮的位置
list.html

<!--用了thymeleaf工具类dates的格式化-->
<td>
   <a class="btn btn-sm btn-primary" th:href="@{/emp/}+${emp.id}">编辑</a>
   <button  th:attr="del_uri=@{/emp/}+${emp.id}" class="btn btn-sm btn-danger deleteBtn">删除</button>
    <!--th:attr 是为了添加自定义的属性,del_uri是用来改变删除员工及他的id的请求的地址的-->

</td>
</main>
	<form id="deleteEmpForm"  method="post">
		<input type="hidden" name="_method" value="delete">
	</form>
				<!--这里我猜测Springboot的自动配置的HiddenHttpMethodFilter没有帮我把post改为delete,所以处理器里我又改成了post-->


<!--用jQuery的语法来使表单发出删除员工及他的id的请求-->
<script>
  $(".deleteBtn").click(function() {
		//删除当前员工的
		$("#deleteEmpForm").attr("action",$(this).attr("del_uri")).submit();
		return false;
  });
	   </script>
  1. 用于处理删除员工的处理器方法
//员工删除
@PostMapping("/emp/{id}")//老师用的是DeleteMapping(不过他的版本低,我猜可能后面Springboot的自动配置的HiddenHttpMethodFilter变了,但具体是啥我也不清楚,哈哈~)
public String deleteEmployee(@PathVariable("id") Integer id){
    employeeDao.delete(id);
    return "redirect:/emps";
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值