Spring基礎(chǔ)(一)

本來(lái)是準(zhǔn)備看一看Spring源碼的。然后在知乎上看到來(lái)一個(gè)帖子,說(shuō)有一群**自己連Spring官方文檔都沒(méi)有完全讀過(guò)就嚷嚷著怎么學(xué)習(xí)Spring源碼,這句話戳中了我的心吶,這不就是說(shuō)我的嘛。然后自己自己想了一下,之前確實(shí)了為了源碼而去讀源碼,所以這次就打算仔細(xì)讀一個(gè)Spring的官方文檔先。

第二章 Spring框架概述

Spring框架的各個(gè)特性被組織成20個(gè)模塊。這些模塊被分組成Core Container(核心容器), Data Access/Integration(數(shù)據(jù)訪問(wèn)/集成), Web(網(wǎng)絡(luò)端), AOP (Aspect Oriented Programming,切面編程), Instrumentation, Messaging(消息),和Test(測(cè)試), 以下圖片顯示的就是Spring的各個(gè)模塊:


image

2.2.1 核心容器

核心容器 包含了 spring-core, spring-beans, spring-context, and spring-expression (Spring表達(dá)式語(yǔ)言) 四個(gè)模塊。

spring-core和spring-beans模塊提供了整個(gè)框架最基礎(chǔ)的部分, 包括了IoC(控制反轉(zhuǎn))和Dependency Injection(依賴注入)特性。 BeanFactory實(shí)現(xiàn)了工廠模式。
Context (spring-context)模塊建立在Core and Beans模塊提供的基礎(chǔ)之上: 它提供了框架式訪問(wèn)對(duì)象的方式,類似于JNDI注冊(cè)。 Context模塊從Beans模塊中繼承了它的特性并且為國(guó)際化(例如使用資源包), 事件傳播, 資源加載和創(chuàng)建上下文,例如Servlet容器。 Context模塊也支持Java EE特性,例如EJB, JMX,和基礎(chǔ)遠(yuǎn)程. ApplicationContext接口是Context模塊的焦點(diǎn)所在。
spring-expression模塊提供了一種強(qiáng)大的用于在運(yùn)行時(shí)查詢操作對(duì)象的表達(dá)式語(yǔ)言。他是對(duì)于在JSP2.1規(guī)范中所聲明的unified expression語(yǔ)言(統(tǒng)一表達(dá)式語(yǔ)言)的擴(kuò)展。 這種語(yǔ)言支持對(duì)屬性值, 屬性參數(shù), 方法調(diào)用, 獲得數(shù)組內(nèi)容, 收集器和索引, 算術(shù)和邏輯運(yùn)算, 變量命名和從Spring IoC容器中根據(jù)名稱獲得對(duì)象。它也為列表映射和選擇提供了支持,就像常見(jiàn)的列表操作一樣。

分離的spring-aspects模塊集成了AspectJ。

2.2.2 AOP

spring-aop模塊提供了AOP面向切面的編程實(shí)現(xiàn),允許你定義,例如, 將攔截器方法和切入點(diǎn)的代碼完全分離開(kāi)來(lái)。 利用源碼中的元數(shù)據(jù), 你可以將行為信息加入到你的代碼中, 一定程度上類似于.NET屬性。

2.2.3 Messaging

Spring 4框架包含了spring-messaging模塊,包含了 Spring Integration項(xiàng)目的高度抽象,我個(gè)人理解的messaging就是Spring實(shí)現(xiàn)了自己的消息隊(duì)列。

2.2.4 Data Access/Integration

Spring的集成層面主要包括對(duì)于JDBC(spring-jdbc),ORM(spring-orm)和事務(wù)(spring-tx)的集成.

2.2.5 Web

Spring的Web模塊主要包括了spring-web, spring-webmvc和spring-websocket。
spring-web模塊主要是包括了例如文件上傳,通過(guò)Servlet Listener初始化IOC容器等等web的基礎(chǔ)功能。
spring-webmvc提供了Spring自己的MVC實(shí)現(xiàn),Spring的MVN模塊在Model 層和Web的表單層面做了一個(gè)很明確的分割。

2.2.6 Test

spring-test模塊支持JUint和TestNG等集成測(cè)試環(huán)境。支持統(tǒng)一加載ApplicationContext和緩存這些對(duì)戲那個(gè)。而且spring-test還提供了mock功能可以幫助你在隔離的環(huán)境中測(cè)試你的代碼~

2.3.1 Spring依賴管理

我們?cè)陧?xiàng)目中經(jīng)常遇到Spring多版本的情況下,如果我們依賴的其他工程中使用了Spring的其他版本,那么在我們的依賴中就會(huì)出現(xiàn)多個(gè)版本,在spring 版本比較多的情況下就有能出現(xiàn)一些因?yàn)榘姹静患嫒莸漠惓#瑸榱私鉀Q這些問(wèn)題,Spring推出了spring-framework-bom,我們可以這樣子配置bom文件在我們的pom文件中:

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-framework-bom</artifactId>
            <version>4.1.3.RELEASE</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

這樣配置之后我們只需要在后面的依賴中添加spring的artifactId和groupId就可以,版本會(huì)默認(rèn)使用上面指定的版本。

<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-web</artifactId>
    </dependency>
<dependencies>

2.3.2 Logging

日志記錄非常重要在,主要有一下幾個(gè)原因:

  • 它是唯一一個(gè)強(qiáng)制的依賴
  • 用戶都希望能在他們使用的框架中看到一些可用的輸出信息
  • spring依賴的很多集成框架都有自己的日志依賴。

spring默認(rèn)依賴的日志框架是commons-logging,但是在大多數(shù)的使用場(chǎng)景中我們都會(huì)提供自己的logging實(shí)現(xiàn),如果要替換掉commons-logging的話,一般需要這兩個(gè)步驟:

  • 在spring-core中排除對(duì)于commons-logging的依賴
<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-core</artifactId>
        <version>4.1.3.RELEASE</version>
        <exclusions>
            <exclusion>
                <groupId>commons-logging</groupId>
                <artifactId>commons-logging</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
</dependencies>
  • 提供其他的logging框架替換commons-logging
<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-core</artifactId>
        <version>4.1.3.RELEASE</version>
        <exclusions>
            <exclusion>
                <groupId>commons-logging</groupId>
                <artifactId>commons-logging</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
    <!--將spring 對(duì)于jcl規(guī)則的實(shí)現(xiàn)導(dǎo)向slf4j-->
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>jcl-over-slf4j</artifactId>
        <version>1.5.8</version>
    </dependency>
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>1.5.8</version>
    </dependency>
    <!--使用log4j來(lái)作為slf4j的具體實(shí)現(xiàn)-->
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-log4j12</artifactId>
        <version>1.5.8</version>
    </dependency>
    <dependency>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
        <version>1.2.14</version>
    </dependency>
</dependencies>

如果使用lokback的話就不需要這么復(fù)雜,因?yàn)閘ogback直接實(shí)現(xiàn)了log4j,所以只需要添加jcl-over-slf4j and logback包就可以了。

5. IoC 容器

5.1 Spring IoC容器和Bean概述

org.springframework.beans和org.springframework.context包是Spring框架IoC容器的基礎(chǔ)。 BeanFactory接口提供了一個(gè)先進(jìn)的配置機(jī)制能夠管理任何類型的對(duì)象。 ApplicationContext(應(yīng)用上下文) 是BeanFactory的一個(gè)子接口。它增加了更方便的集成Spring的AOP功能、消息資源處理(使用國(guó)際化)、事件發(fā)布和特定的應(yīng)用層,如在web應(yīng)用層中使用的WebApplicationContext。在Spring中,被Spring IoC 容器 管理的這些來(lái)自于應(yīng)用主干的這些對(duì)象稱作 beans 。bean是一個(gè)由Spring IoC容器進(jìn)行實(shí)例化、裝配和管理的對(duì)象。此外,bean只是你應(yīng)用中許多對(duì)象中的一個(gè)。Beans以及他們之間的 依賴關(guān)系 是通過(guò)容器使用 配置元數(shù)據(jù) 反應(yīng)出來(lái)。

5.2 容器概述

org.springframework.context.ApplicationContext接口代表了Spring IoC容器,并且負(fù)責(zé)上面提到的Beans的實(shí)例化、配置和裝配。容器通過(guò)讀取配置元數(shù)據(jù)獲取對(duì)象如何實(shí)例化、配置和裝配的指示。配置元數(shù)據(jù)可以用XML、Java注解或Java代碼來(lái)描述。它允許你表示組成你應(yīng)用的對(duì)象,以及對(duì)象間豐富的依賴關(guān)系。

Spring提供了幾個(gè)開(kāi)箱即用的ApplicationContext接口的實(shí)現(xiàn)。在獨(dú)立的應(yīng)用程序中,通常創(chuàng)建 ClassPathXmlApplicationContext 或 FileSystemXmlApplicationContext的實(shí)例。 雖然XML是定義配置元數(shù)據(jù)的傳統(tǒng)格式,但是你可以指示容器使用Java注解或者代碼作為元數(shù)據(jù)格式,你需要通過(guò)提供少量XML配置聲明支持這些額外的元數(shù)據(jù)格式。

5.2.1 配置元數(shù)據(jù)

Spring IoC容器使用了一種 配置元數(shù)據(jù) 的形式,這些配置元數(shù)據(jù)代表了你作為一個(gè)應(yīng)用開(kāi)發(fā)者告訴Spring容器如何去實(shí)例化、配置和裝備你應(yīng)用中的對(duì)象。基于XML配置的元數(shù)據(jù) 不是 唯一允許用來(lái)配置元數(shù)據(jù)的一種形式。Spring IoC容器本身是 完全 和元數(shù)據(jù)配置書寫的形式解耦的。
Spring配置包括至少一個(gè)且通常多個(gè)由容器管理的bean定義。在基于XML配置的元數(shù)據(jù)中,這些beans配置成一個(gè)<bean/>元素,這些<bean/>元素定義在頂級(jí)元素<beans/>的里面。在Java配置中通常在一個(gè)@Configuration注解的類中,在方法上使用@Bean注解。
下面的例子演示了基于XML的配置元數(shù)據(jù)的基礎(chǔ)結(jié)構(gòu):

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="..." class="...">
        <!-- 在這里寫 bean 的配置和相關(guān)引用 -->
    </bean>

    <bean id="..." class="...">
        <!-- 在這里寫 bean 的配置和相關(guān)引用 -->
    </bean>

    <!-- 更多bean的定義寫在這里 -->

</beans>
5.2.2 實(shí)例化容器

實(shí)例化Spring IoC容器很容易。將一個(gè)或多個(gè)位置路徑提供給ApplicationContext的構(gòu)造方法就可以讓容器加載配制元數(shù)據(jù),可以從多種外部資源進(jìn)行獲取,例如文件系統(tǒng)、Java的CLASSPATH等等。

ApplicationContext context =
    new ClassPathXmlApplicationContext(new String[] {"services.xml", "daos.xml"});

bean定義可以跨越多個(gè)XML文件是非常有用的。通常每個(gè)獨(dú)立的XML配置文件表示一個(gè)邏輯層或者是你架構(gòu)中的一個(gè)模塊。

你可以使用應(yīng)用上下文的構(gòu)造方法從多個(gè)XML片段中加載bean的定義。像上面例子中出現(xiàn)過(guò)的一樣,構(gòu)造方法可以接收多個(gè)Resource位置。或者可以在bean定義中使用一個(gè)或多個(gè)<import/>從其他的配置文件引入bean定義。例如:

<beans>
    <import resource="services.xml"/>
    <import resource="resources/messageSource.xml"/>
    <import resource="/resources/themeSource.xml"/>

    <bean id="bean1" class="..."/>
    <bean id="bean2" class="..."/>
</beans>

上面的例子中,外部的bean定義從services.xml、messageSource.xml和themeSource.xml這三個(gè)文件中加載。所有的位置路徑都是相對(duì)于定義執(zhí)行導(dǎo)入的文件,所以 services.xml必須和當(dāng)前定義導(dǎo)入的文件在相同的路徑下。而messageSource.xml和themeSource.xml必須在當(dāng)前定義導(dǎo)入的文件路徑下的resources路徑下。你可以看到,這里忽略了反斜杠,由于這里的路徑是相對(duì)的,因此建議 不使用反斜杠。這些被引入文件的內(nèi)容會(huì)被導(dǎo)入進(jìn)來(lái),包含頂層的<beans/>元素,它必須是一個(gè)符合Spring架構(gòu)的有效的XML bean定義。

5.2.3 使用容器

ApplicationContext是智能的工廠接口,它能夠維護(hù)注冊(cè)不同beans和它們的依賴。通過(guò)使用 T getBean(String name, Class<T> requiredType) 方法,你可以取得這些beans的實(shí)例。

// 創(chuàng)建并配置beans
ApplicationContext context =
    new ClassPathXmlApplicationContext(new String[] {"services.xml", "daos.xml"});

// 取得配置的實(shí)例
PetStoreService service = context.getBean("petStore", PetStoreService.class);

// 使用實(shí)例
List<String> userList = service.getUsernameList();

使用getBean()來(lái)獲取您beans的實(shí)例,ApplicationContext接口還有幾個(gè)其他的可以獲取beans的方法,但是理想情況下,你最好不要使用這些方法。 事實(shí)上,您的應(yīng)用程序代碼不應(yīng)調(diào)用getBean()所有方法,因而不會(huì)和Spring的接口產(chǎn)生依賴。

5.3 Bean概述

一個(gè)Spring IoC容器管理了一個(gè)或者多個(gè) beans。這些beans通過(guò)你提供給容器的配置元數(shù)據(jù)進(jìn)行創(chuàng)建,例如通過(guò)XML形式的<bean/>定義。
在容器內(nèi)本身,這些bean定義表示為BeanDefinition對(duì)象,它包含了如下的元數(shù)據(jù):

  • 包限定的類名: 通常是bean定義的實(shí)現(xiàn)類。
  • Bean行為配置元素,這些狀態(tài)指示bean在容器中的行為(范圍,生命周期回調(diào)函數(shù),等等)。
  • bean工作需要引用的其他beans;這些引用也稱為 協(xié)作者 或 依賴者。
  • 其他配置設(shè)置應(yīng)用于新創(chuàng)建的對(duì)象中設(shè)置,例如連接池中的連接數(shù)或者連接池的大小限制。

ApplicationContext實(shí)現(xiàn)還允許由用戶在容器外創(chuàng)建注冊(cè)現(xiàn)有的對(duì)象。 這是通過(guò)訪問(wèn)ApplicationContext的工廠方法,通過(guò)getBeanFactory()返回DefaultListableBeanFactory工廠方法的實(shí)現(xiàn)。 DefaultListableBeanFactory支持通過(guò)registerSingleton(..)方法和registerBeanDefinition(..)方法進(jìn)行注冊(cè)。 然而,典型的應(yīng)用程序的工作僅僅通過(guò)元數(shù)據(jù)定義的bean定義beans。

5.3.1 bean的命名

每個(gè)bean都有一個(gè)或多個(gè)標(biāo)識(shí)符,這些bean的標(biāo)識(shí)符在它所在的容器中必須唯一。 一個(gè)bean通常只有一個(gè)標(biāo)識(shí)符,但如果它有一個(gè)以上的id標(biāo)識(shí)符,多余的標(biāo)識(shí)符將被認(rèn)為是別名。

  • 通過(guò)構(gòu)造函數(shù)實(shí)例化 當(dāng)你使用構(gòu)造方法來(lái)創(chuàng)建bean的時(shí)候,Spring對(duì)class沒(méi)有特殊的要求。也就是說(shuō),正在開(kāi)發(fā)的類不需要實(shí)現(xiàn)任何特定的接口或者以特定的方式進(jìn)行編碼。但是,根據(jù)你使用那種類型的IoC來(lái)指定bean,你可能需要一個(gè)默認(rèn)(無(wú)參)的構(gòu)造方法。
  • 使用靜態(tài)工廠方法實(shí)例化 下面的bean定義展示了如何通過(guò)工廠方法來(lái)創(chuàng)建bean實(shí)例。注意,此定義并未指定返回對(duì)象的類型,僅指定該類包含的工廠方法。在此例中,createInstance()必須是一個(gè)static方法。
<bean id="clientService"
    class="examples.ClientService"
    factory-method="createInstance"/>
    
public class ClientService {
    private static ClientService clientService = new ClientService();
    private ClientService() {}

    public static ClientService createInstance() {
        return clientService;
    }
}
  • 使用實(shí)例工廠方法實(shí)例化 與通過(guò) 靜態(tài)工廠方法 實(shí)例化類似,通過(guò)調(diào)用工廠實(shí)例的非靜態(tài)方法進(jìn)行實(shí)例化。 使用這種方式時(shí),class屬性必須為空,而factory-bean屬性必須指定為當(dāng)前(或其祖先)容器中包含工廠方法的bean的名稱,而該工廠bean的工廠方法本身必須通過(guò)factory-method屬性來(lái)設(shè)定。
<!-- 工廠bean,包含createInstance()方法 -->
<bean id="serviceLocator" class="examples.DefaultServiceLocator">
    <!-- 其他需要注入的依賴項(xiàng) -->
</bean>

<!-- 通過(guò)工廠bean創(chuàng)建的ben -->
<bean id="clientService"
    factory-bean="serviceLocator"
    factory-method="createClientServiceInstance"/>
public class DefaultServiceLocator {

    private static ClientService clientService = new ClientServiceImpl();
    private DefaultServiceLocator() {}

    public ClientService createClientServiceInstance() {
        return clientService;
    }
}

5.4 依賴

5.4.1 依賴注入

構(gòu)造器參數(shù)解析 構(gòu)造器參數(shù)通過(guò)參數(shù)類型進(jìn)行匹配。如果構(gòu)造器參數(shù)的類型定義沒(méi)有潛在的歧義,那么bean被實(shí)例化的時(shí)候,bean定義中構(gòu)造器參數(shù)的定義順序就是這些參數(shù)的順序并依次進(jìn)行匹配。看下面的代碼:

package x.y;

public class Foo {

    public Foo(Bar bar, Baz baz) {
        // ...
    }
}

<beans>
    <bean id="foo" class="x.y.Foo">
        <constructor-arg ref="bar"/>
        <constructor-arg ref="baz"/>
    </bean>

    <bean id="bar" class="x.y.Bar"/>

    <bean id="baz" class="x.y.Baz"/>
</beans>

當(dāng)另一個(gè)bean被引用,它的類型是已知的,并且匹配也沒(méi)問(wèn)題(跟前面的例子一樣)。當(dāng)我們使用簡(jiǎn)單類型,比如<value>true</value>。Spring并不能知道該值的類型,不借助其他幫助Spring將不能通過(guò)類型進(jìn)行匹配。


public class ExampleBean {

    // Number of years to calculate the Ultimate Answer
    private int years;

    // The Answer to Life, the Universe, and Everything
    private String ultimateAnswer;

    public ExampleBean(int years, String ultimateAnswer) {
        this.years = years;
        this.ultimateAnswer = ultimateAnswer;
    }
}

針對(duì)上面的場(chǎng)景可以使用type屬性來(lái)顯式指定那些簡(jiǎn)單類型那個(gè)的構(gòu)造參數(shù)類型,比如:
<bean id="exampleBean" class="examples.ExampleBean">
    <constructor-arg type="int" value="7500000"/>
    <constructor-arg type="java.lang.String" value="42"/>
</bean>

使用index屬性來(lái)顯式指定構(gòu)造參數(shù)的索引,比如:
<bean id="exampleBean" class="examples.ExampleBean">
    <constructor-arg index="0" value="7500000"/>
    <constructor-arg index="1" value="42"/>
</bean>

你也可以使用構(gòu)造器參數(shù)命名來(lái)指定值的類型:
<bean id="exampleBean" class="examples.ExampleBean">
    <constructor-arg name="years" value="7500000"/>
    <constructor-arg name="ultimateAnswer" value="42"/>
</bean>
請(qǐng)記住為了使這個(gè)起作用,你的代碼編譯時(shí)要打開(kāi)編譯模式,這樣Spring可以檢查構(gòu)造方法的參數(shù)。如果你不打開(kāi)調(diào)試模式(或者不想打開(kāi)),也可以使用 @ConstructorProperties JDK注解明確指出構(gòu)造函數(shù)的參數(shù)。下面是簡(jiǎn)單的例子:

public class ExampleBean {
    // Fields omitted
    @ConstructorProperties({"years", "ultimateAnswer"})
    public ExampleBean(int years, String ultimateAnswer) {
        this.years = years;
        this.ultimateAnswer = ultimateAnswer;
    }

}

ApplicationContext所管理的beans支持構(gòu)造函數(shù)注入和setter注入,在一些依賴已經(jīng)使用構(gòu)造器注入之后它還支持setter注入。你可以以BeanDefinition的形式配置依賴,它能根據(jù)指定的PropertyEditor實(shí)現(xiàn)將屬性從一種格式轉(zhuǎn)化為另外一種格式。但是,大多數(shù)Spring的使用者不會(huì)直接使用這些類(也就是通過(guò)編程的形式),而是采用XML配置這些bean,注解的組件(即用@Component,@Controller等注解類),或者基于@Configuration類的@Bean方法。本質(zhì)上這些資源會(huì)轉(zhuǎn)換成BeanDefinition的實(shí)例并且用于加載整個(gè)Spring IoC容器實(shí)例。

因?yàn)槟憧梢曰旌鲜褂脴?gòu)造器注入和setter注入, 強(qiáng)制性依賴關(guān)系 時(shí)使用構(gòu)造器注入, 可選的依賴關(guān)系 時(shí)使用setter方法或者配置方法是比較好的經(jīng)驗(yàn)法則。
通常你可以信賴Spring。在容器加載時(shí)Spring會(huì)檢查配置,比如不存在的bean和循環(huán)依賴。當(dāng)bean創(chuàng)建時(shí),Spring盡可能遲得設(shè)置屬性和依賴關(guān)系。這意味著即使Spring正常加載,在你需要一個(gè)存在問(wèn)題或者它的依賴存在問(wèn)題的對(duì)象時(shí),Spring會(huì)報(bào)出異常。舉個(gè)例子,bean因設(shè)置缺少或者無(wú)效的屬性會(huì)拋出一個(gè)異常。因?yàn)橐恍┡渲脝?wèn)題存在將會(huì)導(dǎo)致潛在的可見(jiàn)性被延遲,所以默認(rèn)ApplicationContext的實(shí)現(xiàn)bean采用提前實(shí)例化的單例模式。在實(shí)際需要之前創(chuàng)建這些bean會(huì)帶來(lái)時(shí)間和內(nèi)存的開(kāi)銷,當(dāng)ApplicationContext創(chuàng)建完成時(shí)你會(huì)發(fā)現(xiàn)配置問(wèn)題,而不是之后。你也可以重寫默認(rèn)的行為使得單例bean延遲實(shí)例化而不是提前實(shí)例化。

5.4.2 依賴配置詳解

  • 直接變量 (基本類型, String類型等) <property/>元素的value值通過(guò)可讀的字符串形式來(lái)指定屬性和構(gòu)造器參數(shù)。Spring的conversion service 把String轉(zhuǎn)換成屬性或者構(gòu)造器實(shí)際需要的類型。
<bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
    <!-- results in a setDriverClassName(String) call -->
    <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
    <property name="url" value="jdbc:mysql://localhost:3306/mydb"/>
    <property name="username" value="root"/>
    <property name="password" value="masterkaoli"/>
</bean>

接下來(lái)的例子使用p 命名空間簡(jiǎn)化XML配置
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:p="http://www.springframework.org/schema/p"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource"
        destroy-method="close"
        p:driverClassName="com.mysql.jdbc.Driver"
        p:url="jdbc:mysql://localhost:3306/mydb"
        p:username="root"
        p:password="masterkaoli"/>

</beans>

你也可以配置java.util.Properties實(shí)例,就像這樣:
<bean id="mappings"
    class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">

    <!-- typed as a java.util.Properties -->
    <property name="properties">
        <value>
            jdbc.driver.className=com.mysql.jdbc.Driver
            jdbc.url=jdbc:mysql://localhost:3306/mydb
        </value>
    </property>
</bean>
idref元素用來(lái)將容器內(nèi)其他bean的id(值是字符串-不是引用)傳給元素<constructor-arg/> 或者 <property/>

<bean id="theTargetBean" class="..."/>

<bean id="theClientBean" class="...">
    <property name="targetName">
        <idref bean="theTargetBean" />
    </property>
</bean>
上面的bean定義片段完全等同于(在運(yùn)行時(shí))下面片段:

<bean id="theTargetBean" class="..." />

<bean id="client" class="...">
    <property name="targetName" value="theTargetBean" />
</bean>

第一種形式比第二種形式更好,因?yàn)槭褂胕dref標(biāo)簽允許容器在部署時(shí)驗(yàn)證引用的bean是否存在。 在第二種形式中,傳給 client bean中屬性targetName的值并沒(méi)有被驗(yàn)證。 只有當(dāng) client bean完全實(shí)例化的時(shí)候錯(cuò)誤才會(huì)被發(fā)現(xiàn)(可能伴隨著致命的結(jié)果)。
idref和ref的區(qū)別就是idref只能夠引用其他bean的id,而ref可以引用if或者name

  • 內(nèi)部bean 所謂的內(nèi)部bean就是指在 <property/> 或者 <constructor-arg/> 元素內(nèi)部使用<bean/>定義bean。
  • 集合 在<list/>, <set/>, <map/>, 和<props/>元素中,你可以設(shè)置值和參數(shù)分別對(duì)應(yīng)Java的集合類型List, Set, Map, 和 Properties
  • 集合合并 Spring容器也支持集合的 合并。開(kāi)發(fā)者可以定義parent-style<list/>,<map/>, <set/> 或者<props/> 元素并, child-style 的<list/>, <map/>, <set/> 或者 <props/>元素繼承和覆蓋自父集合。也就是說(shuō)。父集合元素合并后的值就是子集合的最終結(jié)果,而且子集中的元素值將覆蓋父集中對(duì)應(yīng)的值。
<beans>
    <bean id="parent" abstract="true" class="example.ComplexObject">
        <property name="adminEmails">
            <props>
                <prop key="administrator">administrator@example.com</prop>
                <prop key="support">support@example.com</prop>
            </props>
        </property>
    </bean>
    <bean id="child" parent="parent">
        <property name="adminEmails">
            <!-- the merge is specified on the child collection definition -->
            <props merge="true">
                <prop key="sales">sales@example.com</prop>
                <prop key="support">support@example.co.uk</prop>
            </props>
        </property>
    </bean>
<beans>
  • Null和空字符串 Spring會(huì)把空屬性當(dāng)做空字符串處理。以下的基于XML配置的片段將email屬性設(shè)置為空字符串。
<bean class="ExampleBean">
    <property name="email" value=""/>
</bean>
先前的例子等同于以下Java代碼:
exampleBean.setEmail("")

<null/>元素處理null值,例如:
<bean class="ExampleBean">
    <property name="email">
        <null/>
    </property>
</bean>
上面的配置等同于下面的Java代碼:
exampleBean.setEmail(null)
5.4.3 使用 depends-on

如果一個(gè)bean是另外一個(gè)bean的依賴,這通常意味著這個(gè)bean可以設(shè)置成為另外一個(gè)bean的屬性。當(dāng)前bean初始化之前顯式地強(qiáng)制一個(gè)或多個(gè)bean被初始化。下面的例子中使用了depends-on屬性來(lái)指定一個(gè)bean的依賴。
為了實(shí)現(xiàn)多個(gè)bean的依賴,你可以在depends-on中將指定的多個(gè)bean名字用分隔符進(jìn)行分隔,分隔符可以是逗號(hào),空格以及分號(hào)等。

<bean id="beanOne" class="ExampleBean" depends-on="manager,accountDao">
    <property name="manager" ref="manager" />
</bean>

<bean id="manager" class="ManagerBean" />
<bean id="accountDao" class="x.y.jdbc.JdbcAccountDao" />
5.4.4 延遲初始化bean

ApplicationContext實(shí)現(xiàn)的默認(rèn)行為就是再啟動(dòng)時(shí)將所有singleton bean提前進(jìn)行實(shí)例化。 通常這樣的提前實(shí)例化方式是好事,因?yàn)榕渲弥谢蛘哌\(yùn)行環(huán)境的錯(cuò)誤就會(huì)被立刻發(fā)現(xiàn),否則可能要花幾個(gè)小時(shí)甚至幾天。如果你不想 這樣,你可以將單例bean定義為延遲加載防止它提前實(shí)例化。

<bean id="lazy" class="com.foo.ExpensiveToCreateBean" lazy-init="true"/>

但是當(dāng)一個(gè)延遲加載的bean是單例bean的依賴,但這個(gè)單例bean又不是 延遲加載,ApplicationContext在啟動(dòng)時(shí)創(chuàng)建了延遲加載 的bean,因?yàn)樗仨殱M足單例bean的依賴。因此延遲加載的bean會(huì)被注入單例bean,然而在其他地方它不會(huì)延遲加載。

5.4.6 方法注入

在大部分的應(yīng)用場(chǎng)景中,容器中的大部分bean是singletons類型的。當(dāng)一個(gè)單例bean需要和另外一個(gè)單例bean, 協(xié)作時(shí),或者一個(gè)費(fèi)單例bean要引用另外一個(gè)非單例bean時(shí),通常情況下將一個(gè)bean定義為另外一個(gè)bean的屬性值就行了。不過(guò)對(duì)于具有不同生命周期的bean 來(lái)說(shuō)這樣做就會(huì)有問(wèn)題了,比如在調(diào)用一個(gè)單例類型bean A的某個(gè)方法,需要引用另一個(gè)非單例(prototype)類型bean B,對(duì)于bean A來(lái)說(shuō),容器只會(huì)創(chuàng)建一次,這樣就沒(méi)法 在需要的時(shí)候每次讓容器為bean A提供一個(gè)新的bean B實(shí)例。
上面問(wèn)題的一個(gè)解決方法是放棄控制反轉(zhuǎn),你可以實(shí)現(xiàn)ApplicationContextAware接口來(lái)讓bean A感知到容器, 并且在需要的時(shí)候通過(guò)使用使用getBean("B")向容器請(qǐng)求一個(gè)(新的)bean B實(shí)例。下面的例子使用了這個(gè)方法:

public class CommandManager implements ApplicationContextAware {

    private ApplicationContext applicationContext;

    public Object process(Map commandState) {
        // grab a new instance of the appropriate Command
        Command command = createCommand();
        // set the state on the (hopefully brand new) Command instance
        command.setState(commandState);
        return command.execute();
    }

    protected Command createCommand() {
        // notice the Spring API dependency!
        return this.applicationContext.getBean("command", Command.class);
    }

    public void setApplicationContext(
            ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}

Lookup 方法注入 Lookup方法具有使容器覆蓋受容器管理的bean方法的能力,從而返回指定名字的bean實(shí)例。在上述場(chǎng)景中,Lookup方法注入適用于原型bean。 Lookup方法注入的內(nèi)部機(jī)制是Spring利用了CGLIB庫(kù)在運(yùn)行時(shí)生成二進(jìn)制代碼的功能,通過(guò)動(dòng)態(tài)創(chuàng)建Lookup方法bean的子類從而達(dá)到復(fù)寫Lookup方法的目的.

public abstract class CommandManager {

    public Object process(Object commandState) {
        // grab a new instance of the appropriate Command interface
        Command command = createCommand();
        // set the state on the (hopefully brand new) Command instance
        command.setState(commandState);
        return command.execute();
    }

    // okay... but where is the implementation of this method?
    protected abstract Command createCommand();
}

<!-- a stateful bean deployed as a prototype (non-singleton) -->
<bean id="command" class="fiona.apple.AsyncCommand" scope="prototype">
    <!-- inject dependencies here as required -->
</bean>

<!-- commandProcessor uses statefulCommandHelper -->
<bean id="commandManager" class="fiona.apple.CommandManager">
    <lookup-method name="createCommand" bean="command"/>
</bean>
標(biāo)識(shí)為commandManager的bean在需要一個(gè)新的command bean實(shí)例時(shí)會(huì)調(diào)用createCommand()方法。你必須將`command`bean部署為 原型(prototype)類型,如果這是實(shí)際需要的話。如果部署為singleton。那么每次將返回相同的 `command`bean。

5.5 Bean作用域

當(dāng)你創(chuàng)建一個(gè) bean 的定義,實(shí)際上是創(chuàng)建了一個(gè)產(chǎn)生真實(shí)實(shí)例的配方(recipe)。bean 定義是一個(gè)配方(recipe)這種概念是很重要的,它的意思是指,和class一樣,你可以從一個(gè)配方(recipe)創(chuàng)建多個(gè)對(duì)象實(shí)例。

5.5.1 單例作用域

僅管理一個(gè)單例 bean 的共享實(shí)例,并且所有通過(guò) id 或者 ids 獲得 bean 定義的請(qǐng)求,都會(huì)從 Spring 容器中得到同一個(gè)特定的 bean 實(shí)例。

5.5.2 原型作用域

bean使用原型作用域而不是單例作用域的話,會(huì)在每次請(qǐng)求該bean,也就是bean被注入至另一個(gè)bean、 或通過(guò)調(diào)用Spring容器的 getBean() 方法時(shí),創(chuàng)建一個(gè)新的bean實(shí)例 。 通常,對(duì)于有狀態(tài)的bean使用原型作用域,無(wú)狀態(tài)的bean則使用單例作用域。
與其他作用域不同的是,Spring容器不會(huì)管理原型域bean的完整生命周期:Spring容器會(huì)初始化、 配置,亦或者組裝原型域的bean對(duì)象,然后交給客戶端,之后就再也不會(huì)管這個(gè)bean對(duì)象了。 因此,對(duì)于bean的生命周期方法來(lái)說(shuō),盡管所有作用域的 初始化方法 都會(huì)被調(diào)用, 但是原型域bean的 銷毀方法 不會(huì) 被Spring容器調(diào)用。客戶端代碼要自己負(fù)責(zé)銷毀原型域bean 以及和bean相關(guān)的資源(特別是開(kāi)銷大的資源)。如果想讓Spring負(fù)責(zé)這些事(銷毀bean、釋放資源), 就得自定義bean的后處理器 bean post-processor ,它會(huì)持用原型域bean的引用。

5.5.3 依賴原型bean的單例bean

如果你的單例bean依賴了原型bean,謹(jǐn)記這些依賴(的原型bean) 只在初始化時(shí)解析 。 因此,假如你將原型bean依賴注入至單例bean,在注入時(shí)會(huì)初始化一個(gè)新的原型bean實(shí)例, 這個(gè)被注入的原型bean實(shí)例是一個(gè)獨(dú)立的實(shí)例。

5.5.4 請(qǐng)求作用域、會(huì)話作用域和全局會(huì)話作用域

僅當(dāng) 你使用web相關(guān)的Spring ApplicationContext(例如 XmlWebApplicationContext)時(shí), 請(qǐng)求 request 、會(huì)話 session 和全局會(huì)話 global session 作用域才會(huì)起作用。如果你在普通的Spring IoC 容器(例如 ClassPathXmlApplicationContext)中使用這幾個(gè)作用域,會(huì)拋出異常 IllegalStateException ,告知你這是一個(gè)未知的bean作用域

5.6 定制bean特性

5.6.1 生命周期回調(diào)

Spring提供了幾個(gè)標(biāo)志接口(marker interface),這些接口用來(lái)改變?nèi)萜髦衎ean的行為;它們包括InitializingBean和DisposableBean。 實(shí)現(xiàn)這兩個(gè)接口的bean在初始化和析構(gòu)時(shí)容器會(huì)調(diào)用前者的afterPropertiesSet()方法,以及后者的destroy()方法。
在現(xiàn)代的Spring應(yīng)用中,The JSR-250 @PostConstruct and @PreDestroy 接口一般認(rèn)為是接收生命周期回調(diào)的最佳做法。 使用這些注解意味著bean沒(méi)有耦合到Spring具體的接口。
Spring在內(nèi)部使用 BeanPostProcessor 實(shí)現(xiàn)來(lái)處理它能找到的任何回調(diào)接口并調(diào)用相應(yīng)的方法。如果你需要自定義特性或者生命周期行為,你可以實(shí)現(xiàn)自己的 BeanPostProcessor 。

初始化回調(diào)函數(shù)

實(shí)現(xiàn) org.springframework.beans.factory.InitializingBean 接口,允許容器在設(shè)置好bean的所有必要屬性后,執(zhí)行初始化事宜。通常,要避免使用 InitializingBean 接口并且不鼓勵(lì)使用該接口,因?yàn)檫@樣會(huì)將代碼和Spring耦合起來(lái)。 使用@PostConstruct注解或者指定一個(gè)POJO的初始化方法。 在XML配置元數(shù)據(jù)的情況下,使用 init-method 屬性去指定方法名,并且該方法無(wú)參數(shù)簽名。

析構(gòu)回調(diào)函數(shù)

實(shí)現(xiàn) org.springframework.beans.factory.DisposableBean 接口,允許一個(gè)bean當(dāng)容器需要其銷毀時(shí)獲得一次回調(diào)。建議不使用 DisposableBean 回調(diào)接口,因?yàn)闀?huì)與Spring耦合。使用@PreDestroy 注解或者指定一個(gè)普通的方法,但能由bean定義支持。基于XML配置的元數(shù)據(jù),使用 <bean/> 的 destroy-method 屬性。

組合生命周期機(jī)制

截至 Spring 2.5,有三種選擇控制bean生命周期行為:InitializingBean 和 DisposableBean 回調(diào)接口;自定義init() 和 destroy() 方法; @PostConstruct and @PreDestroy。如果bean存在多種的生命周期機(jī)制配置并且每種機(jī)制都配置為不同的方法名, 那所有配置的方法將會(huì)按照上面的順利執(zhí)行。順序依次是:注解->afterPropertiesSet()->init()方法。

5.6.2 ApplicationContextAware and BeanNameAware

當(dāng) ApplicationContext 創(chuàng)建一個(gè)實(shí)現(xiàn) org.springframework.context.ApplicationContextAware 接口的對(duì)象的實(shí)例, 該實(shí)例提供一個(gè)參考,ApplicationContext。bean可以通過(guò)編程方式操縱 ApplicationContext 來(lái)創(chuàng)建,通過(guò) ApplicationContext 接口,或者通過(guò)向這個(gè)接口的一個(gè)已知的子類的引用, 如 ConfigurableApplicationContext ,公開(kāi)附加功能。截止Spring 2.5,自動(dòng)裝配是獲取 ApplicationContext 引用傳統(tǒng)的 constructor 和 byType自動(dòng)模式(在 Section 5.4.5, “自動(dòng)裝配協(xié)作者”中描述)可以分別為 ApplicationContext 類型的構(gòu)造函數(shù)參數(shù)或setter方法參數(shù)提供依賴。比較方便的做法直接通過(guò)@Autowired將屬性注入進(jìn)來(lái)。,但是基于xml的配置也是完全可以的~
當(dāng) ApplicationContext 創(chuàng)建一個(gè)實(shí)現(xiàn) org.springframework.beans.factory.BeanNameAware 接口的類,該類為 定義在其相關(guān)對(duì)象定義中的名稱提供一個(gè)索引。實(shí)現(xiàn)方式和上面的ApplicationContextAware一致~

5.6.3 其他 Aware 接口

除了上述的 ApplicationContextAware 和 BeanNameAware ,Spring提供一系列的 Aware 接口,允許bean表示他們需要一定基礎(chǔ)設(shè)施依賴的容器。 最重要的 Aware 接口概括如下,作為一般規(guī)則,這個(gè)名稱是依賴類型的一個(gè)很好的指示:
再次說(shuō)明,這些接口的使用將您的代碼耦合到Spring API,并且不遵循反轉(zhuǎn)控制方式。

  • ApplicationContextAware 聲明ApplicationContext
  • ApplicationEventPublisherAware 封閉的事件發(fā)布者 ApplicationContext
  • BeanClassLoaderAware 用于裝載bean class 的裝載器.
  • BeanFactoryAware 聲明 BeanFactory
  • BeanNameAware 聲明bean的名稱
  • ServletConfigAware 目前 ServletConfig 容器運(yùn)行. 僅在一個(gè)web-aware Spring中有效 ApplicationContext
  • ServletContextAware 目前 ServletContext 容器運(yùn)行.

5.7 Bean定義的繼承

在一個(gè)bean定義中,可以包含配置信息,包括構(gòu)造器參數(shù),屬性值,以及容器的特定信息,比如初始化方法, 靜態(tài)工廠方法名等等。子bean定義,從它的父bean定義繼承配置數(shù)據(jù);子bean定義可以覆蓋一些值, 或者根據(jù)需要添加一些其他值。使用父子bean定義,可以避免很多配置填寫。事實(shí)上,這是一種模板設(shè)計(jì)模式。

<bean id="inheritedTestBean" abstract="true"
        class="org.springframework.beans.TestBean">
    <property name="name" value="parent"/>
    <property name="age" value="1"/>
</bean>

<bean id="inheritsWithDifferentClass"
        class="org.springframework.beans.DerivedTestBean"
        parent="inheritedTestBean" init-method="initialize">
    <property name="name" value="override"/>
    <!--age屬性的值1,將會(huì)從父類繼承-->
</bean>

下面的例子,通過(guò)使用abstract屬性,明確地標(biāo)明這個(gè)父類bean定義是抽象的。如果,父類bean定義 沒(méi)有明確地指出所屬的類,那么標(biāo)記父bean定義為為abstract是必須的,如下:

<bean id="inheritedTestBeanWithoutClass" abstract="true">
    <property name="name" value="parent"/>
    <property name="age" value="1"/>
</bean>

<bean id="inheritsWithClass" class="org.springframework.beans.DerivedTestBean"
        parent="inheritedTestBeanWithoutClass" init-method="initialize">
    <property name="name" value="override"/>
    <!--age屬性的值1,將會(huì)從父類繼承-->
</bean>

這個(gè)父bean不能自主實(shí)例化,因?yàn)樗遣煌暾模瑫r(shí)它也明確地被標(biāo)注為abstract;像這樣, 一個(gè)bean定義為abstract 的,它只能作為一個(gè)純粹的bean模板,為子bean定義,充當(dāng)父bean定義。 嘗試獨(dú)立地使用這樣一個(gè)abstract的父bean,把他作為另一個(gè)bean 的引用,或者根據(jù)這個(gè)父bean的id顯式調(diào)用getBean()方法, 將會(huì)返回一個(gè)錯(cuò)誤。類似地,容器內(nèi)部的preInstantiateSingletons() 方法,也忽略定義為抽象的bean定義。

5.8 容器拓展點(diǎn)

5.8.1 使用BeanPostProcessor自定義beans

BeanPostProcessor 定義了回調(diào)方法,通過(guò)實(shí)現(xiàn)這個(gè)回調(diào)方法,你可以提供你自己的(或者重寫容器默認(rèn)的) 實(shí)例化邏輯,依賴分析邏輯等等。如果你想在Spring容器完成實(shí)例化配置,實(shí)例化一個(gè)bean之后,實(shí)現(xiàn)一些自定義邏輯 你可以插入一個(gè)或多個(gè) BeanPostProcessor 的實(shí)現(xiàn)。

你可以配置多個(gè)BeanPostProcessor實(shí)例,同時(shí)你也能通過(guò)設(shè)置 order 屬性來(lái)控制這些BeanPostProcessors 的執(zhí)行順序。只有BeanPostProcessor實(shí)現(xiàn)了Ordered 接口,你才可以設(shè)置 order 屬性。如果,你編寫了自己的BeanPostProcessor 也應(yīng)當(dāng)考慮實(shí)現(xiàn) Ordered 接口。欲知詳情,請(qǐng)參考BeanPostProcessor 和 Ordered接口的javadoc。
BeanPostProcessors作用范圍是每一個(gè)容器。這僅僅和你正在使用容器有關(guān)。如果你在一個(gè)容器中定義了一個(gè)BeanPostProcessor ,它將 僅僅 后置處理那個(gè)容器中的beans。換言之,一個(gè)容器中的beans不會(huì)被另一個(gè),容器中的BeanPostProcessor處理,即使這兩個(gè)容器,具有相同的父類。
一個(gè)ApplicationContext,自動(dòng)地檢測(cè)所有定義在配置元文件中,并實(shí)現(xiàn)了BeanPostProcessor接口的bean。 該ApplicationContext注冊(cè)這些beans作為后置處理器,使他們可以在bean創(chuàng)建完成之后,被調(diào)用。 bean后置處理器可以像其他bean一樣部署到容器中。

  • 編程式注冊(cè) BeanPostProcessors 雖然推薦使用ApplicationContext的自動(dòng)檢測(cè)來(lái)注冊(cè)BeanPostProcessor,但是對(duì)于使用了addBeanPostProcessor方法的ConfigurableBeanFactory也可以編程式地注冊(cè)他們。 在注冊(cè)之前,或者是在繼承層次的上下文之間復(fù)制bean后置處理器,需要對(duì)邏輯進(jìn)行條件式的評(píng)估時(shí),這是有用的。但是請(qǐng)注意,編程地添加的BeanPostProcessors 不需要考慮Ordered接口 。 也就是注冊(cè)的順序決定了執(zhí)行的順序。也要注意,編程式注冊(cè)的BeanPostProcessors,總是預(yù)先被處理----早于通過(guò)自動(dòng)檢測(cè)方式注冊(cè)的,同時(shí)忽略 任何明確的排序
  • BeanPostProcessors 和 AOP 自動(dòng)代理 實(shí)現(xiàn)了BeanPostProcessor接口的類是特殊的,會(huì)被容器特殊處理。所有BeanPostProcessors和他們直接引用的 beans都會(huì)在容器啟動(dòng)的時(shí)候被實(shí)例化,作為`ApplicationContext特殊啟動(dòng)階段的一部分。接著,所有的BeanPostProcessors 以一個(gè)有序的方式,進(jìn)行注冊(cè),并應(yīng)用于容器中的一切bean。因?yàn)锳OP自動(dòng)代理本身被實(shí)現(xiàn)為BeanPostProcessor, 這個(gè)BeanPostProcessors 和它直接應(yīng)用的beans都沒(méi)有資格進(jìn)行自動(dòng)代理,這樣就沒(méi)有切面編織到他們里面。
BeanPostProcessor使用的實(shí)例
public class InstantiationTracingBeanPostProcessor implements BeanPostProcessor {

    // simply return the instantiated bean as-is
    public Object postProcessBeforeInitialization(Object bean,
            String beanName) throws BeansException {
        return bean; // we could potentially return any object reference here...
    }

    public Object postProcessAfterInitialization(Object bean,
            String beanName) throws BeansException {
        System.out.println("Bean '" + beanName + "' created : " + bean.toString());
        return bean;
    }

}
5.8.2 通過(guò)BeanFactoryPostProcessor來(lái)處理元數(shù)據(jù)

BeanFactoryPostProcessor和BeanPostProcessor非常像,但還有一個(gè)明顯的區(qū)別:BeanFactoryPostProcessor主要處理的是bean的定義元數(shù)據(jù),Spring允許BeanFactoryPostProcessor讀取bean定義的元數(shù)據(jù)并在bean的實(shí)例化之前改變其中的某些元素。它的作用域和BeanPostProcessor保持一致,一個(gè)容器中的BeanFactoryPostProcessor只會(huì)在當(dāng)前元素中生效,我們可以通過(guò)實(shí)現(xiàn)Orderd接口來(lái)決定BeanFactoryPostProcessor的生效順序。Spring在內(nèi)部已經(jīng)生成了很多BeanFactoryPostProcessor的實(shí)現(xiàn)類,比較常見(jiàn)的就是PropertyPlaceholderConfigurer,用來(lái)進(jìn)行占位符的替換操作。

PropertyPlaceholderConfigurer例子
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
    <property name="locations" value="classpath:com/foo/jdbc.properties"/>
</bean>

<bean id="dataSource" destroy-method="close"
        class="org.apache.commons.dbcp.BasicDataSource">
    <property name="driverClassName" value="${jdbc.driverClassName}"/>
    <property name="url" value="${jdbc.url}"/>
    <property name="username" value="${jdbc.username}"/>
    <property name="password" value="${jdbc.password}"/>
</bean>

通過(guò)對(duì)于PropertyPlaceholderConfigurer的聲明,我們可以使用jdbc.proerties文件中的屬性替換占位符對(duì)象。在Spring2.5之后更是提供了注解形式的寫法

<context:property-placeholder location="classpath:com/foo/jdbc.properties"/>

Spring并不只會(huì)讀取priperties文件,還會(huì)在沒(méi)有讀取到配置信息的時(shí)候進(jìn)一步讀取System配置信息。我們可以通過(guò)systemPropertiesMode來(lái)控制對(duì)于Java System元素的讀取優(yōu)先級(jí):

  • 0 :從來(lái)不會(huì)讀取系統(tǒng)配置
  • 1 :如果沒(méi)有在配置文件中讀取到數(shù)據(jù)的時(shí)候,嘗試從System配置中讀取。(默認(rèn)情況)
  • 2 :先讀取System配置,然后在讀取配置文件
PropertyOverrideConfigurer使用樣例

PropertyOverrideConfigurer可以用來(lái)設(shè)置bean的屬性值,如果配置文件中沒(méi)有包含對(duì)應(yīng)bean的屬性值的話,默認(rèn)的屬性值就會(huì)生效。

//db.proerties
dataSource.driverClassName=com.mysql.jdbc.Driver
dataSource.url=jdbc:mysql:mydb

foo.fred.bob.sammy=123

<context:property-override location="classpath:db.properties"/>

然后,名為dataSource的bean的driverClassName屬性和url屬性都會(huì)被替換為系統(tǒng)中的值,嵌套的屬性值也會(huì)被替換掉,例如:foo.fred.bob.sammy

5.9 注解配置

注解和XML那個(gè)更屌?一千個(gè)人眼中有一千個(gè)哈姆雷特,針對(duì)這個(gè)問(wèn)題大家的看法也都不一致。但是我常用給的做法是這樣子:一些對(duì)于第三方依賴配置我會(huì)使用xml配置,例如db層,(kafka)隊(duì)列介入等。對(duì)于內(nèi)部使用的Bean,例如Service,Dao,Conmopnet等等都是通過(guò)注解來(lái)實(shí)現(xiàn)的,特別是對(duì)于Bean屬性的注入等等都是通過(guò)注解的。總結(jié)的話使用比例大概在7:3,部分簡(jiǎn)單的配置也會(huì)直接用@Configuration替換xml配置(偷懶)。但是注解的缺點(diǎn)就是與Spring代碼嚴(yán)重耦合,如果注解多的情況下代碼看起來(lái)可能會(huì)比較亂,維護(hù)的地方比較分散,不像xml那樣基本只會(huì)分布在resource目錄的。

注解是在xml配置生效前生效的,所以如果注解和xml同時(shí)都配置來(lái)某個(gè)信息,xml的配置會(huì)覆蓋注解的。
通常情況下,如果需要使Spring注解生效的話,一般會(huì)在xml中使用以下配置:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">

    <context:annotation-config/>

</beans>

這個(gè)配置主要告訴Spring激活A(yù)utowiredAnnotationBeanPostProcessor, CommonAnnotationBeanPostProcessor, PersistenceAnnotationBeanPostProcessor和RequiredAnnotationBeanPostProcessor.

  • @Required
    @Required注解用在類的set方法上,表明該屬性必須在加載配置的時(shí)候被加載進(jìn)來(lái),如果需要的屬性沒(méi)有傳進(jìn)來(lái)的話會(huì)拋出異常。

  • @Autowired 這個(gè)注解應(yīng)該是我們用的最多的注解了,@Autowired是根據(jù)type進(jìn)行判斷然后注入的,所以如果有多個(gè)接口實(shí)現(xiàn)類的話使用@Autowired是有問(wèn)題的。@Autowired可以使用在屬性上或者set方法上。如果一個(gè)接口有多個(gè)實(shí)現(xiàn),我們要一次性注入進(jìn)來(lái)的話可以這么做(下面代碼):在被注入的對(duì)象是map的情況下,map的key一定是String,key的內(nèi)容就是具體的beanName,value是具體的實(shí)現(xiàn)類的引用。默認(rèn)情況下@Autowired標(biāo)注的屬性是必須的,如果沒(méi)有合適的提供者的話會(huì)報(bào)錯(cuò),如果不一定需要的話可以通過(guò)屬性@Autowired(required=false)來(lái)標(biāo)注。在使用@Autowired的時(shí)候你不緊可以注入自己的實(shí)現(xiàn)類,還可以注入Spirng 的一些已知類,例如BeanFactory, ApplicationContext, Environment, ResourceLoader, ApplicationEventPublisher或者 MessageSource。

public class MovieRecommender {

    @Autowired
    private MovieCatalog[] movieCatalogs;
}

public class MovieRecommender {

    private Set<MovieCatalog> movieCatalogs;
    @Autowired
    public void setMovieCatalogs(Set<MovieCatalog> movieCatalogs) {
        this.movieCatalogs = movieCatalogs;
    }
}
  • @Qualifier 這個(gè)注解通常和@Autowired配合使用,如果一個(gè)接口有兩個(gè)實(shí)現(xiàn)的話,單純使用@Autowired就會(huì)報(bào)錯(cuò),因?yàn)檫@個(gè)時(shí)候如果我們想指定其中一個(gè)Bean的話就需要配合@Qualifier,表示需要某種限定符標(biāo)示的bean。@Qualifier還可以直接用在構(gòu)造函數(shù)的參數(shù)或者set方法的參數(shù)上,例如(如下代碼)。但是對(duì)于@Qualifier有一點(diǎn)值得說(shuō)明的地方就是它并不標(biāo)示“唯一”,可以有多個(gè)bean配置了同樣的Qualifier,@Qualifier主要是用來(lái)區(qū)分某一類,而不是某一個(gè)。我們可以在Set<MovieCatalog> logs屬性上使用@Qualifier注解標(biāo)示我們這個(gè)set中希望注入的部分bean。
public class MovieRecommender {

    private MovieCatalog movieCatalog;

    private CustomerPreferenceDao customerPreferenceDao;

    @Autowired
    public void prepare(@Qualifier("main")MovieCatalog movieCatalog,
            CustomerPreferenceDao customerPreferenceDao) {
        this.movieCatalog = movieCatalog;
        this.customerPreferenceDao = customerPreferenceDao;
    }

    // ...

}
  • @Resource Spring支持 JSR-250提供的 @Resource注解,該注解與@Autowired的主要區(qū)別就是@Resource是byName的,@Autowired是byType的。@Resource使用的優(yōu)先級(jí)依次是:先根據(jù)name屬性查找對(duì)應(yīng)的bean,如果name沒(méi)有指定的話就是用默認(rèn)的駝峰形式為name來(lái)尋找bean,如果依然沒(méi)有找到的話則退而根據(jù)byType來(lái)尋找具體的bean。和@Autowired比較像的是其對(duì)于Spring內(nèi)置的一些Bean尋找,像BeanFactory, ApplicationContext, ResourceLoader, ApplicationEventPublisher, and MessageSource等只需要簡(jiǎn)單的配置@Resource就可以了,不需要其他配置。
  • @PostConstruct and @PreDestroy 這兩個(gè)注解在Bean的生命周期介紹時(shí)候已經(jīng)有介紹過(guò),分別在bean初始化之后和bean銷毀之前生效。他們可以生效的原因是CommonAnnotationBeanPostProcessor已經(jīng)在ApplicationContext中注冊(cè)過(guò)了。
  • @Component @Component是Spring用來(lái)聲明組件的統(tǒng)一注解形式,它是一個(gè)泛化的聲明。如果可以的話,盡可能的使用更加精確的注解,像@Service,@Controller等等。這些注解往往有更加精確的含義說(shuō)明。但是要想這些注解生效的話(將Class解析為BeanDefinnition,然后被ApplicationContext使用),你需要在@Configuration注解上配置@ComponentScan,通過(guò)basePackages屬性指定掃描的路徑。默認(rèn)情況下@ComponentScan會(huì)掃描@Component, @Repository, @Service, @Controller等注解,如果你不希望掃描這些配置的話,可以自己添加過(guò)濾器來(lái)掃描指定的類.
@Configuration
@ComponentScan(basePackages = "org.example",
        includeFilters = @Filter(type = FilterType.REGEX, pattern = ".*Stub.*Repository"),
        excludeFilters = @Filter(Repository.class))
public class AppConfig {
    ...
}
通過(guò)components來(lái)定義Bean metadata

Spring還可以通過(guò)components或者@Configuration來(lái)生成bean definition,你可以在要生成bean的方法上標(biāo)注@Bean注解。下面是個(gè)例子:

@Component
public class FactoryMethodComponent {
    @Bean
    @Qualifier("public")
    public TestBean publicInstance() {
        return new TestBean("publicInstance");
    }
}

這個(gè)例子中就通過(guò)@Bean來(lái)生成一個(gè)bean definition。

@Component
public class FactoryMethodComponent {

    private static int i;

    @Bean
    @Qualifier("public")
    public TestBean publicInstance() {
        return new TestBean("publicInstance");
    }

    @Bean
    protected TestBean protectedInstance(
            @Qualifier("public") TestBean spouse,
            @Value("#{privateInstance.age}") String country) {
        TestBean tb = new TestBean("protectedInstance", 1);
        tb.setSpouse(spouse);
        tb.setCountry(country);
        return tb;
    }

    @Bean
    @Scope(BeanDefinition.SCOPE_SINGLETON)
    private TestBean privateInstance() {
        return new TestBean("privateInstance", i++);
    }

    @Bean
    @Scope(value = WebApplicationContext.SCOPE_SESSION, proxyMode = ScopedProxyMode.TARGET_CLASS)
    public TestBean requestScopedInstance() {
        return new TestBean("requestScopedInstance", 3);
    }

}

Spring組件中的@Bean方法與Spring @Configuration類中的對(duì)應(yīng)方法不同。不同之處是,@Component類沒(méi)有通過(guò)CGLIB增強(qiáng),以攔截方法和字段的調(diào)用。CGLIB代理是在@Configuration類中的@Bean方法中調(diào)用方法或字段的方法,它為協(xié)作對(duì)象創(chuàng)建bean元數(shù)據(jù)引用;這些方法不是用普通的Java語(yǔ)義調(diào)用的。相反,在@Component類中調(diào)用@Bean方法中的方法或字段具有標(biāo)準(zhǔn)的Java語(yǔ)義。
當(dāng)Spring在監(jiān)測(cè)到一個(gè)Component的時(shí)候,如果注解上沒(méi)有定義名稱的話,Spring則會(huì)模式給該bean生成一個(gè)name,生成的規(guī)則還是采用首字母小寫的形式。 如果你想定義自己的默認(rèn)名字生成規(guī)則,可以這個(gè)做:
1.實(shí)現(xiàn)ScopeMetadataResolver接口
2.配置時(shí)候指定自己定義的ScopeMetadataResolver

@Configuration
@ComponentScan(basePackages = "org.example", scopeResolver = MyScopeResolver.class)
public class AppConfig {
    ...
}
  • JSR330的標(biāo)準(zhǔn)注解@Inject基本等價(jià)與@Autowired,只不過(guò)不支持required屬性,@Named基本等價(jià)于@Component+@Qualifier,但是因?yàn)锧Autowired的概念已經(jīng)比較深入,所以建議還是使用Spring 的注解吧。

5.12 基于Java(Java-based)的容器配置

5.12.1 基本概念: @Bean and @Configuration

Spring新的Java配置支持中核心構(gòu)件是 @Configuration 注釋類和 @Bean 注釋方法。

@Bean 注釋是用來(lái)表示一個(gè)方法實(shí)例化,配置和初始化一個(gè)由Spring IoC容器管理的新的對(duì)象。對(duì)于那些熟悉Spring的 <beans/> XML 配置, @Bean 注釋和 <bean/> 一樣起著同樣的作用。你可以使用 @Bean 注釋任何Spring @Component 的方法,但是, 最經(jīng)常使用的是 @Configuration(@Configuration就是一種被@Component標(biāo)示的注解) 注釋bean。

用 @Configuration 注釋一個(gè)類表明它的主要目的是作為bean定義的來(lái)源。此外, @Configuration 注釋的類允許inter-bean依賴關(guān)系 在相同的類中,通過(guò)簡(jiǎn)單地調(diào)用其他 @Bean 方法被定義。最簡(jiǎn)單的 @Configuration 類定義如下:

@Configuration
public class AppConfig {
    @Bean
    public MyService myService() {
        return new MyServiceImpl();
    }
}

在使用@Bean注解的時(shí)候也可以做到Bean的生命周期管理,如下:

public class Foo {
    public void init() {
        // initialization logic
    }
}

public class Bar {
    public void cleanup() {
        // destruction logic
    }
}

@Configuration
public class AppConfig {

    @Bean(initMethod = "init")
    @Scope("prototype")
    public Foo foo() {
        return new Foo();
    }

    @Bean(name = "testBean", destroyMethod = "cleanup")
    public Bar bar() {
        return new Bar();
    }
    
    //只有在使用@Configuration的時(shí)候,內(nèi)部@Bean才可以使用
    @Bean
    public Demo demo(){
        retrun new Demo(bar());
    }

}
  • @Import注解可以像xml中的import一樣使用,如下:
@Configuration
public class ConfigA {
     @Bean
    public A a() {
        return new A();
    }
}

@Configuration
@Import(ConfigA.class)
public class ConfigB {

    @Bean
    public B b() {
        return new B();
    }

}

我們?cè)贎Configuration生效的時(shí)候也可以進(jìn)行一定的定制,比如在測(cè)試環(huán)境生效,線上環(huán)境不生效這種操作,這時(shí)候可以依賴另外一個(gè)注解@Conditional,做法就是實(shí)現(xiàn)Conditinal,然后在@Conditional的注解上指定其實(shí)現(xiàn)類就可以做到有條件的激活。

5.13 Environment抽象

Environment 是集成在容器中的抽象,他包含了兩個(gè)兩個(gè)方面:profiles 和 properties.

profile是一個(gè)命名,是一組邏輯上bean定義的組,只有相應(yīng)的profile被激活的情況下才會(huì)起作用。可以通過(guò)XML或者注解將bean分配給一個(gè)profile,Environment對(duì)象在profile中的角色是判斷哪一個(gè)profile應(yīng)該在當(dāng)前激活和哪一個(gè)profile應(yīng)該在默認(rèn)情況下激活。

屬性在幾乎所有應(yīng)用中都扮演了非常重要的角色,并且可能來(lái)源于各種各樣的資源:屬性文件,JVM系統(tǒng)屬性,系統(tǒng)環(huán)境變量,JNDI,servlet上下文參數(shù),點(diǎn)對(duì)點(diǎn)的屬性對(duì)象,映射等等。Environment對(duì)象在屬性中的角色是提供一個(gè)方便的服務(wù)接口來(lái)配置屬性資源和解決它們的屬性。

5.13.1 Bean定義配置文件

Beand定義配置文件是核心容器的一種機(jī)制,它允許為不同的bean在不同的環(huán)境中注冊(cè)。 environment這個(gè)詞可以意味著不同的事情不同的用戶,而且這個(gè)功能可以幫助很多用例,包括:

在工作中使用內(nèi)存數(shù)據(jù)源并且在QA和生產(chǎn)環(huán)境中通過(guò)JDNI查找相同的數(shù)據(jù)源
只有當(dāng)部署應(yīng)用到一個(gè)性能測(cè)試環(huán)境時(shí)注冊(cè)監(jiān)視工具
給客戶A注冊(cè)定制的bean實(shí)現(xiàn)而不需要給客戶B時(shí)

@Profile 注解用于當(dāng)一個(gè)或多個(gè)配置文件激活的時(shí)候,用來(lái)指定組件是否有資格注冊(cè)。使用上面的例子,我們可以按如下方式重寫dataSource配置:

@Configuration
@Profile("dev")
public class StandaloneDataConfig {

    @Bean
    public DataSource dataSource() {
        return new EmbeddedDatabaseBuilder()
            .setType(EmbeddedDatabaseType.HSQL)
            .addScript("classpath:com/bank/config/sql/schema.sql")
            .addScript("classpath:com/bank/config/sql/test-data.sql")
            .build();
    }
}

如果帶@Configuration的類被標(biāo)記了@Profile,那么只有當(dāng)這個(gè)配置是激活狀態(tài)的時(shí)候,這個(gè)類中標(biāo)記@Bean的方法和@Import關(guān)聯(lián)的類才有效,否則就會(huì)被忽略。 如果一個(gè)@Component或@Configuration類標(biāo)記了@Profile({"p1", "p2"}),這樣的類只有當(dāng)p1和(或)p2激活的時(shí)候才有效。如果一個(gè)配置使用了!前綴,只有當(dāng)這個(gè)配置不激活的時(shí)候才有效。例如@Profile({"p1", "!p2"}),只有當(dāng)p1激活,p2不激活的時(shí)候才有效。

啟用配置文件

現(xiàn)在我們已經(jīng)更新了我們的配置,我們還需要指示那個(gè)配置文件處于激活狀態(tài)。如果我們現(xiàn)在啟動(dòng)我們的示例程序,我們會(huì)看到拋出NoSuchBeanDefinitionException異常,因?yàn)槲覀兊娜萜髡也坏矫麨閐ataSource的bean對(duì)象。

激活配置文件可以采取多種方式,但是最直接的方式就是以編程的方式使用ApplicationContext API:

AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.getEnvironment().setActiveProfiles("dev");
ctx.register(SomeConfig.class, StandaloneDataConfig.class, JndiDataConfig.class);
ctx.refresh();

配置文件還可以以聲明的方式通過(guò)spring.profiles.active屬性來(lái)激活,可以通過(guò)系統(tǒng)環(huán)境變量,JVM系統(tǒng)屬性。啟動(dòng)Tomcat的時(shí)候只要簡(jiǎn)單的加一條啟動(dòng)參數(shù)-Dspring.profiles.active="profile1,profile2"就可以激活目標(biāo)profile。另外需要注意一點(diǎn)的是,如果一個(gè)profile的name配置了“default”的話,是默認(rèn)激活的項(xiàng)。

5.15.2 Spring標(biāo)準(zhǔn)的和用戶自定義事件

Spring本身也是支持事件監(jiān)聽(tīng)機(jī)制的,其基本的事件和事件監(jiān)聽(tīng)器主要是ApplicationEvent和ApplicationListener接口,如果有ApplicationEvent發(fā)布的話,對(duì)應(yīng)的監(jiān)聽(tīng)器類就會(huì)觸發(fā)自己監(jiān)聽(tīng)操作,下面介紹一些Spring自己的事件。

  • ContextRefreshedEvent Spring容器的初始化或者refresh都會(huì)發(fā)布這個(gè)事件。
  • ContextStartedEvent Spring容器調(diào)用start方法的時(shí)候發(fā)布此事件
  • ContextStoppedEvent Spring容器調(diào)用stop方法的時(shí)候發(fā)布此事件
  • RequestHandledEvent 這個(gè)是一個(gè)特殊的web事件,每次有HTTP請(qǐng)求到達(dá)的時(shí)候都會(huì)發(fā)布這個(gè)事件,但是這個(gè)事件只有在請(qǐng)求結(jié)束之后才會(huì)發(fā)布

我們也可以定義自己的事件機(jī)制,下面簡(jiǎn)單介紹一下:

//定義事件
public class BlackListEvent extends ApplicationEvent {

    private final String address;
    private final String test;

    public BlackListEvent(Object source, String address, String test) {
        super(source);
        this.address = address;
        this.test = test;
    }
}

public class EmailService implements ApplicationEventPublisherAware {

    private List<String> blackList;
    //事件發(fā)布者
    private ApplicationEventPublisher publisher;

    public void setBlackList(List<String> blackList) {
        this.blackList = blackList;
    }

    public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
        this.publisher = publisher;
    }

    public void sendEmail(String address, String text) {
        if (blackList.contains(address)) {
            BlackListEvent event = new BlackListEvent(this, address, text);
            publisher.publishEvent(event);
            return;
        }
    }
}

//特殊事件監(jiān)聽(tīng)器(通過(guò)事件類型區(qū)分)
public class BlackListNotifier implements ApplicationListener<BlackListEvent> {

    private String notificationAddress;
    public void setNotificationAddress(String notificationAddress) {
        this.notificationAddress = notificationAddress;
    }

    public void onApplicationEvent(BlackListEvent event) {
        // notify appropriate parties via notificationAddress...
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,797評(píng)論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,179評(píng)論 3 414
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 175,628評(píng)論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 62,642評(píng)論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,444評(píng)論 6 405
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 54,948評(píng)論 1 321
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,040評(píng)論 3 440
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 42,185評(píng)論 0 287
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,717評(píng)論 1 333
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,602評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,794評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,316評(píng)論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,045評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 34,418評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 35,671評(píng)論 1 281
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,414評(píng)論 3 390
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,750評(píng)論 2 370

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