手?jǐn)]一個(gè)Spring IOC容器——漸進(jìn)式實(shí)現(xiàn)

前言

本文原載于我的博客边败,地址:https://blog.guoziyang.top/archives/53/

最近閱讀了Spring Framework中的IOC容器部分的實(shí)現(xiàn),手癢,決定自己實(shí)現(xiàn)一個(gè)比較簡單的版本瓢棒。

具體代碼可以查看我的Github的倉庫:https://github.com/CN-GuoZiyang/My-Spring-IOC

目前實(shí)現(xiàn)的功能有:

  • xml配置文件讀取
  • 屬性注入
  • 引用依賴注入
  • 遞歸引用注入
  • singleton與prototype模式注入
  • 注解配置
  • 基于該容器的SpringMVC的實(shí)現(xiàn)(下一篇)

待實(shí)現(xiàn):

  • AOP實(shí)現(xiàn)
  • 循環(huán)依賴

基于xml配置文件的注入

該部分對應(yīng)的提交在:

https://github.com/CN-GuoZiyang/My-Spring-IOC/tree/82967670e52fe66ad55a6b2a539dbb4d48b46805

最終效果

主要過程按自頂向下的方式實(shí)現(xiàn)祷膳,最終實(shí)現(xiàn)的是將以下的配置文件讀取后,在容器中注入Bean:

<?xml version="1.0" encoding="UTF-8"?>
<beans>

    <bean id="helloWorldService" class="top.guoziyang.main.service.HelloWorldServiceImpl" scope="prototype">
        <property name="text" value="Hello World"></property>
    </bean>

    <bean id="wrapService" class="top.guoziyang.main.service.WrapService">
        <property name="helloWorldService" ref="helloWorldService"></property>
    </bean>

</beans>

該配置文件仿照Spring的配置文件格式乃戈,注入以下的兩個(gè)Bean:

package top.guoziyang.main.service;

public class HelloWorldServiceImpl implements HelloWorldService {
    private String text;
    @Override
    public void saySomething() {
        System.out.println(text);
    }
}
package top.guoziyang.main.service;

public class WrapService {
    private HelloWorldService helloWorldService;
    public void say() {
        helloWorldService.saySomething();
    }
}

ApplicationContext的實(shí)現(xiàn)

ApplicationContext褂痰,即應(yīng)用程序上下文,是Spring框架中最為核心的類症虑,也是Spring的入口類缩歪。該接口繼承自BeanFactory接口,實(shí)現(xiàn)了BeanFactory(實(shí)例工廠)的所有功能谍憔,還支持資源訪問(如URL和文件)匪蝙、事務(wù)傳播等功能。但是我們還是只實(shí)現(xiàn)其核心的功能习贫。

我們首先定義ApplicationContext接口:

package top.guoziyang.springframework.context;

/**
 * 應(yīng)用程序上下文接口
 *
 * @author ziyang
 */
public interface ApplicationContext {
    Object getBean(Class clazz) throws Exception;
    Object getBean(String beanName) throws Exception;
}

這個(gè)接口只定義了兩個(gè)方法逛球,分別通過類對象和實(shí)例的名稱從容器中獲取對象。

我們接著仿照Spring沈条,編寫一個(gè)抽象類AbstractApplicationContext需忿,來實(shí)現(xiàn)ApplicationContext接口,書寫一些通用的方法蜡歹。注意屋厘,在Spring中,ApplicationContext實(shí)現(xiàn)BeanFactory的方式月而,是在ApplicationContext對象的內(nèi)部汗洒,保存了一個(gè)BeanFactory對象的實(shí)例,實(shí)質(zhì)上類似一種代理模式:

package top.guoziyang.springframework.context;

import top.guoziyang.springframework.factory.BeanFactory;

public abstract class AbstractApplicationContext implements ApplicationContext {
  
    BeanFactory beanFactory;
  
    @Override
    public Object getBean(Class clazz) throws Exception {
        return beanFactory.getBean(clazz);
    }
    @Override
    public Object getBean(String beanName) throws Exception {
        return beanFactory.getBean(beanName);
    }
}

那么現(xiàn)在父款,從ApplicationContext中取出對象的方法都實(shí)現(xiàn)完了溢谤,那么ApplicationContext的具體實(shí)現(xiàn)類的工作瞻凤,就是用某種方式讀取配置,然后把對象信息存入到BeanFactory中世杀,等待用戶來取阀参。

那么在我們查看ApplicationContext的具體實(shí)現(xiàn)類之前,我們先來看看BeanFactory瞻坝,這個(gè)實(shí)例工廠蛛壳。

從AbstractApplicationContext中,我們可以知道所刀,這個(gè)接口衙荐,有g(shù)etBean這兩種方法,除此以外浮创,我還定義了一個(gè)方法:void registerBeanDefinition(String name, BeanDefinition beanDefinition) throws Exception;忧吟,表示像工廠中注冊Bean的定義,至于BeanDefinition的實(shí)現(xiàn)斩披,后面再說溜族。

BeanFactory的實(shí)現(xiàn)

BeanFactory,毫無疑問就是一個(gè)工廠雏掠,而且ApplicationContext就是從它這兒拿Bean的斩祭。根據(jù)名字來拿Bean,顯而易見是一個(gè)類似Map的結(jié)構(gòu)乡话,這里我們采用ConcurrentHashMap來存儲這個(gè)結(jié)構(gòu)摧玫。那么這樣,兩個(gè)getBean的實(shí)現(xiàn)也就很顯然了绑青,仿照Spring的結(jié)構(gòu)诬像,我們還是先創(chuàng)建一個(gè)抽象類來實(shí)現(xiàn)BeanFactory接口:

package top.guoziyang.springframework.factory;

import top.guoziyang.springframework.entity.BeanDefinition;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public abstract class AbstractBeanFactory implements BeanFactory {

    ConcurrentHashMap<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>();

    @Override
    public Object getBean(String name) throws Exception {
        BeanDefinition beanDefinition = beanDefinitionMap.get(name);
        if(beanDefinition == null) return null;
        if(!beanDefinition.isSingleton() || beanDefinition.getBean() == null) {
            return doCreateBean(beanDefinition);
        } else {
            return doCreateBean(beanDefinition);
        }
    }

    @Override
    public Object getBean(Class clazz) throws Exception {
        BeanDefinition beanDefinition = null;
        for(Map.Entry<String, BeanDefinition> entry : beanDefinitionMap.entrySet()) {
            Class tmpClass = entry.getValue().getBeanClass();
            if(tmpClass == clazz || clazz.isAssignableFrom(tmpClass)) {
                beanDefinition = entry.getValue();
            }
        }
        if(beanDefinition == null) {
            return null;
        }
        if(!beanDefinition.isSingleton() || beanDefinition.getBean() == null) {
            return doCreateBean(beanDefinition);
        } else {
            return beanDefinition.getBean();
        }
    }

    @Override
    public void registerBeanDefinition(String name, BeanDefinition beanDefinition) {
        beanDefinitionMap.put(name, beanDefinition);
    }

    /**
     * 創(chuàng)建Bean實(shí)例
     * @param beanDefinition Bean定義對象
     * @return Bean實(shí)例對象
     * @throws Exception 可能出現(xiàn)的異常
     */
    abstract Object doCreateBean(BeanDefinition beanDefinition) throws Exception;

    public void populateBeans() throws Exception {
        for(Map.Entry<String, BeanDefinition> entry : beanDefinitionMap.entrySet()) {
            doCreateBean(entry.getValue());
        }
    }
}

這里,我們留了一個(gè)doCreateBean方法作為抽象方法闸婴,表示真正創(chuàng)建Bean實(shí)例對象的操作坏挠,留給具體的實(shí)現(xiàn)類來實(shí)現(xiàn)。

我們要實(shí)現(xiàn)的BeanFactory邪乍,是一個(gè)可以自動注入屬性的BeanFactory降狠,可以創(chuàng)建完成實(shí)例對象后,注入其中的屬性庇楞,如果屬性是一個(gè)對象引用榜配,那么就去創(chuàng)建那個(gè)被引用的實(shí)例對象,并遞歸地完成屬性注入吕晌。在Spring中蛋褥,這個(gè)實(shí)現(xiàn)類叫做AutowiredCapableBeanFactory。于是睛驳,我們的AutowiredCapableBeanFactory的實(shí)現(xiàn)是這樣的:

package top.guoziyang.springframework.factory;

import top.guoziyang.springframework.entity.BeanDefinition;
import top.guoziyang.springframework.entity.BeanReference;
import top.guoziyang.springframework.entity.PropertyValue;
import java.lang.reflect.Field;

public class AutowiredCapableBeanFactory extends AbstractBeanFactory {

    @Override
    Object doCreateBean(BeanDefinition beanDefinition) throws Exception {
        if(beanDefinition.isSingleton() && beanDefinition.getBean() != null) {
            return beanDefinition.getBean();
        }
        Object bean = beanDefinition.getBeanClass().newInstance();
        if(beanDefinition.isSingleton()) {
            beanDefinition.setBean(bean);
        }
        applyPropertyValues(bean, beanDefinition);
        return bean;
    }

    /**
     * 為新創(chuàng)建了bean注入屬性
     * @param bean 待注入屬性的bean
     * @param beanDefinition bean的定義
     * @throws Exception 反射異常
     */
    void applyPropertyValues(Object bean, BeanDefinition beanDefinition) throws Exception {
        for(PropertyValue propertyValue : beanDefinition.getPropertyValues().getPropertyValues()) {
            Field field = bean.getClass().getDeclaredField(propertyValue.getName());
            Object value = propertyValue.getValue();
            if(value instanceof BeanReference) {
                BeanReference beanReference = (BeanReference) propertyValue.getValue();
                BeanDefinition refDefinition = beanDefinitionMap.get(beanReference.getName());
                if(refDefinition.getBean() == null) {
                    value = doCreateBean(refDefinition);
                }
            }
            field.setAccessible(true);
            field.set(bean, value);
        }
    }
}

這里主要還是使用了反射來創(chuàng)建對象實(shí)例烙心,原理比較簡單膜廊,就不過多說明。

那么說了這么多淫茵,BeanDefinition到底是什么呢爪瓜,又從哪里來呢?

BeanDefinition的定義如下:

public class BeanDefinition {

    private Object bean;    // 實(shí)例化后的對象
    private Class beanClass;
    private String beanClassName;
    private Boolean singleton;  // 是否是單例模式
    private PropertyValues propertyValues;  // Bean的屬性

}

PropertyValues實(shí)際上是一個(gè)List匙瘪,表示一組屬性的定義钥勋,內(nèi)部存儲的對象是PropertyValue對象,表示一個(gè)屬性定義和其對應(yīng)的注入屬性:

public class PropertyValue {

    private final String name;
    private final Object value;
 
}

注意這里的value辆苔,如果是引用其他對象的話,value就是一個(gè)BeanReference實(shí)例扼劈,表示對一個(gè)對象的引用驻啤,而不是立即初始化,因?yàn)锽eanDefinition是在讀取配置文件時(shí)就被創(chuàng)建的荐吵,這時(shí)還沒有任何Bean被初始化骑冗,BeanReference僅僅是一個(gè)記錄而已:

public class BeanReference {
    private String name;
    private Object bean;
}

BeanDefinitionReader的實(shí)現(xiàn)

回到正題,BeanDefinition從哪里來先煎?目前是從文件中讀取的贼涩,定義一個(gè)抽象的AbstractBeanDefinitionReader,如下:

/**
 * BeanDefinitionReader實(shí)現(xiàn)的抽象類
 *
 * @author ziyang
 */
public abstract class AbstractBeanDefinitionReader implements BeanDefinitionReader {
  
    private Map<String, BeanDefinition> registry;

    private ResourceLoader resourceLoader;

    public AbstractBeanDefinitionReader(ResourceLoader resourceLoader) {
        this.registry = new HashMap<>();
        this.resourceLoader = resourceLoader;
    }

    public Map<String, BeanDefinition> getRegistry() {
        return registry;
    }

    public ResourceLoader getResourceLoader() {
        return resourceLoader;
    }
}

registry也是一個(gè)Map薯蝎,用于暫存Bean的名稱和BeanDefinition的映射遥倦。

最終,最后的具體實(shí)現(xiàn)類實(shí)現(xiàn)了對配置文件的讀取占锯,由于我們讀取的是Xml配置文件袒哥,所以我們的實(shí)現(xiàn)類名叫XmlBeanDefinitionReader,使用Java內(nèi)置的XML解析器消略,可以將其解析為Document堡称,具體的解析過程較長,不貼代碼了艺演,文件參考這里却紧。

回到ApplicationContext

這就是完整的,一個(gè)Bean從配置文件到被實(shí)例化的過程胎撤。那么晓殊,第一節(jié)的ApplicationContext的具體實(shí)現(xiàn)類所要做的,就很簡單了哩照,只需要?jiǎng)?chuàng)建一個(gè)BeanDefinitionReader讀取配置文件挺物,并且將讀取到的配置存到BeanFactory中,并且由BeanFactory創(chuàng)建對應(yīng)的實(shí)例對象即可飘弧。由于我們是讀取xml文件识藤,那么這個(gè)ApplicationContext的實(shí)現(xiàn)類砚著,就叫ClassPathXmlApplicationContext,具體的邏輯在obtainBeanFactory()方法中:

private AbstractBeanFactory obtainBeanFactory() throws Exception {
    XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(new ResourceLoader());
    beanDefinitionReader.loadBeanDefinitions(location);
    AbstractBeanFactory beanFactory = new AutowiredCapableBeanFactory();
    for (Map.Entry<String, BeanDefinition> beanDefinitionEntry : beanDefinitionReader.getRegistry().entrySet()) {
        beanFactory.registerBeanDefinition(beanDefinitionEntry.getKey(), beanDefinitionEntry.getValue());
    }
    return beanFactory;
}

看看效果痴昧!

讓我們書寫一些測試代碼稽穆,看看效果:

public class Main {

    public static void main(String[] args) throws Exception {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("application.xml");
        WrapService wrapService = (WrapService) applicationContext.getBean("wrapService");
        wrapService.say();
        HelloWorldService helloWorldService = (HelloWorldService) applicationContext.getBean("helloWorldService");
        HelloWorldService helloWorldService2 = (HelloWorldService) applicationContext.getBean("helloWorldService");
        System.out.println("prototype驗(yàn)證:" + (helloWorldService == helloWorldService2));
        WrapService wrapService2 = (WrapService) applicationContext.getBean("wrapService");
        System.out.println("singleton驗(yàn)證:" + (wrapService == wrapService2));
    }

}

運(yùn)行結(jié)果如下:

Hello World
prototype驗(yàn)證:false
singleton驗(yàn)證:true

這里驗(yàn)證了一下prototype和singleton,這里首先獲取了兩次HelloWorldService的實(shí)例赶撰,由于這個(gè)Bean在配置文件中被標(biāo)為prototype舌镶,所以兩次獲取到的都不是同一個(gè)對象,使用等號比較時(shí)得到了false豪娜。而后面獲取的wrapService餐胀,和第一次獲取的WrapService比較,由于是singleton的瘤载,所以使用等號比較時(shí)返回true否灾。

基于注解的注入

該部分對應(yīng)的提交在

https://github.com/CN-GuoZiyang/My-Spring-IOC/tree/8a3a9c640e532c5d4aa8d62f18b42fa336c94f2e

聲明注解

首先我們需要自定義一些注解,仿照Spring鸣奔,我們聲明一下五個(gè)注解:Autowired墨技、Component、Qualifier挎狸、Scope和Value扣汪,用過Spring的人應(yīng)該都知道以下注解的作用。

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Autowired{}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Component {
    String name() default "";
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Qualifier {
    String value();
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Scope {
    String value() default "singleton";
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Value {
    public String value();
}

由于不是SpringBoot锨匆,我們?nèi)匀恍枰谂渲梦募袝鴮懽詣幼⑷氲膾呙璺秶副穑渲梦募缦拢?/p>

<?xml version="1.0" encoding="UTF-8"?>
<beans>
    <component-scan base-package="top.guoziyang.main"></component-scan>
</beans>

啟動后,會自動掃描該包及其子包下所有使用注解標(biāo)明的Bean统刮,并注入容器紊遵。

掃描注解

由于配置文件發(fā)生了改變,自然我們需要改變xml文件的解析方式侥蒙,在XmlBeanDefinitionReader的parseBeanDefinitions()方法中暗膜,一旦我們發(fā)現(xiàn)了component-scan標(biāo)簽,說明我們是使用注解來注入Bean的:

protected void parseBeanDefinitions(Element root) {
  ...
  for(int i = 0; i < nodeList.getLength(); i ++) {
      if(nodeList.item(i) instanceof Element) {
          Element ele = (Element)nodeList.item(i);
          if(ele.getTagName().equals("component-scan")) {
              basePackage = ele.getAttribute("base-package");
              break;
          }
      }
  }
  if(basePackage != null) {
      parseAnnotation(basePackage);
      return;
  }
  ...
}

我們增加了parseAnnotation方法鞭衩,來對目標(biāo)包進(jìn)行注解掃描学搜,實(shí)質(zhì)上需要遞歸地掃描到該包下的所有類,并使用反射來查看該類是否使用了@Component注解论衍,并獲取相關(guān)的信息瑞佩,如屬性注入或者singleton或者prototype之類的信息。并將beanDefinition存入registry中:

    protected void processAnnotationBeanDefinition(Class<?> clazz) {
        if(clazz.isAnnotationPresent(Component.class)) {
            String name = clazz.getAnnotation(Component.class).name();
            if(name == null || name.length() == 0) {
                name = clazz.getName();
            }
            String className = clazz.getName();
            boolean singleton = true;
            if(clazz.isAnnotationPresent(Scope.class) && "prototype".equals(clazz.getAnnotation(Scope.class).value())) {
                singleton = false;
            }
            BeanDefinition beanDefinition = new BeanDefinition();
            processAnnotationProperty(clazz, beanDefinition);
            beanDefinition.setBeanClassName(className);
            beanDefinition.setSingleton(singleton);
            getRegistry().put(name, beanDefinition);
        }
    }

具體的實(shí)現(xiàn)可以看本文件

實(shí)際上坯台,由于產(chǎn)生的結(jié)果一致(產(chǎn)生beanDefinition存入registry)炬丸,可以仿照Spring的實(shí)現(xiàn)使用委托模式,這樣耦合度就不會太高。但是由于使用注解同樣還需要讀取配置文件稠炬,較為繁瑣焕阿,就沒有解耦(實(shí)際上是我偷懶了)。

看看效果首启!

這時(shí)暮屡,我們就可以去測試一下。測試所用的兩個(gè)類加上相應(yīng)的注解即可:

@Component(name = "helloWorldService")
@Scope("prototype")
public class HelloWorldServiceImpl implements HelloWorldService {
    @Value("Hello, world")
    private String text;

    @Override
    public void saySomething() {
        System.out.println(text);
    }
}
@Component(name = "wrapService")
public class WrapService {
    @Autowired
    private HelloWorldService helloWorldService;

    public void say() {
        helloWorldService.saySomething();
    }
}

測試代碼如下:

public class Main() {
  public static void annotationTest() throws Exception {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("application-annotation.xml");
        WrapService wrapService = (WrapService) applicationContext.getBean("wrapService");
        wrapService.say();
        HelloWorldService helloWorldService = (HelloWorldService) applicationContext.getBean("helloWorldService");
        HelloWorldService helloWorldService2 = (HelloWorldService) applicationContext.getBean("helloWorldService");
        System.out.println("prototype驗(yàn)證:相等" + (helloWorldService == helloWorldService2));
        WrapService wrapService2 = (WrapService) applicationContext.getBean("wrapService");
        System.out.println("singleton驗(yàn)證:相等" + (wrapService == wrapService2));
    }
}

結(jié)果和第一次測試一致毅桃。

最后

到這里褒纲,自己手?jǐn)]的Spring的控制反轉(zhuǎn)容器的簡單實(shí)現(xiàn)就完成了!還是挺有成就感的钥飞。使用體驗(yàn)和Spring基本沒啥差別(誤)莺掠。

下一篇文章,會基于已經(jīng)實(shí)現(xiàn)的IOC容器读宙,在其上層手?jǐn)]一個(gè)SpringMVC的簡單實(shí)現(xiàn)汁蝶。

挺晚了,睡覺论悴!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市墓律,隨后出現(xiàn)的幾起案子膀估,更是在濱河造成了極大的恐慌,老刑警劉巖耻讽,帶你破解...
    沈念sama閱讀 206,378評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件察纯,死亡現(xiàn)場離奇詭異,居然都是意外死亡针肥,警方通過查閱死者的電腦和手機(jī)饼记,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來慰枕,“玉大人具则,你說我怎么就攤上這事【甙铮” “怎么了博肋?”我有些...
    開封第一講書人閱讀 152,702評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長蜂厅。 經(jīng)常有香客問我匪凡,道長,這世上最難降的妖魔是什么掘猿? 我笑而不...
    開封第一講書人閱讀 55,259評論 1 279
  • 正文 為了忘掉前任病游,我火速辦了婚禮,結(jié)果婚禮上稠通,老公的妹妹穿的比我還像新娘衬衬。我一直安慰自己买猖,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,263評論 5 371
  • 文/花漫 我一把揭開白布佣耐。 她就那樣靜靜地躺著政勃,像睡著了一般。 火紅的嫁衣襯著肌膚如雪兼砖。 梳的紋絲不亂的頭發(fā)上奸远,一...
    開封第一講書人閱讀 49,036評論 1 285
  • 那天,我揣著相機(jī)與錄音讽挟,去河邊找鬼懒叛。 笑死,一個(gè)胖子當(dāng)著我的面吹牛耽梅,可吹牛的內(nèi)容都是我干的薛窥。 我是一名探鬼主播,決...
    沈念sama閱讀 38,349評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼眼姐,長吁一口氣:“原來是場噩夢啊……” “哼诅迷!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起众旗,我...
    開封第一講書人閱讀 36,979評論 0 259
  • 序言:老撾萬榮一對情侶失蹤罢杉,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后贡歧,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體滩租,經(jīng)...
    沈念sama閱讀 43,469評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,938評論 2 323
  • 正文 我和宋清朗相戀三年利朵,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了律想。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,059評論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡绍弟,死狀恐怖技即,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情樟遣,我是刑警寧澤姥份,帶...
    沈念sama閱讀 33,703評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站年碘,受9級特大地震影響澈歉,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜屿衅,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,257評論 3 307
  • 文/蒙蒙 一埃难、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦涡尘、人聲如沸忍弛。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽细疚。三九已至,卻和暖如春川梅,著一層夾襖步出監(jiān)牢的瞬間疯兼,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工贫途, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留吧彪,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,501評論 2 354
  • 正文 我出身青樓丢早,卻偏偏與公主長得像姨裸,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子怨酝,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,792評論 2 345

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