SpringBoot自动配置
本篇文章只是梳理SpringBoot启动流程,不涉及过多源码讲解。
@SpringBootApplication
public class ApiTestApplication {
public static void main(String[] args) {
SpringApplication.run(ApiTestApplication.class, args);
}
}
如上,SpringBoot的启动主要涉及到一个注解@SpringBootApplication和一行代码SpringApplication.run(ApiTestApplication.class, args);
SpringApplication.run(ApiTestApplication.class, args)方法主要功能是
1.创建SpringApplication对象,并使用factories机制获取ApplicationListener、ApplicationContextInitializer等一些列相关类。
2.刷新Spring容器上下文,发布事件等。
而在SpringbootApplication的解析就是在刷新Spring容器的过程中,整个解析过程主要包含以下几个注解:
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
1.@SpringBootConfiguration
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
/**
* Specify whether {@link Bean @Bean} methods should get proxied in order to enforce
* bean lifecycle behavior, e.g. to return shared singleton bean instances even in
* case of direct {@code @Bean} method calls in user code. This feature requires
* method interception, implemented through a runtime-generated CGLIB subclass which
* comes with limitations such as the configuration class and its methods not being
* allowed to declare {@code final}.
* <p>
* The default is {@code true}, allowing for 'inter-bean references' within the
* configuration class as well as for external calls to this configuration's
* {@code @Bean} methods, e.g. from another configuration class. If this is not needed
* since each of this particular configuration's {@code @Bean} methods is
* self-contained and designed as a plain factory method for container use, switch
* this flag to {@code false} in order to avoid CGLIB subclass processing.
* <p>
* Turning off bean method interception effectively processes {@code @Bean} methods
* individually like when declared on non-{@code @Configuration} classes, a.k.a.
* "@Bean Lite Mode" (see {@link Bean @Bean's javadoc}). It is therefore behaviorally
* equivalent to removing the {@code @Configuration} stereotype.
* @return whether to proxy {@code @Bean} methods
* @since 2.2
*/
@AliasFor(annotation = Configuration.class)
boolean proxyBeanMethods() default true;
}
这个注解只是对@Configuration的简单包装,标明这是一个配置类,因此在启动类中设置配置相关内容。
2.@ComponentScan
这个注解指明需要扫描的包的路径(自动扫描@Component (@Service,@Controller,@Repostory,@RestController)注解的类),默认是启动类所在的包及其子包,可以使用@Filter进行包过滤。请注意,启动类所在的包及其子包下不一定只有这几个Spring框架的注解,还有类似@Mapper这类需要实例化Bean进入IOC容器的注解。
3.@EnableAutoConfiguration
这个注解正如它的名字一样,开启自动装配
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.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 @Import(AutoConfigurationImportSelector.class)
@AutoConfigurationPackage
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {
/**
* Base packages that should be registered with {@link AutoConfigurationPackages}.
* <p>
* Use {@link #basePackageClasses} for a type-safe alternative to String-based package
* names.
* @return the back package names
* @since 2.3.0
*/
String[] basePackages() default {};
/**
* Type-safe alternative to {@link #basePackages} for specifying the packages to be
* registered with {@link AutoConfigurationPackages}.
* <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 the base package classes
* @since 2.3.0
*/
Class<?>[] basePackageClasses() default {};
}
@AutoConfigurationPackage注解的作用是将 添加该注解的类所在的package 作为 自动配置package 进行管理。
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
// 最终向容器注入组件 名称是全类名 值是PackageImports 里面包含一个集合 集合中存储packageNames 也就是我们注解所在包
register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0]));
}
@Override
public Set<Object> determineImports(AnnotationMetadata metadata) {
return Collections.singleton(new PackageImports(metadata));
}
}
public static void register(BeanDefinitionRegistry registry, String... packageNames) {
if (registry.containsBeanDefinition(BEAN)) {
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);
}
}
这个注解作用看起来不明显,这里通过简单的例子看一下它的作用:
@SpringBootApplication
public class ApiTestApplication implements CommandLineRunner {
@Autowired
private BeanFactory beanFactory;
public static void main(String[] args) {
SpringApplication.run(ApiTestApplication.class, args);
}
@Override
public void run(String... args) throws Exception {
System.out.println("==================================");
System.out.println("beanFactory:" + beanFactory.getClass().getName());
System.out.println(AutoConfigurationPackages.get(beanFactory));
System.out.println("==================================");
}
}
这个注解有什么用?还记得上面说过@ComponentScan并不能扫描所有的Bean进入IOC容器吗?这个注解就可以帮助我们完成这个功能,以Mybatis为例,mybatis-spring-boot-autoconfigure这个jar包的META-INF/spring.factories内容如下:
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.mybatis.spring.boot.autoconfigure.MybatisLanguageDriverAutoConfiguration,\
org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration
org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration这个自动配置类就是关键,它有一个内部类。
public static class AutoConfiguredMapperScannerRegistrar implements BeanFactoryAware, ImportBeanDefinitionRegistrar {
private BeanFactory beanFactory;
public AutoConfiguredMapperScannerRegistrar() {
}
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
if (!AutoConfigurationPackages.has(this.beanFactory)) {
MybatisAutoConfiguration.logger.debug("Could not determine auto-configuration package, automatic mapper scanning disabled.");
} else {
MybatisAutoConfiguration.logger.debug("Searching for mappers annotated with @Mapper");
//获取自动配置包
List<String> packages = AutoConfigurationPackages.get(this.beanFactory);
if (MybatisAutoConfiguration.logger.isDebugEnabled()) {
packages.forEach((pkg) -> {
MybatisAutoConfiguration.logger.debug("Using auto-configuration base package '{}'", pkg);
});
}
// 构建beanDefinition
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
builder.addPropertyValue("processPropertyPlaceHolders", true);
// 指定扫描注解@Mapper(也就是扫描自动配置包下的@Mapper注解)
builder.addPropertyValue("annotationClass", Mapper.class);
// 指定扫描的包路径,也就是前面注册的自动配置包路径
builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(packages));
BeanWrapper beanWrapper = new BeanWrapperImpl(MapperScannerConfigurer.class);
Set<String> propertyNames = (Set)Stream.of(beanWrapper.getPropertyDescriptors()).map(FeatureDescriptor::getName).collect(Collectors.toSet());
if (propertyNames.contains("lazyInitialization")) {
builder.addPropertyValue("lazyInitialization", "${mybatis.lazy-initialization:false}");
}
if (propertyNames.contains("defaultScope")) {
builder.addPropertyValue("defaultScope", "${mybatis.mapper-default-scope:}");
}
//MapperScannerConfigurer是mybatis与spring整合的工具类,具体可以自行了解
registry.registerBeanDefinition(MapperScannerConfigurer.class.getName(), builder.getBeanDefinition());
}
}
public void setBeanFactory(BeanFactory beanFactory) {
this.beanFactory = beanFactory;
}
}
到这里,也就明白了@AutoConfigurationPackage 注解作用,就只是将自动配置包名保存一下,方便其它组件接入springboot。但还是要声明一点,它只是将当前注解标识的包名存入组件中加入到容器里;作用是用于一些第三方调用这个组件里面的包名用于扫描包内自身实体类加入到容器里,但是其本身并不会有扫描的作用。
@Import(AutoConfigurationImportSelector.class)
接下来就是最后一个注解,这是SpringBoot自动配置的核心。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.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 {};
}
其核心在于@Import(AutoConfigurationImportSelector.class),因此我们来看一看AutoConfigurationImportSelector类。
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
.....
}
除了DeferredImportSelector,其它的都是一些Aware,是为了获取相关组件的接口,因此我们的重点是DeferredImportSelector。
public interface DeferredImportSelector extends ImportSelector {
...
}
public interface ImportSelector {
/**
* Select and return the names of which class(es) should be imported based on
* the {@link AnnotationMetadata} of the importing @{@link Configuration} class.
* @return the class names, or an empty array if none
*/
String[] selectImports(AnnotationMetadata importingClassMetadata);
/**
* Return a predicate for excluding classes from the import candidates, to be
* transitively applied to all classes found through this selector's imports.
* <p>If this predicate returns {@code true} for a given fully-qualified
* class name, said class will not be considered as an imported configuration
* class, bypassing class file loading as well as metadata introspection.
* @return the filter predicate for fully-qualified candidate class names
* of transitively imported configuration classes, or {@code null} if none
* @since 5.2.4
*/
@Nullable
default Predicate<String> getExclusionFilter() {
return null;
}
}
String[] selectImports(AnnotationMetadata importingClassMetadata);这个方法就是自动配置的核心。
在Spring进行上下文加载时,会解析@Import注解,这个注解的解析流程可以参考:https://mp.weixin.qq.com/s?__biz=Mzg5MDY1NzI0MQ==&mid=2247485826&idx=1&sn=530d725e4c0279a36c838a1f4f93ebdd&chksm=cfd80934f8af8022a9b6521861b92a55cf13de4ae60609ccacd0386c633dd00334a1a7a3ad8e&token=26397892&lang=zh_CN#rd
@Import会进入到ConfigurationClassParser的
#processImports()方法中
ConfigurationClassParser是解析@Configuration注解的具体实现者,内部会递归解析@Import、@CommponentScan。
if (selector instanceof DeferredImportSelector) {
/**
根据上文知道会进入这个分支
把DeferredImportSelector封装成DeferredImportSelectorHolder放入到this.deferredImportSelectors集合中。
他会等Spring对配置类相关其他注解进行解析完之后,才执行这里的注入逻辑,可从ConfigurationClassParser的#parse()方法得到验证。
*/
this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);
}
else {
String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames, exclusionFilter);
processImports(configClass, currentSourceClass, importSourceClasses, exclusionFilter, false);
}
public void parse(Set<BeanDefinitionHolder> configCandidates) {
for (BeanDefinitionHolder holder : configCandidates) {
BeanDefinition bd = holder.getBeanDefinition();
try {
if (bd instanceof AnnotatedBeanDefinition) {
parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
}
else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) {
parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName());
}
else {
parse(bd.getBeanClassName(), holder.getBeanName());
}
}
catch (BeanDefinitionStoreException ex) {
throw ex;
}
catch (Throwable ex) {
throw new BeanDefinitionStoreException(
"Failed to parse configuration class [" + bd.getBeanClassName() + "]", ex);
}
}
// 等上面的解析完成之后再执行
this.deferredImportSelectorHandler.process();
}
经过一系列调用后会到String[] selectImports(AnnotationMetadata importingClassMetadata)方法,在AutoConfigurationImportSelector类中的具体实现如下:
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return NO_IMPORTS;
} else {
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
// 获取自动配置类entry
AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
}
protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
} else {
AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
// 获取到所有自动配置类的全限定类名
List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
// 根据相关设置进行排除
configurations = this.removeDuplicates(configurations);
Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
this.checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = this.filter(configurations, autoConfigurationMetadata);
this.fireAutoConfigurationImportEvents(configurations, exclusions);
// 封装成AutoConfigurationEntry返回
return new AutoConfigurationEntry(configurations, exclusions);
}
}
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
//获取spring.factories下配置的所有实现类的全限定类名
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.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;
}
SpringFactoriesLoader.loadFactories():是根据参数factoryClass获取spring.factories下配置的所有实现类实例,返回List<T>
的。
SpringFactoriesLoader.loadFactoryNames():是根据参数factoryClass获取spring.factories下配置的所有实现类的全限定类名,返回List<String>
的。
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
try {
Enumeration<URL> urls = (classLoader != null ?
classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
....
return result;
}
catch (IOException ex) {
}
}
public final class SpringFactoriesLoader {
/**
* The location to look for factories.
* <p>Can be present in multiple JAR files.
*/
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
}
这里就可以看到是去"META-INF/spring.factories"加载相关内容。
到这里Springboot的自动配置就说完了,下面总结一下相关内容。
SpringApplication.run()方法注册启动类,处理注解@Configuration、@CommponentScan 、@EnableAutoConfiguration,在@EnableAutoConfiguration中有一个@Import@Import(AutoConfigurationImportSelector.class),其会通过Spring的factories机制加载相关配置类,并根据Filter、@Coditional一系列条件过滤不必要的自动配置类。
如何实现一个Spring-boot-starter ?可以参考:https://blog.youkuaiyun.com/a982847942/article/details/131604341?spm=1001.2014.3001.5502,看完也就知道了yml文件中的相关属性都是由XxxxProperties类定义的。