一次搞懂SpringBoot核心原理:自動配置函喉、事件驅(qū)動、Condition

前言

SpringBoot是Spring的包裝蓄氧,通過自動配置使得SpringBoot可以做到開箱即用函似,上手成本非常低槐脏,但是學(xué)習(xí)其實(shí)現(xiàn)原理的成本大大增加喉童,需要先了解熟悉Spring原理。如果還不清楚Spring原理的顿天,可以先查看博主之前的文章堂氯,本篇主要分析SpringBoot的啟動、自動配置牌废、Condition咽白、事件驅(qū)動原理。

啟動原理

SpringBoot啟動非常簡單鸟缕,因其內(nèi)置了Tomcat晶框,所以只需要通過下面幾種方式啟動即可:

@SpringBootApplication(scanBasePackages = {"cn.dark"})
public class SpringbootDemo {

    public static void main(String[] args) {
        // 第一種
        SpringApplication.run(SpringbootDemo .class, args);

        // 第二種
        new SpringApplicationBuilder(SpringbootDemo .class)).run(args);

        // 第三種
        SpringApplication springApplication = new SpringApplication(SpringbootDemo.class);
        springApplication.run();        
    }
}

可以看到第一種是最簡單的,也是最常用的方式懂从,需要注意類上面需要標(biāo)注@SpringBootApplication注解授段,這是自動配置的核心實(shí)現(xiàn),稍后分析番甩,先來看看SpringBoot啟動做了些什么侵贵?

在往下之前,不妨先猜測一下缘薛,run方法中需要做什么窍育?對比Spring源碼,我們知道宴胧,Spring的啟動都會創(chuàng)建一個ApplicationContext的應(yīng)用上下文對象漱抓,并調(diào)用其refresh方法啟動容器,SpringBoot只是Spring的一層殼恕齐,肯定也避免不了這樣的操作乞娄。另一方面,以前通過Spring搭建的項(xiàng)目,都需要打成War包發(fā)布到Tomcat才行补胚,而現(xiàn)在SpringBoot已經(jīng)內(nèi)置了Tomcat码耐,只需要打成Jar包啟動即可,所以在run方法中肯定也會創(chuàng)建對應(yīng)的Tomcat對象并啟動溶其。以上只是我們的猜想骚腥,下面就來驗(yàn)證,進(jìn)入run方法:

    public ConfigurableApplicationContext run(String... args) {
        // 統(tǒng)計時間用的工具類
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        ConfigurableApplicationContext context = null;
        Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
        configureHeadlessProperty();
        // 獲取實(shí)現(xiàn)了SpringApplicationRunListener接口的實(shí)現(xiàn)類瓶逃,通過SPI機(jī)制加載
        // META-INF/spring.factories文件下的類
        SpringApplicationRunListeners listeners = getRunListeners(args);

        // 首先調(diào)用SpringApplicationRunListener的starting方法
        listeners.starting();
        try {
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);

            // 處理配置數(shù)據(jù)
            ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
            configureIgnoreBeanInfo(environment);

            // 啟動時打印banner
            Banner printedBanner = printBanner(environment);

            // 創(chuàng)建上下文對象
            context = createApplicationContext();

            // 獲取SpringBootExceptionReporter接口的類束铭,異常報告
            exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
                    new Class[] { ConfigurableApplicationContext.class }, context);

            prepareContext(context, environment, listeners, applicationArguments, printedBanner);

            // 核心方法,啟動spring容器
            refreshContext(context);
            afterRefresh(context, applicationArguments);

            // 統(tǒng)計結(jié)束
            stopWatch.stop();
            if (this.logStartupInfo) {
                new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
            }
            // 調(diào)用started
            listeners.started(context);

            // ApplicationRunner
            // CommandLineRunner
            // 獲取這兩個接口的實(shí)現(xiàn)類厢绝,并調(diào)用其run方法
            callRunners(context, applicationArguments);
        }
        catch (Throwable ex) {
            handleRunFailure(context, ex, exceptionReporters, listeners);
            throw new IllegalStateException(ex);
        }

        try {
            // 最后調(diào)用running方法
            listeners.running(context);
        }
        catch (Throwable ex) {
            handleRunFailure(context, ex, exceptionReporters, null);
            throw new IllegalStateException(ex);
        }
        return context;
    }

SpringBoot的啟動流程就是這個方法契沫,先看getRunListeners方法,這個方法就是去拿到所有的SpringApplicationRunListener實(shí)現(xiàn)類昔汉,這些類是用于SpringBoot事件發(fā)布的懈万,關(guān)于事件驅(qū)動稍后分析,這里主要看這個方法的實(shí)現(xiàn)原理:

    private SpringApplicationRunListeners getRunListeners(String[] args) {
        Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
        return new SpringApplicationRunListeners(logger,
                getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args));
    }

    private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
        ClassLoader classLoader = getClassLoader();
        // Use names and ensure unique to protect against duplicates
        Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
        // 加載上來后反射實(shí)例化
        List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
        AnnotationAwareOrderComparator.sort(instances);
        return instances;
    }

    public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
        String factoryTypeName = factoryType.getName();
        return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
    }

    public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";

    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()) {
                    String factoryTypeName = ((String) entry.getKey()).trim();
                    for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
                        result.add(factoryTypeName, factoryImplementationName.trim());
                    }
                }
            }
            cache.put(classLoader, result);
            return result;
        }
    }

一步步追蹤下去可以看到最終就是通過SPI機(jī)制根據(jù)接口類型從META-INF/spring.factories文件中加載對應(yīng)的實(shí)現(xiàn)類并實(shí)例化靶病,SpringBoot的自動配置也是這樣實(shí)現(xiàn)的会通。為什么要這樣實(shí)現(xiàn)呢?通過注解掃描不可以么娄周?當(dāng)然不行涕侈,這些類都在第三方j(luò)ar包中,注解掃描實(shí)現(xiàn)是很麻煩的煤辨,當(dāng)然你也可以通過@Import注解導(dǎo)入裳涛,但是這種方式不適合擴(kuò)展類特別多的情況,所以這里采用SPI的優(yōu)點(diǎn)就顯而易見了众辨。

回到run方法中端三,可以看到調(diào)用了createApplicationContext方法,見名知意泻轰,這個就是去創(chuàng)建應(yīng)用上下文對象:

    public static final String DEFAULT_SERVLET_WEB_CONTEXT_CLASS = "org.springframework.boot."
            + "web.servlet.context.AnnotationConfigServletWebServerApplicationContext";

    protected ConfigurableApplicationContext createApplicationContext() {
        Class<?> contextClass = this.applicationContextClass;
        if (contextClass == null) {
            try {
                switch (this.webApplicationType) {
                case SERVLET:
                    contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
                    break;
                case REACTIVE:
                    contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
                    break;
                default:
                    contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
                }
            }
            catch (ClassNotFoundException ex) {
                throw new IllegalStateException(
                        "Unable create a default ApplicationContext, please specify an ApplicationContextClass", ex);
            }
        }
        return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
    }

注意這里通過反射實(shí)例化了一個新的沒見過的上下文對象AnnotationConfigServletWebServerApplicationContext技肩,這個是SpringBoot擴(kuò)展的,看看其構(gòu)造方法:

    public AnnotationConfigServletWebServerApplicationContext() {
        this.reader = new AnnotatedBeanDefinitionReader(this);
        this.scanner = new ClassPathBeanDefinitionScanner(this);
    }

如果你有看過Spring注解驅(qū)動的實(shí)現(xiàn)原理浮声,這兩個對象肯定不會陌生虚婿,一個實(shí)支持注解解析的,另外一個是掃描包用的泳挥。
上下文創(chuàng)建好了然痊,下一步自然就是調(diào)用refresh方法啟動容器:


    private void refreshContext(ConfigurableApplicationContext context) {
        refresh(context);
        if (this.registerShutdownHook) {
            try {
                context.registerShutdownHook();
            }
            catch (AccessControlException ex) {
                // Not allowed in some environments.
            }
        }
    }

    protected void refresh(ApplicationContext applicationContext) {
        Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
        ((AbstractApplicationContext) applicationContext).refresh();
    }

這里首先會調(diào)用到其父類中ServletWebServerApplicationContext

    public final void refresh() throws BeansException, IllegalStateException {
        try {
            super.refresh();
        }
        catch (RuntimeException ex) {
            stopAndReleaseWebServer();
            throw ex;
        }
    }

可以看到是直接委托給了父類:

    public void refresh() throws BeansException, IllegalStateException {
        synchronized (this.startupShutdownMonitor) {
            // Prepare this context for refreshing.
            prepareRefresh();

            // Tell the subclass to refresh the internal bean factory.
            ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

            // Prepare the bean factory for use in this context.
            prepareBeanFactory(beanFactory);

            try {
                // Allows post-processing of the bean factory in context subclasses.
                postProcessBeanFactory(beanFactory);

                // Invoke factory processors registered as beans in the context.
                invokeBeanFactoryPostProcessors(beanFactory);

                // Register bean processors that intercept bean creation.
                registerBeanPostProcessors(beanFactory);

                // Initialize message source for this context.
                initMessageSource();

                // Initialize event multicaster for this context.
                initApplicationEventMulticaster();

                // Initialize other special beans in specific context subclasses.
                onRefresh();

                // Check for listener beans and register them.
                registerListeners();

                // Instantiate all remaining (non-lazy-init) singletons.
                finishBeanFactoryInitialization(beanFactory);

                // Last step: publish corresponding event.
                finishRefresh();
            }

            catch (BeansException ex) {
                if (logger.isWarnEnabled()) {
                    logger.warn("Exception encountered during context initialization - " +
                            "cancelling refresh attempt: " + ex);
                }

                // Destroy already created singletons to avoid dangling resources.
                destroyBeans();

                // Reset 'active' flag.
                cancelRefresh(ex);

                // Propagate exception to caller.
                throw ex;
            }

            finally {
                // Reset common introspection caches in Spring's core, since we
                // might not ever need metadata for singleton beans anymore...
                resetCommonCaches();
            }
        }
    }

這個方法不會陌生吧,之前已經(jīng)分析過了屉符,這里不再贅述剧浸,至此SpringBoot的容器就啟動了锹引,但是Tomcat啟動是在哪里呢?run方法中也沒有看到唆香。實(shí)際上Tomcat的啟動也是在refresh流程中嫌变,這個方法其中一步是調(diào)用了onRefresh方法,在Spring中這是一個沒有實(shí)現(xiàn)的模板方法躬它,而SpringBoot就通過這個方法完成了Tomcat的啟動:

    protected void onRefresh() {
        super.onRefresh();
        try {
            createWebServer();
        }
        catch (Throwable ex) {
            throw new ApplicationContextException("Unable to start web server", ex);
        }
    }

    private void createWebServer() {
        WebServer webServer = this.webServer;
        ServletContext servletContext = getServletContext();
        if (webServer == null && servletContext == null) {
            ServletWebServerFactory factory = getWebServerFactory();
            // 主要看這個方法
            this.webServer = factory.getWebServer(getSelfInitializer());
        }
        else if (servletContext != null) {
            try {
                getSelfInitializer().onStartup(servletContext);
            }
            catch (ServletException ex) {
                throw new ApplicationContextException("Cannot initialize servlet context", ex);
            }
        }
        initPropertySources();
    }

這里首先拿到TomcatServletWebServerFactory對象腾啥,通過該對象再去創(chuàng)建和啟動Tomcat:

    public WebServer getWebServer(ServletContextInitializer... initializers) {
        if (this.disableMBeanRegistry) {
            Registry.disableRegistry();
        }
        Tomcat tomcat = new Tomcat();
        File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");
        tomcat.setBaseDir(baseDir.getAbsolutePath());
        Connector connector = new Connector(this.protocol);
        connector.setThrowOnFailure(true);
        tomcat.getService().addConnector(connector);
        customizeConnector(connector);
        tomcat.setConnector(connector);
        tomcat.getHost().setAutoDeploy(false);
        configureEngine(tomcat.getEngine());
        for (Connector additionalConnector : this.additionalTomcatConnectors) {
            tomcat.getService().addConnector(additionalConnector);
        }
        prepareContext(tomcat.getHost(), initializers);
        return getTomcatWebServer(tomcat);
    }

上面的每一步都可以對比Tomcat的配置文件,需要注意默認(rèn)只支持了http協(xié)議:

    Connector connector = new Connector(this.protocol);

    private String protocol = DEFAULT_PROTOCOL;
    public static final String DEFAULT_PROTOCOL = "org.apache.coyote.http11.Http11NioProtocol";

如果想要擴(kuò)展的話則可以對additionalTomcatConnectors屬性設(shè)置值冯吓,需要注意這個屬性沒有對應(yīng)的setter方法倘待,只有addAdditionalTomcatConnectors方法,也就是說我們只能通過實(shí)現(xiàn)BeanFactoryPostProcessor接口的postProcessBeanFactory方法组贺,而不能通過BeanDefinitionRegistryPostProcessor的postProcessBeanDefinitionRegistry方法凸舵,因?yàn)榍罢呖梢酝ㄟ^傳入的BeanFactory對象提前獲取到TomcatServletWebServerFactory對象調(diào)用addAdditionalTomcatConnectors即可;而后者只能拿到BeanDefinition對象失尖,該對象只能通過setter方法設(shè)置值啊奄。

事件驅(qū)動

Spring原本就提供了事件機(jī)制,而在SpringBoot中又對其進(jìn)行擴(kuò)展增热,通過發(fā)布訂閱事件在容器的整個生命周期的不同階段進(jìn)行不同的操作。我們先來看看SpringBoot啟動關(guān)閉的過程中默認(rèn)會發(fā)布哪些事件胧辽,使用下面的代碼即可:

@SpringBootApplication
public class SpringEventDemo {

    public static void main(String[] args) {
        new SpringApplicationBuilder(SpringEventDemo.class)
                .listeners(event -> {
                    System.err.println("接收到事件:" + event.getClass().getSimpleName());
                })
                .run()
                .close();
    }

}

這段代碼會在控制臺打印所有的事件名稱恶迈,按照順序如下:

  • ApplicationStartingEvent:容器啟動
  • ApplicationEnvironmentPreparedEvent:環(huán)境準(zhǔn)備好
  • ApplicationContextInitializedEvent:上下文初始化完成
  • ApplicationPreparedEvent:上下文準(zhǔn)備好
  • ContextRefreshedEvent:上下文刷新完
  • ServletWebServerInitializedEvent:webServer初始化完成
  • ApplicationStartedEvent:容器啟動完成
  • ApplicationReadyEvent:容器就緒
  • ContextClosedEvent:容器關(guān)閉

以上是正常啟動關(guān)閉,如果發(fā)生異常還有發(fā)布ApplicationFailedEvent事件嗜暴。事件的發(fā)布遍布在整個容器的啟動關(guān)閉周期中虫啥,事件發(fā)布對象剛剛我們也看到了是通過SPI加載的SpringApplicationRunListener實(shí)現(xiàn)類EventPublishingRunListener,同樣事件監(jiān)聽器也是在spring.factories文件中配置的斤吐,默認(rèn)實(shí)現(xiàn)了以下監(jiān)聽器:

org.springframework.context.ApplicationListener=\
org.springframework.boot.ClearCachesApplicationListener,\
org.springframework.boot.builder.ParentContextCloserApplicationListener,\
org.springframework.boot.cloud.CloudFoundryVcapEnvironmentPostProcessor,\
org.springframework.boot.context.FileEncodingApplicationListener,\
org.springframework.boot.context.config.AnsiOutputApplicationListener,\
org.springframework.boot.context.config.ConfigFileApplicationListener,\
org.springframework.boot.context.config.DelegatingApplicationListener,\
org.springframework.boot.context.logging.ClasspathLoggingApplicationListener,\
org.springframework.boot.context.logging.LoggingApplicationListener,\
org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener

可以看到有用于文件編碼的(FileEncodingApplicationListener),有加載日志框架的(LoggingApplicationListener)腮恩,還有加載配置的(ConfigFileApplicationListener)等等一系列監(jiān)聽器梢杭,SpringBoot也就是通過這系列監(jiān)聽器將必要的配置和組件加載到容器中來,這里不再詳細(xì)分析秸滴,感興趣的讀者可以通過其實(shí)現(xiàn)的onApplicationEvent方法看到每個監(jiān)聽器究竟是監(jiān)聽的哪一個事件武契,當(dāng)然事件發(fā)布和監(jiān)聽我們自己也是可以擴(kuò)展的。

自動配置原理

SpringBoot最核心的還是自動配置缸榛,為什么它能做到開箱即用吝羞,不再需要我們手動使用@EnableXXX等注解來開啟?這一切的答案就在@SpringBootApplication注解中:

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

這里重要的注解有三個:@SpringBootConfiguration内颗、@EnableAutoConfiguration、@ComponentScan敦腔。@ComponentScan就不用再說了均澳,@SpringBootConfiguration等同于@Configuration,而@EnableAutoConfiguration就是開啟自動配置:

@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {

}

@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {

}

@AutoConfigurationPackage注解的作用就是將該注解所標(biāo)記類所在的包作為自動配置的包符衔,簡單看看就行找前,主要看AutoConfigurationImportSelector,這個就是實(shí)現(xiàn)自動配置的核心類判族,注意這個類是實(shí)現(xiàn)的DeferredImportSelector接口躺盛。

在這個類中有一個selectImports方法。這個方法在我之前的文章這一次搞懂Spring事務(wù)注解的解析也有分析過形帮,只是實(shí)現(xiàn)類不同槽惫,它同樣會被ConfigurationClassPostProcessor類調(diào)用周叮,先來看這個方法做了些什么:

    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        if (!isEnabled(annotationMetadata)) {
            return NO_IMPORTS;
        }
        AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
                .loadMetadata(this.beanClassLoader);
        // 獲取所有的自動配置類
        AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,
                annotationMetadata);
        return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
    }

    protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
            AnnotationMetadata annotationMetadata) {
        if (!isEnabled(annotationMetadata)) {
            return EMPTY_ENTRY;
        }
        AnnotationAttributes attributes = getAttributes(annotationMetadata);
        // SPI獲取EnableAutoConfiguration為key的所有實(shí)現(xiàn)類
        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);
        // 包裝成自動配置實(shí)體類
        return new AutoConfigurationEntry(configurations, exclusions);
    }

    protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
        // SPI獲取EnableAutoConfiguration為key的所有實(shí)現(xiàn)類
        List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
                getBeanClassLoader());
        Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you "
                + "are using a custom packaging, make sure that file is correct.");
        return configurations;
    }

追蹤源碼最終可以看到也是從META-INF/spring.factories文件中拿到所有EnableAutoConfiguration對應(yīng)的值(在spring-boot-autoconfigure中)并通過反射實(shí)例化,過濾后包裝成AutoConfigurationEntry對象返回界斜。

看到這里你應(yīng)該會覺得自動配置的實(shí)現(xiàn)就是通過這個selectImports方法仿耽,但實(shí)際上這個方法通常并不會被調(diào)用到,而是會調(diào)用該類的內(nèi)部類AutoConfigurationGroup的process和selectImports方法各薇,前者同樣是通過getAutoConfigurationEntry拿到所有的自動配置類项贺,而后者這是過濾排序并包裝后返回。

下面就來分析ConfigurationClassPostProcessor是怎么調(diào)用到這里的峭判,直接進(jìn)入processConfigBeanDefinitions方法:

    public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
        List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
        String[] candidateNames = registry.getBeanDefinitionNames();

        for (String beanName : candidateNames) {
            BeanDefinition beanDef = registry.getBeanDefinition(beanName);
            if (beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE) != null) {
                if (logger.isDebugEnabled()) {
                    logger.debug("Bean definition has already been processed as a configuration class: " + beanDef);
                }
            }
            else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
                configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
            }
        }

        // Return immediately if no @Configuration classes were found
        if (configCandidates.isEmpty()) {
            return;
        }

        // Sort by previously determined @Order value, if applicable
        configCandidates.sort((bd1, bd2) -> {
            int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());
            int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());
            return Integer.compare(i1, i2);
        });

        // Detect any custom bean name generation strategy supplied through the enclosing application context
        SingletonBeanRegistry sbr = null;
        if (registry instanceof SingletonBeanRegistry) {
            sbr = (SingletonBeanRegistry) registry;
            if (!this.localBeanNameGeneratorSet) {
                BeanNameGenerator generator = (BeanNameGenerator) sbr.getSingleton(
                        AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR);
                if (generator != null) {
                    this.componentScanBeanNameGenerator = generator;
                    this.importBeanNameGenerator = generator;
                }
            }
        }

        if (this.environment == null) {
            this.environment = new StandardEnvironment();
        }

        // Parse each @Configuration class
        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);

            // Read the model and create bean definitions based on its content
            if (this.reader == null) {
                this.reader = new ConfigurationClassBeanDefinitionReader(
                        registry, this.sourceExtractor, this.resourceLoader, this.environment,
                        this.importBeanNameGenerator, parser.getImportRegistry());
            }
            this.reader.loadBeanDefinitions(configClasses);
            alreadyParsed.addAll(configClasses);

            // 省略开缎。。林螃。奕删。
    }

前面一大段主要是拿到合格的Configuration配置類,主要邏輯是在ConfigurationClassParser.parse方法中治宣,該方法完成了對@Component急侥、@Bean、@Import侮邀、@ComponentScans等注解的解析坏怪,這里主要看對@Import的解析,其它的讀者可自行分析绊茧。一步步追蹤铝宵,最終會進(jìn)入到processConfigurationClass方法:

    protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {
        if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
            return;
        }

        ConfigurationClass existingClass = this.configurationClasses.get(configClass);
        if (existingClass != null) {
            if (configClass.isImported()) {
                if (existingClass.isImported()) {
                    existingClass.mergeImportedBy(configClass);
                }
                // Otherwise ignore new imported config class; existing non-imported class overrides it.
                return;
            }
            else {
                // Explicit bean definition found, probably replacing an import.
                // Let's remove the old one and go with the new one.
                this.configurationClasses.remove(configClass);
                this.knownSuperclasses.values().removeIf(configClass::equals);
            }
        }

        // Recursively process the configuration class and its superclass hierarchy.
        SourceClass sourceClass = asSourceClass(configClass);
        do {
            sourceClass = doProcessConfigurationClass(configClass, sourceClass);
        }
        while (sourceClass != null);

        this.configurationClasses.put(configClass, configClass);
    }

這里需要注意this.conditionEvaluator.shouldSkip方法的調(diào)用,這個方法就是進(jìn)行Bean加載過濾的华畏,即根據(jù)@Condition注解的匹配值判斷是否加載該Bean鹏秋,具體實(shí)現(xiàn)稍后分析,繼續(xù)跟蹤主流程doProcessConfigurationClass

    protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass)
            throws IOException {
        省略....

        // Process any @Import annotations
        processImports(configClass, sourceClass, getImports(sourceClass), true);

        省略....
        return null;
    }

這里就是完成對一系列注解的支撐亡笑,我省略掉了侣夷,主要看processImports方法,這個方法就是處理@Import注解的:

    private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
            Collection<SourceClass> importCandidates, boolean checkForCircularImports) {

        if (importCandidates.isEmpty()) {
            return;
        }

        if (checkForCircularImports && isChainedImportOnStack(configClass)) {
            this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
        }
        else {
            this.importStack.push(configClass);
            try {
                for (SourceClass candidate : importCandidates) {
                    if (candidate.isAssignable(ImportSelector.class)) {
                        // Candidate class is an ImportSelector -> delegate to it to determine imports
                        Class<?> candidateClass = candidate.loadClass();
                        ImportSelector selector = ParserStrategyUtils.instantiateClass(candidateClass, ImportSelector.class,
                                this.environment, this.resourceLoader, this.registry);
                        if (selector instanceof DeferredImportSelector) {
                            this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);
                        }
                        else {
                            String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
                            Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames);
                            processImports(configClass, currentSourceClass, importSourceClasses, false);
                        }
                    }
                    else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
                        Class<?> candidateClass = candidate.loadClass();
                        ImportBeanDefinitionRegistrar registrar =
                                ParserStrategyUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class,
                                        this.environment, this.resourceLoader, this.registry);
                        configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
                    }
                    else {
                        this.importStack.registerImport(
                                currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
                        processConfigurationClass(candidate.asConfigClass(configClass));
                    }
                }
            }
        }
    }

剛剛我提醒過AutoConfigurationImportSelector是實(shí)現(xiàn)DeferredImportSelector接口的仑乌,如果不是該接口的實(shí)現(xiàn)類則是直接調(diào)用selectImports方法百拓,反之則是調(diào)用DeferredImportSelectorHandler.handle方法:

        private List<DeferredImportSelectorHolder> deferredImportSelectors = new ArrayList<>();

        public void handle(ConfigurationClass configClass, DeferredImportSelector importSelector) {
            DeferredImportSelectorHolder holder = new DeferredImportSelectorHolder(
                    configClass, importSelector);
            if (this.deferredImportSelectors == null) {
                DeferredImportSelectorGroupingHandler handler = new DeferredImportSelectorGroupingHandler();
                handler.register(holder);
                handler.processGroupImports();
            }
            else {
                this.deferredImportSelectors.add(holder);
            }
        }

首先創(chuàng)建了一個DeferredImportSelectorHolder對象,如果是第一次執(zhí)行則是添加到deferredImportSelectors屬性中晰甚,等到ConfigurationClassParser.parse的最后調(diào)用process方法:

    public void parse(Set<BeanDefinitionHolder> configCandidates) {
        省略.....

        this.deferredImportSelectorHandler.process();
    }

    public void process() {
        List<DeferredImportSelectorHolder> deferredImports = this.deferredImportSelectors;
        this.deferredImportSelectors = null;
        try {
            if (deferredImports != null) {
                DeferredImportSelectorGroupingHandler handler = new DeferredImportSelectorGroupingHandler();
                deferredImports.sort(DEFERRED_IMPORT_COMPARATOR);
                deferredImports.forEach(handler::register);
                handler.processGroupImports();
            }
        }
        finally {
            this.deferredImportSelectors = new ArrayList<>();
        }
    }

反之則是直接執(zhí)行衙传,首先通過register拿到AutoConfigurationGroup對象:

    public void register(DeferredImportSelectorHolder deferredImport) {
        Class<? extends Group> group = deferredImport.getImportSelector()
                .getImportGroup();
        DeferredImportSelectorGrouping grouping = this.groupings.computeIfAbsent(
                (group != null ? group : deferredImport),
                key -> new DeferredImportSelectorGrouping(createGroup(group)));
        grouping.add(deferredImport);
        this.configurationClasses.put(deferredImport.getConfigurationClass().getMetadata(),
                deferredImport.getConfigurationClass());
    }

    public Class<? extends Group> getImportGroup() {
        return AutoConfigurationGroup.class;
    }

然后在processGroupImports方法中進(jìn)行真正的處理:

        public void processGroupImports() {
            for (DeferredImportSelectorGrouping grouping : this.groupings.values()) {
                grouping.getImports().forEach(entry -> {
                    ConfigurationClass configurationClass = this.configurationClasses.get(
                            entry.getMetadata());
                    try {
                        processImports(configurationClass, asSourceClass(configurationClass),
                                asSourceClasses(entry.getImportClassName()), false);
                    }
                    catch (BeanDefinitionStoreException ex) {
                        throw ex;
                    }
                    catch (Throwable ex) {
                        throw new BeanDefinitionStoreException(
                                "Failed to process import candidates for configuration class [" +
                                        configurationClass.getMetadata().getClassName() + "]", ex);
                    }
                });
            }
        }

        public Iterable<Group.Entry> getImports() {
            for (DeferredImportSelectorHolder deferredImport : this.deferredImports) {
                this.group.process(deferredImport.getConfigurationClass().getMetadata(),
                        deferredImport.getImportSelector());
            }
            return this.group.selectImports();
        }

getImports方法中就完成了對processselectImports方法的調(diào)用,拿到自動配置類后再遞歸調(diào)用調(diào)用processImports方法完成對自動配置類的加載厕九。至此蓖捶,自動配置的加載過程就分析完了,下面是時序圖:

image

Condition注解原理

在自動配置類中有很多Condition相關(guān)的注解扁远,以AOP為例:

Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(prefix = "spring.aop", name = "auto", havingValue = "true", matchIfMissing = true)
public class AopAutoConfiguration {

    @Configuration(proxyBeanMethods = false)
    @ConditionalOnClass(Advice.class)
    static class AspectJAutoProxyingConfiguration {

        @Configuration(proxyBeanMethods = false)
        @EnableAspectJAutoProxy(proxyTargetClass = false)
        @ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "false",
                matchIfMissing = false)
        static class JdkDynamicAutoProxyConfiguration {

        }

        @Configuration(proxyBeanMethods = false)
        @EnableAspectJAutoProxy(proxyTargetClass = true)
        @ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true",
                matchIfMissing = true)
        static class CglibAutoProxyConfiguration {

        }

    }

    @Configuration(proxyBeanMethods = false)
    @ConditionalOnMissingClass("org.aspectj.weaver.Advice")
    @ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true",
            matchIfMissing = true)
    static class ClassProxyingConfiguration {

        ClassProxyingConfiguration(BeanFactory beanFactory) {
            if (beanFactory instanceof BeanDefinitionRegistry) {
                BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;
                AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry);
                AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
            }
        }

    }

}

這里就能看到@ConditionalOnProperty俊鱼、@ConditionalOnClass刻像、@ConditionalOnMissingClass,另外還有@ConditionalOnBean亭引、@ConditionalOnMissingBean等等很多條件匹配注解绎速。這些注解表示條件匹配才會加載該Bean,以@ConditionalOnProperty為例焙蚓,表明配置文件中符合條件才會加載對應(yīng)的Bean纹冤,prefix表示在配置文件中的前綴,name表示配置的名稱购公,havingValue表示配置為該值時才匹配趁桃,matchIfMissing則是表示沒有該配置是否默認(rèn)加載對應(yīng)的Bean陌知。其它注解可類比理解記憶聪舒,下面主要來分析該注解的實(shí)現(xiàn)原理廓俭。

這里注解點(diǎn)進(jìn)去看會發(fā)現(xiàn)每個注解上都標(biāo)注了@Conditional注解,并且value值都對應(yīng)一個類比庄,比如OnBeanCondition求妹,而這些類都實(shí)現(xiàn)了Condition接口,看看其繼承體系:

image

上面只展示了幾個實(shí)現(xiàn)類佳窑,但實(shí)際上Condition的實(shí)現(xiàn)類是非常多的制恍,我們還可以自己實(shí)現(xiàn)該接口來擴(kuò)展@Condition注解。

Condition接口中有一個matches方法神凑,這個方法返回true則表示匹配净神。該方法在ConfigurationClassParser中多處都有調(diào)用,也就是剛剛我提醒過的shouldSkip方法溉委,具體實(shí)現(xiàn)是在ConditionEvaluator類中:

    public boolean shouldSkip(@Nullable AnnotatedTypeMetadata metadata, @Nullable ConfigurationPhase phase) {
        if (metadata == null || !metadata.isAnnotated(Conditional.class.getName())) {
            return false;
        }

        if (phase == null) {
            if (metadata instanceof AnnotationMetadata &&
                    ConfigurationClassUtils.isConfigurationCandidate((AnnotationMetadata) metadata)) {
                return shouldSkip(metadata, ConfigurationPhase.PARSE_CONFIGURATION);
            }
            return shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN);
        }

        List<Condition> conditions = new ArrayList<>();
        for (String[] conditionClasses : getConditionClasses(metadata)) {
            for (String conditionClass : conditionClasses) {
                Condition condition = getCondition(conditionClass, this.context.getClassLoader());
                conditions.add(condition);
            }
        }

        AnnotationAwareOrderComparator.sort(conditions);

        for (Condition condition : conditions) {
            ConfigurationPhase requiredPhase = null;
            if (condition instanceof ConfigurationCondition) {
                requiredPhase = ((ConfigurationCondition) condition).getConfigurationPhase();
            }
            if ((requiredPhase == null || requiredPhase == phase) && !condition.matches(this.context, metadata)) {
                return true;
            }
        }

        return false;
    }

再來看看matches的實(shí)現(xiàn)鹃唯,但OnBeanCondition類中沒有實(shí)現(xiàn)該方法,而是在其父類SpringBootCondition中:

    public final boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        String classOrMethodName = getClassOrMethodName(metadata);
        try {
            ConditionOutcome outcome = getMatchOutcome(context, metadata);
            logOutcome(classOrMethodName, outcome);
            recordEvaluation(context, classOrMethodName, outcome);
            return outcome.isMatch();
        }

getMatchOutcome方法也是一個模板方法瓣喊,具體的匹配邏輯就在這個方法中實(shí)現(xiàn)坡慌,該方法返回的ConditionOutcome對象就包含了是否匹配日志消息兩個字段。進(jìn)入到OnBeanCondition類中:

    public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
        ConditionMessage matchMessage = ConditionMessage.empty();
        MergedAnnotations annotations = metadata.getAnnotations();
        if (annotations.isPresent(ConditionalOnBean.class)) {
            Spec<ConditionalOnBean> spec = new Spec<>(context, metadata, annotations, ConditionalOnBean.class);
            MatchResult matchResult = getMatchingBeans(context, spec);
            if (!matchResult.isAllMatched()) {
                String reason = createOnBeanNoMatchReason(matchResult);
                return ConditionOutcome.noMatch(spec.message().because(reason));
            }
            matchMessage = spec.message(matchMessage).found("bean", "beans").items(Style.QUOTE,
                    matchResult.getNamesOfAllMatches());
        }
        if (metadata.isAnnotated(ConditionalOnSingleCandidate.class.getName())) {
            Spec<ConditionalOnSingleCandidate> spec = new SingleCandidateSpec(context, metadata, annotations);
            MatchResult matchResult = getMatchingBeans(context, spec);
            if (!matchResult.isAllMatched()) {
                return ConditionOutcome.noMatch(spec.message().didNotFind("any beans").atAll());
            }
            else if (!hasSingleAutowireCandidate(context.getBeanFactory(), matchResult.getNamesOfAllMatches(),
                    spec.getStrategy() == SearchStrategy.ALL)) {
                return ConditionOutcome.noMatch(spec.message().didNotFind("a primary bean from beans")
                        .items(Style.QUOTE, matchResult.getNamesOfAllMatches()));
            }
            matchMessage = spec.message(matchMessage).found("a primary bean from beans").items(Style.QUOTE,
                    matchResult.getNamesOfAllMatches());
        }
        if (metadata.isAnnotated(ConditionalOnMissingBean.class.getName())) {
            Spec<ConditionalOnMissingBean> spec = new Spec<>(context, metadata, annotations,
                    ConditionalOnMissingBean.class);
            MatchResult matchResult = getMatchingBeans(context, spec);
            if (matchResult.isAnyMatched()) {
                String reason = createOnMissingBeanNoMatchReason(matchResult);
                return ConditionOutcome.noMatch(spec.message().because(reason));
            }
            matchMessage = spec.message(matchMessage).didNotFind("any beans").atAll();
        }
        return ConditionOutcome.match(matchMessage);
    }

可以看到該類支持了@ConditionalOnBean藻三、@ConditionalOnSingleCandidate八匠、@ConditionalOnMissingBean注解,主要的匹配邏輯在getMatchingBeans方法中:

    protected final MatchResult getMatchingBeans(ConditionContext context, Spec<?> spec) {
        ClassLoader classLoader = context.getClassLoader();
        ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
        boolean considerHierarchy = spec.getStrategy() != SearchStrategy.CURRENT;
        Set<Class<?>> parameterizedContainers = spec.getParameterizedContainers();
        if (spec.getStrategy() == SearchStrategy.ANCESTORS) {
            BeanFactory parent = beanFactory.getParentBeanFactory();
            Assert.isInstanceOf(ConfigurableListableBeanFactory.class, parent,
                    "Unable to use SearchStrategy.ANCESTORS");
            beanFactory = (ConfigurableListableBeanFactory) parent;
        }
        MatchResult result = new MatchResult();
        Set<String> beansIgnoredByType = getNamesOfBeansIgnoredByType(classLoader, beanFactory, considerHierarchy,
                spec.getIgnoredTypes(), parameterizedContainers);
        for (String type : spec.getTypes()) {
            Collection<String> typeMatches = getBeanNamesForType(classLoader, considerHierarchy, beanFactory, type,
                    parameterizedContainers);
            typeMatches.removeAll(beansIgnoredByType);
            if (typeMatches.isEmpty()) {
                result.recordUnmatchedType(type);
            }
            else {
                result.recordMatchedType(type, typeMatches);
            }
        }
        for (String annotation : spec.getAnnotations()) {
            Set<String> annotationMatches = getBeanNamesForAnnotation(classLoader, beanFactory, annotation,
                    considerHierarchy);
            annotationMatches.removeAll(beansIgnoredByType);
            if (annotationMatches.isEmpty()) {
                result.recordUnmatchedAnnotation(annotation);
            }
            else {
                result.recordMatchedAnnotation(annotation, annotationMatches);
            }
        }
        for (String beanName : spec.getNames()) {
            if (!beansIgnoredByType.contains(beanName) && containsBean(beanFactory, beanName, considerHierarchy)) {
                result.recordMatchedName(beanName);
            }
            else {
                result.recordUnmatchedName(beanName);
            }
        }
        return result;
    }

這里邏輯看起來比較復(fù)雜趴酣,但實(shí)際上就做了兩件事,首先通過getNamesOfBeansIgnoredByType方法調(diào)用beanFactory.getBeanNamesForType拿到容器中對應(yīng)的Bean實(shí)例坑夯,然后根據(jù)返回的結(jié)果判斷哪些Bean存在岖寞,哪些Bean不存在(Condition注解中是可以配置多個值的)并返回MatchResult對象,而MatchResult中只要有一個Bean沒有匹配上就返回false柜蜈,也就決定了當(dāng)前Bean是否需要實(shí)例化仗谆。

總結(jié)

本篇分析了SpringBoot核心原理的實(shí)現(xiàn)指巡,通過本篇相信讀者也將能更加熟練地使用和擴(kuò)展SpringBoot。另外還有一些常用的組件我沒有展開分析隶垮,如事務(wù)藻雪、MVC、監(jiān)聽器的自動配置狸吞,這些我們有了Spring源碼基礎(chǔ)的話下來看一下就明白了勉耀,這里就不贅述了。最后讀者可以思考一下我們應(yīng)該如何自定義starter啟動器蹋偏,相信看完本篇應(yīng)該難不倒你便斥。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市威始,隨后出現(xiàn)的幾起案子枢纠,更是在濱河造成了極大的恐慌,老刑警劉巖黎棠,帶你破解...
    沈念sama閱讀 218,386評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件晋渺,死亡現(xiàn)場離奇詭異,居然都是意外死亡脓斩,警方通過查閱死者的電腦和手機(jī)木西,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,142評論 3 394
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來俭厚,“玉大人户魏,你說我怎么就攤上這事∨布罚” “怎么了叼丑?”我有些...
    開封第一講書人閱讀 164,704評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長扛门。 經(jīng)常有香客問我鸠信,道長,這世上最難降的妖魔是什么论寨? 我笑而不...
    開封第一講書人閱讀 58,702評論 1 294
  • 正文 為了忘掉前任星立,我火速辦了婚禮,結(jié)果婚禮上葬凳,老公的妹妹穿的比我還像新娘绰垂。我一直安慰自己,他們只是感情好火焰,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,716評論 6 392
  • 文/花漫 我一把揭開白布劲装。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪占业。 梳的紋絲不亂的頭發(fā)上绒怨,一...
    開封第一講書人閱讀 51,573評論 1 305
  • 那天,我揣著相機(jī)與錄音谦疾,去河邊找鬼南蹂。 笑死,一個胖子當(dāng)著我的面吹牛念恍,可吹牛的內(nèi)容都是我干的六剥。 我是一名探鬼主播,決...
    沈念sama閱讀 40,314評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼樊诺,長吁一口氣:“原來是場噩夢啊……” “哼仗考!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起词爬,我...
    開封第一講書人閱讀 39,230評論 0 276
  • 序言:老撾萬榮一對情侶失蹤秃嗜,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后顿膨,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體锅锨,經(jīng)...
    沈念sama閱讀 45,680評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,873評論 3 336
  • 正文 我和宋清朗相戀三年恋沃,在試婚紗的時候發(fā)現(xiàn)自己被綠了必搞。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,991評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡囊咏,死狀恐怖恕洲,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情梅割,我是刑警寧澤霜第,帶...
    沈念sama閱讀 35,706評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站户辞,受9級特大地震影響泌类,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜底燎,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,329評論 3 330
  • 文/蒙蒙 一刃榨、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧双仍,春花似錦枢希、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,910評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春呕屎,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背敬察。 一陣腳步聲響...
    開封第一講書人閱讀 33,038評論 1 270
  • 我被黑心中介騙來泰國打工秀睛, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人莲祸。 一個月前我還...
    沈念sama閱讀 48,158評論 3 370
  • 正文 我出身青樓蹂安,卻偏偏與公主長得像,于是被迫代替她去往敵國和親锐帜。 傳聞我的和親對象是個殘疾皇子田盈,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,941評論 2 355

推薦閱讀更多精彩內(nèi)容