文章目录

  • SpringBoot
  • SpringBoot优点
  • SpringBoot入门案例
  • 创建Maven工程
  • 自动配置原理
  • SpringBoot特点
  • 依赖管理
  • 自动配置
  • 容器功能
  • 组件添加
  • 原生配置文件引入
  • 配置绑定
  • 源码分析
  • 引导加载自动配置类
  • 按需开启自动配置项
  • 修改默认配置


SpringBoot

SpringBoot优点

  1. 创建独立的Spring应用
  2. 内嵌Web服务器。
  3. 自动starter依赖,简化构建配置
  4. 自动配置Spring以及第三方功能
  5. 提供生产级别的监控、健康检查及外部化配置
  6. 无代码生成,无需编写XML。

SpringBoot是整合Spring技术栈的一站式框架,也是简化Spring技术栈的快速开发脚手架。

SpringBoot入门案例

创建Maven工程

引入依赖

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


    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>

创建主程序

package com.atguigu.boot;

import ch.qos.logback.core.db.DBHelper;
import com.atguigu.boot.bean.Pet;
import com.atguigu.boot.bean.User;
import com.atguigu.boot.config.MyConfig;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;

/**
 * @version 1.0
 * @Description
 * @Author 月上叁竿
 * @Date 2022-03-12 15:14
 **/
@SpringBootApplication
public class MainApplication {
    public static void main(String[] args) {
    	SpringApplication.run(MainApplication.class, args);
    }
}

编写业务

package com.atguigu.boot.controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @version 1.0
 * @Description
 * @Author 月上叁竿
 * @Date 2022-03-12 15:16
 **/
@RestController
public class HelloController {
    @RequestMapping("/hello")
    public String handle01(){
        return "Hello,SpringBoot2";
    }
}

测试直接运行main方法即可。

简化配置

在src\main\resources下新建application.properties文件,即可在文件中进行配置

springboot oauth2 示例_spring


例如,修改Tomcat的默认端口号:

server.port=8888

简化部署

引入依赖

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

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

springboot oauth2 示例_后端_02

自动配置原理

SpringBoot特点

依赖管理

父项目做依赖管理

依赖管理
    <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场景启动器

spring-boot-starter-*,*代表某种场景。

只要引入starter,这个场景的所有常规需要的依赖都自动引入。

而见到的*-spring-boot-starter是第三方提供的简化开发的场景启动器。

所有场景启动器最底层的依赖如下:

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

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

引入依赖默认都可以不写版本。

引入非版本仲裁的jar包,要写版本号。

可以修改默认版本号

查看spring-boot-dependencies里规定当前依赖的版本用的key。

在当前项目中重写配置。

<properties>
        <mysql.version>5.1.43</mysql.version>
    </properties>
自动配置

自动配好Tomcat

引入tomcat依赖,配置Tomcat。

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

自动配好SpringMVC

  • 引入SpringMVC全套组件。
  • 自动配好SpringMVC常用组件。

自动配好Web常见功能,如:字符编码功能。

默认的包结构

  • 主程序所在的包及其下面的所有的子包里面的组件都会被默认扫描进来。
  • 无需以前的包扫描位置。
  • 想要改变扫描路径,@SpringBootApplication(scanBasePackage=“com.atguigu”)或者@ComponentScan指定扫描路径
@SpringBootApplication
等同于
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan("com.atguigu.boot")

各种配置拥有默认值

  • 默认配置最终都是映射到某个类上。
  • 配置文件的值最终会绑定每个类上,这个类会在容器中创建对象。

按需加载所有自动配置项

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

容器功能

组件添加

@Configuration

Full模式(proxyBeanMethods = true):创建Bean实例时SpringBoot总会检查这个组件是否已经在容器中,如果在容器中,就返回容器中的实例;若不存在则创建。因此使用Full模式能确保每个@Bean方法返回的组件都是单实例的,且能解决组件中的依赖关系。

Lite模式(proxyBeanMethods = false):创建Bean实例时SpringBoot不会检查该组件是否在容器中,而是立即创建。因此能加速容器启动过程,但不能解决组件之间的依赖关系,且@Bean所注解的方法返回的组件都是新创建的。

  • 配置类(自身也是组件):
package com.atguigu.boot.config;

import com.atguigu.boot.bean.Pet;
import com.atguigu.boot.bean.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @version 1.0
 * @Description
 * 1、配置类里面使用@Bean标注在方法上给容器注册组件,默认是单实例的
 * 2、配置类本身也是组件
 * 3、proxyBeanMethods:代理bean的方法
 *      Full(proxyBeanMethods = true) 解决组件依赖 开启代理对象调用方法 创建Bean对象时SpringBoot总会检查这个组件是否在容器中存在
 *      Lite(proxyBeanMethods = false) 不能解决组件依赖,且创建对象时不会检查组件是否已经在容器存在  因此更快
 * @Author 月上叁竿
 * @Date 2022-03-13 9:12
 **/
@Configuration(proxyBeanMethods = true)  // 告诉Spring Boot这是一个配置类  == 配置文件
public class MyConfig {
    /**
     * 外部无论对配置类中的这个组件注册方法调用多少次获取的都是之前注册容器中的单实例
     * @return
     */
    @Bean // 给容器中添加组件,以方法名作为组件的id,返回类型就是组件类型。返回的值,就是组件在容器中的实例。
    public User user01(){
        User user = new User("张三", 20);
        user.setPet(tomcat());
        return user;
    }

    @Bean("tom")
    public Pet tomcat(){
        return new Pet("tomcat");
    }
}
  • 测试代码:
package com.atguigu.boot;

import com.atguigu.boot.bean.Pet;
import com.atguigu.boot.bean.User;
import com.atguigu.boot.config.MyConfig;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;

/**
 * @version 1.0
 * @Description
 * @Author 月上叁竿
 * @Date 2022-03-12 15:14
 **/
@SpringBootApplication
public class MainApplication {
    public static void main(String[] args) {
        // 返回IOC容器
        ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);

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

		// 从容器中获得组件
        User user01 = run.getBean("user01", User.class);
        User user02 = run.getBean("user01", User.class);
        System.out.println(user01 == user02);

        MyConfig config = run.getBean(MyConfig.class);
        // 如果@Configuration(proxyBeanMethods = true)代理对象调用方法
        // SpringBoot总会检查这个组件是否存在于容器中。
        User user = config.user01();
        User user1 = config.user01();
        System.out.println(user == user1);

        User user2 = run.getBean("user01", User.class);
        Pet tom = run.getBean("tom", Pet.class);
        System.out.println(user2.getPet() == tom);
    }
}

实战建议:

  • 配置类组件之间无依赖关系用Lite模式加速容器启动过程,从而减少判断
  • 配置类组件之间有依赖关系,方法会被调用得到之前的单实例组件,用Full模式

@Import

给容器中自动创建出这两个类型的组件,默认组件的名字就是全类名。

@Import({User.class, DBHelper.class})
@Configuration(proxyBeanMethods = false)
public class MyConfig {
}

@Conditional

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

  • 配置类
@Configuration(proxyBeanMethods = false)
// @ConditionalOnBean(name = "tom")
@ConditionalOnMissingBean(name = "tom22")
public class MyConfig {
    /**
     * 外部无论对配置类中的这个组件注册方法调用多少次获取的都是之前注册容器中的单实例
     * @return
     */
    @Bean // 给容器中添加组件,以方法名作为组件的id,返回类型就是组件类型。返回的值,就是组件在容器中的实例。
    public User user01(){
        User user = new User("张三", 20);
        user.setPet(tomcat());
        return user;
    }

    @Bean("tom")
    public Pet tomcat(){
        return new Pet("tomcat");
    }
}
  • 主程序测试
package com.atguigu.boot;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;

/**
 * @version 1.0
 * @Description
 * @Author 月上叁竿
 * @Date 2022-03-12 15:14
 **/
@SpringBootApplication
public class MainApplication {
    public static void main(String[] args) {
        // 返回IOC容器
        ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);
.       
        boolean tom = run.containsBean("tom");
        System.out.println("容器中是否有tom组件" + tom);

        boolean user01 = run.containsBean("user01");
        System.out.println("容器中是否有user组件" + user01);

        boolean tom22 = run.containsBean("tom22");
        System.out.println("容器中是否有tom22" + tom22);
    }
}
原生配置文件引入

@ImportResource

用于导入Spring的配置文件。

  • Spring配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <beans>
        <bean id="xixi" class="com.atguigu.boot.bean.User">
            <property name="name" value="zhangsan"></property>
            <property name="age" value="18"></property>
        </bean>

        <bean id="haha" class="com.atguigu.boot.bean.Pet">
            <property name="name" value="tomcat"></property>
        </bean>
    </beans>
</beans>
  • 配置类
package com.atguigu.boot.config;

import ch.qos.logback.core.db.DBHelper;
import com.atguigu.boot.bean.Pet;
import com.atguigu.boot.bean.User;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.ImportResource;

/**
 * @version 1.0
 * @Description
 * 1、配置类里面使用@Bean标注在方法上给容器注册组件,默认是单实例的
 * 2、配置类本身也是组件
 * 3、proxyBeanMethods:代理bean的方法
 *      Full(proxyBeanMethods = true) 解决组件依赖 开启代理对象调用方法 创建Bean对象时SpringBoot总会检查这个组件是否在容器中存在
 *      Lite(proxyBeanMethods = false) 不能解决组件依赖,且创建对象时不会检查组件是否已经在容器存在  因此更快
 * 4、@Import()注解给容器中自动创建组件,默认组件的名字就是全类名
 * 5、@ImportResource("classpath:beans.xml")用于导入Spring的配置文件。
 * @Author 月上叁竿
 * @Date 2022-03-13 9:12
 **/
@Import({User.class, DBHelper.class})
@Configuration(proxyBeanMethods = false)  // 告诉Spring Boot这是一个配置类  == 配置文件
//@ConditionalOnMissingBean(name = "tom22")
@ImportResource("classpath:beans.xml")
public class MyConfig {
    /**
     * 外部无论对配置类中的这个组件注册方法调用多少次获取的都是之前注册容器中的单实例
     * @return
     */
    @Bean // 给容器中添加组件,以方法名作为组件的id,返回类型就是组件类型。返回的值,就是组件在容器中的实例。
    public User user01(){
        User user = new User("张三", 20);
        user.setPet(tomcat());
        return user;
    }

    @Bean("tom")
    public Pet tomcat(){
        return new Pet("tomcat");
    }
}
  • 主程序进行测试
package com.atguigu.boot;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;

/**
 * @version 1.0
 * @Description
 * @Author 月上叁竿
 * @Date 2022-03-12 15:14
 **/
@SpringBootApplication
public class MainApplication {
    public static void main(String[] args) {
        // 返回IOC容器
        ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);

        boolean xixi = run.containsBean("xixi");
        boolean haha = run.containsBean("haha");
        System.out.println("xixi:" + xixi);
        System.out.println("haha:" + haha);
    }
}
配置绑定

Bean中写:@Component + @ConfigurationProperties

  • 配置文件application.properties
mycar.brand=Gallardo
mycar.price=3500000
package com.atguigu.boot.bean;

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

/**
 * @version 1.0
 * @Description
 * @Author 月上叁竿
 * @Date 2022-03-28 9:19
 **/
@Component
@ConfigurationProperties(prefix = "mycar")
public class Car {
    private String brand;
    private Integer price;

    public Car() {
    }

    public Car(String brand, Integer price) {
        this.brand = brand;
        this.price = 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 +
                '}';
    }
}
  • Controller进行测试
package com.atguigu.boot.controller;

import com.atguigu.boot.bean.Car;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @version 1.0
 * @Description
 * @Author 月上叁竿
 * @Date 2022-03-12 15:16
 **/
@RestController
public class HelloController {
    @Autowired
    Car car;

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

配置类中写:@EnableConfigurationProperties + @ConfigurationProperties

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

此时Car类不需要添加@Component注解。

源码分析

引导加载自动配置类

springboot oauth2 示例_spring boot_03

@SpringBootConfiguration

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

@ComponentScan

指定扫描哪些包路径。

@EnableAutoConfiguration

@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
  1. @AutoConfigurationPackage 指定了默认的包规则。
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {

Registrar源码:

static class Registrar implements ImportBeanDefinitionRegistrar,DeterminableImports {

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

springboot oauth2 示例_User_04


PackageImports(metadata).getPackageNames()用于得到元数据所在的包名,即得到MainApplication的包名:com.atguigu.boot

springboot oauth2 示例_java-ee_05


即将元数据所在包路径以字符串数组进行注册。

@AutoConfigurationPackage底层利用Registrar给容器中导入指定包下的所有组件。

  1. @Import(AutoConfigurationImportSelector.class)
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
	if (!isEnabled(annotationMetadata)) {
		return EMPTY_ENTRY;
	}
	AnnotationAttributes attributes = getAttributes(annotationMetadata);
	List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
	configurations = removeDuplicates(configurations);
	Set<String> exclusions = getExclusions(annotationMetadata, attributes);
	checkExcludedClasses(configurations, exclusions);
	configurations.removeAll(exclusions);
	configurations = getConfigurationClassFilter().filter(configurations);
	fireAutoConfigurationImportEvents(configurations, exclusions);
	return new AutoConfigurationEntry(configurations, exclusions);
}

利用getAutoConfigurationEntry(annotationMetadata)给容器中批量导入一些组件:

(1)通过调用

List<String> configurations = getCandidateConfigurations(annotationMetadata, attribute)

获取到所有需要导入到容器中的配置类:

springboot oauth2 示例_User_06


(2)调用

List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),getBeanClassLoader());

获取到所有需要导入到容器中的配置类。

springboot oauth2 示例_java-ee_07


(3)利用工厂加载

Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader)

得到所有的组件。

springboot oauth2 示例_User_08


(4)从META-INF/spring.factories位置来加载一个文件,默认扫描当前系统里所有META-INF/spring.factories位置的文件,SpringBoot启动时默认全部加载spring-boot-autoconfigure-2.3.4.RELEASE.jar包中的META-INF/spring.factories:

springboot oauth2 示例_spring_09

按需开启自动配置项

虽然Spring Boot在自动配置启动的时候默认全部加载META-INF/spring.factories中的场景,但按照条件装配规则(@Conditional),最终会按需配置。

springboot oauth2 示例_java-ee_10

修改默认配置

springboot oauth2 示例_后端_11


(一)

@ConditionalOnBean(MultipartResolver.class)

容器中有此组件类型,符合装配规则。

(二)

@ConditionalOnMissingBean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME)

容器中没有这个名字为multipartResolver的组件。

springboot oauth2 示例_后端_12


(三)

@Bean
public MultipartResolver multipartResolver(MultipartResolver resolver) {
	// Detect if the user has created a MultipartResolver but named it incorrectly
	return resolver;
}

当@Bean标注的方法传入到对象参数,这个参数的值就会在容器中找。

通过此方法可以防止用户配置的文件上传解析器不符合规范。

总结:

  1. SpringBoot先加载所有的自动配置类,即后缀为AutoConfiguration的类。
  2. 每个自动配置类按照条件进行生效,默认都会绑定配置文件指定的值。xxxProperties和配置文件进行了绑定。
  3. 生效的配置类就会给容器中装配很多组件。
  4. 只要容器中有这些组件,相当于这些功能都有了。
  5. 还支持定制化配置:(1)用户直接@Bean替换底层的组件,自己进行装配。(2)用户去底层查看组件获取的配置文件的值,在application.properties文件进行就该即可。

流程如下:

xxxAutoConfiguration ---- 组件 ---- xxxProperties里面拿值 ---- application.properties中进行修改配置