1、前言web
上一篇介紹了註解,也是爲這一篇作鋪墊,傳統的都是經過配置文件來啓動spring,那spring boot究竟是作了什麼能讓咱們快速開發暱?spring
2、啓動原理app
看下程序啓動的入口,主要兩處地方一是SpringBootApplication註解,另外就是run方法,首先咱們看註解部分,上一篇咱們也說過註解應該不難看懂,咱們看下這個註解裏面有什麼神奇的東西;less
@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個註解,ide
@ComponentScan函數
這個不須要咱們多說太多,這個主要有2個做用,組件掃描和自動裝配;ui
@SpringBootConfigurationthis
這個咱們也不須要說太多,這個註解主要是繼承@Configuration註解,這個咱們就是爲了加載配置文件用的;url
@EnableAutoConfigurationspa
這個是咱們的重點:
看圖咱們來走一下代碼,這裏有一個重點就是@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
也能夠關注我公衆號