一、前言
上一篇介紹了注解,也是為這一篇做鋪墊,傳統的都是通過配置文件來啟動spring,那spring boot到底是做了什么能讓我們快速開發昵?
二、啟動原理
看下程序啟動的入口,主要兩處地方一是SpringBootApplication注解,另外就是run方法,首先我們看注解部分,上一篇我們也說過注解應該不難看懂,我們看下這個注解里面有什么神奇的東西;

@SpringBootApplication public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } } @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 { /** * Exclude specific auto-configuration classes such that they will never be applied. * @return the classes to exclude */ @AliasFor(annotation = EnableAutoConfiguration.class) 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 */ @AliasFor(annotation = EnableAutoConfiguration.class) String[] excludeName() default {}; /** * Base packages to scan for annotated components. Use {@link #scanBasePackageClasses} * for a type-safe alternative to String-based package names. * @return base packages to scan * @since 1.3.0 */ @AliasFor(annotation = ComponentScan.class, attribute = "basePackages") String[] scanBasePackages() default {}; /** * Type-safe alternative to {@link #scanBasePackages} for specifying the packages to * scan for annotated components. The package of each class specified will be scanned. * <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 base packages to scan * @since 1.3.0 */ @AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses") Class<?>[] scanBasePackageClasses() default {}; }
看上面代碼,除去元注解,主要有3個注解,
@ComponentScan
這個不需要我們多說太多,這個主要有2個作用,組件掃描和自動裝配;
@SpringBootConfiguration
這個我們也不需要說太多,這個注解主要是繼承@Configuration注解,這個我們就是為了加載配置文件用的;
@EnableAutoConfiguration
這個是我們的重點:
看圖我們來走一下代碼,這里有一個重點就是@Import注解,這個里面引入了AutoConfigurationImportSelector.class這個文件,所以我們就需要看下這里面有那些玩意,值得我們注意的,這個類里面代碼有點多我將重點放到下一個代碼片段中,讓大家結構清晰一些;

@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 {}; }
這是中間比較關鍵的代碼,我們主要看下loadFactories方法,這個里面有個常量的配置,位置如下圖所示,整段代碼實現了把配置文件中的信息通過反射實例化成為@Configuration的配置文件,然后通過@Configuration最后匯總到容器當中;

protected List<AutoConfigurationImportFilter> getAutoConfigurationImportFilters() { return SpringFactoriesLoader.loadFactories(AutoConfigurationImportFilter.class, this.beanClassLoader); } public abstract 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"; private static final Log logger = LogFactory.getLog(SpringFactoriesLoader.class); private static final Map<ClassLoader, MultiValueMap<String, String>> cache = new ConcurrentReferenceHashMap<>(); /** * Load and instantiate the factory implementations of the given type from * {@value #FACTORIES_RESOURCE_LOCATION}, using the given class loader. * <p>The returned factories are sorted through {@link AnnotationAwareOrderComparator}. * <p>If a custom instantiation strategy is required, use {@link #loadFactoryNames} * to obtain all registered factory names. * @param factoryClass the interface or abstract class representing the factory * @param classLoader the ClassLoader to use for loading (can be {@code null} to use the default) * @see #loadFactoryNames * @throws IllegalArgumentException if any factory implementation class cannot * be loaded or if an error occurs while instantiating any factory */ public static <T> List<T> loadFactories(Class<T> factoryClass, @Nullable ClassLoader classLoader) { Assert.notNull(factoryClass, "'factoryClass' must not be null"); ClassLoader classLoaderToUse = classLoader; if (classLoaderToUse == null) { classLoaderToUse = SpringFactoriesLoader.class.getClassLoader(); } List<String> factoryNames = loadFactoryNames(factoryClass, classLoaderToUse); if (logger.isTraceEnabled()) { logger.trace("Loaded [" + factoryClass.getName() + "] names: " + factoryNames); } List<T> result = new ArrayList<>(factoryNames.size()); for (String factoryName : factoryNames) { result.add(instantiateFactory(factoryName, factoryClass, classLoaderToUse)); } AnnotationAwareOrderComparator.sort(result); return result; } /** * Load the fully qualified class names of factory implementations of the * given type from {@value #FACTORIES_RESOURCE_LOCATION}, using the given * class loader. * @param factoryClass the interface or abstract class representing the factory * @param classLoader the ClassLoader to use for loading resources; can be * {@code null} to use the default * @see #loadFactories * @throws IllegalArgumentException if an error occurs while loading factory names */ public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) { 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()) { List<String> factoryClassNames = Arrays.asList( StringUtils.commaDelimitedListToStringArray((String) entry.getValue())); result.addAll((String) entry.getKey(), factoryClassNames); } } cache.put(classLoader, result); return result; } catch (IOException ex) { throw new IllegalArgumentException("Unable to load factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex); } } @SuppressWarnings("unchecked") private static <T> T instantiateFactory(String instanceClassName, Class<T> factoryClass, ClassLoader classLoader) { try { Class<?> instanceClass = ClassUtils.forName(instanceClassName, classLoader); if (!factoryClass.isAssignableFrom(instanceClass)) { throw new IllegalArgumentException( "Class [" + instanceClassName + "] is not assignable to [" + factoryClass.getName() + "]"); } return (T) ReflectionUtils.accessibleConstructor(instanceClass).newInstance(); } catch (Throwable ex) { throw new IllegalArgumentException("Unable to instantiate factory class: " + factoryClass.getName(), ex); } } }
基本上注解這塊就是說完了,但是中間少說了幾個比較重要的東西,這里要說下需要注意的2個問題,
1.exclude和excludeName這個兩個主要時排除你不想加載的配置,用法很簡答,不需要說他太多;
2.scanBasePackages和scanBasePackageClasses這個是為了指定運行目錄,好多小伙伴做了項目分離以后,會讀取不到Mappr等,可以考慮下是不是這個錯誤;
重點來了,上面說了加載什么東西,那這些東西啥時候被調用被觸發,那我們看下我們重點run方法:
1.調用run方法之前,首先初始化SpringApplication對象實例,這個對象初始化的過程中也做了不少事情讓我們來慢慢看起來,接上上面思路,繼續完成我們的取經;

//初始化SpringApplication對象 public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) { //加載classpatch文件下面的配置文件 this.resourceLoader = resourceLoader; Assert.notNull(primarySources, "PrimarySources must not be null"); this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources)); //判斷是否是web運行環境 this.webApplicationType = deduceWebApplicationType(); //使用SpringFactoriesLoader在應用的classpath中查找並加載所有可用的ApplicationContextInitializer。 setInitializers((Collection) getSpringFactoriesInstances( ApplicationContextInitializer.class)); //使用SpringFactoriesLoader在應用的classpath中查找並加載所有可用的ApplicationListener。 setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class)); //獲得當前執行main方法的類對象 this.mainApplicationClass = deduceMainApplicationClass(); }
ApplicationContextInitializer 接口是在spring容器刷新之前執行的一個回調函數,主要有2點作用:1.在上下文(ConfigurableApplicationContext)刷新(refresh)之前調用,2.通常被用作web應用,在一些程序設計在spring容器初始化使用。比如說注冊一些配置或者激活一些配置文件針對(ConfigurableApplicationContext的getEnvironment()方法)。另外這個函數支持支持Order注解。並且代表着執行順序。我在下面也寫了一個簡單的例子,同時這個也是支持在配置文件中配置的context.initializer.classes=后面加上回調函數的全限定名稱;另外假設我們在當前項目中要引入別的jar,這個jar要在加載前做一些配置,這個時候我們項目下的resources下新建META-INF文件夾,文件夾下新建spring.factories文件,然后寫上org.springframework.context.ApplicationContextInitializer=后面加上需要回調函數的全限定名稱,這個是在主項目啟動的時候就會優先加載了;
ApplicationListener接口是spring boot的監聽器,有7種類型,我准備好了demo大家執行一下,我相信對下面run方法的運行就不是很迷惑了;

@Order(3) public class TestApplicationContextInitializer implements ApplicationContextInitializer { @Override public void initialize(ConfigurableApplicationContext applicationContext) { System.out.println(applicationContext.getBeanDefinitionCount()+applicationContext.getBeanDefinitionNames().toString()); } } @Order(1) public class TestApplicationContextInitializer2 implements ApplicationContextInitializer { @Override public void initialize(ConfigurableApplicationContext applicationContext) { System.out.println(applicationContext.getDisplayName()); } } @SpringBootApplication public class DemoApplication { public static void main(String[] args) { // SpringApplication.run(DemoApplication.class, args); SpringApplication springApplication=new SpringApplication(DemoApplication.class); springApplication.addListeners((ApplicationListener<ApplicationStartingEvent>) event->{ System.out.println("Starting"); }); springApplication.addListeners((ApplicationListener<ApplicationStartedEvent>) event->{ System.out.println("Started"); }); springApplication.addListeners((ApplicationListener<ApplicationFailedEvent>) event->{ System.out.println("Failed"); }); springApplication.addListeners((ApplicationListener<ApplicationPreparedEvent>) event->{ System.out.println("Prepared"); }); springApplication.addListeners((ApplicationListener<SpringApplicationEvent>) event->{ System.out.println("SpringApplication"); }); springApplication.addListeners((ApplicationListener<ApplicationEnvironmentPreparedEvent>) event->{ System.out.println("EnvironmentPrepare"); }); springApplication.addListeners((ApplicationListener<ApplicationReadyEvent>) event->{ System.out.println("Ready"); }); springApplication.addInitializers(new TestApplicationContextInitializer()); springApplication.addInitializers(new TestApplicationContextInitializer2()); springApplication.run(args); } }
2.實例化完成開始執行run方法,這個里面流程比較多,我們先來看一個繼承關系,然后結合上面ApplicationListener的demo我相信大家已經對其廣播實現已經有了一個了解,這里我還是提一下通過SpringApplicationRunListener在ApplicationContext初始化過程中各個時點發布各種廣播事件,並由ApplicationListener負責接收廣播事件。接下來我們看下啟動流程:

public ConfigurableApplicationContext run(String... args) { StopWatch stopWatch = new StopWatch(); stopWatch.start(); ConfigurableApplicationContext context = null; //收集異常 Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>(); //設置Headless模式為全局 configureHeadlessProperty(); //加載所有classpath下面的META-INF/spring.factories SpringApplicationRunListener(不同的時間點發送事件通知) SpringApplicationRunListeners listeners = getRunListeners(args); //spring boot啟動初始化開始 listeners.starting(); try { //裝配參數和環境 ApplicationArguments applicationArguments = new DefaultApplicationArguments( args); ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments); configureIgnoreBeanInfo(environment); //打印Banner Banner printedBanner = printBanner(environment); //創建ApplicationContext() context = createApplicationContext(); //返回異常 exceptionReporters = getSpringFactoriesInstances( SpringBootExceptionReporter.class, new Class[] { ConfigurableApplicationContext.class }, context); //裝配Context prepareContext(context, environment, listeners, applicationArguments, printedBanner); //執行context的refresh方法,並且調用context的registerShutdownHook方法(這一步執行完成之后,spring容器加載完成) refreshContext(context); //回調,獲取容器中所有的ApplicationRunner、CommandLineRunner接口 afterRefresh(context, applicationArguments); stopWatch.stop(); if (this.logStartupInfo) { new StartupInfoLogger(this.mainApplicationClass) .logStarted(getApplicationLog(), stopWatch); } //容器初始化完成 listeners.started(context); //遍歷所有注冊的ApplicationRunner和CommandLineRunner,並執行其run()方法。 //該過程可以理解為是SpringBoot完成ApplicationContext初始化前的最后一步工作, 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; }
寫了這么多我忘記放入執行結果了這里補進去:
三、總結
要是想在spring boot初始化的時候搞點事情的化,那么有3種方法:
1.創建ApplicationContextInitializer的實現類
2.創建ApplicationListener的實現類
3.創建ApplicationRunner和CommandLineRunner的實現類
上面2種已經有了demo,我再來寫一個第3種的demo;

@Order(2) @Component public class CommandLineRunnerDemo implements CommandLineRunner { @Override public void run(String... args) throws Exception { System.out.println("CommandLineRunnerDemo"); } } @Order(1) @Component public class ApplicationRunnerDemo implements ApplicationRunner { @Override public void run(ApplicationArguments args) throws Exception { System.out.println("ApplicationRunner"); } }
知道啟動的流程又懂了擴展,我們接下來開始spring cloud吧。
上面有什么的不懂的可以加群:438836709
也可以關注我公眾號