spring boot到底幫咱們作了那些事?

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 {};

}
View Code

   看上面代碼,除去元註解,主要有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 {};

}
View Code

  這是中間比較關鍵的代碼,咱們主要看下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);
        }
    }

}
View Code

   

  基本上註解這塊就是說完了,可是中間少說了幾個比較重要的東西,這裏要說下須要注意的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();
}
View Code

  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);
    }
}
View Code

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;
}
View Code

  寫了這麼多我忘記放入執行結果了這裏補進去:

  

、總結

  要是想在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");
    }
}
View Code

  知道啓動的流程又懂了擴展,咱們接下來開始spring cloud吧。

  上面有什麼的不懂的能夠加羣:438836709

  也能夠關注我公衆號

  

相關文章
相關標籤/搜索