SpringBoot-Web

Spring boot

Spring Boot 是由Pivotal团队提供的全新框架,其设计目的是用来简化Spring应用的初始搭建以及开发过程。该框架使用了特定的方式来进行配置,从而使开发人员不再需要定义样板化的配置。通过这种方式,Spring Boot致力于在蓬勃发展的快速应用开发领域(rapid application development)成为领导者。

SpringBoot 框架中还有两个非常重要的策略:开箱即用约定大于配置

开箱即用:是指在开发过程中,通过在MAVEN项目的pom文件中添加相关依赖包,然后使用对应注解来代替繁琐的XML配置文件以管理对象的生命周期。这个特点使得开发人员摆脱了复杂的配置工作以及依赖的管理工作,更加专注于业务逻辑。

约定大于配置:是一种由SpringBoot本身来配置目标结构,由开发者在结构中添加信息的软件设计范式。这一特点虽降低了部分灵活性,增加了BUG定位的复杂性,但减少了开发人员需要做出决定的数量,同时减少了大量的XML配置,并且可以将代码编译、测试和打包等工作自动化。

快速创建 Spring boot

1、官网创建

SpringBoot-Web

  • 将下载的压缩包,解压并使用 IDEA 打开 创建完成

一般不使用这种方法创建

2、IDEA创建(常用)

  • 打开 IDEA 新建项目。(填写和官网一样)

    SpringBoot-Web

    SpringBoot-Web

  • 删除无用的东西

    SpringBoot-Web

  • 运行程序

SpringBoot-Web

会结束进程 是因为我们没有添加 Web 依赖

  • pom.xml 中导入依赖
<!--版本会从父依赖里面找,不需要设置,也可以设置,设置什么版本优先使用自己设置的-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
  • 再次启动程序,(记得刷新 Maven依赖库)

SpringBoot-Web

SpringBoot-Web

3、修改启动端口号

resources目录下的 application.properties 中添加如下配置

# 更改项目启动端口
server.port=9999

修改后测试访问 http://localhost:9999 也能正常启动

resources目录下添加 banner.txt 里面填写的内容可以替换掉启动 LOG

SpringBoot-Web

Spring boot自动装配原理

1、依赖管理

项目依赖管理

创建 SpringBoot 项目,都会导入一个父依赖,如下

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

点开上面的依赖 里面还有一个父依赖,如下

<!--这个依赖包含了基本上会用到依赖版本-->
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-dependencies</artifactId>
    <version>2.4.5</version>
</parent>

点开 spring-boot-dependencies 可以看到许多的依赖版本

<!--这个依赖很长,列出一小部分-->
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <modelVersion>4.0.0</modelVersion>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-dependencies</artifactId>
  <version>2.4.5</version>
  <packaging>pom</packaging>
  <name>spring-boot-dependencies</name>
  <description>Spring Boot Dependencies</description>
  <url>https://spring.io/projects/spring-boot</url>
  <licenses>
    <license>
      <name>Apache License, Version 2.0</name>
      <url>https://www.apache.org/licenses/LICENSE-2.0</url>
    </license>
  </licenses>
  <developers>
    <developer>
      <name>Pivotal</name>
      <email>info@pivotal.io</email>
      <organization>Pivotal Software, Inc.</organization>
      <organizationUrl>https://www.spring.io</organizationUrl>
    </developer>
  </developers>
  <scm>
    <url>https://github.com/spring-projects/spring-boot</url>
  </scm>
  <properties>
    <activemq.version>5.16.1</activemq.version>
    <antlr2.version>2.7.7</antlr2.version>
    <appengine-sdk.version>1.9.88</appengine-sdk.version>
    <artemis.version>2.15.0</artemis.version>
    <aspectj.version>1.9.6</aspectj.version>
    <assertj.version>3.18.1</assertj.version>
    <atomikos.version>4.0.6</atomikos.version>
    <awaitility.version>4.0.3</awaitility.version>
    <bitronix.version>2.1.4</bitronix.version>

几乎声明了所有开发中常用的依赖的版本号,自动版本仲裁机制,所有我们基本上不需要导入依赖

启动器

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

4、见到的 *-spring-boot-starter: 第三方为我们提供的简化开发的场景启动器。
5、所有场景启动器最底层的依赖

<!--所有场景启动器最底层的依赖,所有场景都需要这个启动器-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
    <version>2.4.5</version>
    <scope>compile</scope>
</dependency>

注意点:

1、引入依赖默认都可以不写版本
2、引入非版本仲裁的jar,要写版本号。

3、不想使用仲裁版本时,添加依赖,自己给出版本号就可以了

2、自动配置

  • 自动配好Tomcat

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-tomcat</artifactId>
        <version>2.4.5</version>
        <scope>compile</scope>
    </dependency>
    
  • 自动配好SpringMVC

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-webmvc</artifactId>
        <version>5.3.6</version>
        <scope>compile</scope>
    </dependency>
    
  • 自动配好Web常见功能,如:字符编码问题,拦截器配置。

  • 默认的包结构

    主程序所在包及其下面的所有子包里面的组件都会被默认扫描进来

    无需以前的包扫描配置

    想要改变扫描路径,@SpringBootApplication(scanBasePackages="com")

    也可以将主程序上的 @SpringBootApplication 注解修改为下面三条记录

    @SpringBootConfiguration
    @EnableAutoConfiguration
    @ComponentScan("com")
    
    这三个注解和 @SpringBootApplication(scanBasePackages="com") 等价
    
  • 各种配置拥有默认值

    默认配置最终都是映射到某个类上,如:MultipartProperties

    配置文件的值最终会绑定每个类上,这个类会在容器中创建对象

  • 按需加载所有自动配置项

    所有自动配置,都是按需加载,当添加该项目的启动项时,才会配置。

    SpringBoot所有的自动配置功能都在 spring-boot-autoconfigure 包里面

3、底层注解

在了解 Spring-boot 自动装配原理前,先了解一些底层注解。

@Configuration

告诉 SpringBoot 这是一个配置类相当于配置文件

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

@Bean :向IOC容器注入一个类。

@Component:将类注入 IOC 容器。

//@Component 等价于 <bean id="user" class="com.wyx.pojo.User"/>
@Component
public class User {
    // @Value 等价于 <property name="name" value="王玉星"/> 
    @Value("但是公司")
    public String name;
}

下面三个注解和 @Component一样,只是使用场景不同

@Controller:标志这是一个Controller组件(控制器)

@Service:标志这是一个Service组件(Service层)

@Repository:标志这是一个Dao组件 (Dao层)

@Bean 与 @Component 的区别

仔细观察,发现上面的两个注解都是,向IOC 容器注入一个类

但是一般他们的使用情况不太一样。一般@Confiuration配合@Bean使用,写在配置文件中,返回的是某一个对象的实例;而@Component是对应某一个类,配合@ComponentScan使用,然后让spring ioc容器实例化。而且使用@Confiugraion和@Bean, 会进行cglib增强,拿到的是一个代理对象;@Component拿到的是一个普通的java对象。

@ComponentScan:扫描包

//扫描 com.wyx.dao 包  将下面的User类注入IOC容器
@componentScan("com.wyx.dao")
public class config(){
    
    @Bean //方法名作为组件的id 返回类型就是组件类型,返回值就是容器中的实例
    public User user(){
        return new User();
    }
}

@Import: 合并配置类,类似于两个 beans.xml 合并

@Import(User.class) //参数类型是一个数组,可以用{}来添加多个

@Conditional:条件装配,满足条件才开始装配。

SpringBoot-Web

@ImportResource:导入资源

作用:以前的配置是使用配置文件 beans.xml配置,不想在使用配置类来重新写配置文件,只需要在类上面写如下配置,就可以将配置文件的东西解析出来。

@ImportResource("classpath:beans.xml")

配置绑定

@Component + @ConfigurationProperties

1、在 application.properties 中添加配置属性

user.name=王玉星
user.sex=男
user.age=20

2、编写实体类

package com.wyx.dao;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;


@Component
@ConfigurationProperties(prefix = "user")
public class User {
    private String name;
    private String sex;
    private int age;


    public String getName() {
        return name;
    }

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

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public int getAge() {
        return age;
    }

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

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

3、编写控制器

package com.wyx.controller;


import com.wyx.dao.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class UserController {

    @Autowired
    User user;

    @ResponseBody
    @RequestMapping("/user")
    public User user(){
        return user;
    }
}

4、测试方法

SpringBoot-Web

从结果可以看出,application.properties 中配置的属性被加入了类

第二种方法:将实体类中的 @Component 删除掉 ,在配置类上加入@EnableConfigurationProperties(User.class) 即可

4、自动装配原理

了解了Spring Boot的依赖,自动配置及其很多底层注解,下面我们开始深入了解自动装配原理。

自动配置原理图

SpringBoot-Web

1、自动包规则

从原理图中可以看出 @EnableAutoConfiguration 自动导入配置下面的注解有 @AutoConfigurationPackage@Import(AutoConfigurationImportSelector.class) 分别是 自动配置包和自动选择导入配置。

自动配置包@AutoConfigurationPackage,下面有@Import(AutoConfigurationPackages.Registrar.class),点击 Registrar可以看到以下信息(部分)。

//继承ImportBeanDefinitionRegistrar和DeterminableImports
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {

    //重写它们的方法,利用new PackageImports(metadata).getPackageNames().toArray(new String[0]),将主程序启动类的目录及其下面的目录导入配置类的信息,从而实现自动配置包
    @Override
    public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
        register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0]));
    }
	//重写该方法将,将上一步获得的属性利用集合方式去重,并确定导入。
    @Override
    public Set<Object> determineImports(AnnotationMetadata metadata) {
        return Collections.singleton(new PackageImports(metadata));
    }
}

2、自动配置选择器

自动配置选择器,@Import(AutoConfigurationImportSelector.class)点开类,可以看到 selectImports 方法

SpringBoot-Web

selectImports 方法接着调用了 getAutoConfigurationEntry方法,查看 getAutoConfigurationEntry方法

//此方法是获取候选的配置进行一系列处理后返回给getAutoConfigurationEntry
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
    if (!isEnabled(annotationMetadata)) {
        return EMPTY_ENTRY;
    }
    AnnotationAttributes attributes = getAttributes(annotationMetadata);
    //getCandidateConfigurations() 获取候选配置。
    List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
    configurations = removeDuplicates(configurations);
    Set<String> exclusions = getExclusions(annotationMetadata, attributes);
    checkExcludedClasses(configurations, exclusions);
    configurations.removeAll(exclusions);
    //通过调试发现经过这一步后发现 configurations 由130减少到27 
    configurations = getConfigurationClassFilter().filter(configurations);
    fireAutoConfigurationImportEvents(configurations, exclusions);
    return new AutoConfigurationEntry(configurations, exclusions);
}

我们接着查看 getCandidateConfigurations 方法

// 获取spring-boot-autoconfigure-2.4.5.jar 下的META-INF/spring.factories 将它加载进来
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
    //利用Spring的工厂加载器加载 spring.factories 到类中
    List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
                                                                         getBeanClassLoader());
    Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you "
                    + "are using a custom packaging, make sure that file is correct.");
    return configurations;
}

查看 spring-boot-autoconfigure-2.4.5.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.Neo4jReactiveDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.neo4j.Neo4jReactiveRepositoriesAutoConfiguration,\
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.redis.RedisAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisReactiveAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.rest.RepositoryRestMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.data.web.SpringDataWebAutoConfiguration,\
org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchRestClientAutoConfiguration,\
org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration,\
org.springframework.boot.autoconfigure.freemarker.FreeMarkerAutoConfiguration,\
org.springframework.boot.autoconfigure.groovy.template.GroovyTemplateAutoConfiguration,\
org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration,\
org.springframework.boot.autoconfigure.h2.H2ConsoleAutoConfiguration,\
org.springframework.boot.autoconfigure.hateoas.HypermediaAutoConfiguration,\
org.springframework.boot.autoconfigure.hazelcast.HazelcastAutoConfiguration,\
org.springframework.boot.autoconfigure.hazelcast.HazelcastJpaDependencyAutoConfiguration,\
org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration,\
org.springframework.boot.autoconfigure.http.codec.CodecsAutoConfiguration,\
org.springframework.boot.autoconfigure.influx.InfluxDbAutoConfiguration,\
org.springframework.boot.autoconfigure.info.ProjectInfoAutoConfiguration,\
org.springframework.boot.autoconfigure.integration.IntegrationAutoConfiguration,\
org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.JndiDataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.XADataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.JmsAutoConfiguration,\
org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.JndiConnectionFactoryAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.activemq.ActiveMQAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.artemis.ArtemisAutoConfiguration,\
org.springframework.boot.autoconfigure.jersey.JerseyAutoConfiguration,\
org.springframework.boot.autoconfigure.jooq.JooqAutoConfiguration,\
org.springframework.boot.autoconfigure.jsonb.JsonbAutoConfiguration,\
org.springframework.boot.autoconfigure.kafka.KafkaAutoConfiguration,\
org.springframework.boot.autoconfigure.availability.ApplicationAvailabilityAutoConfiguration,\
org.springframework.boot.autoconfigure.ldap.embedded.EmbeddedLdapAutoConfiguration,\
org.springframework.boot.autoconfigure.ldap.LdapAutoConfiguration,\
org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration,\
org.springframework.boot.autoconfigure.mail.MailSenderAutoConfiguration,\
org.springframework.boot.autoconfigure.mail.MailSenderValidatorAutoConfiguration,\
org.springframework.boot.autoconfigure.mongo.embedded.EmbeddedMongoAutoConfiguration,\
org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration,\
org.springframework.boot.autoconfigure.mongo.MongoReactiveAutoConfiguration,\
org.springframework.boot.autoconfigure.mustache.MustacheAutoConfiguration,\
org.springframework.boot.autoconfigure.neo4j.Neo4jAutoConfiguration,\
org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration,\
org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration,\
org.springframework.boot.autoconfigure.r2dbc.R2dbcAutoConfiguration,\
org.springframework.boot.autoconfigure.r2dbc.R2dbcTransactionManagerAutoConfiguration,\
org.springframework.boot.autoconfigure.rsocket.RSocketMessagingAutoConfiguration,\
org.springframework.boot.autoconfigure.rsocket.RSocketRequesterAutoConfiguration,\
org.springframework.boot.autoconfigure.rsocket.RSocketServerAutoConfiguration,\
org.springframework.boot.autoconfigure.rsocket.RSocketStrategiesAutoConfiguration,\
org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration,\
org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration,\
org.springframework.boot.autoconfigure.security.servlet.SecurityFilterAutoConfiguration,\
org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration,\
org.springframework.boot.autoconfigure.security.reactive.ReactiveUserDetailsServiceAutoConfiguration,\
org.springframework.boot.autoconfigure.security.rsocket.RSocketSecurityAutoConfiguration,\
org.springframework.boot.autoconfigure.security.saml2.Saml2RelyingPartyAutoConfiguration,\
org.springframework.boot.autoconfigure.sendgrid.SendGridAutoConfiguration,\
org.springframework.boot.autoconfigure.session.SessionAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.client.servlet.OAuth2ClientAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.client.reactive.ReactiveOAuth2ClientAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.resource.servlet.OAuth2ResourceServerAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.resource.reactive.ReactiveOAuth2ResourceServerAutoConfiguration,\
org.springframework.boot.autoconfigure.solr.SolrAutoConfiguration,\
org.springframework.boot.autoconfigure.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

SpringBoot-Web

观察spring.factories下面两张图片,发现开始是 21 结束时 151 刚好满足上面图片加载的 130 配置类加载的数量

SpringBoot-Web

SpringBoot-Web

然后我们接着调试

当走到以下位置时

protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
    if (!isEnabled(annotationMetadata)) {
        return EMPTY_ENTRY;
    }
    AnnotationAttributes attributes = getAttributes(annotationMetadata);
    //getCandidateConfigurations() 获取候选配置。
    List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
    configurations = removeDuplicates(configurations);
    Set<String> exclusions = getExclusions(annotationMetadata, attributes);
    checkExcludedClasses(configurations, exclusions);
    configurations.removeAll(exclusions);
    //通过调试发现经过这一步后发现 configurations 由130减少到23 
    configurations = getConfigurationClassFilter().filter(configurations);
    fireAutoConfigurationImportEvents(configurations, exclusions);
    return new AutoConfigurationEntry(configurations, exclusions);
}

SpringBoot-Web

SpringBoot-Web

由上面两张图,明显看出 configurations 大量减少,于是我们查看 getConfigurationClassFilter().filter();

getConfigurationClassFilter 调试发现,这个方法会将需要过滤的配置信息封装到 configurationClassFilter 变量中

private ConfigurationClassFilter getConfigurationClassFilter() {
    // configurationClassFilter 在类初始化时就是 null 所以一定会走
    if (this.configurationClassFilter == null) {
        // 获取过滤规则这里过滤规则 会调用Spring工厂加载器的loadFactories方法,规则具体参见源码,不做过多讲解
        /* 具体调用如下
protected List<AutoConfigurationImportFilter> getAutoConfigurationImportFilters() {
		return SpringFactoriesLoader.loadFactories(AutoConfigurationImportFilter.class, 		this.beanClassLoader);
	}
        */ 下面的invokeAwareMethods查看源码说判断这个过滤规则需要用什么资源加载器加载,具体查看源码
        List<AutoConfigurationImportFilter> filters = getAutoConfigurationImportFilters();
        for (AutoConfigurationImportFilter filter : filters) {
            invokeAwareMethods(filter);
        }
        // 将过滤规则和beanClassLoader加载器通过ConfigurationClassFilter
        this.configurationClassFilter = new ConfigurationClassFilter(this.beanClassLoader, filters);
    }
    return this.configurationClassFilter;
}

上面的 configurationClassFilter 过滤规则就是将配置文件读取的(spring.factories)与 Spring 的 ioc容器通过过滤规则比较,如果有则不添加到过滤范围,否则添加。具体对比参见ConfigurationClassFilter类的构造器调用的 AutoConfigurationMetadataLoader.loadMetadata(classLoader),这个类(AutoConfigurationMetadataLoader)的主要功能就是将 spring-autoconfigure-metadata.properties(和spring.factories在同级目录)的信息用过滤规则过滤后注入 autoConfigurationMetadata 常量

//这里是 ConfigurationClassFilter 的构造器,它下面封装了filter方法
ConfigurationClassFilter(ClassLoader classLoader, List<AutoConfigurationImportFilter> filters) {
    this.autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(classLoader);
    this.filters = filters;
}

解释一下,getAutoConfigurationImportFilters(过滤规则)主要就是将容器中没有注入的容器的组件添加成规则,用于后面过滤。

容器是否添加 这里使用的是底层注解 @Conditional 来判断是否向ioc容器注入组件

查看filter 方法是将配置文件spring.factories 中读取的信息与上一步经过处理的configurationsautoConfigurationMetadata进行比较,如果相同则不保留,取它们不相同的值,并返回结果。如果全相同也返回结果,如果logger.isTraceEnabled() 日志判断为 true 就打印日志信息

List<String> filter(List<String> configurations) {
    long startTime = System.nanoTime();
    String[] candidates = StringUtils.toStringArray(configurations);	//获取上一步经过处理的configurations
    boolean skipped = false;
    
    //将configurations与autoConfigurationMetadata(filter的类加载器加载出来的,具体看前面ConfigurationClassFilter的构造器)进行比较如果相同令它为空,相当于取不同的值(全部的configurations和过滤规则下autoConfigurationMetadata,相当于取SpringBoot已经需要加载的)
    for (AutoConfigurationImportFilter filter : this.filters) {
        boolean[] match = filter.match(candidates, this.autoConfigurationMetadata);
        for (int i = 0; i < match.length; i++) {
            if (!match[i]) {
                candidates[i] = null;
                skipped = true;
            }
        }
    }
    if (!skipped) {
        return configurations;
    }
    List<String> result = new ArrayList<>(candidates.length);
    for (String candidate : candidates) {
        if (candidate != null) {
            result.add(candidate);
        }
    }
    if (logger.isTraceEnabled()) {
        int numberFiltered = configurations.size() - result.size();
        logger.trace("Filtered " + numberFiltered + " auto configuration class in "
                     + TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime) + " ms");
    }
    return result;
}
}

使用filter 过滤后的配置文件就是需要加载的配置类。

到了这里我们了解了 Spring-Boot 是如何选择自动装配需要装配的组件了。

底层是通过反射(类加载器)判断容器中是否有该组件,在来修改装配的文件那么我们底层到底是如何装配的呢? 我们接着看下面

IOC 容器的组件自动装配

首先我们点开 spring-boot-autoconfigure-2.4.5.jar 查看

SpringBoot-Web

由上图可以看出很多的 XXXXAutoConfiguration 意思是XXX自动装配

随便点开一个类,这里点开的的 AopAutoConfiguration

SpringBoot-Web

通过上图,我们可以看出很多底层注解都是 @ConditionalXXX 意思是按照某种条件装配,这也是 SpringBoot底层注解的核心,按照某种条件装配,如果条件成立才会装配配置类。我们随便举一些例子

//如果application.properties 配置文件中有spring.aop.auto等于true则装配,默认值就是true 
@ConditionalOnProperty(prefix = "spring.aop", name = "auto", havingValue = "true", matchIfMissing = true)

//如果有Advice类则配置生效
@ConditionalOnClass(Advice.class) 

//如果容器中没有这个Bean则装配
@ConditionalOnMissingBean
//容器中没有这个名字 multipartResolver 的组件则装配
 @ConditionalOnMissingBean(name = multipartResolver)

//判断是上面类型的应用程序,是则配置
@ConditionalOnWebApplication(type = Type.SERVLET)

//如果容器中存在这个Bean才装配
@ConditionalOnBean(MultipartResolver.class)


/*
	以下是SpringBoot的一个注解,作用就是保证名字规范
*/
        @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-Web

总结:

  • SpringBoot先加载所有的自动配置类 xxxxxAutoConfiguration

  • 每个自动配置类按照条件进行生效,默认都会绑定配置文件指定的值。如果我们需要修改值可以通过application.properties直接修改,具体如何修改,看对应类的 @ConditionalOnProperty@ConfigurationProperties()@EnableConfigurationProperties(User.class)携带的值

  • 生效的配置类就会给容器中装配很多组件

  • 只要容器中有这些组件,对应组件的功能就能有

  • 自定义配置

    • 第一种方法修改application.properties对应组件的值
    • 第二种方法利用注解原理使用 @Bean @Component 等注解,来重写底层原理

以下是使用 SpringBoot 使用的最佳方法

引入场景依赖

查看自动配置了哪些(选做)

    • 自己分析,引入场景对应的自动配置一般都生效了
    • 配置文件中debug=true开启自动配置报告。Negative(不生效)\Positive(生效)

是否需要修改

Yaml

YAML是"YAML Ain't a Markup Language"(YAML不是一种标记语言)的递归缩写。在开发的这种语言时,YAML 的意思其实是:"Yet Another Markup Language"(仍是一种标记语言),但为了强调这种语言以数据做为中心,而不是以标记语言为重点,而用反向缩略语重命名。

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

基本语法

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

使用方法

  • 字面量:单个的、不可再分的值。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

配置绑定

自定义的类和配置文件绑定一般没有提示。 需要加入下面依赖,并且打包时不加入其中打包文件中

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

SpringMVC自动配置

Spring Boot为Spring MVC提供了自动配置,适用于大多数应用程序

自动配置在Spring的默认值之上添加了以下功能:

  • 包含 ContentNegotiatingViewResolver (内容协商视图解析器) 和 BeanNameViewResolver (BeanName视图解析器)的 beans。
  • 支持提供静态资源,包括对WebJars的支持。
  • 自动注册 Converter (转换器), GenericConverter(通用转换器) 和 Formatter(格式化器) 的 beans。
  • 支持 HttpMessageConverters(HTTP消息转换器) 。
  • 自动注册 MessageCodesResolver (国际化)。
  • 静态 index.html 支持。
  • 自定义 Favicon 支持。
  • 自动使用 ConfigurableWebBindingInitializer (可配置绑定初始化器)bean。

静态资源

  • 默认情况下,Spring Boot从类路径中的/static(或 /public /resources/META-INF/resources )目录或 ServletContext 的根目录中提供静态内容。

    • 默认情况下在上面列出的目录我们通过 项目发布路径/+资源名 都能访问到。

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

  • 它使用来自Spring MVC的 ResourceHttpRequestHandler(Http资源请求处理程序) ,以便您可以通过添加自己的 WebMvcConfigurer 并覆 盖 addResourceHandlers 方法来修改该行为。

默认情况下,资源映射到 /** ,但您可以使用 spring.mvc.static-path-pattern 属性对其进行调整。例如,将所有资源重新定位到 /res/** 可以实现如下:

#在res文件夹下放入资源
spring:
  mvc:
    static-path-pattern: /res/**

上面是修改静态资源在哪个包下面,当然我们也可以用 spring.resources.static-locations 来改变Spring Boot默认的静态资源路径

# 修改默认的资源访问路径,参数是一个数组,可以传递多个值,请求的时候Servlet的根目录也会自动添加
spring:
  web:
    resources:
      static-locations: [classpath:/res/,classpath:/static/]

Webjars

如果我们想要访问的资源是以Webjars格式打成的jar包,那么具有 /webjars/** 中路径 的任何资源都将从jar文件中提供。

https://www.webjars.org/ 这个路径是Webjars打包的官网,可以去里面找相关jar包的依赖

例如:我们引入在 pom.xml 中引入以下依赖

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

那么我们通过访问 项目发布路径/+/webjars/jquery/3.5.1/jquery.min.js 就能获得 jquery的相关信息(源码)

欢迎页

Spring Boot支持静态和模板化的欢迎页面。它首先在配置的静态内容位置中查找 index.html 文件。如果找不到,则会查找 index 模板。如果找到任何一个,它将自动用作应用程序的欢迎页面。

上面这段话的意思是:默认会找 index.html 如果没有找到,就会找 controller 处理 /index 请求的路径来作为欢迎页

注意点

  • 可以配置静态资源路径spring.resources.static-locations

  • 但是不可以配置静态资源的访问前缀spring.mvc.static-path-pattern。否则导致 index.htmlFavicon不能被默认访问

Favicon

favicon.ico 放在静态资源目录下即可。

静态资源底层源码分析

首先我们找到 spring-boot-autoconfigure-2.4.5.jar Spring自动配置的源码,接下来找 web 目录下的 WebMvcAutoConfiguration配置类

这个类的注解生效规则如下

@Configuration(proxyBeanMethods = false)   //声明是一个配置类
@ConditionalOnWebApplication(type = Type.SERVLET)	//是Servlet类才生效
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })		//存在这3个类才生效,一般导入mvc都会生效
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)		//没有这个类才生效
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)			//控制配置类的加载顺序
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,
		ValidationAutoConfiguration.class })
// 从上面的配置看,基本上配置都会生效

接着看配置类分别配置了什么

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

WebMvcProperties 它绑定了 spring.mvc

org.springframework.boot.autoconfigure.web.ResourceProperties 它绑定了 spring.resources

WebProperties 它绑定了 spring.web

查看配置类分别配置了什么

private final WebMvcProperties mvcProperties;	//获取和spring.mvc绑定的所有的值的对象

private final ListableBeanFactory beanFactory;	//获取 beanFactory IOC容器工厂 返回值是List

private final ObjectProvider<HttpMessageConverters> messageConvertersProvider;	//HTTP消息转换器

private final ObjectProvider<DispatcherServletPath> dispatcherServletPath;		//视图解析器路径

private final ObjectProvider<ServletRegistrationBean<?>> servletRegistrations;	//Servlet注册

final ResourceHandlerRegistrationCustomizer resourceHandlerRegistrationCustomizer;	//资源处理程序注册

public WebMvcAutoConfigurationAdapter(WebProperties webProperties, WebMvcProperties mvcProperties,
                                      ListableBeanFactory beanFactory, ObjectProvider<HttpMessageConverters> messageConvertersProvider,
                                      ObjectProvider<ResourceHandlerRegistrationCustomizer> resourceHandlerRegistrationCustomizerProvider,
                                      ObjectProvider<DispatcherServletPath> dispatcherServletPath,
                                      ObjectProvider<ServletRegistrationBean<?>> servletRegistrations) {
    this.mvcProperties = mvcProperties;
    this.beanFactory = beanFactory;
    this.messageConvertersProvider = messageConvertersProvider;
    this.resourceHandlerRegistrationCustomizer = resourceHandlerRegistrationCustomizerProvider.getIfAvailable();
    this.dispatcherServletPath = dispatcherServletPath;
    this.servletRegistrations = servletRegistrations;
    this.mvcProperties.checkConfiguration();
}

静态资源

配置静态资源,我们在类中找到一个方法addResourceHandlers

@Override
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
    super.addResourceHandlers(registry);

    /*resourceProperties,一个变量在上面类中获取了spring.web.resources的值 isAddMappings()方法是获取一个变量为 			addMappings 的值 默认值是true,由这个判断,我们可以将 spring.web.resources.addMappings的值设置为false,
    就相当于禁用默认的静态资源规则。
    */
    if (!this.resourceProperties.isAddMappings()) {
        logger.debug("Default resource handling disabled");
        return;
    }
    //获取Servlet的Context 即项目发布路径
    ServletContext servletContext = getServletContext();
    //添加Webjars的访问路径规则
    addResourceHandler(registry, "/webjars/**", "classpath:/META-INF/resources/webjars/");
    //添加静态资源访问路径规则
    addResourceHandler(registry, this.mvcProperties.getStaticPathPattern(), (registration) -> {
        registration.addResourceLocations(this.resourceProperties.getStaticLocations());
        if (servletContext != null) {
            registration.addResourceLocations(new ServletContextResource(servletContext, SERVLET_LOCATION));
        }
    });
}

从上面的源码分析,我们可以的到一个结论,我们要禁用静态资源规则只需要在配置文件中添加

# 添加这个可以禁用SpringBoot的默认静态资源规则
spring:
  web:
    resources:
      add-mappings: false

欢迎页

配置欢迎页,我们在类中找一个方法 WelcomePageHandlerMapping

@Bean
public WelcomePageHandlerMapping welcomePageHandlerMapping(ApplicationContext applicationContext,
                                                           FormattingConversionService mvcConversionService, ResourceUrlProvider mvcResourceUrlProvider) {
    // 这里 new 了一个WelcomePageHandlerMapping
    WelcomePageHandlerMapping welcomePageHandlerMapping = new WelcomePageHandlerMapping(
        new TemplateAvailabilityProviders(applicationContext), applicationContext, getWelcomePage(),
        this.mvcProperties.getStaticPathPattern());
    welcomePageHandlerMapping.setInterceptors(getInterceptors(mvcConversionService, mvcResourceUrlProvider));
    welcomePageHandlerMapping.setCorsConfigurations(getCorsConfigurations());
    return welcomePageHandlerMapping;
}
// WelcomePageHandlerMapping类 的构造器
WelcomePageHandlerMapping(TemplateAvailabilityProviders templateAvailabilityProviders,
                          ApplicationContext applicationContext, Resource welcomePage, String staticPathPattern) {
    //判断 welcomePage 默认值就是index.html,并且 /** 的请求路径等不等于静态资源路径。
    if (welcomePage != null && "/**".equals(staticPathPattern)) {
        logger.info("Adding welcome page: " + welcomePage);
        // 如果是就设置视图解析器为转发到 index.html
        setRootViewName("forward:index.html");
    }
    //如果上面条件不满足就 找controller请求有没有/index有则设置视图的解析为index
    else if (welcomeTemplateExists(templateAvailabilityProviders, applicationContext)) {
        logger.info("Adding welcome page template: index");
        setRootViewName("index");
    }
}

RestFul风格使用

RestFul 风格的讲解参考 SpringMVC

在 SpringBoot中使用 RestFul风格 ,因为表单中只有 getpost 两种提交方式,所以在SpringBoot中使用 RestFul风格必须走以下流程

1、开启 RestFul 风格支持

# 默认值是 false 需要开启才能使用
spring:
  mvc:
    hiddenmethod:
      filter:
        enabled: true

2、在提交的表单中添加以下隐藏域属性 <input type="text" name="_method" value="提交方法" hidden>

<!--原来的网页是这样写的-->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

<form action="/user" method="get">
    <input type="submit" value="get提交">
</form>

<form action="/user" method="post">
    <input type="submit" value="post提交">
</form>

<form action="/user" method="put">
    <input type="submit" value="put提交">
</form>

<form action="/user" method="delete">
    <input type="submit" value="delete提交">
</form>

</body>
</html>


<!--使用RestFul风格后,提交的表单需要添加一个隐藏域,隐藏一个叫 _method-->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

<form action="/user" method="get">
    <input type="submit" value="get提交">
</form>

<form action="/user" method="post">
    <input type="submit" value="post提交">
</form>

<form action="/user" method="post">
    <input type="text" name="_method" value="put" hidden>
    <input type="submit" value="put提交">
</form>

<form action="/user" method="post">
    <input type="text" name="_method" value="delete" hidden>
    <input type="submit" value="delete提交">
</form>

</body>
</html>

3、Controller编写

package com.wyx.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

@Controller
public class RestFulController {

    @ResponseBody
    @GetMapping("/user")
    public String get(){
        return "get方式提交";
    }
    @ResponseBody
    @PostMapping("/user")
    public String post(){
        return "post方式提交";
    }
    @ResponseBody
    @PutMapping("/user")
    public String put(){
        return "put方式提交";
    }
    @ResponseBody
    @DeleteMapping("/user")
    public String delete(){
        return "delete方式提交";
    }
}

RestFul 风格底层源码分析

首先我们找到 spring-boot-autoconfigure-2.4.5.jar Spring自动配置的源码,接下来找 web 目录下的 WebMvcAutoConfiguration配置类,找到 hiddenHttpMethodFilter方法,翻译为Http方法过滤,查看源码如下

@Bean
@ConditionalOnMissingBean(HiddenHttpMethodFilter.class) //如果没有这个Bean才注入
//spring.mvc.hiddenmethod.filter为true才注入,默认值是false,这里说明了第一步,我们为什么要修改配置文件为 true 
@ConditionalOnProperty(prefix = "spring.mvc.hiddenmethod.filter", name = "enabled", matchIfMissing = false)
public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
    return new OrderedHiddenHttpMethodFilter();
}

接着查看它 new 的类 OrderedHiddenHttpMethodFilter 这个方法继承了HiddenHttpMethodFilter类 `查看源码其中有一段

protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
    //将原生的请求赋值给 requestToUse 后面在放行它 filterChain.doFilter((ServletRequest)requestToUse, response)
    HttpServletRequest requestToUse = request;	
    //判断请求方式是不是 POST请求并查看请求有没有出错的地方,这里也说明了表单提交为什么要使用 post提交
    if ("POST".equals(request.getMethod()) && request.getAttribute("javax.servlet.error.exception") == null) {
        /*获取请求参数中名为methodParam的参数,
        methodParam在上面有定义:private String methodParam = "_method";,就是_method,这里也说明了为什么我们隐藏域
        中要这样写 <input type="text" name="_method" value="提交方法" hidden>
        */
        String paramValue = request.getParameter(this.methodParam);
        //判断请求参数有没有长度
        if (StringUtils.hasLength(paramValue)) {
            //将请求参数转换为全大写,证明我们在写请求参数时不区分大小写
            String method = paramValue.toUpperCase(Locale.ENGLISH);
            /*ALLOWED_METHODS 静态常量
                static {	ALLOWED_METHODS = Collections.unmodifiableList(Arrays.asList(
                HttpMethod.PUT.name(),
                HttpMethod.DELETE.name(), 
                HttpMethod.PATCH.name()));
    			}
    			支持 PUT DELETE PATCH 请求
    			//判断是不是支持的请求,如果是,就用HttpMethodRequestWrapper来包装它并返回给 requestToUse
            */
            if (ALLOWED_METHODS.contains(method)) {
                requestToUse = new HiddenHttpMethodFilter.HttpMethodRequestWrapper(request, method);
            }
        }
    }
    filterChain.doFilter((ServletRequest)requestToUse, response);
}

拓展 修改请求参数 _method

​ 查看源码,我们知道,容器中没有HiddenHttpMethodFilter我们才注入,我们不妨来自己注入 HiddenHttpMethodFilter

package com.wyx.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//注入IOC容器
    public HiddenHttpMethodFilter hiddenHttpMethodFilter(){
        HiddenHttpMethodFilter methodFilter = new HiddenHttpMethodFilter();
        // 查看源码,它的 methodParam参数可以修改值
        methodFilter.setMethodParam("requestName");
        return methodFilter;
    }
}
//	编写完成后我们隐藏域中的
//	<input type="text" name="_method" value="提交方法" hidden> 需要改写为
//	<input type="text" name="requestName" value="提交方法" hidden>

SpringMVC配置扩展

SpringBoot-Web

上面是官方文档的一段翻译,意思是我们可以扩展 SpringMVC的功能,实现功能的扩展,那么我们需要以下几步

1、创建一个类并实现 WebMvcConfigurer 接口

2、在配置定义的类上添加 @Configuration 注解

package com.wyx.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.View;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import java.util.Locale;

@Configuration
public class MyWebConfig implements WebMvcConfigurer {


    //添加Controller请求跳转视图,WebMvcConfigurer 里面有非常多的方法可以重写
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/hello").setViewName("index");
    }

    //自定义视图解析器
    public static class MyViewResolver implements ViewResolver{
        @Override
        public View resolveViewName(String viewName, Locale locale) throws Exception {
            return null;
        }
    }  
    //将自定义的视图解析器装载到 IOC 容器
    @Bean
    public MyViewResolver myViewResolver(){
        MyViewResolver resolver = new MyViewResolver();
        return resolver;
    }
    
}

官网特别说明:不能加 @EnableWebMvc 我们查看这个注解 它给我们导入了一个类 DelegatingWebMvcConfiguration

@Import(DelegatingWebMvcConfiguration.class)
public @interface EnableWebMvc {
}

在查看 DelegatingWebMvcConfiguration 发现它继承 WebMvcConfigurationSupport

@Configuration(proxyBeanMethods = false)
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
}

当我们添加@EnableWebMvc 时就会将 WebMvcConfigurationSupport 注入IOC 容器,然而在 WebMvcAutoConfiguration的配置类中有这么一条条件装配注解 @ConditionalOnMissingBean(WebMvcConfigurationSupport.class) 如果存在这个类就不会生效。

总结:如果我们添加@EnableWebMvc 注解,那么Spring自动装配的功能都会失效。

Spring Boot拦截器

1、编写拦截器类

SpringBoot 中有一个接口 HandlerInterceptor 专门用来做拦截功能,我们想要实现拦截器,可以编写一个类实现HandlerInterceptor 接口在重写接口中的三个方法即可。下面是三个方法的解释

preHandle():目标方法执行之前执行

postHandle():目标方法执行完成以后执行

afterCompletion():页面渲染以后执行

package com.wyx.config;

import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class LoginHandlerInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        Object loginUser = request.getSession().getAttribute("loginUser");
        if(loginUser == null){
            request.setAttribute("msg","请登录后访问");
            request.getRequestDispatcher("/").forward(request,response);
            return false;
        }
        return true;
    }
    
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
    }
}

2、将编写的拦截器类注入IOC容器(实现WebMvcConfigurer的addInterceptors

package com.wyx.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class MyMvcConfig implements WebMvcConfigurer {

    //拦截过滤器
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginHandlerInterceptor())
                .addPathPatterns("/**")  //拦截所有请求
                .excludePathPatterns("/","/index.html","/css/*","/js/*","/img/*","/login");  //放行哪些请求
    }
}

3、当有多个拦截器时同时作用一个方法时

preHandle():按照实现拦截器的顺序优先进行拦截,postHandle()afterCompletion()按照拦截器的顺序,反向执行。(类似于IO流的先用后关操作)

文件上传

SpringBoot 已经为我们向容器中添加了文件上传功能 MultipartFile,使用文件上传功能我们只需要

1、在表单中使用enctype="multipart/form-data"代表是文件上传

2、在控制层中接收表单的参数解析成为@RequestPart("headerImg") MultipartFile headerImg即可

使用方式

<form method="post" action="/upload" enctype="multipart/form-data">
    <input type="file" name="file"><br>
    <input type="submit" value="提交">
</form>
@PostMapping("/upload")
public String upload(@RequestPart("file") MultipartFile headerImg
                    ) throws IOException {

    if(!headerImg.isEmpty()){
        //保存到文件服务器,OSS服务器
        String originalFilename = headerImg.getOriginalFilename();
        headerImg.transferTo(new File("H:\\cache\\"+originalFilename));
    }
    return "main";
}

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

1、使用Servlet API

@ServletComponentScan(basePackages = "com.wyx.servlet") :指定原生Servlet组件都放在那里

@WebServlet(urlPatterns = "/my"):效果:直接响应,没有经过Spring的拦截器

@WebFilter(urlPatterns={"/css/*","/images/*"})

@WebListener

2、使用RegistrationBean

ServletRegistrationBean`, `FilterRegistrationBean`, and `ServletListenerRegistrationBean
@Configuration
public class MyRegistConfig {

    @Bean
    public ServletRegistrationBean myServlet(){
        MyServlet myServlet = new MyServlet();

        return new ServletRegistrationBean(myServlet,"/my","/my02");
    }


    @Bean
    public FilterRegistrationBean myFilter(){

        MyFilter myFilter = new MyFilter();
//        return new FilterRegistrationBean(myFilter,myServlet());
        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(myFilter);
        filterRegistrationBean.setUrlPatterns(Arrays.asList("/my","/css/*"));
        return filterRegistrationBean;
    }

    @Bean
    public ServletListenerRegistrationBean myListener(){
        MySwervletContextListener mySwervletContextListener = new MySwervletContextListener();
        return new ServletListenerRegistrationBean(mySwervletContextListener);
    }
}

连接数据源

1、整合JDBC

1、导入依赖

<!--JDBC启动器-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<!--MySQL连接驱动-->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
</dependency>

2、配置数据库连接源

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/mybatis_plus?useSSL=true&useUnicode=true&charterEncoding=utf8&serverTimezone=GMT%2B8
    password: 970699
    username: root
    driver-class-name: com.mysql.cj.jdbc.Driver

3、测试连接数据源

@SpringBootTest
class SpringbootDataJdbcApplicationTests {

    //DI注入数据源
    @Autowired
    DataSource dataSource;

    @Test
    public void contextLoads() throws SQLException {
        //看一下默认数据源
        System.out.println(dataSource.getClass());
        //获得连接
        Connection connection =   dataSource.getConnection();
        System.out.println(connection);
        //关闭连接
        connection.close();
        // 测试得到的数据源是 HikariDataSource
    }
}

4、使用JDBC模板

SpringBoot中即使不使用第三方第数据库操作框架,如 MyBatis等,Spring 本身也对原生的JDBC 做了轻量级的封装,即JdbcTemplate。

1、数据库操作的所有 CRUD 方法都在 JdbcTemplate 中。

2、Spring Boot 不仅提供了默认的数据源,同时默认已经配置好了 JdbcTemplate 放在了容器中,程序员只需自己注入即可使用

3、JdbcTemplate 的自动配置是依赖 org.springframework.boot.autoconfigure.jdbc 包下的 JdbcTemplateConfiguration 类

JdbcTemplate主要提供以下几类方法:

  • execute方法:可以用于执行任何SQL语句,一般用于执行DDL语句;
  • update方法及batchUpdate方法:update方法用于执行新增、修改、删除等语句;batchUpdate方法用于执行批处理相关语句;
  • query方法及queryForXXX方法:用于执行查询相关语句;
  • call方法:用于执行存储过程、函数相关语句。
package com.kuang.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Date;
import java.util.List;
import java.util.Map;

@RestController
@RequestMapping("/jdbc")
public class JdbcController {

    /**
     * Spring Boot 默认提供了数据源,默认提供了 org.springframework.jdbc.core.JdbcTemplate
     * JdbcTemplate 中会自己注入数据源,用于简化 JDBC操作
     * 还能避免一些常见的错误,使用起来也不用再自己来关闭数据库连接
     */
    @Autowired
    JdbcTemplate jdbcTemplate;

    //查询employee表中所有数据
    //List 中的1个 Map 对应数据库的 1行数据
    //Map 中的 key 对应数据库的字段名,value 对应数据库的字段值
    @GetMapping("/list")
    public List<Map<String, Object>> userList(){
        String sql = "select * from employee";
        List<Map<String, Object>> maps = jdbcTemplate.queryForList(sql);
        return maps;
    }
    
    //新增一个用户
    @GetMapping("/add")
    public String addUser(){
        //插入语句,注意时间问题
        String sql = "insert into employee(last_name, email,gender,department,birth)" +
                " values ('狂神说','24736743@qq.com',1,101,'"+ new Date().toLocaleString() +"')";
        jdbcTemplate.update(sql);
        //查询
        return "addOk";
    }

    //修改用户信息
    @GetMapping("/update/{id}")
    public String updateUser(@PathVariable("id") int id){
        //插入语句
        String sql = "update employee set last_name=?,email=? where id="+id;
        //数据
        Object[] objects = new Object[2];
        objects[0] = "秦疆";
        objects[1] = "24736743@sina.com";
        jdbcTemplate.update(sql,objects);
        //查询
        return "updateOk";
    }

    //删除用户
    @GetMapping("/delete/{id}")
    public String delUser(@PathVariable("id") int id){
        //插入语句
        String sql = "delete from employee where id=?";
        jdbcTemplate.update(sql,id);
        //查询
        return "deleteOk";
    }
}

2、整合Druid

Druid 是阿里巴巴开源平台上一个数据库连接池实现,结合了 C3P0、DBCP 等 DB 池的优点,同时加入了日志监控。

Spring Boot 2.0 以上默认使用 Hikari 数据源,可以说 Hikari 与 Driud 都是当前 Java Web 上最优秀的数据源,我们来重点介绍 Spring Boot 如何集成 Druid 数据源,如何实现数据库监控。

1、导入Druid依赖

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.1.21</version>
</dependency>

2、切换数据源;之前已经说过 Spring Boot 2.0 以上默认使用 Hikari 数据源,但可以 通过 spring.datasource.type 指定数据源。

spring:
  datasource:
    username: root
    password: 970699
    url: jdbc:mysql://localhost:3306/springboot?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
    driver-class-name: com.mysql.cj.jdbc.Driver
    type: com.alibaba.druid.pool.DruidDataSource # 自定义数据源

下面是对 Druid数据源的一些参数配置讲解

spring:
  datasource:
	#Spring Boot 默认是不注入这些属性值的,需要自己绑定
    #druid 数据源专有配置
    initialSize: 5
    minIdle: 5
    maxActive: 20
    maxWait: 60000
    timeBetweenEvictionRunsMillis: 60000
    minEvictableIdleTimeMillis: 300000
    validationQuery: SELECT 1 FROM DUAL
    testWhileIdle: true
    testOnBorrow: false
    testOnReturn: false
    poolPreparedStatements: true

    #配置监控统计拦截的filters,stat:监控统计、log4j:日志记录、wall:防御sql注入
    #如果允许时报错  java.lang.ClassNotFoundException: org.apache.log4j.Priority
    #则导入 log4j 依赖即可,Maven 地址:https://mvnrepository.com/artifact/log4j/log4j
    filters: stat,wall,log4j
    maxPoolPreparedStatementPerConnectionSize: 20
    useGlobalDataSourceStat: true
    connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500

我们以上配置的属性该如何让它生效呢?

利用 SpringBoot 的自动配置原理,我们只需要编写一个类来获取数据即可

@Configuration
public class DruidConfig {

    /*
       将自定义的 Druid数据源添加到容器中,不再让 Spring Boot 自动创建
       绑定全局配置文件中的 druid 数据源属性到 com.alibaba.druid.pool.DruidDataSource从而让它们生效
       @ConfigurationProperties(prefix = "spring.datasource"):作用就是将 全局配置文件中
       前缀为 spring.datasource的属性值注入到 com.alibaba.druid.pool.DruidDataSource 的同名参数中
     */
    @ConfigurationProperties(prefix = "spring.datasource")
    @Bean
    public DataSource druidDataSource() {
        return new DruidDataSource();
    }
}

测试自己配置的东西是否生效

@SpringBootTest
class SpringbootDataJdbcApplicationTests {

    //DI注入数据源
    @Autowired
    DataSource dataSource;

    @Test
    public void contextLoads() throws SQLException {
        //看一下默认数据源
        System.out.println(dataSource.getClass());
        //获得连接
        Connection connection =   dataSource.getConnection();
        System.out.println(connection);

        DruidDataSource druidDataSource = (DruidDataSource) dataSource;
        System.out.println("druidDataSource 数据源最大连接数:" + druidDataSource.getMaxActive());
        System.out.println("druidDataSource 数据源初始化连接数:" + druidDataSource.getInitialSize());

        //关闭连接
        connection.close();
    }
}

Druid的后台监控功能实现

//由于没有Web.xml配置文件,所以使用 Spring Boot 的注册 Servlet 方式

@Configuration
public class MyRegistConfig {
    @Bean
    public ServletRegistrationBean statViewServlet() {
        ServletRegistrationBean bean = new ServletRegistrationBean(new StatViewServlet(), "/druid/*");

        // 这些参数可以在 com.alibaba.druid.support.http.StatViewServlet 
        // 的父类 com.alibaba.druid.support.http.ResourceServlet 中找到
        Map<String, String> initParams = new HashMap<>();
        initParams.put("loginUsername", "admin"); //后台管理界面的登录账号
        initParams.put("loginPassword", "123456"); //后台管理界面的登录密码

        //后台允许谁可以访问
        //initParams.put("allow", "localhost"):表示只有本机可以访问
        //initParams.put("allow", ""):为空或者为null时,表示允许所有访问
        initParams.put("allow", "");
        //deny:Druid 后台拒绝谁访问
        //initParams.put("kuangshen", "192.168.1.20");表示禁止此ip访问

        //设置初始化参数
        bean.setInitParameters(initParams);
        return bean;
    }
    
    //WebStatFilter:用于配置Web和Druid数据源之间的管理关联监控统计
    //配置过滤器,因为SpringBoot中配置的过滤器并不会拦截原生组件配置的Servlet请求
    @Bean
    public FilterRegistrationBean webStatFilter() {
        FilterRegistrationBean bean = new FilterRegistrationBean();
        bean.setFilter(new WebStatFilter());

        //exclusions:设置哪些请求进行过滤排除掉,从而不进行统计
        Map<String, String> initParams = new HashMap<>();
        initParams.put("exclusions", "*.js,*.css,/druid/*,/jdbc/*");
        bean.setInitParameters(initParams);

        //"/*" 表示过滤所有请求
        bean.setUrlPatterns(Arrays.asList("/*"));
        return bean;
    }
}

访问:http://localhost:8080/druid/login.html 可以查看监控页面

3、整合Mybatis(重点)

1、导入相关依赖

<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.1.1</version>
</dependency>

2、配置数据源

spring:
  datasource:
    username: root
    password: 970699
    url: jdbc:mysql://localhost:3306/springboot?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
    driver-class-name: com.mysql.cj.jdbc.Driver

3、创建实体类

package com.wyx.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {

    private String name;
    private Integer age;
    private String sex;

}

4、创建Mapper接口

//@Mapper : 表示本类是一个 MyBatis 的 Mapper
@Mapper
@Repository
public interface DepartmentMapper {

    // 获取所有用户
    List<User> getUsers();
    // 通过姓名获得用户
    User getUser(String name);
    
}

5、编写对应的Mapper文件

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.wyx.mapper.User">

    <select id="getUsers" resultType="User">
       select * from user;
    </select>

    <select id="getUser" resultType="User" parameterType="string">
       select * from user where name = #{name};
    </select>
</mapper>

6、处理Maven资源过滤问题

<resources>
    <resource>
        <directory>src/main/java</directory>
        <includes>
            <include>**/*.xml</include>
        </includes>
        <filtering>true</filtering>
    </resource>
</resources>

7、编写测试方法

@RestController
public class UserController {
    
    @Autowired
    UserMapper userMapper;
    
    
    @GetMapping("/getUser")
    public List<Department> getUsers(){
        return userMapper.userMapper();
    }

    @GetMapping("/getDepartment/{name}")
    public Department getDepartment(@PathVariable("name") String name){
        return userMapper.getUser(name);
    }
}
上一篇:Kubernetes客户端和管理界面大集合


下一篇:人机交互—9月28日