SpringBoot:自动配置原理(一文搞懂)

本文详细解读了Spring Boot的自动装配原理,介绍了pom.xml中依赖管理的关键部分,包括`spring-boot-starter-parent`和`spring-boot-dependencies`的作用,以及如何修改默认版本和使用场景启动器。还探讨了`@SpringBootApplication`注解的工作机制和`@AutoConfiguration`的自动配置流程。
摘要由CSDN通过智能技术生成

自动装配原理

pom.xml

依赖管理

<!--有一个父项目,用来做依赖管理的-->
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.4.5</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>

点进去后发现还有一个父依赖,这里的父依赖为包的版本管理

这里才是真正管理SpringBoot应用里面所有依赖版本的地方,SpringBoot的版本控制中心;

以后我们导入依赖默认是不需要写版本;但是如果导入的包没有在依赖中管理着就需要手动配置版本了;

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-dependencies</artifactId>
    <version>2.4.5</version>
</parent>

修改默认默认版本号

1、查看spring-boot-dependencies里面规定当前依赖的版本 用的 key。
2、在当前项目里面重写配置
<properties>
    <mysql.version>5.1.43</mysql.version>
</properties>

启动器 spring-boot-starter

<!--web 依赖,tomcat,dispatcherServlet,xml-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

spring-boot-starter-xxx:就是spring-boot的场景启动器,xxx就是某种场景

spring-boot-starter-web:帮我们导入了web模块正常运行所依赖的组件;

SpringBoot将所有的功能场景都抽取出来,做成一个个的spring-boot-starter-xxx(启动器),只需要在项目中引入这些starter即可,所有相关的依赖都会导入进来 , 我们要用什么功能就导入什么样的场景启动器即可 ;我们未来也可以自己自定义 starter;

见到***-springboot-boot-starter:第三方为我们提供的简化开发的场景启动器

  • 默认的包结构
  • 主程序所在包及其下面的所有子包里面的组件都会被默认扫描进来,无需以前的包扫描配置,想要改变扫描路径,@SpringBootApplication(scanBasePackages=“com.atguigu”)
  • 或者@ComponentScan 指定扫描路径

查看IOC容器里面的所有组件

@SpringBootApplication
public class HellobootApplication {
    public static void main(String[] args) {
        //1、返回IOC容器
        ConfigurableApplicationContext run = SpringApplication.run(HellobootApplication.class, args);
        //2、查看容器里面的组件
        String[] names=run.getBeanDefinitionNames();
        for (String name : names) {
            System.out.println(name);
        }
    }
}
注解

@SpringBootApplication其实就是下面三个注解的集合,依次分析

  • @SpringBootConfiguration
  • @EnableAutoConfiguration
  • @ComponentScan()
@SpringBootConfiguration//springboot的配置
@EnableAutoConfiguration//自动配置,开启配置功能
//自动扫描
@ComponentScan(excludeFilters = { 
    @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
	 @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
}

@SpringBootConfiguration

作用:标注在某个类上说明这个类是SpringBoot的主配置类 , SpringBoot就应该运行这个类的main方法来启动SpringBoot应用;

@Configuration    //代表spring配置类,配置类就是对应Spring的xml 配置文件;
public @interface SpringBootConfiguration {
}

@Component//说明启动类本身也是Spring中的一个组件而已,负责启动应用!
public @interface Configuration {
}

@EnableAutoConfiguration

@EnableAutoConfiguration告诉SpringBoot开启自动配置功能,这样自动配置才能生效

@AutoConfigurationPackage		//自动配置包
//AutoConfigurationImportSelector:自动配置导入选择
@Import(AutoConfigurationImportSelector.class)//给容器导入组件,导入选择器
public @interface EnableAutoConfiguration {
}

@AutoConfigurationPackage注解源码解析

//@import :Spring底层注解@import,给容器中导入一个组件
//Registrar.class 作用:利用Registrar给容器批量导入一系列组件,MainApplication所在包下
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {
}

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

@Import(AutoConfigurationImportSelector.class)注解源码解析

流程

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

AutoConfigurationImportSelector:自动配置导入选择器,那么它会导入哪些组件的选择器

public class AutoConfigurationImportSelector implements xxxxx {
    //获得所有自动配置的实体
    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);
	}
    // 获得候选的配置
    protected List<String> getCandidateConfigurations(
        AnnotationMetadata metadata, AnnotationAttributes attributes) {
        //这里加载的就是标注了EnableAutoConfiguration注解的类,说白了就是主启动类SpringBootApplication
        //返回的就是启动自动导入配置文件的注解类:EnableAutoConfiguration
        List<String> configurations =SpringFactoriesLoader.loadFactoryNames(
            getSpringFactoriesLoaderFactoryClass(),
            getBeanClassLoader());
        //没找到META-INF/spring.factories文件,此文件为自动配置的核心文件
        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;
    }
    //返回的就是启动自动导入配置文件的注解类;EnableAutoConfiguration
    protected Class<?> getSpringFactoriesLoaderFactoryClass() {
		return EnableAutoConfiguration.class;
	}
}

这个方法又调用了SpringFactoriesLoader 类的静态方法!我们进入SpringFactoriesLoader类loadFactoryNames() 方法

public final class SpringFactoriesLoader {
    public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
    //获取所有的配置
    public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
        ClassLoader classLoaderToUse = classLoader;
        if (classLoader == null) {
            classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
        }
        String factoryTypeName = factoryType.getName();
        //这里它又调用了loadSpringFactories 方法
        return (List)loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
    }
    //********************************************************
    //加载"META-INF/spring.factories"配置文件,所有的自动配置类都在这里了
    //???思考:这么多自动配置为什么有的没有生效,需要导入对应的start才能有作用;核心注解:@ConditionalOnClass(),如果这里面的条件都成立,才会生效
    private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
      //获得classLoader,得到的就是EnableAutoConfiguration标注的类本身
		Map<String, List<String>> result = cache.get(classLoader);
		if (result != null) {
			return result;
		}
		result = new HashMap<>();
		try {
         //以前的是去获取一个资源 "META-INF/spring.factories"
			Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
         //将读取到的资源"判断有没有更多元素"==遍历,封装成为一个Properties
			while (urls.hasMoreElements()) {
				URL url = urls.nextElement();
				UrlResource resource = new UrlResource(url);
            //资源加载到properties里面
				Properties properties = PropertiesLoaderUtils.loadProperties(resource);
				for (Map.Entry<?, ?> entry : properties.entrySet()) {
					String factoryTypeName = ((String) entry.getKey()).trim();
					String[] factoryImplementationNames =
							StringUtils.commaDelimitedListToStringArray((String) entry.getValue());
					for (String factoryImplementationName : factoryImplementationNames) {
						result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>())
								.add(factoryImplementationName.trim());
					}
				}
			}
			// Replace all lists with unmodifiable lists containing unique elements
			result.replaceAll((factoryType, implementations) -> implementations.stream().distinct()
					.collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList)));
			cache.put(classLoader, result);
		}
		catch (IOException ex) {
			throw new IllegalArgumentException("Unable to load factories from location [" +
					FACTORIES_RESOURCE_LOCATION + "]", ex);
		}
		return result;
	}
}

发现一个多次出现的文件:spring.factories,
请添加图片描述

//所有的spring.factories资源加载到properties类中
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
while (urls.hasMoreElements()) {
    URL url = urls.nextElement();
    UrlResource resource = new UrlResource(url);
    Properties properties = PropertiesLoaderUtils.loadProperties(resource);
}

按需开启自动项配置

思考:一开始加载了这么多自动配置,但为什么有的没有生效,需要导入对应的start才能有作用;

答:核心注解:@ConditionalOnClass(),如果这里面的条件都成立,才会生效, 比如RabbitAutoConfiguration配置

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ RabbitTemplate.class, Channel.class })//需要使里面的条件都成立
@EnableConfigurationProperties(RabbitProperties.class)
@Import(RabbitAnnotationDrivenConfiguration.class)
public class RabbitAutoConfiguration {
}

步骤总结:

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

  • 每个自动配置类按照条件进行生效,默认都会绑定配置文件指定的值。xxxxProperties里面拿。xxxProperties和配置文件进行了绑定

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

  • 只要容器中有这些组件,相当于这些功能就有了

  • 定制化配置

  • 用户直接自己@Bean替换底层的组件

  • 用户去看这个组件是获取的配置文件什么值就去修改。

  • 如果用户自己配置了以用户的优先

总流程

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

@ComponentScan

作用:扫描当前主启动类同级的包

方式:@SpringBootApplication(scanBasePackages=“com.atguigu”)或者@ComponentScan指定扫描路径

结论:SpringBoot所有的自动配置都是在启动的时候扫描并加载:META-INF/spring.factories,所有的自动配置都在这里面了,但是不一定生效,要判断条件是否成立,只要导入了对应的start,就有对应的启动器了,有了启动器,自动转配就会生效,然后就配置成功了;

所有自动配置的东西,都在spring-boot-autoconfigure-2.4.5.jar里面

HttpEncodingAutoConfiguration类的源码解析:

//表示这是一个配置类,和以前编写的配置文件一样,也可以给容器中添加组件;
@Configuration(proxyBeanMethods = false)
//启动指定类的ConfigurationProperties功能;
//进入这个HttpProperties查看,将配置文件中对应的值和HttpProperties绑定起来;
//并把HttpProperties加入到ioc容器中
@EnableConfigurationProperties(ServerProperties.class)
//Spring底层@Conditional注解
//根据不同的条件判断,如果满足指定的条件,整个配置类里面的配置就会生效;
//这里的意思就是判断当前应用是否是web应用,如果是,当前配置类生效
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
//判断当前项目有没有这个类CharacterEncodingFilter;SpringMVC中进行乱码解决的过滤器;
@ConditionalOnClass(CharacterEncodingFilter.class)
//判断配置文件中是否存在某个配置:spring.http.encoding.enabled;
//如果不存在,判断也是成立的
//即使我们配置文件中不配置spring.http.encoding.enabled=true,也是默认生效的;
@ConditionalOnProperty(prefix = "server.servlet.encoding", value = "enabled", matchIfMissing = true)
public class HttpEncodingAutoConfiguration {
   //他已经和SpringBoot的配置文件映射了
	private final Encoding properties;
   //只有一个有参构造器的情况下,参数的值就会从容器中拿
	public HttpEncodingAutoConfiguration(ServerProperties properties) {
		this.properties = properties.getServlet().getEncoding();
	}
   
	//给容器中添加一个组件,这个组件的某些值需要从properties中获取
	@Bean
	@ConditionalOnMissingBean 	//判断容器没有这个组件?
	public CharacterEncodingFilter characterEncodingFilter() {
      //字符编码的过滤
		CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
		filter.setEncoding(this.properties.getCharset().name());
		filter.setForceRequestEncoding(this.properties.shouldForce(Encoding.Type.REQUEST));
		filter.setForceResponseEncoding(this.properties.shouldForce(Encoding.Type.RESPONSE));
		return filter;
	}
	。。。
}

与配置文件的关联关系:

请添加图片描述

WebMvcAutoConfiguration关联

在这里插入图片描述

xxxxAutoConfigurartion:自动配置类;给容器中添加组件

xxxxProperties:封装配置文件中相关属性;

主启动方法

SpringApplication.run(HellobootApplication.class, args);

这个类主要做了以下四件事情:

1、推断应用的类型是普通的项目还是Web项目

2、查找并加载所有可用初始化器 , 设置到initializers属性中

3、找出所有的应用程序监听器,设置到listeners属性中

4、自动推断并设置main方法的定义类,找到运行的主类

查看它的构造方法

public class SpringApplication {
    //构造方法
    public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
        this.resourceLoader = resourceLoader;
        Assert.notNull(primarySources, "PrimarySources must not be null");
        this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
        //1、推断应用类型是否为WEB
        this.webApplicationType = WebApplicationType.deduceFromClasspath();
        //2、加载所有可用初始化器
        this.bootstrapRegistryInitializers = getBootstrapRegistryInitializersFromSpringFactories();
        setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
        //3、设置所有可用程序监听器
        setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
        //4、推断并设置main方法的定义类
        this.mainApplicationClass = deduceMainApplicationClass();
    }
    public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
		return run(new Class<?>[] { primarySource }, args);
	}
    public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
		return new SpringApplication(primarySources).run(args);
	}
    
    public ConfigurableApplicationContext run(String... args) {
        //计时器实例化并启动,应用监听器开始监听
		StopWatch stopWatch = new StopWatch();
		stopWatch.start();
		DefaultBootstrapContext bootstrapContext = createBootstrapContext();
		ConfigurableApplicationContext context = null;
        //1、Headless系统熟悉设置
		configureHeadlessProperty();
        //2、初始化监听器:getRunListeners(args)
		SpringApplicationRunListeners listeners = getRunListeners(args);
        //3、启动已准备好的监听器
		listeners.starting(bootstrapContext, this.mainApplicationClass);
		try {
            //4、装配环境参数:DefaultApplicationArguments(args)
			ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
			ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
			configureIgnoreBeanInfo(environment);
            //5、打印banner图案
			Banner printedBanner = printBanner(environment);
            //6、上下文区域
			context = createApplicationContext();
            //7、准备上下文异常报告器
			context.setApplicationStartup(this.applicationStartup);
            //8、上下文前置处理:prepareContext()
			prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
            //9、上下文刷新:refreshContext-->bean工厂加载,通过工厂生成bean,刷新生命周期
			refreshContext(context);
            //10、上下文后置结束处理:afterRefresh
			afterRefresh(context, applicationArguments);
			stopWatch.stop();
			if (this.logStartupInfo) {
				new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
			}
			listeners.started(context);
            //11、执行Runner运行器,发布应用上下文
			callRunners(context, applicationArguments);
		}
		catch (Throwable ex) {
			handleRunFailure(context, ex, listeners);
			throw new IllegalStateException(ex);
		}
		try {
			listeners.running(context);
		}
		catch (Throwable ex) {
			handleRunFailure(context, ex, null);
			throw new IllegalStateException(ex);
		}
        //12、返回应用上下文
		return context;
	}
}
  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值