IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> spring boot自动装配源码分析 -> 正文阅读

[Java知识库]spring boot自动装配源码分析

原创不易,转载请注明出处


前言

在没有spring boot的年代,有幸体验到了ssm 整合配置的痛苦,那个时候,要想将springmvc ,spring ,mybatis整合起来,需要配置一堆的xml文件,说不痛苦那是假的。spring boot 面市之后,你还别说,是真的香,想用啥组件的时候,找找它的starter,引入starter依赖,就能整合在一起了,也没有那堆xml配置文件了,其实依赖starter就能整合,得益于spring boot 自动装配特性,starter中存在自动配置类,项目启动的时候,能够自动装配对应组件的一些对象啥的,本文主要就是看看spring boot 自动装配到底是怎么搞得。

1. @SpringBootApplication

我们都知道,创建一个spring boot项目的时候,都会有一个主启动类,该类上面需要有一个@SpringBootApplication 注解,就像下面这个启动类一样

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

那么我们需要看看这个 @SpringBootApplication 注解内藏的玄机了。

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
    excludeFilters = {@Filter(
    type = FilterType.CUSTOM,
    classes = {TypeExcludeFilter.class}
), @Filter(
    type = FilterType.CUSTOM,
    classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {}

由三个注解组成的
@SpringBootConfiguration:spring boot 配置类,里面有个@Configuration 注解,这个注解大家应该都熟悉,spring的配置类注解
@EnableAutoConfiguration: 自动装配的注解,今天主角儿。
@ComponentScan:组件扫描注解
接下来我们看看@EnableAutoConfiguration注解

2.@EnableAutoConfiguration

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
	String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
	Class<?>[] exclude() default {};
	String[] excludeName() default {};
}

由@AutoConfigurationPackage 注解和@Import(AutoConfigurationImportSelector.class) 注解组成。里面还有2个属性,都是指定需要排除的类。
@AutoConfigurationPackage 从名字上看是自动配置package
@Import 这个注解更牛逼,可以导入4种类型的类, 可以导入 配置类,ImportSelector实现类,ImportBeanDefinitionRegistrar实现类,普通类。
我们挨个看看这两个注解

2.1 @AutoConfigurationPackage

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {}

有个@Import注解,这里导入的是个registrar类型的
我们看下AutoConfigurationPackages.Registrar 代码

/**
 * {@link ImportBeanDefinitionRegistrar} to store the base package from the importing
 * configuration.
 */
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
	@Override
	public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
		register(registry, new PackageImport(metadata).getPackageName());
	}
	@Override
	public Set<Object> determineImports(AnnotationMetadata metadata) {
		return Collections.singleton(new PackageImport(metadata));
	}
}

看下上面的注释,说是为了存储 基本的package
看下registerBeanDefinitions 方法,从名字上就是注册bean的。
new PackageImport(metadata).getPackageName() 就是为了获取 你主启动类所在的包。

public static void register(BeanDefinitionRegistry registry, String... packageNames) {
	if (registry.containsBeanDefinition(BEAN)) {// AutoConfigurationPackages.class.getName()
		BeanDefinition beanDefinition = registry.getBeanDefinition(BEAN);
		ConstructorArgumentValues constructorArguments = beanDefinition.getConstructorArgumentValues();
		constructorArguments.addIndexedArgumentValue(0, addBasePackages(constructorArguments, packageNames));
	}
	else {
		GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
		beanDefinition.setBeanClass(BasePackages.class);
		beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0, packageNames);
		beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
		registry.registerBeanDefinition(BEAN, beanDefinition);
	}
}

判断,如果注册表中存在AutoConfigurationPackages 这个bean的话,就向设置一下它构造的第一参数值是 主启动类所在的包。
如果不存在的话,先创建BasePackages这个类对应beanDefinition ,在设置构造第一个参数值是主启动类所有在包。
默认是不存在。
我们看下这个BasePackages

static final class BasePackages {
	private final List<String> packages;
	private boolean loggedBasePackageInfo;
	BasePackages(String... names) {
		List<String> packages = new ArrayList<>();
		for (String name : names) {
			if (StringUtils.hasText(name)) {
				packages.add(name);
			}
		}
		this.packages = packages;
	}
}

其实就是将主启动类的包,塞到它packages 这个集合中,BasePackages 还提供了一个get方法,用来获取packages 里面的值的。
说白了,这个BasePackages 就是存储主启动类对应包的,如果后续有哪些组件想获取base package 就可以找到BasePackages bean,获取存储的主启动类package。这也是spring boot 主启动类要放在外层的原因,因为一些组件默认是扫面主启动类所在包下面的类。
好了,了解了@AutoConfigurationPackage 注解的作用,我们在来看下@Import(AutoConfigurationImportSelector.class)

2.2 @Import(AutoConfigurationImportSelector.class)

主要就是看下AutoConfigurationImportSelector

@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
	if (!isEnabled(annotationMetadata)) {
		return NO_IMPORTS;
	}
	// 加载元数据
	AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
			.loadMetadata(this.beanClassLoader);
	//spi找出那些EnableAutoConfiguration 对应的类 / 也就是自动配置类
	AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(
			autoConfigurationMetadata, annotationMetadata);
	//将需要加载的自动配置类返回
	return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}

这里selectImports方法核心作用就是找到你那些自动配置类 ,返回给spring,让spring给你实例化,加载配置。
我们直接看下getAutoConfigurationEntry 方法,该方法就是找自动配置类的。

protected AutoConfigurationEntry getAutoConfigurationEntry(
		AutoConfigurationMetadata autoConfigurationMetadata,
		AnnotationMetadata annotationMetadata) {
	if (!isEnabled(annotationMetadata)) {
		return EMPTY_ENTRY;
	}
	// 获取注解下的属性值
	AnnotationAttributes attributes = getAttributes(annotationMetadata);
	// 到spring.factories中找出所有的EnableAutoConfiguration 实现类,就是那些自动配置类
	List<String> configurations = getCandidateConfigurations(annotationMetadata,
			attributes);
	// 去重
	configurations = removeDuplicates(configurations);
	// 找出那些需要排除掉的
	Set<String> exclusions = getExclusions(annotationMetadata, attributes);
	//校验
	checkExcludedClasses(configurations, exclusions);
	// 移除那些排除掉的 configuration
	configurations.removeAll(exclusions);
	// 拿 AutoConfigurationImportFilter 进行过滤
	configurations = filter(configurations, autoConfigurationMetadata);
	// 通知 AutoConfigurationImportListener
	fireAutoConfigurationImportEvents(configurations, exclusions);
	// 封装AutoConfigurationEntry 对象返回
	return new AutoConfigurationEntry(configurations, exclusions);
}

getAutoConfigurationEntry 方法的每行代码都做了注释,最核心的还是调用getCandidateConfigurations 方法找到自动装配类。
找到之后,先排除掉你在注解中配置的那些需要排除的自动装配类,接着使用你自定义的AutoConfigurationImportFilter 过滤器过滤一下,最后才将剩下的封装 AutoConfigurationEntry 对象返回,到上一个方法中也是取得AutoConfigurationEntry 的configurations 属性返回的。
接下来我们看下 getCandidateConfigurations 方法是怎样找到自动装配类的。

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
		AnnotationAttributes attributes) {
	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 spi 来加载的,spring spi 原理与jdk 提供spi是一样的,都是根据接口 去文件中找对应的实现。
这里简单说下spi 的原理:
就拿jdbc 来说吧,jdk 提供了一个Driver驱动接口,然后由各大数据库厂商 做响应的实现,mysql 提供的MysqlDriver,orcale提供的OrcaleDriver(这里不一定真实存在),比如说我项目中 使用了mysql的驱动,jdbc 创建连接的时候需要我的驱动,但是它不知道我用的是哪家的驱动,这个时候 就需要在项目中配置一个文件,告诉一下jdbc 我用的是MysqlDriver
所以在mysql的驱动jar 的META-INF/services文件夹下面就有一个java.sql.Driver 文件, 这个文件名就是jdbc driver接口的全类名,表示是里面内容是java.sql.Driver 接口的实现,里面内容就是com.mysql.jdbc.Driver ,表示mysql的驱动实现是com.mysql.jdbc.Driver 这个类。

spring spi不一样的是路径是META-INF,然后文件名也是统一的spring.factories ,里面内容采用的是 接口全类名=接口实现类全类名 的格式。
它这边就是加载的EnableAutoConfiguration 全类名org.springframework.boot.autoconfigure.EnableAutoConfiguration对应的实现。
在这里插入图片描述
多个实现类就用英文逗号隔开就行了。
可以看到它的实现类都是AutoConfiguration 结尾的,这个是一个约定俗成的,如果你要写一个自动配置类,最好是以AutoConfiguration 结尾,见名知意。
随便拿出来一个看下。

@Configuration
@ConditionalOnClass({ EnableAspectJAutoProxy.class, Aspect.class, Advice.class,
		AnnotatedElement.class })
@ConditionalOnProperty(prefix = "spring.aop", name = "auto", havingValue = "true", matchIfMissing = true)
public class AopAutoConfiguration {}

都是带有@Configuration 注解的,这个spring 就能够知道了,他是个配置类。

总结

@EnableAutoConfiguration 自动装配注解由两个注解组成, @AutoConfigurationPackage 这个注解主要是为了将主启动类对应包 存储到BasePackages 这个bean对象中,@Import(AutoConfigurationImportSelector.class) 主要就是通过spi找出那些自动装配配置类,交个spring 实例化,加载。

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2021-09-01 11:47:38  更:2021-09-01 11:49:01 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 13:15:56-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码