SpringBoot2(一)初识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文件,即可在文件中进行配置

在这里插入图片描述
例如,修改Tomcat的默认端口号:

server.port=8888

简化部署

引入依赖

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

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

在这里插入图片描述

自动配置原理

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注解。

源码分析

引导加载自动配置类

在这里插入图片描述

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

在这里插入图片描述
PackageImports(metadata).getPackageNames()用于得到元数据所在的包名,即得到MainApplication的包名:com.atguigu.boot
请添加图片描述
即将元数据所在包路径以字符串数组进行注册。

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

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

在这里插入图片描述
(2)调用

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

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

在这里插入图片描述
(3)利用工厂加载

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

得到所有的组件。

在这里插入图片描述
(4)从META-INF/spring.factories位置来加载一个文件,默认扫描当前系统里所有META-INF/spring.factories位置的文件,SpringBoot启动时默认全部加载

spring-boot-autoconfigure-2.3.4.RELEASE.jar包中的META-INF/spring.factories:
在这里插入图片描述

按需开启自动配置项

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

修改默认配置

在这里插入图片描述
(一)

@ConditionalOnBean(MultipartResolver.class)

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

(二)

@ConditionalOnMissingBean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME)

容器中没有这个名字为multipartResolver的组件。在这里插入图片描述
(三)

@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中进行修改配置

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值