SpringBoot2(G笔记)

目录

SpringBoot2核心技术雷神笔记
springboot官方文档

基础入门

一 SpringBoot2入门

1、系统要求

● Java 8 & 兼容java14 .
● Maven 3.3+
● idea 2019.1.2

1.1、maven设置

修改 D:\apache-maven-3.5.2\conf\settings.xml 参数

<mirrors>
    <mirror>
      <id>nexus-aliyun</id>
      <mirrorOf>central</mirrorOf>
      <name>Nexus aliyun</name>
      <url>http://maven.aliyun.com/nexus/content/groups/public</url>
    </mirror>
</mirrors>
 
<profiles>
       <profile>
            <id>jdk-1.8</id>
            <activation>
              <activeByDefault>true</activeByDefault>
              <jdk>1.8</jdk>
            </activation>
            <properties>
              <maven.compiler.source>1.8</maven.compiler.source>
              <maven.compiler.target>1.8</maven.compiler.target>
              <maven.compiler.compilerVersion>1.8</maven.compiler.compilerVersion>
            </properties>
       </profile>
</profiles>

2、HelloWorld

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

2.1、创建maven工程

create project -> maven -> 不勾create ->

2.2、引入依赖

在pom.xml中添加

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.3.4.RELEASE</version>
</parent>

<!--web场景启动器-->
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

</dependencies>

2.3、创建主程序

在src/main/java下新建class: com.yu.boot.MainApplication

/**
 * 主程序类
 * @SpringBootApplication:这是一个SpringBoot应用
 */
@SpringBootApplication
public class MainApplication {

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

2.4、编写业务

在src/main/java下新建class: controller.HelloController

@RestController
public class HelloController {


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

}

2.5、测试

直接运行main方法

2.6、简化配置

配置列表
在resources下新建:
application.properties

server.port=8888

2.7、简化部署

在pom.xml中添加

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>
把项目打成jar包,直接在目标服务器执行即可。

存放在

执行jar包

java -jar boot-01-helloworld-1.0-SNAPSHOT.jar

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

二 了解自动配置原理

1、SpringBoot特点

1.1、依赖管理

● 父项目做依赖管理

依赖管理    
<parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.4.RELEASE</version>
</parent>

他的父项目
 <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-dependencies</artifactId>
    <version>2.3.4.RELEASE</version>
  </parent>

几乎声明了所有开发中常用的依赖的版本号,自动版本仲裁机制

● 开发导入starter场景启动器

1、见到很多 spring-boot-starter-* : *就某种场景
2、只要引入starter,这个场景的所有常规需要的依赖我们都自动引入
3、SpringBoot所有支持的场景

https://docs.spring.io/spring-boot/docs/current/reference/html/using-spring-boot.html#using-boot-starter

4、见到的  *-spring-boot-starter: 第三方为我们提供的简化开发的场景启动器。
5、所有场景启动器最底层的依赖
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter</artifactId>
  <version>2.3.4.RELEASE</version>
  <scope>compile</scope>
</dependency>

● 无需关注版本号,自动版本仲裁

1、引入依赖默认都可以不写版本
如:
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
    </dependency>
2、引入非版本仲裁的jar,要写版本号。

● 可以修改默认版本号

1、查看spring-boot-dependencies里面规定当前依赖的版本 用的 key。
2、在当前项目里面重写配置
  在pom.xml中添加

    <properties>
        <mysql.version>5.1.43</mysql.version>
    </properties>

1.2、自动配置

● 自动配好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开发的常见场景
● 默认的包结构
  ○ 主程序所在包及其下面的所有子包里面的组件都会被默认扫描进来
        比如创建一个WorldController,放在yu下就不会被默认扫描
  ○ 无需以前的包扫描配置
  ○ 想要改变扫描路径,在主程序类中修改为 @SpringBootApplication(scanBasePackages="com.yu")
       或者@ComponentScan 指定扫描路径
@SpringBootApplication
等同于
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan("com.yu.boot")
● 各种配置拥有默认值
  ○ 默认配置最终都是映射到某个类上,如:MultipartProperties
  ○ 配置文件的值最终会绑定某个类上,这个类会在容器中创建对象
● 按需加载所有自动配置项
  ○ 非常多的starter
  ○ 引入了哪些场景这个场景的自动配置才会开启
  ○ SpringBoot所有的自动配置功能都在 spring-boot-autoconfigure 包里面
  ○

2、容器功能

添加yu.boot.bean.Pet和User
Pet

package com.yu.boot.bean;

/**
 * 宠物
 */
public class Pet {
    private String name;

    public Pet() {
    }

    public Pet(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Pet{" +
                "name='" + name + '\'' +
                '}';
    }
}


User

package com.yu.boot.bean;

public class User {
    private String name;
    private Integer age;

    private Pet pet;
    public User() {
    }

    public User(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public Pet getPet() {
        return pet;
    }

    public void setPet(Pet pet) {
        this.pet = pet;
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", pet=" + pet +
                '}';
    }
}


在resources下new一个 xml configuration file ->spring config -> beans.xml
beans.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="user01" class="com.yu.boot.bean.User">
        <property name="name" value="zhangsan"></property>
        <property name="age" value="13"></property>
    </bean>

    <bean id="cat" class="com.yu.boot.bean.Pet">
        <property name="name" value="tomcat"></property>
    </bean>
</beans>
  • 上面是用Spring的xml配置方式,springboot中不需要

2.1 组件添加

1、@Configuration

组件单实例

● 基本使用
  配置类里面使用@Bean标注在方法上给容器注册组件,默认也是单实例的
  配置类本身也是组件
● Full模式与Lite模式
  ○ 示例
  ○ 最佳实战
  ■ 配置 类组件之间无依赖关系用Lite模式加速容器启动过程,减少判断
  ■ 配置类组件之间有依赖关系,方法会被调用得到之前单实例组件,用Full模式
 *      Full(proxyBeanMethods = true)、【保证每个@Bean方法被调用多少次返回的组件都是单实例的】 (默认)
 *      Lite(proxyBeanMethods = false)【轻量级模式,每个@Bean方法被调用多少次返回的组件都是新创建的】
 *      组件依赖必须使用Full模式(默认)。其他可使用 Lite模式

在boot下新建一个class: config.MyConfig

#############################Configuration使用示例######################################################
/**
 * 1、配置类里面使用@Bean标注在方法上给容器注册组件,默认也是单实例的
 * 2、配置类本身也是组件
 * 3、proxyBeanMethods:代理bean的方法
 *      Full(proxyBeanMethods = true)、【保证每个@Bean方法被调用多少次返回的组件都是单实例的】
 *      Lite(proxyBeanMethods = false)【轻量级模式,每个@Bean方法被调用多少次返回的组件都是新创建的】
 *      组件依赖必须使用Full模式默认。其他默认是否Lite模式
 *
 *
 *
 */
@Configuration(proxyBeanMethods = false) //告诉SpringBoot这是一个配置类 == 配置文件 默认proxyBeanMethods是true
public class MyConfig {

    /**
     * Full:外部无论对配置类中的这个组件注册方法调用多少次获取的都是之前注册容器中的单实例对象
     * @return
     */
    @Bean //给容器中添加组件。以方法名作为组件的id。返回类型就是组件类型。返回的值,就是组件在容器中的实例
    public User user01(){
        User zhangsan = new User("zhangsan", 18);
        //user组件依赖了Pet组件
        zhangsan.setPet(tomcatPet());
        return zhangsan;       
    }

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

MainApplication.java


################################@Configuration测试代码如下########################################
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan("com.yu.boot")
public class MainApplication {

    public static void main(String[] args) {
        //1、返回我们IOC容器
        ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);

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

        //3、从容器中获取组件

        Pet tom01 = run.getBean("tom", Pet.class);

        Pet tom02 = run.getBean("tom", Pet.class);

        System.out.println("组件:"+(tom01 == tom02));


        //com.yu.boot.config.MyConfig$$EnhancerBySpringCGLIB$$9b515a23@535b8c24
        MyConfig bean = run.getBean(MyConfig.class);
        System.out.println(bean);

        //如果@Configuration(proxyBeanMethods = true)代理对象调用方法。SpringBoot总会检查这个组件是否在容器中有。
        //保持组件单实例
        User user = bean.user01();
        User user1 = bean.user01();
        System.out.println(user == user1);


        User user01 = run.getBean("user01", User.class);
        Pet tom = run.getBean("tom", Pet.class);

        System.out.println("用户的宠物是不是容器的:"+(user01.getPet() == tom));

        //5 获取组件
        String[] beanNamesForType = run.getBeanNamesForType(User.class);
        System.out.println("=====================");
        for (String s : beanNamesForType) {
            System.out.println(s);
        }

        DBHelper bean1 = run.getBean(DBHelper.class);
        System.out.println(bean1);

    }
}

2、@Component等

@Component:泛指各种组件(把普通pojo实例化到spring容器中,相当于配置文件中的
@Controller:控制器(注入服务)
@Service:业务逻辑组件(注入dao)
@Repository:数据库层组件(实现dao访问)

3、@ComponentScan:指定包扫描规则,
4、@Import:导入组件 (导入配置类或者一些需要前置加载的类)
 /* 4、@Import({User.class, DBHelper.class})
 *      给容器中自动创建出这两个类型的组件、默认组件的名字就是全类名
 *
 */

@Import({User.class, DBHelper.class})
@Configuration(proxyBeanMethods = false) //告诉SpringBoot这是一个配置类 == 配置文件
public class MyConfig {
}

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

5、@Conditional

条件装配:满足Conditional指定的条件,则进行组件注入
ctrl+N 搜素 @Conditional 然后ctrl+H打开继承树

@ConditionalOnBean :容器中有指定组件时才
@ConditionalOnMissingBean :容器中没有指定组件时才
@ConditionalOnClass :容器中有某个类时才
@ConditionalOnResource :项目的类路径中存在某一资源时才
@ConditionalOnJava :是某一指定版本号时才
@ConditionalOnWebApplication :当是一个web应用时才
@ConditionalOnNotWebApplication :当不是一个web应用时才
@ConditionalOnSingleCandidate :容器中指定组件只有一个实例,或有一个主实例
@ConditionalOnProperty :当配置文件中配置了某个属性的时候

MyConfig.java

=====================测试条件装配==========================
@Configuration(proxyBeanMethods = false) //告诉SpringBoot这是一个配置类 == 配置文件
//@ConditionalOnBean(name = "tom")
@ConditionalOnMissingBean(name = "tom")
public class MyConfig {


    /**
     * Full:外部无论对配置类中的这个组件注册方法调用多少次获取的都是之前注册容器中的单实例对象
     * @return
     */
    //@ConditionalOnBean(name = "tom")
    @Bean //给容器中添加组件。以方法名作为组件的id。返回类型就是组件类型。返回的值,就是组件在容器中的实例
    public User user01(){
        User zhangsan = new User("zhangsan", 18);
        //user组件依赖了Pet组件
        zhangsan.setPet(tomcatPet());
        return zhangsan;
    }

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

MainApplication.java

public static void main(String[] args) {
        //1、返回我们IOC容器
        ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);

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

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

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

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


    }

2.2、原生xml配置文件引入

1、@ImportResource 导入资源

beans.xml

======================beans.xml=========================
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       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.atguigu.boot.bean.User">
        <property name="name" value="zhangsan"></property>
        <property name="age" value="18"></property>
    </bean>

    <bean id="hehe" class="com.atguigu.boot.bean.Pet">
        <property name="name" value="tomcat"></property>
    </bean>
</beans>

MyConfig.java

@ImportResource("classpath:beans.xml")
public class MyConfig {}

MainApplication.java

        //======================测试=================
        boolean haha = run.containsBean("haha");
        boolean hehe = run.containsBean("hehe");
        System.out.println("haha:"+haha);//true
        System.out.println("hehe:"+hehe);//true

2.3、配置绑定

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

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

application.properties

mycar.brand = BYD
mycar.price = 10000000
1、@Component + @ConfigurationProperties

在beans下新建一个 Car

/**
 * 只有在容器中的组件,才会拥有SpringBoot提供的强大功能 所以需要@Component
 */
@Component
@ConfigurationProperties(prefix = "mycar")
public class Car {

    private String brand;
    private Integer price;

    public String getBrand() {
        return brand;
    }

    public void setBrand(String brand) {
        this.brand = brand;
    }

    public Integer getPrice() {
        return price;
    }

    public void setPrice(Integer price) {
        this.price = price;
    }

    @Override
    public String toString() {
        return "Car{" +
                "brand='" + brand + '\'' +
                ", price=" + price +
                '}';
    }
}

测试
HelloController.java

    @Autowired
    Car car;
    
    @RequestMapping("/car")
    public Car car(){
        return car;
    }
2、@EnableConfigurationProperties + @ConfigurationProperties

MyConfig.java

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

这样就可以不用加@Component
Car.java

//@Component
@ConfigurationProperties(prefix = "mycar")
public class Car {


测试
HelloController.java

    @Autowired
    Car car;
    
    @RequestMapping("/car")
    public Car car(){
        return car;
    }

3、自动配置原理入门

3.1、引导加载自动配置类

@SpringBootApplication 相当于以下三个的合成注解

@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication{}


======================
    

1、@SpringBootConfiguration
相当于@Configuration。代表当前是一个配置类

2、@ComponentScan
包扫描,指定扫描哪些包

3、@EnableAutoConfiguration

@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {}

3.1、@AutoConfigurationPackage
自动配置包 指定了默认的包规则

@Import(AutoConfigurationPackages.Registrar.class)  //给容器中导入一个组件
public @interface AutoConfigurationPackage {}

//利用Registrar给容器中导入一系列组件  批量注册
//将MainApplication 所在包下的所有组件导入进来

3.2、@Import(AutoConfigurationImportSelector.class)

1、AutoConfigurationImportSelector 利用getAutoConfigurationEntry(annotationMetadata);给容器中批量导入一些组件
2、调用List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes)获取到所有需要导入到容器中的配置类
3、getCandidateConfigurations内部利用Spring的工厂加载器加载得到,内部最终返回 Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader);得到所有的组件
4、loadSpringFactories最终是从META-INF/spring.factories位置来加载一个文件。
	默认扫描我们当前系统里面所有META-INF/spring.factories位置的文件

    spring-boot-autoconfigure-2.3.4.RELEASE.jar包里面有META-INF/spring.factories
    

spring.factories文件里面写死了spring-boot一启动就要给容器中加载的所有配置类,和上面的127个相对应
spring-boot-autoconfigure-2.3.4.RELEASE.jar/META-INF/spring.factories
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\
org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\
org.springframework.boot.autoconfigure.context.LifecycleAutoConfiguration,\
org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration,\
org.springframework.boot.autoconfigure.couchbase.CouchbaseAutoConfiguration,\
org.springframework.boot.autoconfigure.dao.PersistenceExceptionTranslationAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraReactiveDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraReactiveRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseReactiveDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseReactiveRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ReactiveElasticsearchRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ReactiveElasticsearchRestClientAutoConfiguration,\
org.springframework.boot.autoconfigure.data.jdbc.JdbcRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.ldap.LdapRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoReactiveDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoReactiveRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.neo4j.Neo4jDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.neo4j.Neo4jRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.solr.SolrRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.r2dbc.R2dbcDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.r2dbc.R2dbcRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.r2dbc.R2dbcTransactionManagerAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisReactiveAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.rest.RepositoryRestMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.data.web.SpringDataWebAutoConfiguration,\
org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchRestClientAutoConfiguration,\
org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration,\
org.springframework.boot.autoconfigure.freemarker.FreeMarkerAutoConfiguration,\
org.springframework.boot.autoconfigure.groovy.template.GroovyTemplateAutoConfiguration,\
org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration,\
org.springframework.boot.autoconfigure.h2.H2ConsoleAutoConfiguration,\
org.springframework.boot.autoconfigure.hateoas.HypermediaAutoConfiguration,\
org.springframework.boot.autoconfigure.hazelcast.HazelcastAutoConfiguration,\
org.springframework.boot.autoconfigure.hazelcast.HazelcastJpaDependencyAutoConfiguration,\
org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration,\
org.springframework.boot.autoconfigure.http.codec.CodecsAutoConfiguration,\
org.springframework.boot.autoconfigure.influx.InfluxDbAutoConfiguration,\
org.springframework.boot.autoconfigure.info.ProjectInfoAutoConfiguration,\
org.springframework.boot.autoconfigure.integration.IntegrationAutoConfiguration,\
org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.JndiDataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.XADataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.JmsAutoConfiguration,\
org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.JndiConnectionFactoryAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.activemq.ActiveMQAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.artemis.ArtemisAutoConfiguration,\
org.springframework.boot.autoconfigure.jersey.JerseyAutoConfiguration,\
org.springframework.boot.autoconfigure.jooq.JooqAutoConfiguration,\
org.springframework.boot.autoconfigure.jsonb.JsonbAutoConfiguration,\
org.springframework.boot.autoconfigure.kafka.KafkaAutoConfiguration,\
org.springframework.boot.autoconfigure.availability.ApplicationAvailabilityAutoConfiguration,\
org.springframework.boot.autoconfigure.ldap.embedded.EmbeddedLdapAutoConfiguration,\
org.springframework.boot.autoconfigure.ldap.LdapAutoConfiguration,\
org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration,\
org.springframework.boot.autoconfigure.mail.MailSenderAutoConfiguration,\
org.springframework.boot.autoconfigure.mail.MailSenderValidatorAutoConfiguration,\
org.springframework.boot.autoconfigure.mongo.embedded.EmbeddedMongoAutoConfiguration,\
org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration,\
org.springframework.boot.autoconfigure.mongo.MongoReactiveAutoConfiguration,\
org.springframework.boot.autoconfigure.mustache.MustacheAutoConfiguration,\
org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration,\
org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration,\
org.springframework.boot.autoconfigure.r2dbc.R2dbcAutoConfiguration,\
org.springframework.boot.autoconfigure.rsocket.RSocketMessagingAutoConfiguration,\
org.springframework.boot.autoconfigure.rsocket.RSocketRequesterAutoConfiguration,\
org.springframework.boot.autoconfigure.rsocket.RSocketServerAutoConfiguration,\
org.springframework.boot.autoconfigure.rsocket.RSocketStrategiesAutoConfiguration,\
org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration,\
org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration,\
org.springframework.boot.autoconfigure.security.servlet.SecurityFilterAutoConfiguration,\
org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration,\
org.springframework.boot.autoconfigure.security.reactive.ReactiveUserDetailsServiceAutoConfiguration,\
org.springframework.boot.autoconfigure.security.rsocket.RSocketSecurityAutoConfiguration,\
org.springframework.boot.autoconfigure.security.saml2.Saml2RelyingPartyAutoConfiguration,\
org.springframework.boot.autoconfigure.sendgrid.SendGridAutoConfiguration,\
org.springframework.boot.autoconfigure.session.SessionAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.client.servlet.OAuth2ClientAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.client.reactive.ReactiveOAuth2ClientAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.resource.servlet.OAuth2ResourceServerAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.resource.reactive.ReactiveOAuth2ResourceServerAutoConfiguration,\
org.springframework.boot.autoconfigure.solr.SolrAutoConfiguration,\
org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration,\
org.springframework.boot.autoconfigure.task.TaskSchedulingAutoConfiguration,\
org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration,\
org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration,\
org.springframework.boot.autoconfigure.transaction.jta.JtaAutoConfiguration,\
org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration,\
org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration,\
org.springframework.boot.autoconfigure.web.embedded.EmbeddedWebServerFactoryCustomizerAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.HttpHandlerAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.ReactiveWebServerFactoryAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.error.ErrorWebFluxAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.function.client.ClientHttpConnectorAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.HttpEncodingAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.MultipartAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.websocket.reactive.WebSocketReactiveAutoConfiguration,\
org.springframework.boot.autoconfigure.websocket.servlet.WebSocketServletAutoConfiguration,\
org.springframework.boot.autoconfigure.websocket.servlet.WebSocketMessagingAutoConfiguration,\
org.springframework.boot.autoconfigure.webservices.WebServicesAutoConfiguration,\
org.springframework.boot.autoconfigure.webservices.client.WebServiceTemplateAutoConfiguration

3.2、按需开启自动配置项

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

3.3、修改默认配置

在看源码过程中发现一个有趣的组件注入
可以防止有些用户配置的文件上传解析器不符合规范

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

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

        @Bean
	@ConditionalOnMissingBean
	public CharacterEncodingFilter characterEncodingFilter() {
    }

总结:

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

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

3.4 实践

SpringBoot应用如何编写?

● 引入场景依赖
    ○ https://docs.spring.io/spring-boot/docs/current/reference/html/using-spring-boot.html#using-boot-starter

https://docs.spring.io/spring-boot/docs/current/reference/html/using-spring-boot.html#using-boot-starter

● 查看自动配置了哪些(选做)
    ○ 自己分析,引入场景对应的自动配置一般都生效了
    ○ 配置文件application.properties中 debug=true 开启自动配置报告。      Negative(不生效)\Positive(生效)
● 是否需要修改
    ○ 参照文档修改配置项
        ■ https://docs.spring.io/spring-boot/docs/current/reference/html/appendix-application-properties.html#common-application-properties

https://docs.spring.io/spring-boot/docs/current/reference/html/appendix-application-properties.html#common-application-properties

        ■ 自己分析。xxxxProperties绑定了配置文件的哪些。                              
例如:


将文件导入到resources下
那么在application.properties添加如下代码即可

spring.banner.image.location=classpath:222.jpg
    ○ 自定义加入或者替换组件
        ■ @Bean、@Component。。。
    ○ 自定义器  XXXXXCustomizer;
......

4、开发小技巧

4.1、Lombok

pom.xml

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

idea中搜索安装 lombok 插件(settings的plugins)

简化JavaBean开发

在User中添加 @Data 和 @ToString
@AllArgsConstructor 全参构造器
@NoArgsConstructor 无参构造器
@EqualsAndHashCode 重写hashcode和equals方法

//===============================简化JavaBean开发===================================
@NoArgsConstructor
//@AllArgsConstructor
@Data
@ToString
@EqualsAndHashCode
public class User {

    private String name;
    private Integer age;

    private Pet pet;

    public User(String name,Integer age){
        this.name = name;
        this.age = age;
    }


}
简化日志开发

在HelloController.java中添加
@Slf4j
就可以添加 log.info

  //================================简化日志开发===================================
@Slf4j
@RestController
public class HelloController {
    @RequestMapping("/hello")
    public String handle01(@RequestParam("name") String name){
        
        log.info("请求进来了....");
        
        return "Hello, Spring Boot 2!"+"你好:"+name;
    }
}

4.2、dev-tools 热更新(重启)

在pom.xml中添加

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

项目或者页面修改以后:Ctrl+F9 就可以实现热更新

4.3、Spring Initailizr(项目初始化向导)

0、选择我们需要的开发场景

new project -> Spring Initializr -> default ->


如果没有对应版本,可以进去修改

	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.3.4.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>

删去暂时没用的

和.mvn

1、自动依赖引入

2、自动创建项目结构

3、自动编写好主配置类

核心功能

一 配置文件

1、文件类型

1.1、properties

同以前的properties用法

1.2、yaml

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

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


1.2.2、基本语法
● key: value;kv之间有空格
● 大小写敏感
● 使用缩进表示层级关系
● 缩进不允许使用tab,只允许空格(IDEA用了也没事)
● 缩进的空格数不重要,只要相同层级的元素左对齐即可
● '#'表示注释
● 字符串无需加引号,如果要加,''与""表示字符串内容 会被 转义/不转义
    单引号会将 \n 作为字符串输出   双引号会将 \n 作为换行输出
    单引号会转义                 双引号不会转义 
1.2.3、数据类型

● 字面量:单个的、不可再分的值。date、boolean、string、number、null

k: v

● 对象:键值对的集合。map、hash、set、object

行内写法:  k: {k1:v1,k2:v2,k3:v3}
#或
k: 
  k1: v1
  k2: v2
  k3: v3

● 数组:一组按次序排列的值。array、list、queue

行内写法:  k: [v1,v2,v3]
#或者
k:
 - v1
 - v2
 - v3
1.2.4、示例

在boot下新建bean.Person.java 和bean.Pet.java

@ConfigurationProperties(prefix = "person")
@Component
@ToString
@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;
}

@ToString
@Data
public class Pet {
	private String name;
	private Double weight;
}

用yaml表示以上对象
在resources下新建 application.yml

# yaml表示以上对象
person:
# 字符串无需加引号,如果要加,''与""表示字符串内容 会被 转义/不转义
#    单引号会将 \n 作为字符串输出   双引号会将 \n 作为换行输出
#    单引号会转义                 双引号不会转义
  userName: "zhangsan \n 李四"
  boss: false
  birth: 2019/12/12 20:12:33
  age: 18
  pet: 
    name: tomcat
    weight: 23.4
  interests: [篮球,游泳]
  animal: 
    - jerry
    - mario
  score:
    english: 
      first: 30
      second: 40
      third: 50
    math: [131,140,148]
    chinese: {first: 128,second: 136}
  salarys: [3999,4999.98,5999.99]
  allPets:
    sick:
      - {name: tom}
      - {name: jerry,weight: 47}
    health: [{name: mario,weight: 47}]

测试
在boot下新建 controller.helloController

@RestController
public class helloController {
    @Autowired
    Person person;

    @RequestMapping("/person")
    public Person person(){
        String userName = person.getUserName();
        System.out.println(userName);
        return person;
    }
}

若报Failed to configure a DataSource: 'url' attribute is not specified and no embedded datasource could be configured.
则把pom.xml里中的redis和mybatis注释掉。此处不需要数据库

2、配置提示

自定义的类和配置文件绑定一般没有提示。可以通过如下配置让其有提示
pom.xml

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




	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
                                <configuration>
                                    <excludes>
                                        <exclude>
                                            <groupId>org.springframework.boot</groupId>
                                            <artifactId>spring-boot-configuration-processor</artifactId>
                                        </exclude>
                                    </excludes>
                                </configuration>
			</plugin>
		</plugins>
	</build>

二 Web开发

https://www.yuque.com/atguigu/springboot/vgzmgh

1、SpringMVC自动配置概览

Spring Boot provides auto-configuration for Spring MVC that works well with most applications.(大多场景我们都无需自定义配置)
The auto-configuration adds the following features on top of Spring’s defaults:

● Inclusion of ContentNegotiatingViewResolver and BeanNameViewResolver beans.
  ○ 内容协商视图解析器和BeanName视图解析器
● Support for serving static resources, including support for WebJars (covered later in this document)).
  ○ 静态资源(包括webjars)
● Automatic registration of Converter, GenericConverter, and Formatter beans.
  ○ 自动注册 Converter,GenericConverter,Formatter 
● Support for HttpMessageConverters (covered later in this document).
  ○ 支持 HttpMessageConverters (后来我们配合内容协商理解原理)
● Automatic registration of MessageCodesResolver (covered later in this document).
  ○ 自动注册 MessageCodesResolver (国际化用)
● Static index.html support.
  ○ 静态index.html 页支持
● Custom Favicon support (covered later in this document).
  ○ 自定义 Favicon  
● Automatic use of a ConfigurableWebBindingInitializer bean (covered later in this document).
  ○ 自动使用 ConfigurableWebBindingInitializer ,(DataBinder负责将请求数据绑定到JavaBean上)
If you want to keep those Spring Boot MVC customizations and make more MVC customizations 
(interceptors, formatters, view controllers, and other features), you can add your own @Configuration class 
of type WebMvcConfigurer but without @EnableWebMvc.
不用@EnableWebMvc注解。使用 @Configuration + WebMvcConfigurer 自定义规则
If you want to provide custom instances of RequestMappingHandlerMapping, 
RequestMappingHandlerAdapter, or ExceptionHandlerExceptionResolver, and still keep the Spring 
Boot MVC customizations, you can declare a bean of type WebMvcRegistrations and use it to provide 
custom instances of those components.
声明 WebMvcRegistrations 改变默认底层组件
If you want to take complete control of Spring MVC, you can add your own @Configuration annotated with 
@EnableWebMvc, or alternatively add your own @Configuration-annotated 
DelegatingWebMvcConfiguration as described in the Javadoc of @EnableWebMvc.
使用 @EnableWebMvc+@Configuration+DelegatingWebMvcConfiguration 全面接管SpringMVC

2、简单功能分析

new project -> Spring Initializr -> default -> com.yu->boot-05-web-01->
web -> springweb
developer tools ->

resources下新建 application.yaml

2.1、静态资源访问

1、静态资源目录
只要静态资源放在类路径下:  /static (or /public or /resources or /META-INF/resources
访问 : 当前项目根路径/ + 静态资源名  如  localhost:8080/1.jpg

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

改变默认的静态资源路径
spring:
#改变前缀
  mvc:
    static-path-pattern: /res/**
#改变默认的静态资源路径
  resources:
    static-locations: [classpath:/haha/]

2、静态资源访问前缀

默认无前缀
可以通过以下代码改变前缀

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

当前项目 + static-path-pattern + 静态资源名 = 静态资源文件夹下找
即 localhost:8080/res/1.jpg

3、webjar

自动映射 /webjars/**
https://www.webjars.org/

pom.xml

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

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

2.2、欢迎页支持

● 静态资源路径下  index.html
  ○ 可以配置静态资源路径
  ○ 但是不可以配置静态资源的访问前缀。否则导致 index.html不能被默认访问
spring:
#  mvc:
#    static-path-pattern: /res/**   这个会导致welcome page功能失效

  resources:
    static-locations: [classpath:/haha/]
● controller能处理/index

2.3、自定义 Favicon (访问标签小图标)

把文件重命名为 favicon.ico 放在静态资源目录下即可。

spring:
#  mvc:
#    static-path-pattern: /res/**   这个会导致 Favicon 功能失效

2.4、静态资源配置原理(**)

● SpringBoot启动默认加载 xxxAutoConfiguration 类(自动配置类)
● SpringMVC功能的自动配置类 WebMvcAutoConfiguration,生效

@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,
		ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {}

● 给容器中配了什么。

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

● 配置文件的相关属性和xxx进行了绑定。

WebMvcProperties==spring.mvc、
ResourceProperties==spring.resources
1、配置类只有一个有参构造器
//有参构造器所有参数的值都会从容器中确定
//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;
		}
2、资源处理的默认规则
                @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();
			//webjars的规则
                        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));
			}
		}
spring:
#  mvc:
#    static-path-pattern: /res/**

  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/" };

	/**
	 * Locations of static resources. Defaults to classpath:[/META-INF/resources/,
	 * /resources/, /static/, /public/].
	 */
	private String[] staticLocations = CLASSPATH_RESOURCE_LOCATIONS;
3、欢迎页的处理规则
		HandlerMapping:处理器映射。保存了每一个Handler能处理哪些请求。	

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




	WelcomePageHandlerMapping(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");
		}
	}

4、favicon

3、请求参数处理

0、请求映射

1、rest使用与原理(**)
● @xxxMapping;
● Rest风格支持(使用HTTP请求方式动词来表示对资源的操作)
      ○ 以前:/getUser   获取用户     /deleteUser 删除用户    /editUser  修改用户       /saveUser 保存用户
      ○ 现在: /user    GET-获取用户    DELETE-删除用户     PUT-修改用户      POST-保存用户
      ○ 核心Filter;HiddenHttpMethodFilter
            ■ 用法: 表单method=post,隐藏域 _method=put
            ■ SpringBoot中手动开启
      ○  扩展:如何把_method 这个名字换成我们自己喜欢的。
Rest原理(表单提交要使用REST的时候)
  ● 表单提交带上_method=PUT
  ● 请求过来被HiddenHttpMethodFilter拦截
    ○ 请求是否正常,并且是POST
      ■ 获取到_method的值。
      ■ 兼容以下请求;PUT.DELETE.PATCH
      ■ 原生request(post),包装模式requesWrapper重写了getMethod方法,返回的是传入的值。
      ■ 过滤器链放行的时候用wrapper。以后的方法调用getMethod是调用requesWrapper的。
源码中会将get转成大写,所以表单提交时大小写都可

Rest使用客户端工具,
  ● 如PostMan直接发送Put、delete等方式请求,无需开启这个Filter。

源码

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

开启页面表单的Rest功能

spring:
  mvc:
    hiddenmethod:
      filter:
        enabled: true   #开启页面表单的Rest功能

测试REST风格

测试REST风格
    <form action="/user" method="get">
        <input value="REST-get 提交" type="submit">
    </form>
    
    <form action="/user" method="post">
        <input value="REST-post 提交" type="submit">
    </form>
    
    <form action="/user" method="post">
        <input type="hidden" name="_method" value="PUT">
        <input value="REST-put 提交" type="submit">
    </form>
    
    <form action="/user" method="post">
        <input type="hidden" name="_method" value="DELETE">
        <input value="REST-delete 提交" type="submit">
    </form>
//    @RequestMapping(value = "/user",method = RequestMethod.GET)
    @GetMapping("/user")
    public String getUser(){
        return "GET-张三";
    }

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

//    @RequestMapping(value = "/user",method = RequestMethod.PUT)
    @PutMapping("/user")
    public String putUser(){
        return "PUT-张三";
    }

//    @RequestMapping(value = "/user",method = RequestMethod.DELETE)
    @DeleteMapping("/user")
    public String deleteUser(){
        return "DELETE-张三";
    }

如何把_method 这个名字换成我们自己喜欢的?
可以通过自定义filter
在com.yu.boot.config下

package com.yu.boot.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.filter.HiddenHttpMethodFilter;


@Configuration(proxyBeanMethods = false)
public class WebConfig {
    
    @Bean
    public HiddenHttpMethodFilter hiddenHttpMethodFilter(){
        HiddenHttpMethodFilter methodFilter = new HiddenHttpMethodFilter();
        methodFilter.setMethodParam("_m");
        return methodFilter;
    }
}
2、请求映射原理

SpringMVC功能分析都从 org.springframework.web.servlet.DispatcherServlet-》doDispatch()

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
		HttpServletRequest processedRequest = request;
		HandlerExecutionChain mappedHandler = null;
		boolean multipartRequestParsed = false;

		WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

		try {
			ModelAndView mv = null;
			Exception dispatchException = null;

			try {
				processedRequest = checkMultipart(request);
				multipartRequestParsed = (processedRequest != request);

				// 找到当前请求使用哪个Handler(Controller的方法)处理
				mappedHandler = getHandler(processedRequest);
                
                //HandlerMapping:处理器映射。/xxx->>xxxx

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

	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;
	}

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

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

1、普通参数与基本注解

1.1、注解:

@PathVariable: 获取路径变量
@RequestHeader: 获取请求头
@ModelAttribute:
@RequestParam: 获取请求参数
@MatrixVariable: 矩阵变量
@CookieValue: 获取cookie值
@RequestBody: 获取请求体【POST】

@RequestAttribute: 获取request域属性

关于矩阵变量:
/cars/{path }?xxx=xxx&aaa=ccc queryString查询字符串可以通过@RequestParam实现
/cars/sell;low=34;brand=byd,audi,yd  ;矩阵变量
如果在页面开发中,cookie禁用了, session里面的内容怎么使用?
session.set(a, b )---> jsessionid ---> cookie ----〉每次发请求携带。
可以通过url重写:  /abc;jsesssionid=xxxx 把cookie的值使用矩阵变量的方式进行传递.
/boss/1/2
/boss/1;age=20/2;age=20
@PathVariable ,@RequestParam等常用参数注解使用

新建ParameterTestController进行测试
注:获取params想获取多个inters使用 @RequestParam MultiValueMap<String,String> params 即可

package com.yu.boot.controller;

import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.Cookie;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@RestController
public class ParameterTestController {

    // localhost:8080/car/2/owner/zhangsan?age=18&inters=book&inters=lol
    @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 MultiValueMap<String,String> params,
                                     @CookieValue("JSESSIONID") String _ga,
                                     @CookieValue("JSESSIONID") 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("JSESSIONID",_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;
    }

    //1、语法: 请求路径:/cars/sell;low=34;brand=byd,audi,yd
    //2、SpringBoot默认是禁用了矩阵变量的功能
    //      手动开启:原理。对于路径的处理。UrlPathHelper进行解析。
    //              removeSemicolonContent(移除分号内容)支持矩阵变量的
    //3、矩阵变量必须有url路径变量才能被解析
    @GetMapping("/cars/{path}")
    public Map carsSell(@MatrixVariable("low") Integer low,
                        @MatrixVariable("brand") List<String> brand,
                        @PathVariable("path") String path){
        Map<String,Object> map = new HashMap<>();
        map.put("low",low);
        map.put("brand",brand);
        map.put("path",path);

        return map;
    }
    // /boss/1;age=20/2;age=10
    @GetMapping("/boss/{bossId}/{empId}")
    public Map boss(@MatrixVariable(value = "age",pathVar = "bossId") Integer bossAge,
                    @MatrixVariable(value = "age",pathVar = "empId") Integer empAge){
        Map<String,Object> map =new HashMap<>();
        map.put("bossAge",bossAge);
        map.put("empAge",empAge);

        return map;
    }

}

测试结果:

http://localhost:8080/car/2/owner/zhangsan?age=18&inters=book&inters=lol

<form action="/save" method="post">
    用户名:<input name="userName"/> <br>
    邮箱:<input name="email">
    <input type="submit" value="提交"/>
</form>

@MatrixVariable (获取矩阵变量) 与UrlPathHelper
关于矩阵变量:
/cars/{path }?xxx=xxx&aaa=ccc   这种叫做queryString(查询字符串),可以通过@RequestParam获取
/cars/sell;low=34;brand=byd,audi,yd      矩阵变量绑定在路径中   ;以前是真正的访问路径   ;以后是矩阵变量    多个矩阵变量以;做间隔
例:
原路径:          /boss/1/2
加上矩阵变量:    /boss/1;age=20/2;age=20

面试题:如果在页面开发中cookie禁用了, session里面的内容怎么使用?
session.set(a, b )---> jsessionid --->保存在 cookie ----〉每次发请求携带。
答:
可以通过url(路径)重写:  /abc;jsesssionid=xxxx 把cookie的值使用矩阵变量的方式进行传递.

手动开启矩阵变量功能有两种方式
在boot下新建config.WebConfig
方式一
去实现 WebMvcConfigurer,重写方法

@Configuration(proxyBeanMethods = false)
public class WebConfig implements WebMvcConfigurer{

    @Override
    public void configurePathMatch(PathMatchConfigurer configurer) {
        UrlPathHelper urlPathHelper = new UrlPathHelper();
        //不移除;后面的内容,矩阵变量功能就可以生效
        urlPathHelper.setRemoveSemicolonContent(false);
        configurer.setUrlPathHelper(urlPathHelper);
    }
}

方式二
给容器中放入组件 webMvcConfigurer


@Configuration(proxyBeanMethods = false)
public class WebConfig {

    @Bean
    public WebMvcConfigurer webMvcConfigurer(){
        return new WebMvcConfigurer() {
            @Override
            public void configurePathMatch(PathMatchConfigurer configurer) {
                UrlPathHelper urlPathHelper = new UrlPathHelper();
                //不移除;后面的内容,矩阵变量功能就可以生效
                urlPathHelper.setRemoveSemicolonContent(false);
                configurer.setUrlPathHelper(urlPathHelper);
            }
        };
    }
}

测试

    //1、语法: 请求路径:/cars/sell;low=34;brand=byd,audi,yd
    //2、SpringBoot默认是禁用了矩阵变量的功能
    //      手动开启:原理。对于路径的处理。UrlPathHelper进行解析。
    //              removeSemicolonContent(移除分号内容)支持矩阵变量的
    //3、矩阵变量必须有url路径变量才能被解析
    @GetMapping("/cars/{path}")
    public Map carsSell(@MatrixVariable("low") Integer low,
                        @MatrixVariable("brand") List<String> brand,
                        @PathVariable("path") String path){
        Map<String,Object> map = new HashMap<>();
        map.put("low",low);
        map.put("brand",brand);
        map.put("path",path);

        return map;
    }
    
    // /boss/1;age=20/2;age=10
    @GetMapping("/boss/{bossId}/{empId}")
    public Map boss(@MatrixVariable(value = "age",pathVar = "bossId") Integer bossAge,
                    @MatrixVariable(value = "age",pathVar = "empId") Integer empAge){
        Map<String,Object> map =new HashMap<>();
        map.put("bossAge",bossAge);
        map.put("empAge",empAge);

        return map;
    }

测试结果
http://localhost:8080/cars/sell;low=34;brand=byd,audi,yd

http://localhost:8080/boss/1;age=20/2;age=10

@RequestAttribute: 获取request域属性

@ResponseBody详解
新建 RequestController

package com.yu.boot.controller;

@Controller
public class RequestController {
    @GetMapping("/goto")
    public String gotoPage(HttpServletRequest request){
        request.setAttribute("msg","成功了 ~");
        request.setAttribute("code",299);
        return "forward:/success";//转发到 /success请求
    }

    @ResponseBody//将java对象转为json格式的数据
    @GetMapping("/success")
    public Map success(@RequestAttribute("msg") String msg,
                          @RequestAttribute("code") Integer code,
                          HttpServletRequest request){
        Object msg1 =request.getAttribute("msg");

        Map<String,Object> map = new HashMap<>();
        map.put("reqMethod_msg",msg1);
        map.put("code",code);
        map.put("annotation_msg",msg);
        return map;
    }
}

测试
http://localhost:8080/goto

1.2、Servlet API:

WebRequestServletRequestMultipartRequestHttpSessionPushBuilderPrincipalInputStreamReaderHttpMethodLocaleTimeZoneZoneId . HttpServletRequest

以上参数都是在 ServletRequestMethodArgumentResolver.supportsParameter() 里判断的:

	@Override
	public boolean supportsParameter(MethodParameter parameter) {
		Class<?> paramType = parameter.getParameterType();
		return (WebRequest.class.isAssignableFrom(paramType) ||
				ServletRequest.class.isAssignableFrom(paramType) ||
				MultipartRequest.class.isAssignableFrom(paramType) ||
				HttpSession.class.isAssignableFrom(paramType) ||
				(pushBuilder != null && pushBuilder.isAssignableFrom(paramType)) ||
				Principal.class.isAssignableFrom(paramType) ||
				InputStream.class.isAssignableFrom(paramType) ||
				Reader.class.isAssignableFrom(paramType) ||
				HttpMethod.class == paramType ||
				Locale.class == paramType ||
				TimeZone.class == paramType ||
				ZoneId.class == paramType);
	}

然后获取请求:

1.3、复杂参数:

MapModelErrors/BindingResultRedirectAttributes( 重定向携带数据)ServletResponse(response)SessionStatusUriComponentsBuilderServletUriComponentsBuilder

RedirectAttributes参数, 有两种存放方法:

  1. redirectAttributes.addAttributie("param", value);

以上重定向的方法等同于 return "redirect:/hello?param=value"

重定向后需要用 @RequestParam 获取参数 ,然后需要通过model.addAttribute再存放一次

  1. redirectAttributes.addFlashAttributie("param", value); 推荐使用

隐藏了参数,链接地址上不直接暴露

重定向后不需要再存放到model里, 而且跳到页面后会立马自动销毁

通过@ModelAttribute("infoP") String infoP可以获取

更多: https://www.cnblogs.com/lemon-coke-pudding/p/12774995.html

Map<String, Object> map、Model model、HttpServletRequest request 都可以给 request 域中放数据 request.setAttribute()


MapModel类型的参数,会返回 mavContainer.getModel()

// ModelAndViewContainer:模型和视图的容器
private final ModelMap defaultModel = new BindingAwareModelMap();
public ModelMap getModel() {
    if (this.useDefaultModel()) {
        return this.defaultModel; // 默认返回 BindingAwareModelMap,既是 Model 又是 Map
    } else {
        if (this.redirectModel == null) {
            this.redirectModel = new ModelMap(); // 重定向为空,返回一个空的 LinkedHashMap
        }
        return this.redirectModel; // 返回 RedirectAttributesModelMap
    }
}

Model 和 Map 是同一个对象
Model 和 Map 是同一个对象

defaultModel

1.4、自定义对象参数:

可以自动类型转换与格式化,还可以级联封装。

/**
 * html 中引用对象参数
 *     姓名: <input name="userName"/> <br/>
 *     年龄: <input name="age"/> <br/>
 *     生日: <input name="birth"/> <br/>
 *     宠物姓名:<input name="pet.name"/><br/>
 *     宠物年龄:<input name="pet.age"/>
 */
@Data
public class Person {
    private String userName;
    private Integer age;
    private Date birth;
    private Pet pet;
}

@Data
public class Pet {
    private String name;
    private String age;
}

2、POJO封装过程

RequestParamMethodArgumentResolver 支持简单类型的请求方法参数,ServletModelAttributeMethodProcessor 支持非简单类型的请求方法参数。

所以对于请求 http://localhost:8080/testCustomObj?name=张三&age=18,由于请求方法参数Employee 不是简单类型,所以会调用 ServletModelAttributeMethodProcessor 将请求参数封装为 Employee 对象并返回。

@RequestMapping("/testCustomObj")
@ResponseBody
public Employee testCustomObj(Employee e) {
    return e;
}

3、参数处理原理

  1. HandlerMapping 中找到能处理请求的 HandlerController 中请求对应的方法);
  2. 为当前 Handler 找一个适配器 HandlerAdapter,对于请求来说就是 RequestMappingHandlerAdapter
  3. 适配器执行目标方法并确定方法参数的每一个值。

拓展:

SpringMVC 处理请求大致是这样的:

  1. 首先被 DispatcherServlet 截获,DispatcherServlet 通过 handlerMapping 获得 HandlerExecutionChain,然后获得 HandlerAdapter
  2. HandlerAdapter 在内部对于每个请求,都会实例化一个 ServletInvocableHandlerMethod 进行处理,ServletInvocableHandlerMethod 在进行处理的时候,会分两部分别对请求响应进行处理。
    1. 处理请求的时候,会根据 ServletInvocableHandlerMethod 的属性 argumentResolvers(这个属性是它的父类 InvocableHandlerMethod 中定义的)进行处理,其中 argumentResolvers 属性是一个 HandlerMethodArgumentResolverComposite 类 (这里使用了组合模式的一种变形),这个类是实现了 HandlerMethodArgumentResolver 接口的类,里面有各种实现了 HandlerMethodArgumentResolverList 集合。
    2. 处理响应的时候,会根据 ServletInvocableHandlerMethod 的属性 returnValueHandlers (自身属性) 进行处理,returnValueHandlers 属性是一个 HandlerMethodReturnValueHandlerComposite 类(这里使用了组合模式的一种变形),这个类是实现了 HandlerMethodReturnValueHandler 接口的类,里面有各种实现了 HandlerMethodReturnValueHandlerList 集合。
  3. 之后 HandlerAdapter 得到 ModelAndView,然后做相应的处理。

Resolver 结尾的类是实现了 HandlerMethodArgumentResolver 接口的类,以 Processor 结尾的类是实现了 HandlerMethodArgumentResolverHandlerMethodReturnValueHandler 的类。

  • HandlerMethodArgumentResolver :处理请求方法的参数
  • HandlerMethodReturnValueHandler:处理请求方法的返回值

HandlerMethodReturnValueHandler

详解 SpringMVC 中 Controller 的方法中参数的工作原理

1、处理器的适配器HandlerAdapter
// DispatcherServlet -- getHandlerAdapter
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
    if (this.handlerAdapters != null) {
        for (HandlerAdapter adapter : this.handlerAdapters) {
            if (adapter.supports(handler)) {
                return adapter;
            }
        }
    }

    throw new ServletException("No adapter for handler [" + handler +            "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");}

HandlerAdapter

  • RequestMappingHandlerAdapter :支持方法上标注 @RequestMapping 的适配器。
  • HandlerFunctionAdapter:支持函数式编程的适配器。
  • HttpRequestHandlerAdapter:无返回值,用于处理静态资源。
  • SimpleControllerHandlerAdapter:是 Controller 实现类的适配器类,其本质是执行 Controller 中的 handleRequest 方法。
2、执行目标方法
// DispatcherServlet -- doDispatch
// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
// RequestMappingHandlerAdapter -- handleInternal
mav = invokeHandlerMethod(request, response, handlerMethod);//执行目标方法
// RequestMappingHandlerAdapter -- invokeHandlerMethod
invocableMethod.invokeAndHandle(webRequest, mavContainer);
// ServletInvocableHandlerMethod -- invokeAndHandleObject
 return Value = invokeForRequest(webRequest, mavContainer, providedArgs);
// InvocableHandlerMethod -- invokeForRequest
Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
return this.doInvoke(args); // 通过反射调用目标方法
3、参数解析器-HandlerMethodArgumentResolver

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

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

// InvocableHandlerMethod -- invokeForRequest
Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
// InvocableHandlerMethod -- getMethodArgumentValues
if (!this.resolvers.supportsParameter(parameter)) {
 // 判断是否支持当前参数类型
// 解析支持的参数,并放入参数列表
args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
// HandlerMethodArgumentResolverComposite -- resolveArgumentHandler
MethodArgumentResolver resolver = this.getArgumentResolver(parameter);
// HandlerMethodArgumentResolverComposite -- getArgumentResolver
// 先从参数解析器缓存中获取,若获取不到再从参数解析器中获取
Iterator var3 = this.argumentResolvers.iterator();
// 判断解析器是否支持当前参数类型,若支持放入参数解析器缓存中,然后返回参数解析器

argumentResolvers

4、返回值处理器

image.png

5、如何确定目标方法每一个参数的值
============InvocableHandlerMethod==========================
protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
			Object... providedArgs) throws Exception {

		MethodParameter[] parameters = getMethodParameters();
		if (ObjectUtils.isEmpty(parameters)) {
			return EMPTY_ARGS;
		}

		Object[] args = new Object[parameters.length];
		for (int i = 0; i < parameters.length; i++) {
			MethodParameter parameter = parameters[i];
			parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
			args[i] = findProvidedArgument(parameter, providedArgs);
			if (args[i] != null) {
				continue;
			}
			if (!this.resolvers.supportsParameter(parameter)) {
				throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));
			}
			try {
				args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
			}
			catch (Exception ex) {
				// Leave stack trace for later, exception may actually be resolved and handled...
				if (logger.isDebugEnabled()) {
					String exMsg = ex.getMessage();
					if (exMsg != null && !exMsg.contains(parameter.getExecutable().toGenericString())) {
						logger.debug(formatArgumentError(parameter, exMsg));
					}
				}
				throw ex;
			}
		}
		return args;
	}

5.1、挨个判断所有参数解析器那个支持解析这个参数

	@Nullable
	private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
		HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);
		if (result == null) {
			for (HandlerMethodArgumentResolver resolver : this.argumentResolvers) {
				if (resolver.supportsParameter(parameter)) {
					result = resolver;
					this.argumentResolverCache.put(parameter, result);
					break;
				}
			}
		}
		return result;
	}

5.2、解析这个参数的值

调用各自 HandlerMethodArgumentResolver 的 resolveArgument 方法即可

5.3、自定义类型参数 封装POJO

ServletModelAttributeMethodProcessor 负责处理自定义的参数类型(非简单类型)。

简单类型如下:

// BeanUtils -- isSimpleValueType
public static boolean isSimpleValueType(Class<?> type) {
    return (Void.class != type && void.class != type &&            (ClassUtils.isPrimitiveOrWrapper(type) ||            Enum.class.isAssignableFrom(type) ||            CharSequence.class.isAssignableFrom(type) ||            Number.class.isAssignableFrom(type) ||            Date.class.isAssignableFrom(type) ||            Temporal.class.isAssignableFrom(type) ||            URI.class == type ||            URL.class == type ||            Locale.class == type ||            Class.class == type));}

isSimpleValueType

解析参数:

// ModelAttributeMethodProcessor#resolveArgumentWebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);

WebDataBinder:Web 数据绑定器,利用它里面的 Converters 将请求数据转成指定的数据类型,然后封装到指定的 POJO 对象里面。

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

GenericConversionService

@FunctionalInterfacepublic interface Converter<S, T>

ExtendServletRequestDataBinder

converters

可以给WebDataBinder里面放自己的Converter;

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

自定义 Converter:

// 1、WebMvcConfigurer 定制化 SpringMVC 的功能@Beanpublic WebMvcConfigurer webMvcConfigurer (){    return new WebMvcConfigurer () {        @Override        public void configurePathMatch (PathMatchConfigurer configurer) {            UrlPathHelper urlPathHelper = new UrlPathHelper ();            // 不移除分号后面的内容,矩阵变量功能就可以生效            urlPathHelper.setRemoveSemicolonContent (false);            configurer.setUrlPathHelper (urlPathHelper);        }        @Override        public void addFormatters (FormatterRegistry registry) {            registry.addConverter (new Converter<String, Pet>() {                @Override                public Pet convert (String source) {                    // 阿猫,3                    if (!StringUtils.isEmpty (source)){                        Pet pet = new Pet ();                        String [] split = source.split (",");                        pet.setName (split [0]);                        pet.setAge (Integer.parseInt (split [1]));                        return pet;                    }                    return null;                }            });        }    };}
6、目标方法执行完成

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

mavContainer

updateBindResult

7、处理派发结果

exposeModelAsRequestAttribute

4、数据响应与内容协商

4.1 响应 JSON

4.1.1 jackson.jar + @ResponseBody
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Web 场景自动引入了 json 场景,下面的依赖不需要显示声明 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-json</artifactId>
    <version>2.3.4.RELEASE</version>
    <scope>compile</scope>
</dependency>

自动给前端返回 json 数据。

1、返回值解析器

// ServletInvocableHandlerMethod#invokeAndHandle
this.returnValueHandlers.handleReturnValue(
        returnValue, getReturnValueType(returnValue), mavContainer, webRequest);

// HandlerMethodReturnValueHandlerComposite#handleReturnValue
handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);

// RequestResponseBodyMethodProcessor#handleReturnValue
// 使用对应的消息转换器进行写出操作
writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);

returnValueHandlers

2、返回值解析器原理

HandlerMethodReturnValueHandler

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

    supportsReturnType

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

    handleReturnValue

  3. RequestResponseBodyMethodProcessor 可以处理有 @ModelAttribute 且为对象类型的 @ResponseBody 注解。

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

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

      2. 服务器根据自己的能力,决定能生产什么内容类型的数据;

        内容类型

      3. SpringMVC 会遍历所有容器底层的 HttpMessageConverter,看谁能处理。

        1. 得到 MappingJackson2HttpMessageConverter,它可以将对象写为 json
        2. 利用 MappingJackson2HttpMessageConverter 将对象转为 json 再写出去。
4.1.2 支持的返回类型
  • ModelAndView:包含 ModelView 对象,可以通过它访问 @ModelAttribute 注解的对象。

  • Model:仅包含数据访问,通过 RequestToViewNameTranslator 来隐蔽地决定此请求返回的 View 视图对象。

  • Map:和 Model 相似。

  • View:仅包含视图数据,而 Model 数据隐含在 @ModelAttribute 注解标注的对象中、或者

  • String:表示 View 视图的名称。数据信息的保存同上。

  • void:当开发者直接操作 ServletResponse / HttpServletResponse 进行请求跳转,或者 ViewRequestToViewNameTranslator 隐蔽地决定时,可使用此返回值。

  • 任意对象:如果方法被 @ResponseBody 注解,可采用此值。Spring 会使用 HttpMessageConverters 将对象转化成文本输出。

  • HttpEntity<?>ResponseEntity<?>:使用此值,Spring 也会使用 HttpMessageConverters 将对象转化成文本输出。

  • Callable:异步请求时使用。

  • DeferredResult:当 Spring 决定使用选择的某个线程产生值时可以使用此对象。

  • WebAsyncTask:带有超时时间的 Callable 异步任务。

支持的返回类型

4.1.3 HTTPMessageConverter 原理

1、MessageConverter 规范

HTTPMessageConverter

HttpMessageConverter: 调用 canRead()canWrite() 方法判断是否支持将 此 Class 类型的对象,转为 MediaType 类型的数据。

例子: json 转为 Person 对象,或者Person 对象转为 json。

2、默认的MessageConverter

// AbstractMessageConverterMethodArgumentResolver#readWithMessageConverters
for (HttpMessageConverter<?> converter : this.messageConverters) {

messageConverters

  • ByteArrayHttpMessageConverter:只支持字节数组类型的。
  • StringHttpMessageConverterUTF-8 类型的字符串。
  • StringHttpMessageConverterISO-8859-1 类型的字符串。
  • ResourceHttpMessageConverterResource 类型。
  • ResourceRegionHttpMessageConverterResourceHttpMessageConverter 的缺省设置,用于支持 HTTP Range 头部使用时,将静态资源的部分写入到响应对象。
  • SourceHttpMessageConverterDOMSource.classSAXSource.classStAXSource.classStreamSource.classSource.class
  • AllEncompassingFormHttpMessageConverter:对 FormHttpMessageConverter(表单与 MultiValueMap 的相互转换)的扩展,提供了对 xmljson 的支持。
  • MappingJackson2HttpMessageConverter:使用 Jackson 的 ObjectMapper 转换 Json 数据
  • MappingJackson2HttpMessageConverter
  • Jaxb2RootElementHttpMessageConverter:支持注解方式将对象转换为 xml。

最终 MappingJackson2HttpMessageConverter 把对象转为 json(利用底层 jacksonobjectMapper 转换的)。

outputMessage

【小家 Spring】Spring MVC 容器的 web 九大组件之 ---HandlerAdapter 源码详解 ---HttpMessageConverter 消息转换器详解

4.2 内容协商

4.2.1 引入 xml 依赖

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

若客户端无法解析服务端返回的内容,即媒体类型未匹配,那么响应 406

<dependency>
    <groupId>com.fasterxml.jackson.dataformat</groupId>
    <artifactId>jackson-dataformat-xml</artifactId>
</dependency>
4.2.2 返回 json 和 xml

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

Postman

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

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

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

优缺点:

  • 优点:不受浏览器约束。
  • 缺点:需要额外的传递 format 参数,URL 变得冗余繁琐,缺少了 REST 的简洁风范。还有个缺点便是:需要手动显示开启。

发请求:

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

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

getAcceptableMediaTypes

contentNegotiationManager

确定客户端接收什么样的内容类型:

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

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

4.2.4 内容协商原理
  1. 判断当前响应头中是否已经有确定的媒体类型 MediaType

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

    • contentNegotiationManager 内容协商管理器,默认使用基于请求头的策略;

      strategies

    • HeaderContentNegotiationStrategy 确定客户端可以接收的内容类型 。

      getHeaderValues

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

    messageConverters

    上面这四个并不完全一样:

    messageConverter 差异

  4. 找到支持操作 Person 对象的 MessageConverter,把 MessageConverter 支持的媒体类型统计出来;

  5. 客户端需要 application/xml,在服务端能力列表中;

img

6、进行内容协商,选出最佳匹配媒体类型;

7、用 MessageConverter 将对象转为最佳匹配媒体类型 。

导入了 jackson 处理 xml 的包,xmlMessageConverter 就会自动配置进来。

// WebMvcConfigurationSupport
jackson2XmlPresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.xml.XmlMapper", classLoader);

// WebMvcConfigurationSupport#addDefaultHttpMessageConverters
if (jackson2XmlPresent) {
    Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.xml();
    if (this.applicationContext != null) {
        builder.applicationContext(this.applicationContext);
    }
    messageConverters.add(new MappingJackson2XmlHttpMessageConverter(builder.build()));
}
4.2.5 自定义 MessageConverter

实现多协议数据兼容:jsonxml、x-guigu。

  1. @ResponseBody 响应数据出去 调用 RequestResponseBodyMethodProcessor 处理;
  2. Processor 处理方法返回值,通过 MessageConverter 处理;
  3. 所有 MessageConverter 合起来可以支持各种媒体类型数据的操作(读、写);
  4. 内容协商找到最终的 MessageConverter
spring:
  mvc:
    contentnegotiation:
      media-types:
        gg: application/x-guigu
      favor-parameter: true

配置 SpringMVC 的什么功能,只需要给容器中添加一个 WebMvcConfigurer 并配置即可。

@Bean
public WebMvcConfigurer webMvcConfigurer(){
    return new WebMvcConfigurer() {
        @Override
        public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {

        }
    }
}

strategy

contentNegotiationManager

有可能我们添加的自定义的功能会覆盖默认很多功能,导致一些默认的功能失效。

5、视图解析与模板引擎

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

模板引擎:是为了使用户界面与业务数据(内容)分离而产生的,它可以生成特定格式的文档,用于网站的模板引擎就会生成一个标准的文档。

模板引擎

  • Velocity:一个基于 Java 的模板引擎,其提供了一个 Context 容器,在 Java 代码里面我们可以往容器中存值,然后在 vm 文件中使用特定的语法获取。
  • [Freemarker](FreeMarker 教程网):一个基于模板生成文本输出的通用工具,使用纯 Java 编写,模板中没有业务逻辑,外部 Java 程序通过数据库操作等生成数据传入模板(template)中,然后输出页面。它能够生成各种文本:HTMLXMLRTFJava 源代码等等,而且不需要 Servlet 环境,并且可以从任何源载入模板,如本地文件、数据库等等。
  • Thymeleaf:是适用于 Web 和独立环境的现代服务器端 Java 模板引擎,能够处理 HTML,XML,JavaScript,CSS 甚至纯文本。有两种标记模板模式 HTML(常用)和 XML,三种文本模板模式 TEXTJAVASCRIPTCSS无操作模板模式 RAW

SpringBoot 推荐使用 Thymeleaf,因为与 VelocityFreeMarker 等传统的 Java 模板引擎不同,Thymeleaf 支持 HTML 模板模式,模板后缀为 .html,可以直接被浏览器打开,因此,预览时非常方便。

5.1 视图解析原理

  1. 目标方法处理的过程中,所有数据都会被放在 ModelAndViewContainer 里面,包括数据视图地址

    mavContainer

  2. 方法的参数是一个自定义类型对象(从请求参数中确定的),把它重新放在 ModelAndViewContainer

  3. 任何目标方法执行完成以后都会返回 ModelAndView(数据和视图地址);

  4. processDispatchResult 处理派发结果(页面该如何响应)。

    • render(mv, request, response); 进行页面渲染逻辑。

      • 根据方法的 String 返回值得到 View 对象(定义了页面的渲染逻辑)

        1. 所有的视图解析器尝试是否能根据当前返回值得到 View 对象;

          viewResolver

        2. 得到了 redirect:/main.html --> Thymeleaf 使用 new RedirectView() 创建视图;

          RedirectView

        3. ContentNegotiationViewResolver 里面包含了下面所有的视图解析器,内部还是利用上面所有视图解析器得到视图对象;

        4. view.render(mv.getModelInternal(), request, response); 视图对象调用自定义的 render 进行页面渲染工作。

          render

          • RedirectView 如何渲染(重定向到一个页面)。

            1. 获取目标 URL 地址;

            2. 调用原生的 response.sendRedirect(encodedURL); 发送重定向。

              sendRedirect

Controller 方法的返回值如下

5.1.1 返回 ModelAndView
  • ModelAndView 存放数据,addObject(),往 modelrequest 域)添加数据;

  • ModelAndView 添加逻辑视图名,setViewName(),经过视图解析器,得到物理视图,转发到物理视图。

@RequestMapping("/getUser.action")
public ModelAndView getUser(@RequestParam(name="userId",required = true) Integer id) throws Exception{
    System.out.println("id=" + id);
    ModelAndView modelAndView = new ModelAndView();
    User user = userService.queryOne(id);
    modelAndView.addObject("user", user);
    modelAndView.setViewName("userinfo");
    return modelAndView;
}
5.1.2 返回 String
  • 逻辑视图名: 经过视图解析器,得到物理视图,转发到物理视图;

    @RequestMapping("/index.action")
    public String  toIndex() {
        return "index";
    }
    
  • redirect:资源路径:不经过视图解析器,要求这个资源路径以完整的路径 / 开头,重定向到资源;

    new RedirectView() -> response.sendRedirect(encodedURL);

    @RequestMapping("/index.action")
    public String  toIndex() {
        return "redirect:/jsp/index.jsp";
    }
    
  • forward:资源路径: 不经过视图解析器,要求这个资源路径以完整的路径 / 开头,转发到资源;

    new InternalResourceView(forwardUrl); -> request.getRequestDispatcher(path).forward(request, response);

    @RequestMapping("/index.action")
    public String  toIndex() {
        return "forward:/jsp/index.jsp";
    }
    
  • 普通字符串、对象:

    new ThymeleafView() -> 调用模板引擎的 process 方法进行页面渲染(用 writer 输出)

    // 将 user 对象以 json 的格式响应给前端页面
    @RequestMapping("/queryUserByCondition.action")
    @ResponseBody // 需要结合该注解,表示响应的不是视图
    public User queryUserByCondition(User user) throws Exception{
        return user;
    }
    

5.2 Thymeleaf 语法

5.2.1 表达式
表达式名字 语法 用途
变量取值 $ 获取请求域、session 域、对象等值
选择变量 * 获取上下文对象值
消息 # 获取国际化等值
链接 @ 生成链接
片段表达式 ~ 类似于 jsp:include,引入公共页面片段
5.2.2 字面量

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

数字:0343.012.3、…

布尔值:truefalse

空值:null

变量:onetwo、.... 变量不能有空格

5.2.3 文本操作

字符串拼接:+

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

5.2.4 数学运算

运算符:+-*/%

5.2.5布尔运算

运算符:andor

一元运算:!not

5.2.6比较运算

比较:><>=<= (gtltgele)

等式:==!= (eqne )

5.2.7 条件运算

If-then:(if) ? (then)

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

Default:(value) ?: (defaultvalue)

5.2.8 特殊操作

无操作:_

5.2.9 设置属性值 th:attr

设置单个值:

<form action="subscribe.html" th:attr="action=@{/subscribe}">
  <fieldset>
    <input type="text" name="email" />
    <input type="submit" value="Subscribe!" th:attr="value=#{subscribe.submit}"/>
  </fieldset>
</form>

设置多个值:

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

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

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

所有 HTML5 兼容的标签写法:

https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html#setting-value-to-specific-attributes

5.2.10 迭代
<tr th:each="prod : ${prods}">
        <td th:text="${prod.name}">Onions</td>
        <td th:text="${prod.price}">2.41</td>
        <td th:text="${prod.inStock}? #{true} : #{false}">yes</td>
</tr>
<tr th:each="prod,iterStat : ${prods}" th:class="${iterStat.odd}? 'odd'">
  <td th:text="${prod.name}">Onions</td>
  <td th:text="${prod.price}">2.41</td>
  <td th:text="${prod.inStock}? #{true} : #{false}">yes</td>
</tr>
5.2.11 条件运算
<a href="comments.html"
th:href="@{/product/comments(prodId=${prod.id})}"
th:if="${not #lists.isEmpty(prod.comments)}">view</a>
<div th:switch="${user.role}">
  <p th:case="'admin'">User is an administrator</p>
  <p th:case="#{roles.manager}">User is a manager</p>
  <p th:case="*">User is some other thing</p>
</div>
5.2.12属性优先级

属性优先级

5.3 Thymeleaf 使用

5.3.1 引入 starter
<properties>
    <java.version>1.8</java.version>
    <thymeleaf.version>3.0.11.RELEASE</thymeleaf.version>
    <!-- 布局功能的支持程序 thymeleaf3 主程序 layout2 以上版本 -->
    <thymeleaf-layout-dialect.version>2.4.1</thymeleaf-layout-dialect.version>
</properties>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
5.3.2 Thymeleaf 自动配置
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(ThymeleafProperties.class)
@ConditionalOnClass({ TemplateMode.class, SpringTemplateEngine.class })
@AutoConfigureAfter({ WebMvcAutoConfiguration.class, WebFluxAutoConfiguration.class })
public class ThymeleafAutoConfiguration { }

自动配好的策略

  • 所有 Thymeleaf 的配置值都在 ThymeleafProperties

  • 配置好了 SpringTemplateEngine

  • 配置好了 ThymeleafViewResolver

  • 我们只需要直接开发页面。

@ConfigurationProperties(prefix = "spring.thymeleaf")
public class ThymeleafProperties {
    private static final Charset DEFAULT_ENCODING = StandardCharsets.UTF_8;

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

    public static final String DEFAULT_SUFFIX = ".html";

只要我们把 HTML 页面放在 classpath:/templates/Thymeleaf 就能自动渲染。

5.3.3、Thymeleaf页面开发

在templates下新建 success.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1 th:text="${msg}">哈哈</h1>
<h2>
    <a href="www.atguigu.com" th:href="${link}">去百度</a>  <br/>
    <!--@{}里应该加上链接,而且在运行时会自动加前缀-->
    <a href="https://www.cnblogs.com/xingkongcanghai/" th:href="@{/link}">去百度2</a>
</h2>
</body>
</html>

在controller下新建 ViewTestController

package com.yu.boot.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class ViewTestController {
    @GetMapping("/yu")
    public String  yu(Model model){
        //model中的数据会被放在请求域中 request.setAttribute("a",aa)
        model.addAttribute("msg","你好,yu");
        model.addAttribute("link","https://www.baidu.com/");
        return "success";
    }
}

需要注意:@{}里应该加上链接,而且在运行时会自动加前缀 ,例如此例子中
若在application.yaml中加上

server:
  servlet:
    context-path: /world

则:

4、构建后台管理系统

1、项目创建

new project -> Spring Initializr -> default -> com.yu->boot-05-web-admin->
web -> springweb
developer tools -> devtools、lombok、configuration processor
template engines -> thymeleaf



删去暂时没用的文件

注意

2、静态资源处理

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

将index.html和login.html放到templates下
将index重命名为main
在templates新建table文件夹
将dynamic_table.html、editable_table.html、pricing_table.html、responsive_table.html、basic_table.html 放入

3、路径构建

th:action="@{/login}"

login.html

<form class="form-signin" action="index.html" method="post" th:action="@{/login}">

完整代码在文末

4、模板抽取

th:insert/replace/include
th:insert:保留自己的主标签,保留th:fragment的主标签
th:replace:不保留自己的主标签,保留th:fragment的主标签
th:include:保留自己的主标签,不保留th:fragment的主标签(官方3.0不推荐)

抽取公共页面
templates下新建common.html


5、页面跳转

在src.main.java.com.yu.admin下创建controller.
IndexController

    @PostMapping("/login")
    public String main(User user, HttpSession session, Model model){

        if(StringUtils.hasLength(user.getUserName()) && "123456".equals(user.getPassword())){
            //把登陆成功的用户保存起来
            session.setAttribute("loginUser",user);
            //登录成功重定向到main.html;  重定向防止表单重复提交
            return "redirect:/main.html";
        }else {
            model.addAttribute("msg","账号密码错误");
            //回到登录页面
            return "login";
        }

    }
6、数据渲染
    @GetMapping("/dynamic_table")
    public String dynamic_table(Model model){
        //表格内容的遍历
        List<User> users = Arrays.asList(new User("zhangsan", "123456"),
                new User("lisi", "123444"),
                new User("haha", "aaaaa"),
                new User("hehe ", "aaddd"));
        model.addAttribute("users",users);

        return "table/dynamic_table";
    }
        <table class="display table table-bordered" id="hidden-table-info">
        <thead>
        <tr>
            <th>#</th>
            <th>用户名</th>
            <th>密码</th>
        </tr>
        </thead>
        <tbody>
        <tr class="gradeX" th:each="user,stats:${users}">
            <td th:text="${stats.count}">Trident</td>
            <td th:text="${user.userName}">Internet</td>
            <td >[[${user.password}]]</td>
        </tr>
        </tbody>
        </table>

login.html


main.html


admin.bean.User


6、拦截器

7、文件上传

8、异常处理

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

10、嵌入式Servlet容器

11、定制化原理

posted @ 2021-08-05 17:09  星空与沧海  阅读(224)  评论(0编辑  收藏  举报