SpringBoot启动 SpringBoot因为内置了tomcat或jetty服务器,不需要直接部署War文件,所以SpringBoot的程序起点是一个普通的主函数。主函数如下:
1 2 3 4 5 6 @SpringBootApplication public class SpringbootStudyApplication { public static void main (String[] args) { SpringApplication.run(SpringbootStudyApplication.class, args); } }
整个SpringBoot的启动过程其实都是通过@SpringBootApplication注解和SpringApplication.run方法来实现的。
整个启动的过程可以概括为:
读取所有依赖的META-INF/spring.factories文件,该文件指明了哪些依赖可以被自动加载。
根据importSelector类选择加载哪些依赖,使用conditionOn系列注解排除掉不需要的配置文件
将剩余的配置文件所代表的bean加载到IOC容器中。
比如spring-boot-2.1.8RELEASE.jar中的spring.factories文件的内容是整个样子的(节选):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 # PropertySource Loaders org.springframework.boot.env.PropertySourceLoader=\ org.springframework.boot.env.PropertiesPropertySourceLoader,\ org.springframework.boot.env.YamlPropertySourceLoader # 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.web.context.ServerPortInfoApplicationContextInitializer
这个文件中的内容最终会被解析为Map<K,List<V>>这种格式。键和值都是一个类的全限定名。
跟踪源码,探索原理 我们从这段代码开始跟踪SpringApplication.run(SpringbootStudyApplication.class, args);
这段代码经过重重调用最终来到了:
1 2 3 public static ConfigurableApplicationContext run (Class<?>[] primarySources, String[] args) { return new SpringApplication (primarySources).run(args); }
这个方法完成了SpringApplication的实例化。具体的实例化过程如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 public SpringApplication (ResourceLoader resourceLoader, Class<?>... primarySources) { this .resourceLoader = resourceLoader; Assert.notNull(primarySources, "PrimarySources must not be null" ); this .primarySources = new LinkedHashSet <>(Arrays.asList(primarySources)); this .webApplicationType = WebApplicationType.deduceFromClasspath(); setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class)); setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class)); this .mainApplicationClass = deduceMainApplicationClass(); }
整个Application的实例化过程中,下面这两行代码比较关键。
1 2 setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class)); setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
方法的具体实现如下:
1 2 3 4 5 6 7 8 9 10 private <T> Collection <T> getSpringFactoriesInstances (Class<T> type, Class<?>[] parameterTypes, Object... args) { ClassLoader classLoader = getClassLoader(); Set <String> names = new LinkedHashSet <>(SpringFactoriesLoader.loadFactoryNames(type, classLoader)); List <T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names); AnnotationAwareOrderComparator.sort(instances); return instances; }
这里的loadFactoryNames方法,其实就是从我们之前提到的``spring.factories读取数据,然后以Map的形式进行存储的,loadFactoryNames就是从这个Map`中取数据(类的全限定名)。
读取spring.factories的具体实现:
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 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.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); } }
拿到需要加载的类的全限定名之后,就通过反射进行实例化,然后返回。在Application的构造器中,拿到这些对象后,存入到List中。
1 2 private List<ApplicationContextInitializer<?>> initializers; private List<ApplicationListener<?>> listeners;
到这个地方,我们已经拿到了所有的依赖类,那么SpringBoot是如何进行自动配置的呢?
其实前面我们看到的源码都是SpingApplication的实例化,整个实例化过程就完成了依赖类信息,而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 public ConfigurableApplicationContext run (String... args) { StopWatch stopWatch = new StopWatch (); stopWatch.start(); ConfigurableApplicationContext context = null ; Collection <SpringBootExceptionReporter> exceptionReporters = new ArrayList <>(); configureHeadlessProperty(); SpringApplicationRunListeners listeners = getRunListeners(args); listeners.starting(); try { ApplicationArguments applicationArguments = new DefaultApplicationArguments (args); ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments); configureIgnoreBeanInfo(environment); Banner printedBanner = printBanner(environment); context = createApplicationContext(); exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class [] { ConfigurableApplicationContext.class }, context); prepareContext(context, environment, listeners, applicationArguments, printedBanner); refreshContext(context); afterRefresh(context, applicationArguments); stopWatch.stop(); if (this .logStartupInfo) { new StartupInfoLogger (this .mainApplicationClass).logStarted(getApplicationLog(), stopWatch); } listeners.started(context); callRunners(context, applicationArguments); } catch (Throwable ex) { handleRunFailure(context, ex, exceptionReporters, listeners); throw new IllegalStateException (ex); } try { listeners.running(context); } catch (Throwable ex) { handleRunFailure(context, ex, exceptionReporters, null ); throw new IllegalStateException (ex); } return context; }
run方法中最关键的就是refreshContext(context);。它实际上是调用了refresh方法,这个方法对应读过Spring源码的同学不会陌生。而我们bean的装配过程实际上就是由它完成的。
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 public void refresh () throws BeansException, IllegalStateException { synchronized (this .startupShutdownMonitor) { prepareRefresh(); ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); prepareBeanFactory(beanFactory); try { postProcessBeanFactory(beanFactory); invokeBeanFactoryPostProcessors(beanFactory); registerBeanPostProcessors(beanFactory); initMessageSource(); initApplicationEventMulticaster(); onRefresh(); 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 { resetCommonCaches(); } } }
其中invokeBeanFactoryPostProcessors会解析@import注解,并根据@import的属性进行下一步操作。
1 2 3 4 protected void invokeBeanFactoryPostProcessors (ConfigurableListableBeanFactory beanFactory) { PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors()); }
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 public static void invokeBeanFactoryPostProcessors ( ConfigurableListableBeanFactory beanFactory, List<BeanFactoryPostProcessor> beanFactoryPostProcessors) { Set <String> processedBeans = new HashSet <>(); if (beanFactory instanceof BeanDefinitionRegistry) { BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory; List <BeanFactoryPostProcessor> regularPostProcessors = new ArrayList <>(); List <BeanDefinitionRegistryPostProcessor> registryProcessors = new ArrayList <>(); List <BeanDefinitionRegistryPostProcessor> currentRegistryProcessors = new ArrayList <>(); invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry); } private static void invokeBeanDefinitionRegistryPostProcessors ( Collection<? extends BeanDefinitionRegistryPostProcessor> postProcessors, BeanDefinitionRegistry registry) { for (BeanDefinitionRegistryPostProcessor postProcessor : postProcessors) { postProcessor.postProcessBeanDefinitionRegistry(registry); } }
invokerBeandefintionRegistryPostProcessors函数对每一个定义类的后置处理器分别进行应用,@Configure的解析就在这个函数中。
1 2 3 4 5 6 7 8 public void postProcessBeanDefinitionRegistry (BeanDefinitionRegistry registry) { this .registriesPostProcessed.add(registryId); processConfigBeanDefinitions(registry); }
进入最关键的类ConfigurationClassPostProcessor,这个类用户来注册所有的@Configure和@Bean, 它的processConfigbeanDefinitions函数如下:
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 public void processConfigBeanDefinitions (BeanDefinitionRegistry registry) { List <BeanDefinitionHolder> configCandidates = new ArrayList <>(); String [] candidateNames = registry.getBeanDefinitionNames(); if (configCandidates.isEmpty()) { return ; } if (this .environment == null ) { this .environment = new StandardEnvironment (); } ConfigurationClassParser parser = new ConfigurationClassParser ( this .metadataReaderFactory, this .problemReporter, this .environment, this .resourceLoader, this .componentScanBeanNameGenerator, registry); Set <BeanDefinitionHolder> candidates = new LinkedHashSet <>(configCandidates); Set <ConfigurationClass> alreadyParsed = new HashSet <>(configCandidates.size()); do { parser.parse(candidates); parser.validate(); Set <ConfigurationClass> configClasses = new LinkedHashSet <>(parser.getConfigurationClasses()); configClasses.removeAll(alreadyParsed); this .reader.loadBeanDefinitions(configClasses); alreadyParsed.addAll(configClasses); } while (!candidates.isEmpty()); if (sbr != null && !sbr.containsSingleton(IMPORT_REGISTRY_BEAN_NAME)) { sbr.registerSingleton(IMPORT_REGISTRY_BEAN_NAME, parser.getImportRegistry()); } if (this .metadataReaderFactory instanceof CachingMetadataReaderFactory) { ((CachingMetadataReaderFactory) this .metadataReaderFactory).clearCache(); } }
在解释候选集parser.parse(candidates)中,会调用suorceClass=doProcessConfigurationClass(configClass,sourceClass)方法依次解析注解,得到所有的候选集。该方法顺次解析@PropertySource,@componentScan,@Import,@importResource,@Bean父类。
解析完成之后,会找到所有以 @PropertySource、@ComponentScan、@Import、@ImportResource、@Bean 注解的类及其对象,如果有 DeferredImportSelector,会将其加入到 deferredImportSelectorHandler 中,并调用 this.deferredImportSelectorHandler.process() 对这些 DeferredImportSelector 进行处理。
实际上,在 spring boot 中,容器初始化的时候,主要就是对 AutoConfigurationImportSelector 进行处理。
Spring 会将 AutoConfigurationImportSelector 封装成一个 AutoConfigurationGroup,用于处理。最终会调用 AutoConfigurationGroup 的 process 方法。
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 @Override public void process (AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) { AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector) .getAutoConfigurationEntry(getAutoConfigurationMetadata(), annotationMetadata); this .autoConfigurationEntries.add(autoConfigurationEntry); for (String importClassName : autoConfigurationEntry.getConfigurations()) { this .entries.putIfAbsent(importClassName, annotationMetadata); } } protected AutoConfigurationEntry getAutoConfigurationEntry (AutoConfigurationMetadata autoConfigurationMetadata, 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 = filter(configurations, autoConfigurationMetadata); fireAutoConfigurationImportEvents(configurations, exclusions); return new AutoConfigurationEntry (configurations, exclusions); } protected List <String> getCandidateConfigurations (AnnotationMetadata metadata, AnnotationAttributes attributes) { List <String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader()); return configurations; }
如上,我们可以看到 process 最终调用了我们非常熟悉的函数 SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());,该方法以 EnableAutoConfiguration 类为键(org.springframework.boot.autoconfigure.EnableAutoConfiguration),取得所有的值。
在该函数中,还会调用 configurations = filter(configurations, autoConfigurationMetadata) 方法,将不需要的候选集全部排除。(该方法内部使用 AutoConfigurationImportFilter 的实现类排除)。
我们看一个常见的 configuration,即 org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration,这个类中有大量的 @Bean 注解的方法,用来产生 bean,如下:
1 2 3 4 5 6 7 8 9 @Bean @Override public RequestMappingHandlerAdapter requestMappingHandlerAdapter () { RequestMappingHandlerAdapter adapter = super .requestMappingHandlerAdapter(); adapter.setIgnoreDefaultModelOnRedirect( this .mvcProperties == null || this .mvcProperties.isIgnoreDefaultModelOnRedirect()); return adapter; }
spring 通过读取所有所需要的 AutoConfiguration,就可以加载默认的上下文容器,实现自动注入。
SpringBoot常用注解简介
@Configuration
作用于类,用于定义配置类,可替换xml配置文件,被注解的类内部包含有一个或多个被@Bean注解的方法,这些方法会被AnnotationConfigApplicationContext或AnncationConfigWebApplicationContext类进行扫描,并用于构建bean定义,初始化spring容器。
@ComponentScan
该注解会扫描@Controller,@Service,@Repository,@component注解到类到spring容器中。
@SpringBootApplication
该注解包含了@ComponentScan注解,所以在使用中我们可以通过@SpringBootApplication注解的scanBasepackages属性进行配置。
@Conditional
该注解作用于类,它可以根据代码中的条件装载不同的bean,在设置注解之前类需要实现Condition接口,然后对该实现接口的类设置是否装载条件。
@Import
通过导入的方式实现吧实例加入spring容器中,可以在需要时间没有被spring管理的类导入至Spring容器中。
@ImportResource
和@Import类似,区别就是该注解导入的是配置文件。
Component
该注解是一个元注解,意思是可以注解其它类注解,如@Controller @Service @Repository。带此注解的类被看作组件,当使用基于注解的配置和类路径扫描的时候,这些类就会被实例化。
@SpringBootApplication
这个注解是Spring Boot最核心的注解,用在SpringBoot的主类上,标识这是一个Spring Boot应用。用来开启Spring Boot的各项能力,实际上这个注解@Configuration,@EnableAutoConfiguration,@ComponentScan三个注解的组合.由于这些注解一般都是一起使用的。
@EnableAutoConfiguration
允许Spring Boot自动配置注解,开启这个注解之后,Spring Boot就能根据当前类路径下的包或者类来配置Spring Bean。配置信息是从META-INF/spring.factories加载的。