SpringBoot自动装配原理
Spring Factories机制
SpringBoot自动装配是根据Spring Factories机制实现的,Spring Factories机制是SpringBoot的一种服务发现机制,这种扩展机制和Java SPI机制相似。Spring Boot会扫描所有jar包下的META-INFO/spring.factories文件,并读取其中内容,进行实例化,这种机制也是Spring Boot Starter的基础。
spring.factories
该文件本质上和properies文件类似,包含一组或多组键值对(key=value),其中key是接口的完全限定类名,value是接口实现类的完全限定类名,一个接口可以有多个实现类。
org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\
org.springframework.boot.autoconfigure.condition.OnBeanCondition,\
org.springframework.boot.autoconfigure.condition.OnClassCondition,\
org.springframework.boot.autoconfigure.condition.OnWebApplicationCondition
Spring Factories实现原理
spring-core包里定义了一个springfactoriesloader类,这个类会扫描所有jar包下的META-INFO/spring.factories文件,并获取指定接口的配置,该类定义了俩对外的方法。
返回值 | 方法 | 描述 |
---|---|---|
List | loadFactories(Class factoryType, @Nullable ClassLoader classLoader) | 根据接口获取实现类的实例,返回的是实现类对象列表。 |
List | loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) | 根据接口获取实现类的名称,返回实现类名称列表。 |
loadFactories()源码如下
public static <T> List<T> loadFactories(Class<T> factoryType, @Nullable ClassLoader classLoader) {
Assert.notNull(factoryType, "'factoryType' must not be null");
ClassLoader classLoaderToUse = classLoader;
if (classLoaderToUse == null) {
classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
}
//使用loadFactoryNames获取接口的实现类
List<String> factoryImplementationNames = loadFactoryNames(factoryType, classLoaderToUse);
if (logger.isTraceEnabled()) {
logger.trace("Loaded [" + factoryType.getName() + "] names: " + factoryImplementationNames);
}
//遍历loadFactoryNames数组,创建实现类的对象
List<T> result = new ArrayList<>(factoryImplementationNames.size());
for (String factoryImplementationName : factoryImplementationNames) {
result.add(instantiateFactory(factoryImplementationName, factoryType, classLoaderToUse));
}
AnnotationAwareOrderComparator.sort(result);
return result;
}
loadFactoryNames()源码如下
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
ClassLoader classLoaderToUse = classLoader;
if (classLoaderToUse == null) {
classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
}
String factoryTypeName = factoryType.getName();
//获取自动配置类
return loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
}
loadSpringFactories()方法能够读取该项目中所有的jar包路径下的META-INFO/spring.factories文件的配置内容,并以map的形式返回。源码如下。
private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
Map<String, List<String>> result = cache.get(classLoader);
if (result != null) {
return result;
}
result = new HashMap<>();
try {
//扫描所有jar包路径下的META-INFO/spring.faactories文件
Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
UrlResource resource = new UrlResource(url);
//将扫描到的文件内容包装成properties对象
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
for (Map.Entry<?, ?> entry : properties.entrySet()) {
//提取properties对象中的key值
String factoryTypeName = ((String) entry.getKey()).trim();
//提取value值
String[] factoryImplementationNames =
StringUtils.commaDelimitedListToStringArray((String) entry.getValue());
//遍历配置类数组,转换成list集合
for (String factoryImplementationName : factoryImplementationNames) {
result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>())
.add(factoryImplementationName.trim());
}
}
}
// Replace all lists with unmodifiable lists containing unique elements
//将properties对象中的key与实现类集合一一对应存入result
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;
}
自动配置的加载
SpringBoot自动化配置也是基于spring factories机制实现,在spring-boot-autoconfigure-xxx.jar类路径下的META-INF/spring.factories中设置了spring boot自动配置内容。配置里面value取值是由多个xxxAutoConfiguration组成,每个xxxAutoConfiguration都是一个自动配置类。SpringBoot启动时,会利用spring-factories机制,将这些配置类实例化到容器,以实现自动配置。
@springbootapplication注解
是一个组合源注解,包含俩核心注解,@SpringBootConfiguration和@EnableAutoConfigration,其中@EnableAutoConfigration是自动配置的核心。
@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 {
@EnableAutoConfigration注解
用于开启springboot自动配置功能,使用@import注解通过AutoConfigrationImportSelector类(选择器)给容器中导入自动配置组件。
@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 {};
}
AutoConfigurationImportSelector类
SpringBoot启动流程
1、进入run方法
@SpringBootApplication
public class BlogApplication {
public static void main(String[] args) {
SpringApplication.run(BlogApplication.class, args);
}
}
2、进入如下
public static ConfigurableApplicationContext run(Object[] sources, String[] args) {
return new SpringApplication(sources).run(args);
}
3、看看SpringApplication的构造流程
private void initialize(Object[] sources) {
if (sources != null && sources.length > 0) {
this.sources.addAll(Arrays.asList(sources));
}
//初始化环境。环境分为三种:非web环境、web环境、reactive环境。
//其判断逻辑就是判断是否存在指定的类,默认是Servlet环境
this.webEnvironment = deduceWebEnvironment();
//getSpringFactoriesInstances加载spring.factories文件,
//设置ApplicationContextInitalizer
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));
//获取监听器
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
//设置启动类信息
this.mainApplicationClass = deduceMainApplicationClass();
}
4、看看run方法
public ConfigurableApplicationContext run(String... args) {
//开启关于启动时间的信息监控
long startTime = System.nanoTime();
//创建一个引导容器,并在此时(容器从未使用前)从spring.factories扫描一些实现了bootstrapper接口的类,
//来引导容器里注册一些需要的东西
DefaultBootstrapContext bootstrapContext = createBootstrapContext();
//准备ApplicationContext
ConfigurableApplicationContext context = null;
//java.awt.headless是j2se的一种模式用于在缺少显示屏、键盘或鼠标时的系统配置
//很多监控工具如jconsole需要将该值设为true 系统变量默认为true
configureHeadlessProperty();
//获取spring的监听器类,从spring.factories中获取,
//默认的是以 org.springframework.boot.SpringApplicationRunListener为key,
//获取到的监听器类型为EventPublishingRunListener
SpringApplicationRunListeners listeners = getRunListeners(args);
//监听器发送启动事件,触发所有存入SpringApplicationRunListeners的satrting事件
listeners.starting(bootstrapContext, this.mainApplicationClass);
try {
//将args内容中的参数(类似 --spring.port=9999)解析成键值对存放到applicationArguments里
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
//创建一个environment对象,添加了好几个功能各异的PropertySource,
//触发所有存入SpringApplicationRunListeners的environmentPrepared事件
//将environment中的spring.main开头的配置数据,一一对应绑定到SpringApplication(即this)字段上
ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
//将environment的spring.beaninfo.ignore配置复制到System.Property去(若不存在)
configureIgnoreBeanInfo(environment);
//根据this.bannerMode判断是否打印Banner,以及打印在哪里,根据this.banner判断打印什么样的banner
Banner printedBanner = printBanner(environment);
//根据web类型创建不同的ApplicationContext,这里的创建仅实例化而已,
//没有从构造方法调用loadBeanDefinitions和refresh的逻辑
context = createApplicationContext();
//将ApplicationStartup(步骤记录器)复制给context
context.setApplicationStartup(this.applicationStartup);
//对context做一些配置,执行initializers的initialize()
//发布prepareContext事件
//关闭引导容器,
//使用BeanDefinilitionLoader根据sources和run方法参数加载BeanDefinition到容器中
//发布contextLoaded事件
prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
//注册一个钩子,当jvm关闭时,相应的关闭context,然后调用容器的refresh()方法
refreshContext(context);
//留给子类扩展
afterRefresh(context, applicationArguments);
//计时结束
Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime);
//打印计时数据
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), timeTakenToStartup);
}
//发布started事件
listeners.started(context, timeTakenToStartup);
//从容器中取出ApplicationRunner/CommandLineRunner两类bean,并调用它们的run方法
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
//发布failed事件
handleRunFailure(context, ex, listeners);
throw new IllegalStateException(ex);
}
try {
//发布ready方法
Duration timeTakenToReady = Duration.ofNanos(System.nanoTime() - startTime);
listeners.ready(context, timeTakenToReady);
}
catch (Throwable ex) {
//发布failed事件
handleRunFailure(context, ex, null);
throw new IllegalStateException(ex);
}
return context;
}
5、重点步骤分析
5.1、获取监听器
就是从spring.factories文件中获取监听器集合,当有事件发生时调用监听器对应事件的方法。
默认是以 org.springframework.boot.SpringApplicationRunListener为key,获取到的监听器为EventPublishingRunListenener。
SpringApplicationRunListeners listeners = getRunListeners(args);
总结:Spring在启动是会从spring.factories加载监听器集合,默认类型是EventPublishingRunListenener,在事件发生时,EventPublishingRunListenener会去容器中寻找ApplicationListener对应的bean,并进行事件通知。
5.2、环境变量构造
加载一些配置文件的内容
ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
进入
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {
// Create and configure the environment
//获取或创建environment
ConfigurableEnvironment environment = getOrCreateEnvironment();
//将入参配置到环境配置中
configureEnvironment(environment, applicationArguments.getSourceArgs());
ConfigurationPropertySources.attach(environment);
//发布环境准备事件
listeners.environmentPrepared(bootstrapContext, environment);
DefaultPropertiesPropertySource.moveToEnd(environment);
Assert.state(!environment.containsProperty("spring.main.environment-prefix"),
"Environment prefix cannot be set via properties.");
bindToSpringApplication(environment);
if (!this.isCustomEnvironment) {
environment = convertEnvironment(environment);
}
ConfigurationPropertySources.attach(environment);
return environment;
}
在准备环境事件时,加载springboot配置文件,
5.3、创建上下文
5.4、SpringApplication#refreshContext
对容器进行一个刷新的工作,在此进行了大量的工作,这里的处理工作由springboot交给spring处理
最终进入到org.springframework.context.support.AbstractApplicationContext#refresh
主程序类(主入口类)
我们现在来分析一下主程序类中的相关基本注解。
@SpringBootApplication
@SpringBootApplication是Springboot应用标注在某个类上,用来说明这个类是Springboot的入口类,springboot就应该运行在这个类的main方法来启动Springboot应用,我们按住Command键点进去,我们主要看里面的注解
@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 {
/**
* Exclude specific auto-configuration classes such that they will never be applied.
* @return the classes to exclude
*/
@AliasFor(annotation = EnableAutoConfiguration.class, attribute = "exclude")
Class<?>[] exclude() default {};
/**
* Exclude specific auto-configuration class names such that they will never be
* applied.
* @return the class names to exclude
* @since 1.3.0
*/
@AliasFor(annotation = EnableAutoConfiguration.class, attribute = "excludeName")
String[] excludeName() default {};
/**
* Base packages to scan for annotated components. Use {@link #scanBasePackageClasses}
* for a type-safe alternative to String-based package names.
* @return base packages to scan
* @since 1.3.0
*/
@AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
String[] scanBasePackages() default {};
/**
* Type-safe alternative to {@link #scanBasePackages} for specifying the packages to
* scan for annotated components. The package of each class specified will be scanned.
* <p>
* Consider creating a special no-op marker class or interface in each package that
* serves no purpose other than being referenced by this attribute.
* @return base packages to scan
* @since 1.3.0
*/
@AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
Class<?>[] scanBasePackageClasses() default {};
}
@SpringBootApplication
在@SpringBootApplication中,一个比较关键的注解就是@SpringBootApplication,@SpringBootConfiguration是Springboot的配置类,标注在某个类上表示,这个类是一个Springboot的配置,同样按住Ctrl键点进去,我们我们会看到@SpringBootConfiguration中有一个@Configuration,这个注解就是标注各种配置类,配置文件的,如果我们对AOP有一定理解的话,其实就知道配置类也是容器中的一个组件,所以在@Configuration中,我们是通过@Component进行标注的。
@EnableAutoConfiguration
回到@SpringBootApplication中,我会可以看到另一个配置类注解@EnableAutoConfiguration,这个注解是标注着开启自动配置功能,这样才使得我们不需要像以前在Spring项目中那样,配置繁琐的东西,而是Springboot会帮我们自动配置,@EnableAutoConfiguration告诉SpringBoot开启自动配置功能,这样启动配置才能生效,这个注解里面的东西,我们要好好讲一下,因为这样我们就会知道,SpringBoot是怎么帮我们自动加载相关依赖,并放置在哪里的,内容如下。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(EnableAutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
/**
* Exclude specific auto-configuration classes such that they will never be applied.
* @return the classes to exclude
*/
Class<?>[] exclude() default {};
/**
* Exclude specific auto-configuration class names such that they will never be
* applied.
* @return the class names to exclude
* @since 1.3.0
*/
String[] excludeName() default {};
}
@AutoConfigurationPackage自动配置包,Spring的底层注解@import,给容器导入一个组件,导入的组件由AutoConfigurationPackage.Registrar.class进行注册,其实AutoConfigurationPackage就是将主配置类(@SpringBootApplication标注的类)的所在包及下面所有的子包里面所有的组件扫描到Spring容器,如果你以前写过SpringMVC项目之类的会,就会知道,我们在写一些Controller或者Service的时候,我们需要在配置文件中,配置bean扫描controller的包名,而我们在SpringBoot就不需要了
AutoConfigurationImportSelector
在@AutoConfigurationPackage下面,我们会发现我们通过import导入了一个类,我们进入到这个类中,然后在下图位置debug一下这个类,如下图
就会发现,其实AutoConfigurationImportSelector就是给容器中导入非常多的自动配置类,也就是给容器中导入这个场景需要的所有组件,并配置好这些组件,有了自动配置类,就免去了我们手动编写配置注入功能组件等工作了。
Springboot在启动的时候,从类路径下的META-INF/Spring.factories中获取EnableAutoConfiguration指定的值,将这些值作为自动配置类导入到容器中,自动配置类就生效,帮我们进行自动配置的工作,如下图。
快速创建SpringBoot项目
前面我们创建一个项目是,先创建一个Maven项目,然后导入相关的Springboot依赖,但是其实我们可以在idea中,利用Idea创建项目,帮我们自动创建我们需要的相关依赖的springboot项目
————————————————
版权声明:本文为CSDN博主「BoCong-Deng」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/DBC_121/article/details/104429074