spring boot到底幫我們做了那些事?


一、前言

    上一篇介紹了注解,也是為這一篇做鋪墊,傳統的都是通過配置文件來啟動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 {};

}
View Code

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

}
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

  也可以關注我公眾號

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM