【深入淺出Spring原理及實(shí)戰(zhàn)】「源碼原理實(shí)戰(zhàn)」從底層角度去分析研究PropertySourcesPlaceholderConfigurer的原理及實(shí)戰(zhàn)注入機(jī)制

Spring提供配置解析功能

主要有一下xml文件占位符解析和Java的屬性@Value的占位符解析配置這兩種場(chǎng)景進(jìn)行分析和實(shí)現(xiàn)解析阱洪,如下面兩種案例便贵。

xml文件的占位符解析配置

<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"  init-method="init" destroy-method="close">
    <property name="url" value="${jdbc.url}"/>
    <property name="username" value="${jdbc.username}"/>
    <property name="password" value="${jdbc.password}"/>
</bean>

Java的屬性@Value的占位符解析配置

@Value 注解值進(jìn)行屬性占位符解析和替換

@Value("${config}")
private String config;

PropertyPlaceholderConfigurer和PropertySourcesPlaceholderConfigurer

通過配置xml來實(shí)現(xiàn)對(duì)Classpath下的配置文件的占位符的屬性進(jìn)行注入,或者實(shí)現(xiàn)Java的屬性@Value的占位符解析配置冗荸。

  • 在Spring3.1版本之前是通過PropertyPlaceholderConfigurer實(shí)現(xiàn)的承璃。
  • 在Spring3.1之后則是通過PropertySourcesPlaceholderConfigurer實(shí)現(xiàn)的。

注意:在Spring Context 3.1或者更高版本中蚌本,缺省使用PropertySourcesPlaceholderConfigurer工具替換了PlaceholderConfigurerSupport盔粹,而<=3.0較老的Spring Context中,為了保持和之前的版本兼容魂毁,缺省還是使用PropertyPlaceholderConfigurer玻佩。

PropertyPlaceholderConfigurer和PropertySourcesPlaceholderConfigurer的實(shí)現(xiàn)分析

  • PropertyPlaceholderConfigurer本質(zhì)是基于PlaceholderConfigurerSupport實(shí)現(xiàn)讀取配置的。
  • PropertySourcesPlaceholderConfigurerPlaceholderConfigurerSupport的特殊化實(shí)現(xiàn)席楚。

下圖介紹對(duì)應(yīng)的配置解析的繼承關(guān)系圖譜咬崔。


image

PropertyPlaceholderConfigurer和PropertySourcesPlaceholderConfigurer的執(zhí)行目標(biāo)

PropertyPlaceholderConfigurerPropertyPlaceholderConfigurer在使用上并無本質(zhì)的區(qū)別,兩者的根本目標(biāo)是將配置文件生成KV對(duì),真正的注入工作并不由它們本身執(zhí)行垮斯。

PropertySourcesPlaceholderConfigurer它用于解析bean定義中的屬性值郎仆,以及注解@Value的值,使用的屬性來源是當(dāng)前的Spring Environment對(duì)象兜蠕,以及設(shè)置給自己的PropertySources對(duì)象扰肌。

Spring Boot 自動(dòng)配置類 PropertyPlaceholderAutoConfiguration
@Configuration
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
public class PropertyPlaceholderAutoConfiguration {
    @Bean
    @ConditionalOnMissingBean(search = SearchStrategy.CURRENT)
    public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
        return new PropertySourcesPlaceholderConfigurer();
    }
}

PropertyPlaceholderAutoConfiguration定義一個(gè)PropertySourcesPlaceholderConfigurer bean,該bean作為一個(gè)BeanFactoryPostProcessor熊杨,會(huì)在容器啟動(dòng)時(shí)容器后置處理階段執(zhí)行自己的任務(wù)曙旭。BeanFactoryPostProcessor的優(yōu)先級(jí)又優(yōu)于其余的Bean。因此可以實(shí)現(xiàn)在bean初始化之前的注入晶府。

postProcessBeanFactory方法的執(zhí)行

如果外部指定了this.propertySources, 則直接使用它桂躏,否則從當(dāng)前Spring的Environment 對(duì)象和自身的 #mergeProperties 方法調(diào)用返回的 Properties 對(duì)象構(gòu)建屬性源對(duì)象 this.propertySources

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        if (this.propertySources == null) {
            this.propertySources = new MutablePropertySources();
            if (this.environment != null) {
                this.propertySources.addLast(
                    new PropertySource<Environment>(ENVIRONMENT_PROPERTIES_PROPERTY_SOURCE_NAME, 
                        this.environment) {
                        @Override
                        @Nullable
                        public String getProperty(String key) {
                            return this.source.getProperty(key);
                        }
                    }
                );
            }
            try {
                PropertySource<?> localPropertySource =
                        new PropertiesPropertySource(LOCAL_PROPERTIES_PROPERTY_SOURCE_NAME, mergeProperties());
                if (this.localOverride) {
                    this.propertySources.addFirst(localPropertySource);
                }
                else {
                    this.propertySources.addLast(localPropertySource);
                }
            }
            catch (IOException ex) {
                throw new BeanInitializationException("Could not load properties", ex);
            }
        }
        processProperties(beanFactory, new PropertySourcesPropertyResolver(this.propertySources));
        this.appliedPropertySources = this.propertySources;
    }

構(gòu)造一個(gè)基于特定屬性源 this.propertySources 對(duì)屬性值進(jìn)行解析的屬性值解析器PropertySourcesPropertyResolver, 對(duì)容器中所有的 bean 定義中的屬性值,構(gòu)造函數(shù)參數(shù)值川陆。

    /**
     * Visit each bean definition in the given bean factory and attempt to replace ${...} property
     * placeholders with values from the given properties.
     */
    protected void processProperties(ConfigurableListableBeanFactory beanFactoryToProcess,
            final ConfigurablePropertyResolver propertyResolver) throws BeansException {
       // 設(shè)置屬性值解析器所使用的占位符格式參數(shù)剂习,缺省為:
       // 占位符前綴 ${
        propertyResolver.setPlaceholderPrefix(this.placeholderPrefix);
       // 占位符后綴 }
        propertyResolver.setPlaceholderSuffix(this.placeholderSuffix);
       // 缺省值分隔符 :
        propertyResolver.setValueSeparator(this.valueSeparator);
       // 結(jié)合屬性 this. ignoreUnresolvablePlaceholders對(duì)propertyResolver 作進(jìn)一步封裝,
       // 封裝出來一個(gè) StringValueResolver valueResolver,這是最終要應(yīng)用的屬性值解析器
        StringValueResolver valueResolver = strVal -> {
            String resolved = (this.ignoreUnresolvablePlaceholders ?
                    propertyResolver.resolvePlaceholders(strVal) :
                    propertyResolver.resolveRequiredPlaceholders(strVal));
            if (this.trimValues) {
                resolved = resolved.trim();
            }
            return (resolved.equals(this.nullValue) ? null : resolved);
        };
       // 調(diào)用基類PlaceholderConfigurerSupport實(shí)現(xiàn)的對(duì)容器中所有 bean定義進(jìn)行遍歷處理屬性值中占位符解析的邏輯
        doProcessProperties(beanFactoryToProcess, valueResolver);
    }

doProcessProperties的方法目的是為了添加解析器StringValueResolver

    protected void doProcessProperties(ConfigurableListableBeanFactory beanFactoryToProcess,
            StringValueResolver valueResolver) {
        // ignore
        ....

        // New in Spring 2.5: resolve placeholders in alias target names and aliases as well.
        beanFactoryToProcess.resolveAliases(valueResolver);

        // New in Spring 3.0: resolve placeholders in embedded values such as annotation attributes.
        beanFactoryToProcess.addEmbeddedValueResolver(valueResolver);
    }

這里的ddEmbeddedValueResolver(StringValueResolver) 是為一個(gè) LinkedList添加值较沪。在取用的時(shí)候是優(yōu)先從鏈表頭開始取用的鳞绕。 一旦發(fā)現(xiàn)無法找到值,直接就拋異常了尸曼。這個(gè)就對(duì)外體現(xiàn)出 PropertySourcesPlaceholderConfigurer 的唯一性们何。

然而Spring內(nèi)部還是有多個(gè)PropertySourcesPlaceholderConfigurer, 只不過除了排列在隊(duì)首的 PropertySourcesPlaceholderConfigurer之外全都被忽略掉了骡苞。

PropertySourcesPlaceholderConfigurer屬性注入的原理

AbstractApplicationContext#obtainFreshBeanFactory

Spring框架進(jìn)行植入元素注入時(shí)機(jī)

針對(duì)于元素的注入依賴于
AutowiredAnnotationBeanPostProcessor#postProcessPropertyValues1垂蜗。

AbstractApplicationContext#finishBeanFactoryInitialization方法

在Spring初始化流程中,執(zhí)行AbstractApplicationContext#finishBeanFactoryInitialization方法解幽。 該方法里面發(fā)生的主要流程為Spring業(yè)務(wù)Bean初始化。 實(shí)際流程跟Spring Bean的初始化沒有任務(wù)區(qū)別烘苹。

InstantiationAwareBeanPostProcessor
  • 通過對(duì)接口 InstantiationAwareBeanPostProcessor 實(shí)現(xiàn)類的方法進(jìn)行執(zhí)行躲株。 僅此而已。
AutowiredAnnotationBeanPostProcessor
  • InjectionMetadataInjectionMetadataInjectedElementInjectedElement這個(gè)類是 InstantiationAwareBeanPostProcessor的一個(gè)實(shí)現(xiàn)類镣衡。
@Value和@Autowired注解實(shí)際執(zhí)行
  1. 用于@Value和@Autowired注解實(shí)際執(zhí)行方法postProcessPropertyValues調(diào)度實(shí)際調(diào)度InjectedElement子類被注入值的獲取來自于DefaultListableBeanFactory將對(duì)應(yīng)@Value(“${configValue}”)里面的值替換的來源值霜定,是PropertySourcesPlaceholderConfigurer生成的StringValueResolver。

  2. Spring原生的Bean是單例的它直接被儲(chǔ)存在了AbstractBeanFactory執(zhí)行Field.set(Object, Object)或者M(jìn)ethod.invoke(Object, Object[])廊鸥。

所以望浩,可以看出 PropertySourcesPlaceholderConfigurer 或者 PropertyPlaceholderConfigurer僅僅是做了一個(gè)配置文件的解析工作,真正的注入并不由它們完成惰说,而是托付給了Spring 的Bean初始化流程磨德。這兩個(gè)類實(shí)現(xiàn)了BeanFactoryPostProcessor 接口,這個(gè)接口的優(yōu)先級(jí)高于后續(xù)的Spring Bean。

通過解析了的PropertySourcesPlaceholderConfigurer查詢得到元素值典挑。 沒有則拋出異常酥宴,如下源碼:

DefaultListableBeanFactory#doResolveDependency

@Value 注解值進(jìn)行屬性占位符解析和替換

// 獲取注解的 value() 值。被寫死為 Class<? extends Annotation> valueAnnotationType = Value.class;
// 見類 QualifierAnnotationAutowireCandidateResolver
Object value = getAutowireCandidateResolver().getSuggestedValue(descriptor);
if (value != null) {
    if (value instanceof String) {
        // 通過PropertySourcesPlaceholderConfigurer寫入的鍵值對(duì)元素獲取元素的值.
        // 方法內(nèi)注冊(cè)了多個(gè)StringValueResolver您觉,循環(huán)查找值拙寡。提供者為PropertySourcesPlaceholderConfigurer,因此配置多個(gè)解析器的時(shí)候是以最后的配置為準(zhǔn)的。
        String strVal = resolveEmbeddedValue((String) value);
        BeanDefinition bd = (beanName != null && containsBean(beanName) ? getMergedBeanDefinition(beanName) : null);
        value = evaluateBeanDefinitionString(strVal, bd);
    }
    TypeConverter converter = (typeConverter != null ? typeConverter : getTypeConverter());
    return (descriptor.getField() != null ?
            converter.convertIfNecessary(value, type, descriptor.getField()) :
            converter.convertIfNecessary(value, type, descriptor.getMethodParameter()));
}

讀取配置的方式介紹

xml文件讀取配置信息案例

通過PropertyPlaceholderConfigurer進(jìn)行配置Bean方式

單個(gè)配置文件琳水。

<bean id="propertyConfigurer"class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
   <property name="location">
     <value>conf/sqlmap/jdbc.properties</value>
   </property>
    <property name="fileEncoding">
      <value>UTF-8</value>
    </property>
</bean>

多個(gè)配置文件

注意這兩種value值的寫法

<bean id="propertyConfigurer"class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">  
    <property name="locations">
        <list>
            <value>/WEB-INF/mail.properties</value>  
            <value>classpath: conf/sqlmap/jdbc.properties</value>
     </list>
    </property>
</bean>

Spring標(biāo)簽方式

<context:property-placeholder location="classpath*:/WEB-INF/mail.properties" />

這總方式的原理就是構(gòu)造一個(gè)PropertySourcesPlaceholderConfigurer, (3.1之前是PropertyPlaceholderConfigurer)

  • ContextNamespaceHandler#init
  • PropertyPlaceholderBeanDefinitionParser#doParse
注入配置觸發(fā)點(diǎn)

Spring初始化Context的時(shí)候讀取XML配置肆糕, 這個(gè)流程優(yōu)先于Spring普通Bean初始化。配合掃包(<context:component-scan />)得到的Bean進(jìn)而實(shí)現(xiàn)對(duì)XML里面配置的Bean的載入在孝。

  • PropertySourcesPlaceholderConfigurer本質(zhì)上是一個(gè)BeanFactoryPostProcessor诚啃。解析XML的流程在BeanFactoryPostProcessor之前, 優(yōu)先將配置文件的路徑以及名字通過Setter傳入PropertySourcesPlaceholderConfigurer浑玛。

總結(jié)Spring Value注入流程

構(gòu)建PropertySourcesPlaceholderConfigurerBean或者PropertyPlaceholderConfigurerBean的組件

  1. 配置Spring @Value("val2Inject") 方式獲取配置文件的屬性绍申,需要依賴于在Spring XML里面配置<context:property-placeholder /> 或者PropertySourcesPlaceholderConfigurerBean來添加配置文件的名稱。

  2. 讀取到context:property-placeholder標(biāo)簽或者PropertySourcesPlaceholderConfigurer
    解析并實(shí)例化一個(gè)PropertySourcesPlaceholderConfigurer顾彰。同時(shí)向其中注入配置文件路徑极阅、名稱PropertySourcesPlaceholderConfigurer自身生成多個(gè)StringValueResolver備用,Bean準(zhǔn)備完畢涨享。

  3. Spring在初始化非BeanFactoryPostProcessor的Bean的時(shí)候筋搏,AutowiredAnnotationBeanPostProcessor負(fù)責(zé)找到Bean內(nèi)有@Value注解的Field或者M(jìn)ethod

    • 通過PropertySourcesPlaceholderConfigurer尋找合適的StringValueResolver并解析得到val值。注入給@Value的Field或Method厕隧。
  4. AutowiredAnnotationBeanPostProcessor負(fù)責(zé)@Autowired和@Value兩個(gè)注解的解析奔脐。

@PropertySource注解配置讀取單個(gè)或多個(gè)配置文件

單個(gè)配置文件:

@PropertySource(value = "classpath:config/application-config.properties")

多個(gè)配置文件:

@PropertySource(value = {"classpath:config/application-config1.properties","classpath:config/application-config2.properties"})

@PropertySource注解使用有兩種方式

  1. @PropertySource + Environment,通過@PropertySource注解將properties配置文件中的值存儲(chǔ)到Spring的Environment中吁讨,Environment接口提供方法去讀取配置文件中的值髓迎,參數(shù)是properties文件中定義的key值。

  2. @PropertySource(PropertySourcesPlaceholderConfigurer) +@Value

@PropertySource + Environment

@Configuration
@ComponentScan(basePackages = "com.libo.config")
@PropertySource(value = "classpath:config/application-config.properties")
public class TestPropertieEnvironment { 

    @Autowired
    Environment environment;
    public String properties(){
        String key = this.environment.getProperty("config.key");
        System.out.println(key);
        return null;
    }
}
配置文件config.properties:
config.key=1
config.value=2
測(cè)試類操作
public class Test {
    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(TestPropertieEnvironment.class);
        ServiceConfiguration hc2 = (TestPropertieEnvironment) context.getBean("testPropertieEnvironment");
        hc2.properties();
    }
}

@PropertySource(PropertySourcesPlaceholderConfigurer)+@Value

PropertySourcesPlaceholderConfigurer是PlaceholderConfigurerSupport的特殊化實(shí)現(xiàn)建丧。它用于解析bean定義中的屬性值排龄,以及注解@Value的值,使用的屬性來源是當(dāng)前的Spring Environment對(duì)象翎朱,以及設(shè)置給自己的PropertySources對(duì)象橄维。

  • 大于3.1更高版本中,缺省使用該工具替換了PlaceholderConfigurerSupport

  • <=3.0較老的Spring中拴曲,為了保持和之前的版本兼容争舞,缺省還是使用PropertyPlaceholderConfigurer。

創(chuàng)建PropertySourcesPlaceholderConfigurer
創(chuàng)建PropertiesConfig
@Component
@PropertySource(value = "classpath:config/application-config.properties")
public class PropertiesConfig {
    @Value("${config.value}")
    private String value;
    @Value("${config.key}")
    private String key;
 
}

測(cè)試類忽略澈灼!

自定義PropertyPlaceholderConfigurer

@Configuration
@ComponentScan(basePackages = "com.libo.config")
public class PropertiesConfiguration2 { 

    @Bean 
    public static PropertyPlaceholderConfigurer configurer() { 
         PropertyPlaceholderConfigurer ppc = new PropertyPlaceholderConfigurer();
         Resource resources = new ClassPathResource( "config/appplication-config.properties" );
         ppc.setLocation(resources);
         return ppc; 
    } 
    
    @Bean
    public Configs2 configs2(@Value("${ds.user}") String user, @Value("${key1}") String key1) {
        Configs2 configs = new Configs2();
        configs.setApiKeyId(user);
        configs.setSecretApiKey(key1);
        System.out.println("in ServiceConfiguration" + configs);
        return configs;
    }
    
}

@Service
public class TestConfigs2 {

    @Autowired
    Configs2 configs2;
    
    @Autowired
    Configs configs;
    
    public void testConfigs2() {
        System.out.println("configs:"+configs.getApiKeyId());
        System.out.println("configs2:"+configs2.getApiKeyId());
    }
}

測(cè)試類

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Test {
    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(ServiceConfiguration2.class);       
        TestConfigs2 hc2 = (TestConfigs2) context.getBean("testConfigs2");
        hc2.testConfigs2();
    }
}

此外需要注意的是:PropertySource是可以支持ignoreResourceNotFound支持無法獲取配置文件的i情況竞川。

Spring4版本的PropertySources的注解

在Spring 4版本中,Spring提供了一個(gè)新的注解——@PropertySources,從名字就可以猜測(cè)到它是為多配置文件而準(zhǔn)備的。

@PropertySources({
//@PropertySource("classpath:db.properties"),
@PropertySource(value="classpath:db.properties", ignoreResourceNotFound=true),
@PropertySource("classpath:spring/config.properties")
    public class AppConfig {
    @Value("${key1}")
    private String key1;
    
    @Value("${key2}")
    private String key2;

    @Override
    public String toString() {
        return "AppConfig [key1=" + key1 + ", key2=" + key2 + "]";
    } 
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末流译,一起剝皮案震驚了整個(gè)濱河市逞怨,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌福澡,老刑警劉巖叠赦,帶你破解...
    沈念sama閱讀 217,277評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異革砸,居然都是意外死亡除秀,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門算利,熙熙樓的掌柜王于貴愁眉苦臉地迎上來册踩,“玉大人,你說我怎么就攤上這事效拭≡菁” “怎么了?”我有些...
    開封第一講書人閱讀 163,624評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵缎患,是天一觀的道長(zhǎng)慕的。 經(jīng)常有香客問我,道長(zhǎng)挤渔,這世上最難降的妖魔是什么肮街? 我笑而不...
    開封第一講書人閱讀 58,356評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮判导,結(jié)果婚禮上嫉父,老公的妹妹穿的比我還像新娘。我一直安慰自己眼刃,他們只是感情好绕辖,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著擂红,像睡著了一般引镊。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上篮条,一...
    開封第一講書人閱讀 51,292評(píng)論 1 301
  • 那天,我揣著相機(jī)與錄音吩抓,去河邊找鬼涉茧。 笑死,一個(gè)胖子當(dāng)著我的面吹牛疹娶,可吹牛的內(nèi)容都是我干的伴栓。 我是一名探鬼主播,決...
    沈念sama閱讀 40,135評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼钳垮!你這毒婦竟也來了惑淳?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,992評(píng)論 0 275
  • 序言:老撾萬榮一對(duì)情侶失蹤饺窿,失蹤者是張志新(化名)和其女友劉穎歧焦,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體肚医,經(jīng)...
    沈念sama閱讀 45,429評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡绢馍,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評(píng)論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了肠套。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片舰涌。...
    茶點(diǎn)故事閱讀 39,785評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖你稚,靈堂內(nèi)的尸體忽然破棺而出瓷耙,到底是詐尸還是另有隱情,我是刑警寧澤刁赖,帶...
    沈念sama閱讀 35,492評(píng)論 5 345
  • 正文 年R本政府宣布搁痛,位于F島的核電站,受9級(jí)特大地震影響乾闰,放射性物質(zhì)發(fā)生泄漏落追。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評(píng)論 3 328
  • 文/蒙蒙 一涯肩、第九天 我趴在偏房一處隱蔽的房頂上張望轿钠。 院中可真熱鬧,春花似錦病苗、人聲如沸疗垛。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽贷腕。三九已至,卻和暖如春咬展,著一層夾襖步出監(jiān)牢的瞬間泽裳,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評(píng)論 1 269
  • 我被黑心中介騙來泰國打工破婆, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留涮总,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,891評(píng)論 2 370
  • 正文 我出身青樓祷舀,卻偏偏與公主長(zhǎng)得像瀑梗,于是被迫代替她去往敵國和親烹笔。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評(píng)論 2 354

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