【Spring實戰(zhàn)】裝配Bean

本章內(nèi)容:

  • 聲明Bean
  • 構(gòu)造器注入和Setter方法注入
  • 裝配Bean
  • 控制bean的創(chuàng)建和銷毀

任何一個成功的應(yīng)用都是由多個為了實現(xiàn)某一業(yè)務(wù)目標(biāo)而相互協(xié)作的組件構(gòu)成的。這些組件必須彼此了解,并且相互協(xié)作來完成工作。

但是,正如在第一章中所看到的,創(chuàng)建應(yīng)用對象之間關(guān)聯(lián)關(guān)系的傳統(tǒng)方法通常會導(dǎo)致結(jié)構(gòu)嚴(yán)重復(fù)雜的代碼,這些代碼很難被復(fù)用也很難進(jìn)行單元測試。

在Spring中,對象無需查找或創(chuàng)建與其關(guān)聯(lián)的其他對象。相反的,容器負(fù)責(zé)把需要相互協(xié)作的對象引用賦予各個對象。

創(chuàng)建應(yīng)用對象之間協(xié)作關(guān)系的行為通常稱為裝配,這也是依賴注入的本質(zhì)。

Spring配置的可選方案

Spring容器負(fù)責(zé)創(chuàng)建應(yīng)用程序中的bean并通過DI來協(xié)調(diào)這些對象之間的關(guān)系。但是開發(fā)人員需要告訴Spring要創(chuàng)建哪些bean并且如何將其裝配在一起。Spring中裝配bean最常見的有三種機(jī)制:

  • 在XML中進(jìn)行顯示配置
  • 在Java中進(jìn)行顯示配置
  • 隱式的bean發(fā)現(xiàn)機(jī)制和自動裝配

最好盡可能地使用自動配置的機(jī)制。顯示配置越少越好。當(dāng)必須要顯示配置bean的時候,推薦使用類型安全并且比XML更加強(qiáng)大的JavaConfig。最后,只有當(dāng)想要使用便利的XML命名空間,并且在JavaConfig中沒有同樣的實現(xiàn)時,才應(yīng)該使用XML。

(自動配置 > 顯示Java配置 > 顯示XML配置)

自動化裝配bean

雖然顯示裝配技術(shù)非常有用,但是在便利性方面最強(qiáng)大的還是Spring的自動化配置。

Spring從兩個角度來實現(xiàn)自動化裝配:

  • 組件掃描: Spring會自動發(fā)現(xiàn)應(yīng)用上下文中所創(chuàng)建的bean。
  • 自動裝配:Spring自動滿足bean之間的依賴。

組件掃描和自動裝配組合在一起能夠?qū)@示配置降低到最少。

現(xiàn)在創(chuàng)建幾個bean,它們代表音響系統(tǒng)中的組件。

接下來為了闡述組件掃描和裝配,創(chuàng)建CompactDisc類,Spring會發(fā)現(xiàn)它并將其創(chuàng)建為一個bean。然后會創(chuàng)建一個CDPlayer類,讓Spring發(fā)現(xiàn)它,并將CompactDisc bean注入進(jìn)來。

創(chuàng)建可被發(fā)現(xiàn)的bean

首先在Java中建立CD的概念,定義CD的一個接口:

package soundsystem;

public interface CompactDisc {
    void play();
}

作為接口,它定義了CD播放器對一盤CD所能進(jìn)行的操作。

現(xiàn)在還需要一個CompactDisc的實現(xiàn):

//帶有@component注解的CompactDisc實現(xiàn)類

package soundsystem;
import org.springframework.stereotype.Component;

@Component
public class SgtPeppers implements CompactDisc {

  private String title = "Sgt. Pepper's Lonely Hearts Club Band";  
  private String artist = "The Beatles";
  
  public void play() {
    System.out.println("Playing " + title + " by " + artist);
  }
  
}

SgtPeppers類上使用了@Component注解。這個簡單的注解表明該類會作為組件類,并告知Spring要為這個類創(chuàng)建bean。沒有必要顯示配置SgtPeppers bean,因為這個類使用了@component注解。

組件掃描默認(rèn)是不啟用的,因此還需要顯示配置一下Spring,從而命令它去尋找?guī)в蠤Component注解的類,并為其創(chuàng)建bean:

package soundsystem;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan
public class CDPlayerConfig { 
}

類CDPlayerConfig通過Java代碼定義了Spring的裝配規(guī)則。CDPlayerConfig類并沒有顯示地聲明任何bean,但它使用了@ComponentScan注解,這個注解啟用了Spring的組件掃描。

如果沒有其他配置,@ComponentScan默認(rèn)會掃描與配置類相同的包。由于CDPlayerConfig類位于soundsystem包中,因此Spring會掃描這個包以及這個包下的所有子包,查找?guī)в蠤Component注解的類。這樣的話,就能發(fā)現(xiàn)CompactDisc并且會在Spring中自動創(chuàng)建一個bean。

也可以使用XML來啟用組件掃描,通過使用Spring Context命名空間的<context:component-scan>元素:

<?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"
  xmlns:c="http://www.springframework.org/schema/c"
  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
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

  <context:component-scan base-package="soundsystem" />

</beans>

為了測試組件掃描的功能,創(chuàng)建一個簡單的JUnit測試,它會創(chuàng)建Spring上下文,并判斷CompactDisc是不是真的創(chuàng)建出來了。

package soundsystem;

import static org.junit.Assert.*;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes=CDPlayerConfig.class)
public class CDPlayerTest {

@Autowired
  private CompactDisc cd;
  
  @Test
  public void cdShouldNotBeNull() {
    assertNotNull(cd);
  }
}

CDPlayerTest使用了Spring創(chuàng)建的SpringJUnit4ClassRunner,在測試開始的時候自動創(chuàng)建Spring的應(yīng)用上下文。注解@ContextConfiguration會告訴它需要在CDPlayerConfig中加載配置。因為CDPlayerConfig類中包含了@ComponentScan,因此最終的應(yīng)用上下文中應(yīng)該包含CompactDisc bean。

測試代碼中有一個CompactDisc類型的屬性。并且這個屬性帶有@Autowired注解,以便于將CompactDisc bean注入到測試代碼之中。最后用一個簡單的測試方法斷言cd屬性不為null。如果它不為null,意味著Spring能夠發(fā)現(xiàn)CompactDisc類,自動在Spring上下文中將其創(chuàng)建為bean并將其注入到測試代碼之中。

為組件掃描的bean命名

Spring應(yīng)用上下文中所有的bean都會給定一個ID。前面的例子沒有明確地為SgtPeppers bean設(shè)置ID,但Spring會根據(jù)類名為其指定一個ID。前面的bean被默認(rèn)給定的ID為sgtPeppers(類名的第一個字母變?yōu)樾懀?/p>

如果想為這個bean設(shè)置不同的ID,則需要把期望的ID作為值傳遞給@Component注解:

package soundsystem;
import org.springframework.stereotype.Component;

@Component("lonelyHeartsClub")
public class SgtPeppers implements CompactDisc {
    ...
}

還有另外一種為bean命名的方式,這種方式不使用@Component注解,使用Java依賴注入規(guī)范中提供的@Name注解來為bean設(shè)置ID:

package soundsystem;
import javax.inject.Named;

@Named("lonelyHeartsClub")
public class SgtPeppers implements CompactDisc {
    ...
}

Spring支持將@Named作為@Component注解的替代方案。在大多數(shù)場景中它們可以互相替換。

設(shè)置組件掃描的基礎(chǔ)包

目前為止,沒有給@ComponentScan設(shè)置任何屬性。意味著它會按照默認(rèn)規(guī)則,以配置類所在的包作為基礎(chǔ)包來掃描組件。如果配置類被放在單獨的包里與應(yīng)用代碼區(qū)分開來,想掃描不同的包,或者掃描多個包。為了指定不同的基礎(chǔ)包,需要在@ComponentScan的value屬性中指定包的名稱:

@Configuration
@ComponentScan("soundsystem")
public class CDPlayerConfig {}

@Configuration
@ComponentScan(basePackages = "soundsystem")
public class CDPlayerConfig {}

注意basePackages屬性是復(fù)數(shù)形式。意味著可以設(shè)置多個基礎(chǔ)包,只需要將basePackages屬性設(shè)置為要掃描包的一個數(shù)組即可:

@Configuration
@ComponentScan(basePackages = {"soundsystem","video"})
public class CDPlayerConfig {}

上面的例子里,基礎(chǔ)包是以String類型表示的。但這種方法是類型不安全的。如果要重構(gòu)代碼,所指定的基礎(chǔ)包就可能會出現(xiàn)錯誤。

除了將包設(shè)置為簡單的String類型之外,@ComponentScan還提供了另外一種辦法 ,將其指定為包中所包含的類或接口:

@Configuration
@ComponentScan(basePackageClasses = {CDPlayer.class,DVDPlayer.class})
public class CDPlayerConfig {}

basePacakges屬性被替換成了basePackageClasses。同時不再使用String類型的名稱來指定包,為basePackageClasses指定的數(shù)組中包含了類。這些類所在的包將會作為組件掃描的基礎(chǔ)包。

在樣例中,為basePackageClasses設(shè)置的是組件類,但也可以在包中創(chuàng)建一個用來掃描的空標(biāo)記接口。通過標(biāo)記接口的方式,能夠保持對重構(gòu)友好的接口引用,避免引用任何實際的應(yīng)用程序代碼。

如果應(yīng)用程序中所有對象都是獨立的,彼此之之間沒有任何依賴,那么只需要組件掃描即可。但很多對象會依賴其他的對象才能完成任務(wù)。這樣就需要一種方法將組件掃描得到的bean和它們的依賴裝配到一起。這就需要Spring自動化配置的另一方面內(nèi)容,自動裝配。

通過為bean添加注解實現(xiàn)自動裝配

自動裝配是讓Spring自動滿足bean依賴的一種方法。在滿足依賴的過程中,會在Spring應(yīng)用上下文中尋找匹配某個bean需求的其他bean。借助@Autowired注解聲明進(jìn)行自動裝配。

比如下面的CDPlayer類。它的構(gòu)造器添加了Autowired注解,表明當(dāng)Spring創(chuàng)建CDPlayer bean的時候,會通過這個構(gòu)造器來進(jìn)行實例化并且會傳入一個可設(shè)置給CompactDisc類型的bean:

package soundsystem;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class CDPlayer implements MediaPlayer {
  private CompactDisc cd;

  @Autowired
  public CDPlayer(CompactDisc cd) {
    this.cd = cd;
  }

  public void play() {
    cd.play();
  }

}

@Autowired注解不僅能用在構(gòu)造器上,還能用在屬性的Setter方法上。比如CDPlayer有一個SetCompactDisc()方法,可以采用如下的注解形式進(jìn)行自動裝配:

@Autowired
public void setCompaticDisc(CompaticDisc cd){
    this.cd = cd;
}

在Spring初始化bean之后,它會盡可能滿足bean的依賴,本例的依賴是通過@Autowired注解的方法進(jìn)行聲明的,也就是setCompacticDisc()。

實際上,@Autowired注解可以用在類的任何方法上,不管是構(gòu)造器、Setter方法還是其他方法,Spring都會嘗試滿足方法參數(shù)上所聲明的依賴。假如只有一個bean匹配依賴需求,則這個bean將會被裝配進(jìn)來;如果沒有匹配的bean,則在應(yīng)用上下文創(chuàng)建的時候,Spring會拋出一個異常。如果要避免異常的出現(xiàn),可以將@Autowired的required屬性設(shè)置為false。

@Autowired(required = false)
public void setCompaticDisc(CompaticDisc cd){
    this.cd = cd;
}

將required屬性設(shè)置為false時,Spring會嘗試執(zhí)行自動裝配,但是如果沒有匹配的bean的話,Spring會讓這個bean處于未裝配的狀態(tài)。但這樣如果在代碼中沒有進(jìn)行null檢查的話。未裝配狀態(tài)的屬性可能會引發(fā)NullPointerException。

如果有多個bean都滿足依賴關(guān)系的話,Spring會拋出一個異常,表明沒有明確指定要選擇哪個bean進(jìn)行自動裝配。自動裝配的歧義性會在下一章討論。

@Autowired是Spring特有的注解。如果不愿意在代碼中使用Spring的特定注解來完成自動裝配任務(wù),可以考慮將其替換為@Inject

package soundsystem;
import javax.inject.Inject;
import javax.inject.Named;

@Named
public class CDPlayer {
    ...

    Inject
    public CDPlayer(CompactDisc cd){
        this.cd = cd;
    }

    ...
}

@Inject注解來源于Java依賴注入規(guī)范,該規(guī)范同時還為我們定義了@Named注解。在大多數(shù)場景下,@Inject@Autowired是可以互相替換的。

驗證自動裝配

現(xiàn)在已經(jīng)在CDPlayer的構(gòu)造器中添加了@Autowired注解,Spring將把一個可分配給CompactDisc類型的bean自動注入進(jìn)來。修改CDPlayerTest,使其能借助CDPlayer bean播放CD:

package soundsystem;

import static org.junit.Assert.*;

import org.junit.Rule;
import org.junit.Test;
import org.junit.contrib.java.lang.system.StandardOutputStreamLog;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes=CDPlayerConfig.class)
public class CDPlayerTest {

  @Rule
  public final StandardOutputStreamLog log = new StandardOutputStreamLog();

  @Autowired
  private MediaPlayer player;
  
  @Autowired
  private CompactDisc cd;
  
  @Test
  public void cdShouldNotBeNull() {
    assertNotNull(cd);
  }

  @Test
  public void play() {
    player.play();
    assertEquals(
        "Playing Sgt. Pepper's Lonely Hearts Club Band by The Beatles\n", 
        log.getLog());
  }

}

現(xiàn)在除了注入CompactDisc外,還將CDPlayer bean注入到測試代碼的player成員變量中。在play()測試方法中,調(diào)用CDPlayer的play()方法,并斷言它的行為與預(yù)期一致。

在測試代碼中使用System.out.println()是有點棘手的事情。因此,在樣例中使用了StandardOutputStreamLog,這來源于System Rules庫的一個JUnit規(guī)則,該規(guī)則能夠基于控制臺的輸出編寫斷言。在這里斷言SgtPeppers.play()方法的輸出被發(fā)送到了控制臺上。

通過Java代碼裝配bean

盡管很多場景下通過組件掃描和自動裝配實現(xiàn)Spring的自動化配置是更為推薦的方式,但有時候自動化配置的方案行不通,因此需要明確配合Spring。比如,要將第三方庫中的組件裝配到你的應(yīng)用中,在這種情況下無法在它的類上添加@Component和@Autowired注解。因此無法使用自動化裝配方案。

在這種情況下必須要采用顯式裝配的方式。像之前說的,顯示配置時,JavaConfig是更好的方案,因為它更為強(qiáng)大、類型安全并且對重構(gòu)友好。

JavaConfig與其他的Java代碼有區(qū)別,在概念上,它與應(yīng)用程序中的業(yè)務(wù)邏輯和領(lǐng)域代碼是不同的。盡管它與其他的組件一樣都使用相同的語言進(jìn)行表述,但JavaConfig是配置代碼。這意味著它不應(yīng)包含任何業(yè)務(wù)邏輯。JavaConfig也不應(yīng)該侵入到業(yè)務(wù)邏輯代碼之中。通常會將JavaConfig放到單獨的包里與其他應(yīng)用程序邏輯分離開來。

創(chuàng)建配置類

創(chuàng)建JavaConfig類的關(guān)鍵在于為其添加@Configuration注解,@Configuration注解表明這個類是一個配置類,該類應(yīng)該包含在Spring應(yīng)用上下文如何創(chuàng)建bean的細(xì)節(jié)。為了更加關(guān)注于顯示配置,將CDPlayer的@ComponentScan注解移除掉。

package soundsystem;
import org.springframework.context.annotation.Configuration;

@Configuration
public class CDPlayerConfig { 
}
聲明簡單的bean

要在JavaConfig中聲明bean, 需要編寫一個方法,這個方法會創(chuàng)建所需類型的實例,然后給這個方法添加@Bean注解。比如下面的代碼聲明了CompaticDisc bean:

@Bean
public CompaticDisc sgtPeppers() {
  return new SgtPeppers();
}

@Bean注解會告訴Sring這個方法將返回一個對象,該對象要注冊為Spring應(yīng)用上下文中的bean。方法體中包含了最終產(chǎn)生bean實例的邏輯。

默認(rèn)情況下,bean的ID與帶有@Bean注解的方法名是一樣的。

如果想要為其設(shè)置成一個不同的名字,則可以重命名該方法,也可以通過name屬性指定一個不同的名字:

@Bean(name="lonelyHeartsClubBand")
public CompaticDisc sgtPeppers() {
  return new SgtPeppers();
}
借助JavaConfig實現(xiàn)注入

CDPlayer bean依賴于CompaticDisc,在JavaConfig中,要如何將它們裝配到一起?

JavaConfig中裝配bean最簡單的方式就是引用創(chuàng)建bean的方法(引用調(diào)用方法)。例如:

@Bean
public CDPlayer cdPlayer() {
    return new CDPlayer(sgtPeppers);
}

cdPlayer()方法像sgtPeppers()方法一樣,同樣使用了@Bean注解,這表明這個方法會創(chuàng)建一個bean實例并將其注冊到Spring應(yīng)用上下文中。創(chuàng)建的bean的ID為cdPlayer,與方法的名字相同。

cdPlayer()的方法體與sgtPeppers()有些區(qū)別,它并沒有使用默認(rèn)的構(gòu)造器創(chuàng)建實例,而是調(diào)用了需要傳入CompactDisc對象的構(gòu)造器來創(chuàng)建CDPlayer實例。

CompactDisc并非是調(diào)用sgtPeppers()得到的,由于sgtPeppers()方法上添加了@Bean注解,Spring將攔截所有對它的調(diào)用,并確保返回該方法所創(chuàng)建的bean,而不是每次都對其進(jìn)行實際的調(diào)用。

在默認(rèn)情況下,Spring的bean都是單例的,不需要為第二個CDPlayer bean創(chuàng)建完全相同的SgtPeppers實例。因此,**Spring會攔截對sgtPeppers()的調(diào)用并確保返回的是Spring所創(chuàng)建的bean。因此兩個CDPlayer bean會得到相同的SgtPeppers實例。

可以看到通過調(diào)用方法來引用bean的方式有點令人困惑。還有一種理解起來更為簡單的方式:

@Bean
Public CDPlayer cdPlayer(CompaticDisc comPaticDisc){
    return new CDPlayer(compactDisc);
} 

cdPlayer()方法請求一個CompaticDisc作為參數(shù)。當(dāng)Spring調(diào)用cdPlayer()創(chuàng)建CDPlayer bean的時候,會自動裝配一個Compact到配置方法中。然后,方法體按照合適的方式來使用它。借助這種技術(shù),cdPlayer()方法同樣也能將ComPaticDisc注入到CDPlayer的構(gòu)造器中,不用明確引用CompactDisc的@Bean方法。

使用這種方法引用其他的bean通常是最佳的選擇,因為它不要求將CompactDisc聲明到同一個配置類中。甚至不要求CompactDisc必須在JavaConfig中聲明。

前面使用CDPlayer的構(gòu)造器實現(xiàn)DI功能,同樣也可以采用其他風(fēng)格的DI配置。例如通過Setter方法注入CompactDisc:

@Bean
Public CDPlayer cdPlayer(CompaticDisc comPaticDisc){
    CDPlayer cdPlayer = new CDPlayer(compactDisc);
    cdPlayer.setCompactDisc(compactDisc)
    return cdPlayer;
} 

帶有@Bean注解的方法可以采用任何必要的Java功能來產(chǎn)生bean實例。構(gòu)造器和Setter方法只是@Bean方法的兩個簡單樣例。

通過XML裝配bean

XML是最初Spring剛出現(xiàn)的時候描述配置的主要方式,這種方式已經(jīng)不是最優(yōu)的或是唯一可選方案,本節(jié)的內(nèi)容主要是用來幫助維護(hù)已有的XML配置。

創(chuàng)建XML配置規(guī)范

在使用XML配置Spring裝配bean之前,需要創(chuàng)建一個新的配置規(guī)范(就像在JavaConfig的時候,要創(chuàng)建一個帶有@Configuration注解的類)。在XML配置中,要創(chuàng)建一個XML文件,并且要以<beans>元素為根

最簡單的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"
  xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context>

  ......

</beans>

需要在配置文件的頂部聲明多個XML模式(XSD)文件,這些文件定義了配置Spring的XML元素。

可借助Spring Tool Suite創(chuàng)建XML配置文件來簡化創(chuàng)建和管理Spring XML配置文件的步驟。

裝配bean的最基本的XML元素包含在spring-beans模式之中,在上面這個XML文件中,它被定義為根命名空間。<beans>是該模式中的一個元素,是所有Spring配置文件的根元素。

聲明一個簡單的<beans>

在基于XML的Spring配置中聲明一個bean,要使用spring-beans模式中的另外一個元素<bean>。可以按照如下的方式生命CompactDisc bean:

<bean class="soundsystem.SgtPeppers" />

聲明了一個很簡單的bean,創(chuàng)建這個bean的類通過class屬性來指定。

由于沒有明確給定ID,所以將會根據(jù)全限定類名來進(jìn)行命名。上面的bean的ID將會是"soundsystem.Sgtpeppers#0"。“#0”是一個計數(shù)的形式,用來區(qū)分相同類型的其他bean。這樣的ID命名方式對稍后的引用會很不友好。因此通常要借助id屬性為每個bean設(shè)置一個自己選擇的名字:

<bean id="cdPlayer" class="soundsystem.CDPlayer" />

減少繁瑣
為了減少XML中配置的繁瑣,只對需要按名字引用的bean進(jìn)行明確地命名。

簡單bean聲明的特征
  1. 不再需要直接負(fù)責(zé)創(chuàng)建SgtPeppers的實例

在基于JavaConfig的配置中,需要直接負(fù)責(zé)創(chuàng)建SgtPeppers的實例。當(dāng)Spring發(fā)現(xiàn)這個<bean>元素時,會調(diào)用SgtPeppers的默認(rèn)構(gòu)造器來創(chuàng)建bean。這樣bean的創(chuàng)建顯得更加的被動。

  1. bean的類型以字符串的形式設(shè)置在了class屬性中,如果重命名那個類,XML配置文件也要修改。
借助構(gòu)造器注入初始化bean

Spring XML配置中只有一種聲明bean的方式。但是在XML中聲明DI時,有多重可選的配置方案和風(fēng)格。具體到構(gòu)造器注入,有兩種基本的配置方案可供選擇:

  • <constructor-arg>元素
  • 使用Spring 3.0引入的 c-命名空間

二者的區(qū)別在于是否冗長繁瑣。<constructor-arg>元素比使用 c-命名空間會更加冗長,從而導(dǎo)致XML難以讀懂。有些事情<constructor-arg>可以做到,但 c-命名空間卻無法實現(xiàn)。

構(gòu)造器注入bean引用

現(xiàn)在已經(jīng)聲明了SgtPeppers bean,且SgtPepepr類實現(xiàn)了CompactDisc接口,所以已經(jīng)有了一個可以注入到CDPlayer bean中的bean。在XML中聲明CDPlayer并通過ID引用SgtPeppers即可:

<bean id="cdPlayer" class="soundsystem.CDPlayer">
    <constructor-arg ref="compactDisc" />
</bean>

當(dāng)Spring遇到這個<bean>元素時,會創(chuàng)建一個CDPlayer實例。<constructor-arg>元素會告知Spring要將ID為compactDisc的bean引用傳遞到CDPlayer的構(gòu)造器中。


作為替代方案,也可以使用Spring的 c-命名空間。它是在XML中更為簡潔地描述構(gòu)造器參數(shù)的方式。要使用它必須要在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:p="http://www.springframework.org/schema/p"
  xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

  ...

</beans>

聲明完 c-命名空間和模式之后,就可以使用它來聲明構(gòu)造器參數(shù)了:

  <bean id="cdPlayer" class="soundsystem.CDPlayer"
        c:cd-ref="compactDisc" />

c-命名空間作為<bean>元素的一個屬性。下圖描述了這個屬性名是如何組成的。

通過Spring的 c-命名空間將bean引用注入到構(gòu)造器參數(shù)中

屬性名以“c:”開頭,也就是命名空間的前綴。接下來是要裝配的構(gòu)造器參數(shù)名,在此之后是“-ref”,這是一個命名約定,它會告訴Spring,正在裝配的是一個bean的引用,這個bean的名字是compactDisc,而不是字面量“compactDisc”。

c-命名空間直接使用了構(gòu)造器參數(shù)的名稱。引用參數(shù)的名字這需要在編譯代碼的時候,將調(diào)試標(biāo)志保存在類代碼中。如果優(yōu)化構(gòu)建過程將調(diào)試標(biāo)志移除掉,這種方式就可能無法正常執(zhí)行。

替代的方案是使用參數(shù)在整個參數(shù)列表中的位置信息:

<bean id="cdPlayer" class="soundsystem.CDPlayer"
        c:_0-ref="compactDisc" />

將參數(shù)名稱替換成了“0”,即參數(shù)的索引。由于在XML中不允許數(shù)字作為屬性的第一個字符,要添加下畫線作為前綴

使用索引識別構(gòu)造器參數(shù)會比使用名字好一些,這樣即便在構(gòu)建的時候移除掉了調(diào)試標(biāo)志,參數(shù)依然會保持相同的順序。當(dāng)只有一個構(gòu)造器參數(shù)的時候,可以不標(biāo)示參數(shù):

<bean id="cdPlayer" class="soundsystem.CDPlayer"
    c:_-ref="compactDisc" />

現(xiàn)在已經(jīng)將引用裝配到了其他的bean中,接下來討論將字面量值裝配到構(gòu)造器之中。

將字面量注入到構(gòu)造器

到目前所做的DI通常指的是類型的裝配——將對象的引用裝配到依賴于它們的其他對象之中。而有時候,需要做的只是一個字面量值來配置對象。假設(shè)要創(chuàng)建一個CompactDisc的新實現(xiàn):

package soundsystem;

public class BlankDisc implements CompactDisc {

  private String title;
  private String artist;

  public BlankDisc(String title, String artist) {
    this.title = title;
    this.artist = artist;
  }

  public void play() {
    System.out.println("Playing " + title + " by " + artist);
  }

}

將已有的SgtPeppers替換為這個類:

<bean id="compactDisc" class="soundsystem.BlankDisc">
  <constructor-arg value="Sgt. Pepper's Lonely Hearts Club Band" />
  <constructor-arg value="The Beatles" />
</bean>

再次使用<constructor-arg>元素進(jìn)行構(gòu)造器參數(shù)的注入。沒有使用ref屬性引用其他的bean,這次使用value屬性,通過該屬性表明給定的值要以字面量的形式注入到構(gòu)造器之中。

如果使用c-命名空間,引用構(gòu)造器參數(shù)的名字:

<bean id="compactDisc" class="soundsystem.BlankDisc"
      c:_title="Sgt. Pepper's Lonely Hearts Club Band" 
      c:_artist="The Beatles" />

裝配自變量與裝配引用的區(qū)別在于屬性名去掉了-ref后綴。類似的,可以通過參數(shù)索引裝配字面量值:

<bean id="compactDisc" class="soundsystem.BlankDisc"
      c:_0="Sgt. Pepper's Lonely Hearts Club Band" 
      c:_1="The Beatles" />

如果構(gòu)造器參數(shù)只有一個,同樣可以簡單的使用下畫線進(jìn)行標(biāo)示,如:

<bean id="compactDisc" class="soundsystem.BlankDisc"
      c:_="Sgt. Pepper's Lonely Hearts Club Band" 
裝配集合

有一種情況是<constructor-arg>能夠?qū)崿F(xiàn),c命名空間卻無法做到的。

真正的CD上面所承載的信息很多,大多數(shù)CD會包含十多個磁道,每個磁道包含了一首歌。

如果使用CompactDisc為真正的CD建模,它也應(yīng)該有磁道列表的概念:

package soundsystem.collections;

import java.util.List;

import soundsystem.CompactDisc;

public class BlankDisc implements CompactDisc {

  private String title;
  private String artist;
  private List<String> tracks;

  public BlankDisc(String title, String artist, List<String> tracks) {
    this.title = title;
    this.artist = artist;
    this.tracks = tracks;
  }

  public void play() {
    System.out.println("Playing " + title + " by " + artist);
    for (String track : tracks) {
      System.out.println("-Track: " + track);
    }
  }

}

這個變更使得在聲明bean的時候,必須要提供一個磁道列表。

不理想的方案是將列表設(shè)置為null:

<bean id="compactDisc" class="soundsystem.BlankDisc">
  <constructor-arg value="Sgt. Pepper's Lonely Hearts Club Band" />
  <constructor-arg value="The Beatles" />
  <constructor-arg><null/></constructor-arg>
</bean>

注入期能夠正常執(zhí)行,當(dāng)調(diào)用play()方法時,會遇到NullPointerException異常。

更好的解決辦法是提供一個磁道名稱的列表。有多個可選的解決方案,其中之一,可以使用<list>元素將其聲明為一個列表:

<bean id="compactDisc"
      class="soundsystem.properties.BlankDisc">
  <constructor-arg value="Sgt. Pepper's Lonely Hearts Club Band" />
  <constructor-arg name="artist" value="The Beatles" />
  <constructor-arg name="tracks">
    <list>
      <value>Sgt. Pepper's Lonely Hearts Club Band</value>
      <value>With a Little Help from My Friends</value>
      <value>Lucy in the Sky with Diamonds</value>
      <value>Getting Better</value>
      <value>Fixing a Hole</value>
      <value>She's Leaving Home</value>
      <value>Being for the Benefit of Mr. Kite!</value>
      <!-- ...others... -->

    </list>
  </property>
</bean>

其中<list>元素時<constructor-arg>的子元素,這表明一個包含值得列表會被傳遞到構(gòu)造器中。<value>元素用來指定列表中的每個元素。

類似的,也可以使用ref元素替代<value>,實現(xiàn)bean引用列表的裝配。

例如有一個Discography類,它的構(gòu)造器如下所示:

public Discography(String artist, List<CompactDisc> cds){ ... }

則可以采用如下的方式配置Discography bean:

<bean id="beatlesDiscography" class="soundsystem.Discography">
  <constructor-arg value="The Beatles" />
  <constructor-arg>
    <List>
      <ref bean="sgtPeppers" />
      <ref bean="whiteAlbum" />
      <ref bean="hardDaysNight" />
      <ref bean="revolver" />
      <!-- ...others... -->
    <List>
  </constructor-arg>
</bean>

當(dāng)構(gòu)造器參數(shù)的類型是 java.util.List時,使用<List>元素合情合理。但也可以按照同樣的方式使用<set>元素:

<bean id="compactDisc" class="soundsystem.BlankDisc">
  <constructor-arg value="Sgt.Pepper's Lonely Hearts Club Band" />
  <constructor-arg value="The Beatles" />
  <constructor-arg>
    <Set>
      <value>Sgt. Pepper's Lonely Hearts Club Band</value>
      <value>With a Little Help from My Friends</value>
      <value>Lucy in the Sky with Diamonds</value>
      <value>Getting Better</value>
      <!-- ...others... -->
    <List>
  </constructor-arg>
</bean>

<set>和<list>元素的區(qū)別不大,最重要的不同在于當(dāng)Spring創(chuàng)建要裝配的集合時,所創(chuàng)建的是java.util.Set還是java.util.List。如果是Set的話,重復(fù)值會被忽略,存放的順序也不會得以保證。無論哪種情況下,<Set>或<List>都可以用來裝配List、Set甚至數(shù)組。

裝配集合方面,<constructor-arg>比 c-命名空間的屬性更有優(yōu)勢。目前,使用 c-命名空間的屬性無法實現(xiàn)裝配集合的功能。

設(shè)置屬性

到目前為止,CDPlayer和BlankDisc類完全是通過構(gòu)造器注入的,沒有使用屬性的Setter方法。接下來將討論如何使用Spring XML實現(xiàn)屬性注入。假設(shè)屬性注入的CDPlayer如下所示:

package soundsystem.properties;
import org.springframework.beans.factory.annotation.Autowired;

import soundsystem.CompactDisc;
import soundsystem.MediaPlayer;

public class CDPlayer implements MediaPlayer {
  private CompactDisc compactDisc;

  @Autowired
  public void setCompactDisc(CompactDisc compactDisc) {
    this.compactDisc = compactDisc;
  }

  public void play() {
    compactDisc.play();
  }

}

對于該選擇構(gòu)造器注入還是屬性注入。作為通用的規(guī)則,建議對強(qiáng)依賴使用構(gòu)造器注入,對可選性的依賴使用屬性注入。

現(xiàn)在CDPlayer沒有任何的構(gòu)造器。也沒有任何的強(qiáng)依賴。因此可以采用如下的方式將其聲明為Spring bean:

<bean id="cdPlayer" class="soundsystem.CDPlayer" />

Spring在創(chuàng)建bean時不會有問題,但在測試時會因為NullPointerException導(dǎo)致測試失敗,因為沒有注入CDPlayer的compactDisc屬性。按照如下方式修改XML:

<bean id="cdPlayer" class="soundsystem.CDPlayer">
  <property name="compactDisc" ref="compactDisc">
</bean>

<property>元素為屬性的Setter方法所提供的功能等同于<constructor-arg>元素為構(gòu)造器所提供的功能。本例中,它引用了ID為compactDisc的bean,并將其注入到compactDisc屬性中(通過setCompactDisc()方法)。

Spring提供了更加簡潔的p命名空間作為<property>元素的替代方案。為了啟用 p-命名空間,必須要在XML文件中與其他的命名空間一起對其進(jìn)行生命:

<?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: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>

使用p-命名空間:

<bean id="cdPlayer"
      class="soundsystem.properties.CDPlayer"
      p:compactDisc-ref="compactDisc" />

p-命名空間中屬性所遵循的命名約定與c-命名空間類似。屬性名組成如下圖所示:

借助Spring的 p-命名空間,將bean引用注入到屬性中
將字面量注入到屬性中

構(gòu)建一個新的完全通過屬性注入進(jìn)行配置的BlankDisc類:

package soundsystem.properties;

import java.util.List;

import soundsystem.CompactDisc;

public class BlankDisc implements CompactDisc {

  private String title;
  private String artist;
  private List<String> tracks;

  public void setTitle(String title) {
    this.title = title;
  }

  public void setArtist(String artist) {
    this.artist = artist;
  }

  public void setTracks(List<String> tracks) {
    this.tracks = tracks;
  }

  public void play() {
    System.out.println("Playing " + title + " by " + artist);
    for (String track : tracks) {
      System.out.println("-Track: " + track);
    }
  }

}

可以借助<property>元素的value屬性裝配屬性:

  <bean id="compactDisc"
        class="soundsystem.properties.BlankDisc">
    <property name="title" value="Sgt. Pepper's Lonely Hearts Club Band" />
    <property name="artist" value="The Beatles" />
    <property name="tracks">
      <list>
        <value>Sgt. Pepper's Lonely Hearts Club Band</value>
        <value>With a Little Help from My Friends</value>
        <value>Lucy in the Sky with Diamonds</value>
        <value>Getting Better</value>
        <!-- ...others... -->
      </list>
    </property>
  </bean>

這里除了使用<property>元素的value屬性來設(shè)置title和artist,還使用了內(nèi)嵌的<lists>元素來設(shè)置tracks屬性。

另一種可選方案是使用p命名空間的屬性來完成該功能:

  <bean id="compactDisc"
        class="soundsystem.properties.BlankDisc"
        p:title="Sgt. Pepper's Lonely Hearts Club Band"
        p:artist="The Beatles">
    <property name="tracks">
      <list>
        <value>Sgt. Pepper's Lonely Hearts Club Band</value>
        <value>With a Little Help from My Friends</value>
        <value>Lucy in the Sky with Diamonds</value>
        <value>Getting Better</value>
        <!-- ...other tracks... -->
      </list>
    </property>
  </bean>

沒有便利的方式使用 p-命名空間來指定一個值的列表。但是,可以使用Spring util-命名空間中的一些功能來簡化BlankDisc bean。

首先,在XML中聲明util-命名空間及其模式:

<?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:p="http://www.springframework.org/schema/p"
  xmlns:util="http://www.springframework.org/schema/util"
  xsi:schemaLocation="http://www.springframework.org/schema/beans 
    http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/util 
    http://www.springframework.org/schema/util/spring-util.xsd">
  ...
</beans>

util-命名空間提供的功能之一就是<util:list>元素,它會創(chuàng)建一個列表的bean。借助<util:list>,可以將磁道列表轉(zhuǎn)移到BlankDisc bean之外。并將其聲明到單獨的bean中:

<util:list id="trackList">  
  <value>Sgt. Pepper's Lonely Hearts Club Band</value>
  <value>With a Little Help from My Friends</value>
  <value>Lucy in the Sky with Diamonds</value>
  <value>Getting Better</value>
  <!-- ...other tracks... -->
</util:list>

現(xiàn)在能夠像使用其他的bean那樣,將磁道列表bean注入到BlankDisc bean的tracks屬性中:

  <bean id="compactDisc"
        class="soundsystem.properties.BlankDisc"
        p:title="Sgt. Pepper's Lonely Hearts Club Band"
        p:artist="The Beatles"
        p:tracks-ref="trackList" />

<util:list>只是util-命名空間中的多個元素之一。下表列出了 util-命名空間提供的所有元素:

元素 描述
<util:constant> 引用某個類型的public static域,并將其暴露為bean
util:list 創(chuàng)建一個java.util.List類型的bean,其中包含值或引用
util:map 創(chuàng)建一個java.util.Map類型的bean,其中包含值或引用
util:properties 創(chuàng)建一個java.util.Properties類型的bean
util:property-path 引用一個bean的屬性(或內(nèi)嵌屬性),并將其暴露為bean
util:set 創(chuàng)建一個java.util.Set,其中包含值或引用

導(dǎo)入和混合配置

Spring中,這些配置方案都不是互斥的。盡可以將JavaConfig的組建掃描和自動裝配和XML配置混合在一起。

混合配置第一件需要了解的事情就是在自動裝配時,自動裝配會將Spring容器中所有的bean都考慮到,不管是在JavaConfig或XML聲明的還是通過組建掃描獲取到的。

在JavaConfig中引用XMl配置

假設(shè)CDPlayerConfig已經(jīng)變得復(fù)雜,想要將其進(jìn)行拆分。實現(xiàn)的第一種方案就是將SgtPeppers從CDPlayerConfig拆分出來,定義到它自己的CDConfig中:

package soundsystem;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class CDConfig {
  
  @Bean
  public CompactDisc compactDisc() {
    return new SgtPeppers();
  }
}

compactDisc()方法已經(jīng)從CDPlayerConfig中移除掉了,需要有一種方式將這兩個類組合在一起。一種方法是在CDPlayerConfig中使用@Import注解導(dǎo)入CDConfig:

package soundsystem;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

@Configuration
@Import(CDPlayerConfig.class)
public class CDPlayerConfig{
  
  @Bean
  public CDPlayer cdPlayer(CompactDisc compactDisc) {
    return new CDPlayer(compactDisc);
  }
}

或者采用一個更好的辦法,也就是不在CDPlayerConfig中使用@Import,創(chuàng)建一個更高級別的SoundSystemConfig,在這個類中使用@Import將兩個配置組合在一起:

package soundsystem;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
@Configuration
@Import({CDPlayerConfig.class,CDConfig.class})
public class SoundSystemConfig {
}

不管使用哪種方法,都將CDPlayer的配置與SgtPeppers的配置分開了。現(xiàn)在假設(shè)希望通過XML來配置BlankDisc,如下所示:

  <bean id="compactDisc" class="soundsystem.BlankDisc"
        c:_0="Sgt. Pepper's Lonely Hearts Club Band" 
        c:_1="The Beatles" />
    <constructor-arg>
      <list>
        <value>Sgt. Pepper's Lonely Hearts Club Band</value>
        <value>With a Little Help from My Friends</value>
        <value>Lucy in the Sky with Diamonds</value>
        <value>Getting Better</value>
        <!-- ...other tracks... -->
      </list>
    </constructor-arg>
  </bean>

現(xiàn)在BlankDisc配置在了XML中,如何讓Spring同時加載它和其他基于Java的配置?

答案是@ImportResource注解,假設(shè)BlankDisc定義在名為cd-config.xml的文件中,該文件位于根類路徑下,那么可以修改SoundSystemConfig,讓它使用@ImportResource注解,如下所示:

package soundsystem;

import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.ImportResource;

@Configuration
@Import(CDPlayerConfig.class)
@ImportResource("classpath:cd-config.xml")
public class SoundSystemConfig{
}

兩個bean——配置在JavaConfig中的CDPlayer以及配置在XML中的BlankDisc都會被加載到Spring容器之中。因為CDPlayer中帶有@Bean注解的方法接受一個CompactDisc作為參數(shù),因此BlankDisc將會被裝配進(jìn)來。

在XMl配置中引用JavaConfig

假設(shè)正在使用Spring基于XMl的配置并且XML注解變得復(fù)雜。

在XML中,可以使用<import>元素來拆分XML配置。

假設(shè)將BlankDisc bean拆分到自己的配置文件中,該文件名為cd-config.xml。可以在XML配置文件中使用<import>元素來引用該文件:

<?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:c="http://www.springframework.org/schema/c"
  xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
  <import resource="cd-config.xml">
  <bean id="cdPlayer" class="soundsystem.CDPlayer" c:cd-ref="compactDisc" />
</beans>

現(xiàn)在假設(shè)BlankDisc配置不在XML中,而是配置在JavaConfig中。

<import>元素只能導(dǎo)入其他的XML配置文件,并沒有XML元素可以導(dǎo)入JavaConfig類。

但有一個元素能夠用來將Java配置導(dǎo)入到XML配置中——<bean>元素:

<?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:c="http://www.springframework.org/schema/c"
  xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

  <bean class="soundsystem.CDConfig">
  <bean id="cdPlayer" class="soundsystem.CDPlayer" c:cd-ref="compactDisc" />
</beans>

采用這樣的方式,兩種配置被組合在了一起。類似地,還可以創(chuàng)建一個更高層次的配置文件,這個文件不聲明任何bean,只負(fù)責(zé)將兩個或更多的配置組合起來。

例如,可以將CDConfig bean從之前的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:c="http://www.springframework.org/schema/c"
  xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

  <bean class="soundsystem.CDConfig">
  <import resource="cdplayer-config.xml" />
</beans>

不管使用JavaXML還是XML進(jìn)行配置,通常都會創(chuàng)建一個根配置,這個配置會將兩個或更多的文件組合起來。也會在根配置中啟用組件掃描(通過<context:component-scan>或@componentScan)。

小結(jié)

Spring框架的核心是Spring容器。容器負(fù)責(zé)管理應(yīng)用中組件的生命周期與依賴關(guān)系。

建議盡可能使用自動化配置,避免顯示配置帶來的維護(hù)成本。如果需要顯示配置Spring,應(yīng)該優(yōu)先選擇基于Java的配置。

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

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