现在Java面试时基本必问的一道题:为什么要用Springboot?SpringBoot相比Spring有什么优点?
说到这个问题,必然会牵扯到自动装配,无需手动添加很多配置,开箱即用。
首先我们都知道,在spring中约定大于配置。
所以我们首先要有一个约定,启动项目的时候去读取某个目录下的配置。
那么所有需要自动装配的工具,都可以在这个约定的目录下设置自己的初始默认配置。项目启动时自动统一加载即可。
当前SpringBoot官网最新版本是2.7.5,咱们这次就分析这个版本下的源码。
由于
凡是写过Springboot项目的人,应该对这个注解都不陌生。@SpringBootApplication注解时SpringBoot项目的核心注解。只需要在启动类上面加上这个注解,Springboot项目就可以成功启动。
点开SpringBootApplication注解,可以简单看一下这个注解的介绍:
这个便利的注解等同于声明了 @Configuration+@EnableAutoConfiguration + @ComponentScan.
三合一注解。
但是自动装配的重点是@EnableAutoConfiguration注解,下面继续介绍这个注解。
在@EnableAutoConfiguration注解中,使用@Import注解引入了AutoConfigurationImportSelector类。
下面分析的重点就是AutoConfigurationImportSelector类中的方法了。
AutoConfigurationImportSelector实现了DeferredImportSelector接口。
DeferredImportSelector接口又继承了ImportSelector接口。
selectImports方法,顾名思义就是要查询出需要导入的类。入参是注解的元数据信息,出参是要导入的类的全类名。
下面我们分析一下AutoConfigurationImportSelector类重写的selectImports方法:
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
//判断是否开启了自动装配
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
//获取自动装配的配置类信息
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
下面我们继续分析一下selectImports方法中的getAutoConfigurationEntry方法,看看是如何找到的配置信息:
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);
}
这个方法就是从配置文件中获取获取自动装配的配置了。可以看到,配置是通过SpringFactoriesLoader加载工厂,下面我们看一下这个加载方法内部是如何实现的。
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
List<String> configurations = new ArrayList<>(
SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader()));
ImportCandidates.load(AutoConfiguration.class, getBeanClassLoader()).forEach(configurations::add);
Assert.notEmpty(configurations,
"No auto configuration classes found in META-INF/spring.factories nor in META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. If you "
+ "are using a custom packaging, make sure that file is correct.");
return configurations;
}
方法内部首先判断了一下传进来的classLoader是否为空,做了默认处理。
然后通过loadSpringFactories方法进行加载,继续跟下去。
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());
}
重点业务逻辑来了:
1、首先判断这个类加载器是否已经放到了缓存,即是否已经加载过,如果已经加载过,直接返回。
2、执行try catch中的业务逻辑。
3、我们可以看到后面的业务逻辑都是对下面这段代码的获取结果进行的加工处理。
Enumeration urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
4、重点就是上面这句代码:获取资源,下面我们看一下这个常量是什么
public static final String FACTORIES_RESOURCE_LOCATION = “META-INF/spring.factories”;
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 {
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);
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-boot-2.7.5中的spring.factories:
# Logging Systems
org.springframework.boot.logging.LoggingSystemFactory=\
org.springframework.boot.logging.logback.LogbackLoggingSystem.Factory,\
org.springframework.boot.logging.log4j2.Log4J2LoggingSystem.Factory,\
org.springframework.boot.logging.java.JavaLoggingSystem.Factory
# PropertySource Loaders
org.springframework.boot.env.PropertySourceLoader=\
org.springframework.boot.env.PropertiesPropertySourceLoader,\
org.springframework.boot.env.YamlPropertySourceLoader
# ConfigData Location Resolvers
org.springframework.boot.context.config.ConfigDataLocationResolver=\
org.springframework.boot.context.config.ConfigTreeConfigDataLocationResolver,\
org.springframework.boot.context.config.StandardConfigDataLocationResolver
# ConfigData Loaders
org.springframework.boot.context.config.ConfigDataLoader=\
org.springframework.boot.context.config.ConfigTreeConfigDataLoader,\
org.springframework.boot.context.config.StandardConfigDataLoader
# Application Context Factories
org.springframework.boot.ApplicationContextFactory=\
org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext.Factory,\
org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext.Factory
# Run Listeners
org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListener
# Error Reporters
org.springframework.boot.SpringBootExceptionReporter=\
org.springframework.boot.diagnostics.FailureAnalyzers
# Application Context Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\
org.springframework.boot.context.ContextIdApplicationContextInitializer,\
org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\
org.springframework.boot.rsocket.context.RSocketPortInfoApplicationContextInitializer,\
org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer
# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.ClearCachesApplicationListener,\
org.springframework.boot.builder.ParentContextCloserApplicationListener,\
org.springframework.boot.context.FileEncodingApplicationListener,\
org.springframework.boot.context.config.AnsiOutputApplicationListener,\
org.springframework.boot.context.config.DelegatingApplicationListener,\
org.springframework.boot.context.logging.LoggingApplicationListener,\
org.springframework.boot.env.EnvironmentPostProcessorApplicationListener
# Environment Post Processors
org.springframework.boot.env.EnvironmentPostProcessor=\
org.springframework.boot.cloud.CloudFoundryVcapEnvironmentPostProcessor,\
org.springframework.boot.context.config.ConfigDataEnvironmentPostProcessor,\
org.springframework.boot.env.RandomValuePropertySourceEnvironmentPostProcessor,\
org.springframework.boot.env.SpringApplicationJsonEnvironmentPostProcessor,\
org.springframework.boot.env.SystemEnvironmentPropertySourceEnvironmentPostProcessor,\
org.springframework.boot.reactor.DebugAgentEnvironmentPostProcessor
# Failure Analyzers
org.springframework.boot.diagnostics.FailureAnalyzer=\
org.springframework.boot.context.config.ConfigDataNotFoundFailureAnalyzer,\
org.springframework.boot.context.properties.IncompatibleConfigurationFailureAnalyzer,\
org.springframework.boot.context.properties.NotConstructorBoundInjectionFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.BeanCurrentlyInCreationFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.BeanDefinitionOverrideFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.BeanNotOfRequiredTypeFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.BindFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.BindValidationFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.UnboundConfigurationPropertyFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.MutuallyExclusiveConfigurationPropertiesFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.NoSuchMethodFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.NoUniqueBeanDefinitionFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.PortInUseFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.ValidationExceptionFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.InvalidConfigurationPropertyNameFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.InvalidConfigurationPropertyValueFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.PatternParseFailureAnalyzer,\
org.springframework.boot.liquibase.LiquibaseChangelogMissingFailureAnalyzer,\
org.springframework.boot.web.context.MissingWebServerFactoryBeanFailureAnalyzer,\
org.springframework.boot.web.embedded.tomcat.ConnectorStartFailureAnalyzer
# Failure Analysis Reporters
org.springframework.boot.diagnostics.FailureAnalysisReporter=\
org.springframework.boot.diagnostics.LoggingFailureAnalysisReporter
# Database Initializer Detectors
org.springframework.boot.sql.init.dependency.DatabaseInitializerDetector=\
org.springframework.boot.flyway.FlywayDatabaseInitializerDetector,\
org.springframework.boot.jdbc.AbstractDataSourceInitializerDatabaseInitializerDetector,\
org.springframework.boot.jdbc.init.DataSourceScriptDatabaseInitializerDetector,\
org.springframework.boot.liquibase.LiquibaseDatabaseInitializerDetector,\
org.springframework.boot.orm.jpa.JpaDatabaseInitializerDetector,\
org.springframework.boot.r2dbc.init.R2dbcScriptDatabaseInitializerDetector
# Depends On Database Initialization Detectors
org.springframework.boot.sql.init.dependency.DependsOnDatabaseInitializationDetector=\
org.springframework.boot.sql.init.dependency.AnnotationDependsOnDatabaseInitializationDetector,\
org.springframework.boot.jdbc.SpringJdbcDependsOnDatabaseInitializationDetector,\
org.springframework.boot.jooq.JooqDependsOnDatabaseInitializationDetector,\
org.springframework.boot.orm.jpa.JpaDependsOnDatabaseInitializationDetector
下面我们打开文件里的第一个配置看看:org.springframework.boot.logging.LoggingSystemFactory=\
/*
* Copyright 2012-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.logging;
import org.springframework.core.io.support.SpringFactoriesLoader;
/**
* Factory class used by {@link LoggingSystem#get(ClassLoader)} to find an actual
* implementation.
*
* @author Phillip Webb
* @since 2.4.0
*/
public interface LoggingSystemFactory {
/**
* Return a logging system implementation or {@code null} if no logging system is
* available.
* @param classLoader the class loader to use
* @return a logging system
*/
LoggingSystem getLoggingSystem(ClassLoader classLoader);
/**
* Return a {@link LoggingSystemFactory} backed by {@code spring.factories}.
* @return a {@link LoggingSystemFactory} instance
*/
static LoggingSystemFactory fromSpringFactories() {
return new DelegatingLoggingSystemFactory(
(classLoader) -> SpringFactoriesLoader.loadFactories(LoggingSystemFactory.class, classLoader));
}
}
可以看出这是一个工厂接口,实现了该接口的类如下:
将这个工厂接口放到了spring容器中,就可以任意调用其子类的方法。使用该日志jar包中的功能。
所以,springboot通过使用@SpringBootApplication注解。将META-INF/spring.factories文件中预定义的所有配置加载到spring容器中。
springboot就自动集成了很多组件,
1、开发者将开发好的jar写到了spring.factories文件中
2、@SpringBootApplication注解加载这个文件,将文件中的类加载到spring容器中。
3、类加载过程中会加载这个类相关的其他配置,然后会读取该jar包中的一些默认配置,形成一个简化的初始默认组件。
4、很多组件都在启动过程中实现了默认的配置加载。
所以,spring做到了无需配置(使用了默认配置),开箱即用。