目录java
最近在学习Spring Boot相关的课程,过程当中以笔记的形式记录下来,方便之后回忆,同时也在这里和你们探讨探讨,文章中有漏的或者有补充的、错误的都但愿你们可以及时提出来,本人在此先谢谢了!react
开始以前呢,但愿你们带着几个问题去学习:
一、Spring Boot 自动装配是什么?
二、这个功能在什么时代背景下发明产生的?
三、这个功能有什么用?
四、怎么实现的?
五、优势和缺点是什么?
六、这个功能能应用在工做中?
这是对自个人提问,我认为带着问题去学习,是一种更好的学习方式,有利于加深理解。好了,接下来进入主题。web
在上篇文章中咱们讲到 Spring 注解虽然能够代替以往XML的形式,帮助咱们自动注册Bean以及初始化组件,简化咱们的开发,但仍是作不到真正意义上的自动装配,今天咱们就来说讲 Spring Boot 是如何深度整合 Spring 注解编程模型、@Enable 模块驱动及条件装配等 Spring 原生特性来实现自动装配的。redis
注:本篇文章所用到的 Spring Boot版本是 2.1.6.BUILD-SNAPSHOTspring
咱们都知道 Spring Boot 的启动过程很是简单,只须要启动一个 main 方法,项目就能够运行,就算依赖了诸多外部模块如:MVC、Redis等,也不须要咱们进行过多的配置,那它的底层原理是什么呢?接下来,咱们就一块儿去看一看。编程
咱们先来看一段 Spring Boot 的启动类代码:设计模式
@SpringBootApplication public class LoongSpringBootApplication { public static void main(String[] args) { SpringApplication.run(LoongSpringBootApplication.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 { @AliasFor(annotation = EnableAutoConfiguration.class) Class<?>[] exclude() default {}; @AliasFor(annotation = EnableAutoConfiguration.class) String[] excludeName() default {}; @AliasFor(annotation = ComponentScan.class, attribute = "basePackages") String[] scanBasePackages() default {}; @AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses") Class<?>[] scanBasePackageClasses() default {}; }
咱们来看一看它的组成部分:app
@SpringBootConfiguration
:它里面标注了 @Configuration
注解,上篇文章说过,代表这是个配置类,功能与 @Configuration
无异。@EnableAutoConfiguration
:这个就是实现自动装配的核心注解,是用来激活自动装配的,其中默认路径扫描以及组件装配、排除等都经过它来实现。@ComponentScan
:上篇文章咱们讲过这是用来扫描被 @Component
标注的类 ,只不过这里是用来过滤 Bean 的,指定哪些类不进行扫描,并且用的是自定义规则。Class<?>[] exclude()
:根据class来排除,排除指定的类加入spring容器,传入的类型是class类型。且继承自 @EnableAutoConfiguration
中的属性。String[] excludeName()
:根据class name来排除,排除特定的类加入spring容器,参数类型是class的全类名字符串数组。一样继承自 @EnableAutoConfiguration
。String[] scanBasePackages()
:能够指定多个包名进行扫描。继承自 @ComponentScan
。Class<?>[] scanBasePackageClasses()
:能够指定多个类或接口的class,而后扫描 class 所在包下的全部组件。一样继承自 @ComponentScan
。 上面咱们说到 @EnableAutoConfiguration
是实现自动装配的核心注解,是用来激活自动装配的,看注解前缀咱们应该知道是上篇文章中所讲的 Spring @Enable 模块驱动的设计模式,因此它必然会有 @Import
导入的被 @Configuration
标注的类或实现 ImportSelector
或 ImportBeanDefinitionRegistrar
接口的类。接着,咱们来看看它的定义:ide
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @AutoConfigurationPackage @Import(AutoConfigurationImportSelector.class) public @interface EnableAutoConfiguration { ... }
能够看到它由两部分组成:
@AutoConfigurationPackage
:这是用来将启动类所在包,以及下面全部子包里面的全部组件扫描到Spring容器中,这里的组件是指被 @Component
或其派生注解标注的类。这也就是为何不用标注@ComponentScan
的缘由。@Import(AutoConfigurationImportSelector.class)
:这里导入的是实现了 ImportSelector
接口的类,组件自动装配的逻辑均在重写的 selectImports
方法中实现。接下来咱们就来看看这二者具体是怎么实现的。
咱们先来看看 Spring Boot
是如何经过 @AutoConfigurationPackage
注解获取默认包扫描路径的,进入它的实现:
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @Import(AutoConfigurationPackages.Registrar.class) public @interface AutoConfigurationPackage { }
能够看到它是经过 @Import
导入了 AutoConfigurationPackages.Registrar
类,该类实现了 ImportBeanDefinitionRegistrar
接口,因此按照上篇文章所讲的,它是在重写的方法中直接注册相关组件。继续往下:
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports { @Override public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { register(registry, new PackageImport(metadata).getPackageName()); } .... }
private static final class PackageImport { private final String packageName; PackageImport(AnnotationMetadata metadata) { this.packageName = ClassUtils.getPackageName(metadata.getClassName()); } .... }
这里主要是经过 metadata
元数据信息构造 PackageImport
类。先获取启动类的类名,再经过 ClassUtils.getPackageName
获取启动类所在的包名。咱们接着往下看:
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); } }
最后就是将这个包名保存至 BasePackages
类中,而后经过 BeanDefinitionRegistry
将其注册,进行后续处理,至此该流程结束。
该部分就是实现自动装配的入口,从上面得知这里也是经过 @Import
来实现的,来看看导入的类:
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered { .... @Override public String[] selectImports(AnnotationMetadata annotationMetadata) { if (!isEnabled(annotationMetadata)) { return NO_IMPORTS; } AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader .loadMetadata(this.beanClassLoader); AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata); return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations()); } .... }
主要关注重写的 selectImports
方法,其中 AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
是加载自动装配的元信息。而AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata)
该方法返回的就是自动装配的组件,咱们进去看看:
protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) { if (!isEnabled(annotationMetadata)) { return EMPTY_ENTRY; } // 获取 @EnableAutoConfigoration 标注类的元信息,也就是获取该注解 exclude 和 excludeName 属性值 AnnotationAttributes attributes = getAttributes(annotationMetadata); // 该方法就是获取自动装配的类名集合 List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes); // 去除重复的自动装配组件,就是将List转为Set进行去重 configurations = removeDuplicates(configurations); // 这部分就是根据上面获取的 exclude 及 excludeName 属性值,排除指定的类 Set<String> exclusions = getExclusions(annotationMetadata, attributes); checkExcludedClasses(configurations, exclusions); configurations.removeAll(exclusions); // 这里是过滤那些依赖不知足的自动装配 Class configurations = filter(configurations, autoConfigurationMetadata); fireAutoConfigurationImportEvents(configurations, exclusions); // 返回的就是通过一系列去重、排除、过滤等操做后的自动装配组件 return new AutoConfigurationEntry(configurations, exclusions); }
该方法中就是先获取待自动装配组件的类名集合,而后经过一些列的去重、排除、过滤,最终返回自动装配的类名集合。主要关注 getCandidateConfigurations(annotationMetadata, attributes)
这个方法,里面是如何获取自动装配的类名集合:
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; }
其中getSpringFactoriesLoaderFactoryClass()
返回的是EnableAutoConfiguration.class
。
继续往下,执行的是 SpringFactoriesLoader#loadFactoryNames
方法:
public final class SpringFactoriesLoader { ... public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories"; public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) { // 前面能够看到,这里的 factoryClass 是 EnableAutoConfiguration.class String factoryClassName = factoryClass.getName(); return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList()); } private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) { MultiValueMap<String, String> result = cache.get(classLoader); if (result != null) { return result; } try { Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) : ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION)); result = new LinkedMultiValueMap<>(); while (urls.hasMoreElements()) { URL url = urls.nextElement(); UrlResource resource = new UrlResource(url); Properties properties = PropertiesLoaderUtils.loadProperties(resource); for (Map.Entry<?, ?> entry : properties.entrySet()) { String factoryClassName = ((String) entry.getKey()).trim(); for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) { result.add(factoryClassName, factoryName.trim()); } } } cache.put(classLoader, result); return result; } catch (IOException ex) { throw new IllegalArgumentException("Unable to load factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex); } } ... }
最终的实现逻辑都在这里,主要过程以下:
(1)搜索classpath路径下以及全部外部jar包下的META-INF文件夹中的spring.factories
文件。主要是spring-boot-autoconfigur
包下的
# Initializers org.springframework.context.ApplicationContextInitializer=\ org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\ org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener # Application Listeners org.springframework.context.ApplicationListener=\ org.springframework.boot.autoconfigure.BackgroundPreinitializer # Auto Configuration Import Listeners org.springframework.boot.autoconfigure.AutoConfigurationImportListener=\ org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener # Auto Configuration Import Filters org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\ org.springframework.boot.autoconfigure.condition.OnBeanCondition,\ org.springframework.boot.autoconfigure.condition.OnClassCondition,\ org.springframework.boot.autoconfigure.condition.OnWebApplicationCondition # Auto Configure org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration,\ org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration,\ org.springframework.boot.autoconfigure.data.redis.RedisReactiveAutoConfiguration,\ org.springframework.boot.autoconfigure.data.redis.RedisRepositoriesAutoConfiguration,\ org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration,\ org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration,\ org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration,\ org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration ...
能够看到其中内容,存储的是key-value格式的数据,且key是一个类的全路径名称,value是多个类的全路径名称,且以逗号分割。
(2)将全部的spring.factories
文件转成Properties
格式,将里面key-value格式的数据转成Map,该Map的value是一个List,以后将相同Key的value合并到List中,将该Map做为方法返回值返回。
(3)返回到 loadFactoryNames
方法,经过上面得知factoryClassName
的值为EnableAutoConfiguration
,因此经过 getOrDefault(factoryClassName, Collections.emptyList())
方法,获取 key 为EnableAutoConfiguration
的类名集合。
ps:
getOrDefault
第一个入参是key的name,若是key不存在,则直接返回第二个参数值
至此,流程结束,最后返回的就是自动装配的组件,其中有咱们比较熟悉的Redis、JDBC、SpringMVC等,能够看到一个特色,这些自动装配的组件都是以 AutoConfiguration
结尾。但该组件列表只是候选组件,由于后面还有去重、排除、过滤等一系列操做,这里就再也不详细述说。下面咱们来看看自动装配的组件内部是怎么样的。
就拿比较熟悉的 Web MVC 来看,看看是如何实现 Web MVC 自动装配的。先来代码组成部分:
@Configuration @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET) @ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class }) @ConditionalOnMissingBean(WebMvcConfigurationSupport.class) @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10) @AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class, ValidationAutoConfiguration.class }) public class WebMvcAutoConfiguration { ... @Configuration @Import(EnableWebMvcConfiguration.class) @EnableConfigurationProperties({WebMvcProperties.class, ResourceProperties.class}) @Order(0) public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer, ResourceLoaderAware { ... @Bean @ConditionalOnBean(View.class) @ConditionalOnMissingBean public BeanNameViewResolver beanNameViewResolver() { ... } ... } @Configuration public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration { @Bean @Override public RequestMappingHandlerAdapter requestMappingHandlerAdapter() { ... } @Bean @Primary @Override public RequestMappingHandlerMapping requestMappingHandlerMapping() { ... } } ... }
@Configuration
:这个你们都比较熟悉,标识该类是一个配置类@ConditionalXXX
:这是上篇文章所讲的 Spring 条件装配,只不过经由 Spring Boot 扩展造成了本身的条件化自动装配,且都是@Conditional
的派生注解。
@ConditionalOnWebApplication
:参数值是 Type 类型的枚举,当前项目类型是任意、Web、Reactive其中之一则实例化该 Bean。这里指定若是为 Web 项目才知足条件。@ConditionalOnClass
:参数是 Class 数组,当给定的类名在类路径上存在,则实例化当前Bean。这里当Servlet.class
、 DispatcherServlet.class
、 WebMvcConfigurer.class
存在才知足条件。@ConditionalOnMissingBean
:参数是也是 Class 数组,当给定的类没有实例化时,则实例化当前Bean。这里指定当 WebMvcConfigurationSupport
该类没有实例化时,才知足条件。@AutoConfigureOrder
:参数是int类型的数值,数越小越先初始化。@AutoConfigureAfter
:参数是 Class 数组,在指定的配置类初始化后再加载。@AutoConfigureBefore
:参数一样是 Class 数组,在指定的配置类初始化前加载。HandlerAdapter
、HandlerMapping
、ViewResolver
等。其中,出现了 DelegatingWebMvcConfiguration
类,这是上篇文章所讲的 @EnableWebMvc
所 @Import
导入的配置类。能够看到,在Spring Boot
自动装配的类中,通过了一系列的 @Conditional
条件判断,而后实例化某个模块须要的Bean,且无需咱们配置任何东西,固然,这都是默认实现,当这些不知足咱们的要求时,咱们还得手动操做。
关于Spring boot自动装配的内容就告一段落,不难看出Spring Boot自动装配所依赖的注解驱动、@Enable
模块驱动、条件装配等特性均来自 Spring Framework。而自动装配的配置类均来源于spring.factories
文件中。核心则是基于“约定大于配置”理念,通俗的说,就是Spring boot为咱们提供了一套默认的配置,只有当默认的配置不知足咱们的需求时,咱们再去修改默认配置。固然它也存在缺点就是组件的高度集成,使用的时候很难知道底层实现,加深了理解难度。
以上就是本章的内容,如过文章中有错误或者须要补充的请及时提出,本人感激涕零。
参考:
《Spring Boot 编程思想》