写在前面 最近在重构一个日志模块,需要将其从项目中抽离出来,于是考虑将其做成一个starter便于后续使用。本篇就来聊聊SpringBoot的自动装配原理,只有了解原理才知道如何将自己的项目制作为starter。
管中窥豹 当你新建一个SpringBoot项目的时候,项目入口类中自动会添加@SpringBootApplication
,那么问题来了,这个@SpringBootApplication
注解的作用是什么呢,查看一下该注解的源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @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
、@EnableAutoConfiguration
和@ComponentScan
。
@SpringBootConfiguration 首先看一下@SpringBootConfiguration
注解的源码:
1 2 3 4 5 6 @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Configuration public @interface SpringBootConfiguration { }
可以看到这个注解上面除了元注解外,只有一个@Configuration
注解,也就是说这个注解其实就相当于@Configuration
注解,即标识此类为配置类,同时不仅可以注册一些额外的Bean对象,还可以导入一些额外的配置。
既然看到这里,那就再看一下@Configuration
注解的源码:
1 2 3 4 5 6 7 8 9 10 @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Component public @interface Configuration { @AliasFor( annotation = Component.class ) String value() default ""; }
可以看到@Configuration
注解其核心就是@Component
注解,这说明Spring配置类其实也是Spring容器管理的对象。
@EnableAutoConfiguration 再来看一下@EnableAutoConfiguration
注解的源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 @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})
注解。
@AutoConfigurationPackage 首先分析@AutoConfigurationPackage
注解,它表示让包中的类及子包中的类能够被自动扫描到Spring容器中。查看一下该注解的源码:
1 2 3 4 5 6 7 @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @Import({Registrar.class}) public @interface AutoConfigurationPackage { }
可以看到这个注解中除了元注解外,还是用@Import
注解导入了Registrar这个字节码类,看一下这个Registrar对象的信息:
1 2 3 4 5 6 7 8 9 10 11 12 static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports { Registrar() { } public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { AutoConfigurationPackages.register(registry, (new AutoConfigurationPackages.PackageImport(metadata)).getPackageName()); } public Set<Object> determineImports(AnnotationMetadata metadata) { return Collections.singleton(new AutoConfigurationPackages.PackageImport(metadata)); } }
可以发现它其实是AutoConfigurationPackages
这个抽象类中的静态内部类,里面有两个方法:registerBeanDefinitions
和determineImports
。registerBeanDefinitions
方法用于获取扫描的包路径并将其注册到能被Spring容器扫描的包中,通过Debug可以看到这个(new AutoConfigurationPackages.PackageImport(metadata)).getPackageName()
对象其实就是项目入口类所在的包:
那么问题来了,传入的metadata是什么呢?可以发现它就是被@SpringBootApplication
注解所修饰的类,即项目的入口类:
也就是说将被@SpringBootApplication
注解所修饰的类(项目入口类)所在的包及子包下的所有类都扫描加载到Spring容器中。这也从另一个侧面反映了项目结构,项目入口类必须放在项目的最外层目录中。
@Import({AutoConfigurationImportSelector.class}) 再来分析@Import({AutoConfigurationImportSelector.class})
注解,我们知道@Import
注解的作用就是导入一些其他的类到Spring容器中,这里导入了AutoConfigurationImportSelector
这个字节码对象,它是一个选择器。首先看一下这个对象的继承关系:
可以看到AutoConfigurationImportSelector
实现了DeferredImportSelector
接口,而DeferredImportSelector
接口又是实现了ImportSelector
接口,查看一下这个ImportSelector
接口的源码:
1 2 3 4 5 6 7 8 public interface ImportSelector { String[] selectImports(AnnotationMetadata var1); @Nullable default Predicate<String> getExclusionFilter() { return null; } }
可以看到它只有两个方法,其中selectImports
用于将所有需要导入的类以全路径方式返回,形成一个字符串数组,之后这些类就会被添加到Spring容器中。可以查看该方法的具体实现逻辑:
1 2 3 4 5 6 7 8 9 public String[] selectImports(AnnotationMetadata annotationMetadata) { if (!this.isEnabled(annotationMetadata)) { return NO_IMPORTS; } else { AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader); AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata); return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations()); } }
首先调用AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader)
方法来将对象的加载器导入其中:
1 2 3 static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader) { return loadMetadata(classLoader, "META-INF/spring-autoconfigure-metadata.properties"); }
可以看到它实际上调用的是loadMetadata
方法,传入的路径是META-INF/spring-autoconfigure-metadata.properties
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader, String path) { try { Enumeration<URL> urls = classLoader != null ? classLoader.getResources(path) : ClassLoader.getSystemResources(path); Properties properties = new Properties(); while(urls.hasMoreElements()) { properties.putAll(PropertiesLoaderUtils.loadProperties(new UrlResource((URL)urls.nextElement()))); } return loadMetadata(properties); } catch (IOException var4) { throw new IllegalArgumentException("Unable to load @ConditionalOnClass location [" + path + "]", var4); } }
通过分析可以看到它首先判断classLoader
是否为空,如果不为空则通过调用classLoader.getResources(path)
方法从path路径中获取信息,而这个path就是之前传入的META-INF/spring-autoconfigure-metadata.properties
:
通过定位到spring-autoconfigure-metadata.properties
文件,可以看到其实就是加载这个文件中内容到Spring容器,而这些都是对应组件的自动配置类。有了这些自动配置类,开发者就不再需要手动编写配置类并注入Spring容器中了。
接着回到selectImports
方法,然后执行的是如下代码:
1 AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata);
可以看到它调用的是AutoConfigurationImportSelector#getAutoConfigurationEntry()
方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 protected AutoConfigurationImportSelector.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); return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions); } }
获取自动配置的入口,其实就是获取到spring-autoconfigure-metadata.properties
文件中被加载到Spring容器的自动配置类。该方法内部调用了getCandidateConfigurations()
方法来获取系统中已经加载好的类:
1 2 3 4 5 protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) { 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; }
那么问题来了,系统中已经加载好的类是哪些类呢?可以看到它内部其实调用的是loadFactoryNames()
方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) { String factoryTypeName = factoryType.getName(); return (List)loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList()); } private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) { MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader); if (result != null) { return result; } else { try { Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories"); LinkedMultiValueMap result = new LinkedMultiValueMap(); while(urls.hasMoreElements()) { URL url = (URL)urls.nextElement(); UrlResource resource = new UrlResource(url); Properties properties = PropertiesLoaderUtils.loadProperties(resource); Iterator var6 = properties.entrySet().iterator(); while(var6.hasNext()) { Entry<?, ?> entry = (Entry)var6.next(); String factoryTypeName = ((String)entry.getKey()).trim(); String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue()); int var10 = var9.length; for(int var11 = 0; var11 < var10; ++var11) { String factoryImplementationName = var9[var11]; result.add(factoryTypeName, factoryImplementationName.trim()); } } } cache.put(classLoader, result); return result; } catch (IOException var13) { throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13); } } }
可以看到它会从META-INF/spring.factories
中获取资源,然后通过PropertiesLoaderUtils.loadProperties()
方法来加载资源:
也就是说SpringBoot会在启动的时候,从类路径下的META-INF/spring.factories
文件中获取EnableAutoConfiguration设置的值(而这些设置的值都是以AutoConfiguration结尾,即自动配置类)并导入到Spring容器中,自动配置类会帮助我们进行自动配置:
@ComponentScan 接着我们再来看一下@ComponentScan
注解:
1 2 3 4 5 6 7 8 9 @ComponentScan( excludeFilters = {@Filter( type = FilterType.CUSTOM, classes = {TypeExcludeFilter.class} ), @Filter( type = FilterType.CUSTOM, classes = {AutoConfigurationExcludeFilter.class} )} )
可以看到这个注解用于扫描包,然后将其放入Spring容器中。
小结@SpringBootApplication 接下来我们总结一下@SpringBootApplication
注解,该注解用于扫描项目入口类所在的包或者子包内的类,并将其导入Spring容器中,同时加载META-INF/spring.factories
文件中的自动配置类到Spring容器中。
run方法 接下来我们再来看项目入口类的main方法,里面其实调用的是run方法:
1 2 3 4 5 6 7 public static void main(String[] args) { SpringApplication.run(TestApplication.class, args); } public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) { return run(new Class[]{primarySource}, args); }
再来看一下实际上它所调用的run方法的源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 public ConfigurableApplicationContext run(String... args) { //定义一个计时器 StopWatch stopWatch = new StopWatch(); stopWatch.start(); ConfigurableApplicationContext context = null; Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList(); this.configureHeadlessProperty(); //获取监听器 SpringApplicationRunListeners listeners = this.getRunListeners(args); listeners.starting(); Collection exceptionReporters; try { ApplicationArguments applicationArguments = new DefaultApplicationArguments(args); ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments); this.configureIgnoreBeanInfo(environment); Banner printedBanner = this.printBanner(environment); //创建应用上下文 context = this.createApplicationContext(); exceptionReporters = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[]{ConfigurableApplicationContext.class}, context); //预刷新上下文 this.prepareContext(context, environment, listeners, applicationArguments, printedBanner); //刷新上下文 this.refreshContext(context); //刷新之后的上下文 this.afterRefresh(context, applicationArguments); stopWatch.stop(); if (this.logStartupInfo) { (new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch); } listeners.started(context); this.callRunners(context, applicationArguments); } catch (Throwable var10) { this.handleRunFailure(context, var10, exceptionReporters, listeners); throw new IllegalStateException(var10); } try { listeners.running(context); //返回上下文 return context; } catch (Throwable var9) { this.handleRunFailure(context, var9, exceptionReporters, (SpringApplicationRunListeners)null); throw new IllegalStateException(var9); } }
我们比较关注的是refreshContext
这个用于刷新上下文的方法:
1 2 3 4 5 6 7 8 9 10 private void refreshContext(ConfigurableApplicationContext context) { this.refresh(context); if (this.registerShutdownHook) { try { context.registerShutdownHook(); } catch (AccessControlException var3) { } } }
再来看一下这个refresh方法:
1 2 3 4 protected void refresh(ApplicationContext applicationContext) { Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext); ((AbstractApplicationContext)applicationContext).refresh(); }
查看一下这个refresh方法,其实际源码如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 @Override public void refresh() throws BeansException, IllegalStateException { synchronized (this.startupShutdownMonitor) { // 准备刷新此上下文 prepareRefresh(); // 告诉子类刷新内部的bean工厂 ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); // 准备此上下文中使用的bean工厂 prepareBeanFactory(beanFactory); try { // 允许在此上下文子类中对bean工厂进行后处理 postProcessBeanFactory(beanFactory); // 调用在上下文中注册为bean的工厂处理器 invokeBeanFactoryPostProcessors(beanFactory); // 注册拦截bean创建的bean处理器 registerBeanPostProcessors(beanFactory); // 为此上下文初始化消息源 initMessageSource(); // 为此上下文初始化事件多播器。 initApplicationEventMulticaster(); // 初始化特定上下文子类中的其他特殊bean。 onRefresh(); // 检查监听器bean并注册它们 registerListeners(); // 实例化所有剩余的(非惰性初始化)单例 finishBeanFactoryInitialization(beanFactory); // 最后一步:发布相应的事件 finishRefresh(); } catch (BeansException ex) { if (logger.isWarnEnabled()) { logger.warn("Exception encountered during context initialization - " + "cancelling refresh attempt: " + ex); } // 销毁已经创建的单例以避免占用资源 destroyBeans(); // 重置“活动”标志 cancelRefresh(ex); // 将异常传播给调用者 throw ex; } finally { // 重置 Spring 核心中的常见自省缓存,因为我们可能不再需要单例 bean 的元数据...... resetCommonCaches(); } } }
可以看到这其实就是一个Spring加载Bean的过程。里面有一个用于初始化特定上下文子类中的其他特殊bean的onRefresh方法:
1 2 3 protected void onRefresh() throws BeansException { // For subclasses: do nothing by default. }
这个方法居然没有实现,因此可以看其实现类:
1 2 3 4 5 6 7 8 9 protected void onRefresh() { super.onRefresh(); try { this.createWebServer(); } catch (Throwable var2) { throw new ApplicationContextException("Unable to start reactive web server", var2); } }
可以看到里面调用了createWebServer()
方法,用于创建Web服务:
1 2 3 4 5 6 7 8 9 10 11 private void createWebServer() { ReactiveWebServerApplicationContext.ServerManager serverManager = this.serverManager; if (serverManager == null) { String webServerFactoryBeanName = this.getWebServerFactoryBeanName(); ReactiveWebServerFactory webServerFactory = this.getWebServerFactory(webServerFactoryBeanName); boolean lazyInit = this.getBeanFactory().getBeanDefinition(webServerFactoryBeanName).isLazyInit(); this.serverManager = ReactiveWebServerApplicationContext.ServerManager.get(webServerFactory, lazyInit); } this.initPropertySources(); }
可以看到这个方法里面调用了getWebServerFactory()
方法用于创建Web容器:
1 2 3 protected ReactiveWebServerFactory getWebServerFactory(String factoryBeanName) { return (ReactiveWebServerFactory)this.getBeanFactory().getBean(factoryBeanName, ReactiveWebServerFactory.class); }
这个方法返回了一个ReactiveWebServerFactory
对象,其实这是一个接口:
1 2 3 public interface ReactiveWebServerFactory { WebServer getWebServer(HttpHandler httpHandler); }
之所以设计为接口,那是因为Web容器有很多种,常用的有Tomcat、Netty、Jetty和Undertow:
以TomcatReactiveWebServerFactory
这一实现类为例,其getWebServer
方法的源码如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 public WebServer getWebServer(HttpHandler httpHandler) { if (this.disableMBeanRegistry) { Registry.disableRegistry(); } Tomcat tomcat = new Tomcat(); File baseDir = this.baseDirectory != null ? this.baseDirectory : this.createTempDir("tomcat"); tomcat.setBaseDir(baseDir.getAbsolutePath()); Connector connector = new Connector(this.protocol); connector.setThrowOnFailure(true); tomcat.getService().addConnector(connector); this.customizeConnector(connector); tomcat.setConnector(connector); tomcat.getHost().setAutoDeploy(false); this.configureEngine(tomcat.getEngine()); Iterator var5 = this.additionalTomcatConnectors.iterator(); while(var5.hasNext()) { Connector additionalConnector = (Connector)var5.next(); tomcat.getService().addConnector(additionalConnector); } TomcatHttpHandlerAdapter servlet = new TomcatHttpHandlerAdapter(httpHandler); this.prepareContext(tomcat.getHost(), servlet); return this.getTomcatWebServer(tomcat); }
这其实就是创建一个内置的Tomcat容器,然后返回,所以开发者在将项目打包成jar包以后,不需要自己再手动启动Tomcat容器。
小结 本篇通过阅读源码对SpringBoot的自动装配原理有了一个较为深刻的理解,后续就可以在此基础上自定义自己的启动器(starter)。