Spring IoC 基于注解的Bean加載過程

前言

現(xiàn)在Spring Boot使用越來越廣泛躲履,基于注解的配置越來越受歡迎,注解及其相關(guān)的技術(shù)很早就出現(xiàn)了南捂,本文主要是依照源碼跟蹤基于注解的bean的初始化流程夯膀,希望讀者已經(jīng)了解原來流行的xml的配置的bean的初始化流程基礎(chǔ)知識继蜡。本文會概述xml相關(guān)的初始化基本原理回俐,這一塊若在閱讀過程中有疑問讀者可以自己搜索相關(guān)詳細內(nèi)容,而關(guān)于注解到Bean Definition(這個不知道可以先往下看稀并,看不懂可以去詳細了解基于XML的IoC容器加載過程)的流程會盡量詳細仅颇。

概述:基于XML的IoC容器初始化流程

IoC容器本身也是一個類,這個類通過定義相關(guān)的屬性來記錄容器的狀態(tài)碘举,存放容器的組件等等忘瓦,定義相關(guān)的方法來進行對容器的各種操作和容器本身運轉(zhuǎn)自己需要的操作。當然現(xiàn)在spring ioc的容器功能很強大引颈,如果要定義成一個非常大的類那就很不好擴展和維護耕皮,所以IoC容器類的定義是分了很多層級的,體現(xiàn)了很多設(shè)計模式思想蝙场,也遵循SOLID設(shè)計原則凌停。
IoC容器在spring中的相關(guān)類按名字分主要是BeanFactory和ApplicationContext。一般ApplicationContext依賴于BeanFactory售滤,更靠近使用者罚拟,所以平常能用到如ClassPathXmlApplicationContext,FileSystemXmlApplicationContext,AnnotationConfigApplicationContext等類來new一個容器對象完箩。
先來看平常開發(fā)很少用的但卻是IoC容器基石的BeanFactory家族:

BeanFactory的類圖

其中有<<interface>>標識的是接口赐俗,沒有的是類,名字前綴為Abstract的為抽象類弊知,spring的命名很規(guī)范阻逮。

BeanFactory就分了很多層,接口按功能分開遵從接口隔離原則秩彤,約束了spring內(nèi)部對象處理時的行為叔扼。這種分層看起來復雜,但是對遵循開閉原則進行擴展是非常有利的呐舔,而且抽象類或接口相互關(guān)聯(lián)是靠抽象和抽象之間關(guān)聯(lián)(面向接口編程币励,依賴倒轉(zhuǎn)),具體的實現(xiàn)就比較容易加在層次之間珊拼,有相當?shù)撵`活性食呻。比如從頂端分出來的ListableBeanFactory、HierarchicalBeanFactory、AutowireCapableBeanFactory就分別規(guī)定了容器的列出組件的行為仅胞、容器之間的父子關(guān)系(比如Spring MVC的servlet context容器的父容器是root context)每辟、容器的裝配行為(依賴注入)。因為java是單繼承干旧,但是可以實現(xiàn)多個接口渠欺,所以這些接口的行為最后又可以在一個匯總的類上體現(xiàn)。上圖并沒有將這些類相關(guān)的所有的接口或父類給列出來椎眯,范圍限制在了名為BeanFactory的類挠将。

再來看ApplicationContext,ApplicationContext與BeanFactory多了很多與企業(yè)應(yīng)用相關(guān)的功能编整,本文關(guān)注的是資源加載這塊的功能舔稀。

ApplicationContext的類圖

展示了XML相關(guān)的ApplicationContext,最上面繼承了相應(yīng)的BeanFactory接口掌测,沒有繼承下來的實現(xiàn)類或抽象類不是用不到了内贮,而是沒在繼承或?qū)崿F(xiàn)里展現(xiàn)出來,比如在一些ApplicationContext類的方法中會new DefaultListableBeanFactory并使用它汞斧。到ApplicationContext就不僅是接口隔離了昼弟,不同的實現(xiàn)類或者再往下衍生的的子類就會越來越“具體”的進行相應(yīng)實現(xiàn)并且成為一個強大IoC容器铣除。同樣捺宗,圖中只是展現(xiàn)了名為ApplicationContext的類萝快,其他一些實現(xiàn)的接口或父類沒有展現(xiàn)出來。比如重要的接口ResourceLoader仲义,讓ApplicationContext不僅是有IoC容器的特性婶熬,ApplicationContext本身也是相應(yīng)的資源加載器。ApplicationContext中處處體現(xiàn)了模板方法模式的思想埃撵,抽象類調(diào)用抽象方法赵颅,然后由子類來實現(xiàn)具體的方法。不同的描述bean的定義的信息源就會有不同的ApplicationContext子類去實現(xiàn)加載資源的邏輯暂刘。比如ClassPathXmlApplicationContext就會有去classpath下找相應(yīng)的定義bean的xml配置文件并加載到容器中的行為饺谬。Bean從我們手寫的配置文件(不管是xml還是注解等等,對谣拣,注解也可以算做描述“資源”)募寨,到最后可用的對象的大致過程如下:

Bean在加載過程中的狀態(tài)演變

基于注解的AnnotationConfigApplicationContext

相對于ClassPathXmlApplicationContext等常用容器類,AnnotationConfigApplicationContext是專門加載注解描述信息的容器森缠。這也體現(xiàn)了spring架構(gòu)的易擴展性拔鹰,注解也可以看做是一種描述Bean的資源。

AnnotationConfigApplicationContext主要實現(xiàn)的接口及繼承的類

注解相關(guān)的類

看一下這幾個相關(guān)的類:

  • AbstractApplicationContext——ApplicationContext接口的抽象實現(xiàn)贵涵,沒有強制規(guī)定配置的存儲類型列肢,僅僅實現(xiàn)了通用的上下文功能恰画。這個實現(xiàn)用到了模板方法設(shè)計模式,需要具體的子類來實現(xiàn)其抽象方法瓷马。幾乎現(xiàn)在能用到的ApplicationContext都繼承了這個抽象類(可回頭看上面的XML相關(guān)的類也是)拴还。

  • GenericApplicationContext——會new并持有一個DefaultListableBeanFactory實例(這個類雖然不會被繼承,但是上一節(jié)所描述的體系底層類中會持有這個類的實例來當容器欧聘,剩下的就是再實現(xiàn)其它接口)片林。GenericApplicationContext有了一個真正可以操作的DefaultListableBeanFactory,通過復寫一部分繼承來的方法操作這個內(nèi)含容器怀骤,比如調(diào)用refresh()方法來初始化那些帶有應(yīng)用上下文語義(org.springframework.context.ApplicationContextAware)的bean费封,自動探測org.springframework.beans.factory.config.BeanFactoryPostProcessor等。Generic系列的ApplicationContext的特點就體現(xiàn)在暴露BeanDefinitionRegistry的相應(yīng)實現(xiàn)方法和refresh不重新創(chuàng)建容器晒喷。ClassPathXmlApplicationContext這些容器沒有暴露動態(tài)的注冊BeanDefinition的接口孝偎,只能靠在容器refresh()的時候載入,refresh時會創(chuàng)建容器并走一遍相關(guān)的流程凉敲,比如bean的描述到BeanDefinition的載入,bean的依賴注入寺旺,生命周期的邏輯擴展執(zhí)行(spring提供的可以在bean加載后或整個容器初始化后做的事情這些擴展點)爷抓,GenericApplicationContext可以通過registerBeanDefinition等方法注冊刪除相應(yīng)的BeanDefinition,并且refresh的時候不創(chuàng)建新的容器阻塑,只是為了實例化新注冊的BeanDefinition等蓝撇。

  • BeanDefinitionRegistry——用于持有像RootBeanDefinition和 ChildBeanDefinition實例的bean definitions的注冊表接口。DefaultListableBeanFactory實現(xiàn)了這個接口陈莽,GenericApplicationContext也實現(xiàn)了這個接口渤昌,GenericApplicationContext只是包了一下相應(yīng)方法。

  • AnnotationConfigRegistry——注解配置注冊表走搁。用于注解配置應(yīng)用上下文的通用接口独柑,擁有一個注冊配置類和掃描配置類的方法。

GenericApplicationContext平常業(yè)務(wù)用的不多私植,也是因為它的特點忌栅,我們一般不會手動的去添加或刪除BeanDefinition,并希望refresh的時候能夠徹底地刷新容器(重建一個)曲稼。下面對比看下其中的刷新過程邏輯索绪,ClassPathXmlApplicationContext繼承的AbstractRefreshableApplicationContext的刷新容器方法(這個方法是refresh調(diào)用的其中一個,refresh還會做其他事贫悄,比如注冊監(jiān)聽器等等容器初始化會干的事):

AbstractRefreshableApplicationContext

可以看到重新創(chuàng)建了一個容器瑞驱,下面再看下GenericApplicationContext的刷新容器方法:


GenericApplicationContext

幾乎沒做什么事,只是設(shè)置了一個序列化要用的id窄坦。

講了這么多GenericApplicationContext是因為AnnotationConfigApplicationContext繼承自它唤反,也有它的特性晰筛,下面我們來剖析AnnotationConfigApplicationContext。

AnnotationConfigApplicationContext的構(gòu)造方法

成員變量及構(gòu)造方法:

private final AnnotatedBeanDefinitionReader reader;
private final ClassPathBeanDefinitionScanner scanner;

/**
 * Create a new AnnotationConfigApplicationContext that needs to be populated
* through {@link #register} calls and then manually {@linkplain #refresh refreshed}.
*/
public AnnotationConfigApplicationContext() {
    this.reader = new AnnotatedBeanDefinitionReader(this);
    this.scanner = new ClassPathBeanDefinitionScanner(this);
}

/**
* Create a new AnnotationConfigApplicationContext, deriving bean definitions
* from the given annotated classes and automatically refreshing the context.
* @param annotatedClasses one or more annotated classes,
* e.g. {@link Configuration @Configuration} classes
*/
public AnnotationConfigApplicationContext(Class<?>... annotatedClasses) {
    this();
    register(annotatedClasses);
    refresh();
}

/**
 * Create a new AnnotationConfigApplicationContext, scanning for bean definitions
 * in the given packages and automatically refreshing the context.
* @param basePackages the packages to check for annotated classes
*/
public AnnotationConfigApplicationContext(String... basePackages) {
    this();
    scan(basePackages);
    refresh();
}

AnnotationConfigApplicationContext有兩個成員變量拴袭,從名字來看一個用來讀取注解定義的BeanDefinition,一個用來掃描對應(yīng)路徑下的BeanDefinition读第。
無參的構(gòu)造函數(shù)通過注釋可以看到是讓手動調(diào)用注冊相應(yīng)BeanDefinition并手動refresh來實例化Bean。當然是可以利用兩個成員變量來做這個事的拥刻,兩個成員變量私有怜瞒,用相應(yīng)的方法可以達到目的。


AnnotationConfigApplicationContext的結(jié)構(gòu)

傳入annotatedClasses參數(shù)則是調(diào)用register方法來從類上獲取相應(yīng)的注解般哼,轉(zhuǎn)化成BeanDefinition到容器中吴汪。然后調(diào)用refresh方法(這個就是AbstractApplicationContext的refresh方法,模板方法模式再調(diào)用子類的一些實現(xiàn)方法)蒸眠,前面講了主要是把新注冊的BeanDefinition實例化(lazy-init的不會實例化漾橙,但是會有綁一些事件這些refresh要進行的操作),refresh涉及的整個流程其實是非常繁瑣的,會調(diào)很多層數(shù)深的函數(shù)楞卡。

傳入basePackages參數(shù)調(diào)用scan方法來掃描某一包下的所有類(包括接口)的注解霜运。

AnnotationConfigApplicationContext的register和scan方法

AnnotationConfigApplicationContext的register方法:

public void register(Class<?>... annotatedClasses) {
    Assert.notEmpty(annotatedClasses, "At least one annotated class must be specified");
    this.reader.register(annotatedClasses);
}

其中reader是構(gòu)造函數(shù)初始化的AnnotatedBeanDefinitionReader,它的register及相關(guān)方法:

public void register(Class<?>... annotatedClasses) {
    for (Class<?> annotatedClass : annotatedClasses) {
        registerBean(annotatedClass);
    }
}

public void registerBean(Class<?> annotatedClass) {
    registerBean(annotatedClass, null, (Class<? extends Annotation>[]) null);
}

public void registerBean(Class<?> annotatedClass, Class<? extends Annotation>... qualifiers) {
    registerBean(annotatedClass, null, qualifiers);
}

public void registerBean(Class<?> annotatedClass, String name, Class<? extends Annotation>... qualifiers) {
    //解析能轉(zhuǎn)化成BeanDefinition的注解的所在類蒋腮,創(chuàng)建Spring容器中對BeanDefinition的封裝的數(shù)據(jù)結(jié)構(gòu)
    AnnotatedGenericBeanDefinition abd = new AnnotatedGenericBeanDefinition(annotatedClass);
    AnnotationMetadata metadata = abd.getMetadata();
    //Spring的Profile機制淘捡,和maven的profile機制差不多,只有當Profile被激活時才繼續(xù)解析加載相應(yīng)的Bean
    if (metadata.isAnnotated(Profile.class.getName())) {
        AnnotationAttributes profile = MetadataUtils.attributesFor(metadata, Profile.class);
        if (!this.environment.acceptsProfiles(profile.getStringArray("value"))) {
            return;
        }
    }
    //解析@Scope注解定義的域并設(shè)置到BeanDefinition池摧,如Singleton焦除,Prototype
    ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(abd);
    abd.setScope(scopeMetadata.getScopeName());
    //由beanNameGenerator生成Bean的名稱,這個generator可以通過AnnotationConfigApplicationContext的方法設(shè)置的
    String beanName = (name != null ? name : this.beanNameGenerator.generateBeanName(abd, this.registry));
    //處理BeanDefinition的通用注解:@Primary作彤,@DependsOn膘魄,@Lazy,@Role
    AnnotationConfigUtils.processCommonDefinitionAnnotations(abd);
    //處理限定符注解@Qualifier竭讳,這個注解為了區(qū)分有歧義的Bean创葡,比如在Autowire裝配的時候可以注入的Bean有兩個,就可以通過加這個注解區(qū)分
    if (qualifiers != null) {
        for (Class<? extends Annotation> qualifier : qualifiers) {
            if (Primary.class.equals(qualifier)) {
                abd.setPrimary(true);
            }
            else if (Lazy.class.equals(qualifier)) {
                abd.setLazyInit(true);
            }
            else {
                abd.addQualifier(new AutowireCandidateQualifier(qualifier));
            }
        }
    }
    //封裝Bean名字和BeanDefinition
    BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(abd, beanName);
    //根據(jù)注解Bean定義類中配置的作用域代咸,創(chuàng)建相應(yīng)的代理對象
    definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
    //向容器中注冊BeanDefinition
    BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, this.registry);
}

AnnotationConfigApplicationContext的scan方法:

public void scan(String... basePackages) {
    Assert.notEmpty(basePackages, "At least one base package must be specified");
    this.scanner.scan(basePackages);
}

其中reader是構(gòu)造函數(shù)初始化的ClassPathBeanDefinitionScanner蹈丸,它在構(gòu)造的時候指定(通過boolean參數(shù))是否使用默認的掃描過濾規(guī)則。即Spring默認掃描配置:@Component呐芥、@Repository逻杖、@Service、@Controller注解的Bean思瘟,同時也支持JavaEE6的@ManagedBean和JSR-330的@Named注解荸百。ClassPathBeanDefinitionScanner的scan及相關(guān)方法:

public int scan(String... basePackages) {
    int beanCountAtScanStart = this.registry.getBeanDefinitionCount();

    doScan(basePackages);

    // Register annotation config processors, if necessary.
    if (this.includeAnnotationConfig) {
        AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
    }

    return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);
}

protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
    Assert.notEmpty(basePackages, "At least one base package must be specified");
    //創(chuàng)建一個集合,存放掃描到Bean定義的封裝類
    Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<BeanDefinitionHolder>();
    //遍歷掃描所有給定的包
    for (String basePackage : basePackages) {
        //調(diào)用父類ClassPathScanningCandidateComponentProvider的方法  
        //掃描給定類路徑滨攻,按照過濾規(guī)則(默認規(guī)則是掃描@Component等)獲取符合條件的Bean定義  
        Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
        //遍歷掃描到的Bean
        for (BeanDefinition candidate : candidates) {
            //接下來同樣是處理Scope够话,Bean名字蓝翰,通用注解
            ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
            candidate.setScope(scopeMetadata.getScopeName());
            String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
            if (candidate instanceof AbstractBeanDefinition) {
                postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
            }
            if (candidate instanceof AnnotatedBeanDefinition) {
                AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
            }
            if (checkCandidate(beanName, candidate)) {
                BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
                definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
                beanDefinitions.add(definitionHolder);
                registerBeanDefinition(definitionHolder, this.registry);
            }
        }
    }
    //返回掃到的BeanDefinition
    return beanDefinitions;
}

經(jīng)常用到的注解@ComponentScan,這個注解定義在@Configuration類上女嘲,但是register方法注冊的時候只是將其載進了BeanDefinition畜份,并沒有進行處理,其實對@ComponentScan是進行了調(diào)用類似scan方法的過程欣尼,但什么時候調(diào)的呢爆雹?不在register流程中,這個注解到BeanDefinition里并注冊到容器后愕鼓,在refresh的過程中會調(diào)用到BeanDefinitionRegistryPostProcessor的postProcessBeanDefinitionRegistry方法钙态,在這個方法執(zhí)行過程中會處理@ComponentScan注解并載入BeanDefinition進行處理。

還有一個要注意的點菇晃,ClassPathXmlApplicationContext在構(gòu)造時先是setLocations設(shè)置路徑值(字符串)册倒,然后在refresh完成了將路徑轉(zhuǎn)化為Resource,并讀取Resource成BeanDefinition磺送,還有后面一系列邏輯驻子。而AnnotationConfigApplicationContext在register方法里就將注解轉(zhuǎn)化為了BeanDefinition并注冊,refresh里做的事要少一些(符合Generic系的ApplicationContext特征)册着。

SpringBoot對AnnotationConfigApplicationContext的利用

一般是在Main函數(shù)中這么啟動SpringBoot的應(yīng)用的:

SpringApplication.run(Application.class, args);

參數(shù)args就是把main函數(shù)的參數(shù)傳遞一下拴孤,第一個參數(shù)是一個類,這個類就是AnnotationConfigApplicationContext需要的annotatedClasses甲捏,run函數(shù)有重載的,可以傳一個annotatedClass數(shù)組也可以只傳一個:

public static ConfigurableApplicationContext run(Object source, String... args) {
    return run(new Object[] { source }, args);
}

public static ConfigurableApplicationContext run(Object[] sources, String[] args) {
    return new SpringApplication(sources).run(args);
}

這時候還沒用到AnnotationConfigApplicationContext呢鞭执,所以參數(shù)名先叫sources司顿,后面會傳給它的⌒址模可以看到調(diào)用的SpringApplication的構(gòu)造函數(shù)(new了一個SpringApplication)大溜,構(gòu)造函數(shù)主要調(diào)用了initialize方法:

private void initialize(Object[] sources) {
    if (sources != null && sources.length > 0) {
        this.sources.addAll(Arrays.asList(sources));
    }
    //推斷是否是web環(huán)境,如果是會專門針對web上下文進行設(shè)置
    this.webEnvironment = deduceWebEnvironment();
    //設(shè)置初始化器
    setInitializers((Collection) getSpringFactoriesInstances(
            ApplicationContextInitializer.class));
    //設(shè)置監(jiān)聽器
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    //設(shè)置Main函數(shù)估脆,這個就是我們寫的程序的Main函數(shù)
    this.mainApplicationClass = deduceMainApplicationClass();
}

新建完SpringApplication馬上又調(diào)用了它的run方法钦奋,這個run方法就不再是一開始從我們main函數(shù)調(diào)的那些靜態(tài)方法了,這個是個普通方法疙赠,參數(shù)只有一個args付材。

public ConfigurableApplicationContext run(String... args) {
    //spring的StopWatch類,可以做類似任務(wù)執(zhí)行時間控制
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    //這個引用真正會用到的ApplicationContext圃阳,在后面的邏輯中決定用什么
    ConfigurableApplicationContext context = null;
    FailureAnalyzers analyzers = null;
    configureHeadlessProperty();
    // spring boot啟動監(jiān)聽器
    SpringApplicationRunListeners listeners = getRunListeners(args);
    listeners.starting();
    try {
        //將參數(shù)包裝一下
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(
                args);
        // 準備應(yīng)用環(huán)境
        ConfigurableEnvironment environment = prepareEnvironment(listeners,
                applicationArguments);
        Banner printedBanner = printBanner(environment);
        // 創(chuàng)建ApplicationContext
        context = createApplicationContext();
        analyzers = new FailureAnalyzers(context);
        // 準備context
        prepareContext(context, environment, listeners, applicationArguments,
                printedBanner);
        // refresh!
        refreshContext(context);
        // 后期工作
        afterRefresh(context, applicationArguments);
        listeners.finished(context, null);
        stopWatch.stop();
        if (this.logStartupInfo) {
            new StartupInfoLogger(this.mainApplicationClass)
                    .logStarted(getApplicationLog(), stopWatch);
        }
        return context;
    }
    catch (Throwable ex) {
        handleRunFailure(context, listeners, analyzers, ex);
        throw new IllegalStateException(ex);
    }
}

終于用到AnnotationConfigApplicationContext了厌衔,就在createApplicationContext()方法里:

protected ConfigurableApplicationContext createApplicationContext() {
    Class<?> contextClass = this.applicationContextClass;
    if (contextClass == null) {
        try {
            //若非Web環(huán)境,那么就用AnnotationConfigApplicationContext
            contextClass = Class.forName(this.webEnvironment
                    ? DEFAULT_WEB_CONTEXT_CLASS : DEFAULT_CONTEXT_CLASS);
        }
        catch (ClassNotFoundException ex) {
            throw new IllegalStateException(
                    "Unable create a default ApplicationContext, "
                            + "please specify an ApplicationContextClass",
                    ex);
        }
    }
    return (ConfigurableApplicationContext) BeanUtils.instantiate(contextClass);
}

沒錯捍岳,DEFAULT_CONTEXT_CLASS的值就是"org.springframework.context.annotation.AnnotationConfigApplicationContext"富寿。
至于Web環(huán)境在spring boot里就是用的AnnotationConfigEmbeddedWebApplicationContext睬隶,這個類還有AnnotationConfigWebApplicationContext都是相應(yīng)的衍生。

剩下的就是spring boot去初始化和維護這個容器了页徐。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末苏潜,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子变勇,更是在濱河造成了極大的恐慌恤左,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,686評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件贰锁,死亡現(xiàn)場離奇詭異赃梧,居然都是意外死亡,警方通過查閱死者的電腦和手機豌熄,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,668評論 3 385
  • 文/潘曉璐 我一進店門授嘀,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人锣险,你說我怎么就攤上這事蹄皱。” “怎么了芯肤?”我有些...
    開封第一講書人閱讀 158,160評論 0 348
  • 文/不壞的土叔 我叫張陵巷折,是天一觀的道長。 經(jīng)常有香客問我崖咨,道長锻拘,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,736評論 1 284
  • 正文 為了忘掉前任击蹲,我火速辦了婚禮署拟,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘歌豺。我一直安慰自己推穷,他們只是感情好,可當我...
    茶點故事閱讀 65,847評論 6 386
  • 文/花漫 我一把揭開白布类咧。 她就那樣靜靜地躺著馒铃,像睡著了一般。 火紅的嫁衣襯著肌膚如雪痕惋。 梳的紋絲不亂的頭發(fā)上区宇,一...
    開封第一講書人閱讀 50,043評論 1 291
  • 那天,我揣著相機與錄音血巍,去河邊找鬼萧锉。 笑死,一個胖子當著我的面吹牛述寡,可吹牛的內(nèi)容都是我干的柿隙。 我是一名探鬼主播叶洞,決...
    沈念sama閱讀 39,129評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼禀崖!你這毒婦竟也來了衩辟?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,872評論 0 268
  • 序言:老撾萬榮一對情侶失蹤波附,失蹤者是張志新(化名)和其女友劉穎艺晴,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體掸屡,經(jīng)...
    沈念sama閱讀 44,318評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡封寞,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,645評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了仅财。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片狈究。...
    茶點故事閱讀 38,777評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖盏求,靈堂內(nèi)的尸體忽然破棺而出抖锥,到底是詐尸還是另有隱情,我是刑警寧澤碎罚,帶...
    沈念sama閱讀 34,470評論 4 333
  • 正文 年R本政府宣布磅废,位于F島的核電站,受9級特大地震影響荆烈,放射性物質(zhì)發(fā)生泄漏拯勉。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 40,126評論 3 317
  • 文/蒙蒙 一憔购、第九天 我趴在偏房一處隱蔽的房頂上張望谜喊。 院中可真熱鬧,春花似錦倦始、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,861評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至账蓉,卻和暖如春枚碗,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背铸本。 一陣腳步聲響...
    開封第一講書人閱讀 32,095評論 1 267
  • 我被黑心中介騙來泰國打工肮雨, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人箱玷。 一個月前我還...
    沈念sama閱讀 46,589評論 2 362
  • 正文 我出身青樓怨规,卻偏偏與公主長得像陌宿,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子波丰,可洞房花燭夜當晚...
    茶點故事閱讀 43,687評論 2 351

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

  • 2.1 我們的理念是:讓別人為你服務(wù) IoC是隨著近年來輕量級容器(Lightweight Container)的...
    好好學習Sun閱讀 2,705評論 0 11
  • spring源碼分析(二) 目錄五壳坪、Spring 源碼解讀--5.1、什么是IOC/DI--5.2掰烟、Spring ...
    毛子果閱讀 398評論 0 0
  • Spring容器高層視圖 Spring 啟動時讀取應(yīng)用程序提供的Bean配置信息爽蝴,并在Spring容器中生成一份相...
    Theriseof閱讀 2,804評論 1 24
  • 本來是準備看一看Spring源碼的。然后在知乎上看到來一個帖子纫骑,說有一群**自己連Spring官方文檔都沒有完全讀...
    此魚不得水閱讀 6,928評論 4 21
  • 今天是愚人節(jié)先馆!即使不愛我发框,也請記得騙我 時間不一定把我們變得偉大,但時間一定會在我們臉上寫下滄桑磨隘。 可是缤底,我的疑惑...
    AIA文爸閱讀 1,064評論 0 51