注解方式自定義實(shí)現(xiàn)Spring Ioc容器 + 事務(wù) + 動(dòng)態(tài)代理

@TOC

前言

上一篇點(diǎn)擊查看使用xml來(lái)實(shí)現(xiàn)自定義IOC以及依賴(lài)注入關(guān)系維護(hù),動(dòng)態(tài)代理,以及事務(wù)操作;

這次使用注解來(lái)實(shí)現(xiàn)IOC以及依賴(lài)關(guān)系維護(hù)

步驟以及思路分析

基于xml實(shí)現(xiàn)方式時(shí),僅僅只需要在xml里面配置好bean的id以及bean的權(quán)限定命名,然后反射實(shí)例化對(duì)象,最后加入到ioc容器中
依賴(lài)注入時(shí)候僅僅需要獲取property標(biāo)簽以及父級(jí)標(biāo)簽,根據(jù)property名從ioc容器中獲取到需要注入的bean示例即可;

如果是基于注解實(shí)現(xiàn)呢!

  • 1 首先需要自定義注解
  • 2 如何獲取自定義到的注解
  • 3 實(shí)例化打了注解的實(shí)例
  • 4 在指定bean成員變量上是否包含需要注入的注解,然后依賴(lài)注入
  • 5 生成代理對(duì)象,基于接口判斷是否選擇JDK動(dòng)態(tài)代理或者CGLIB代理

代碼實(shí)現(xiàn)

首先自定義注解

實(shí)例bean的注解 @Repository@Service

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Service {
    String value() default "";
}

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Repository {
    String value() default "";
}

自動(dòng)裝配的注解

@Target({ ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {
    String name() default "";
}

事務(wù)注解 Transactional

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Transactional {
}

然后 在類(lèi)上標(biāo)注注解,以及依賴(lài)注入


在這里插入圖片描述

事務(wù)注解以及DI 以及Bean自動(dòng)裝配


在這里插入圖片描述

項(xiàng)目結(jié)構(gòu)

在這里插入圖片描述

配置信息

版本 JDK8 , tomcat 7 , IDEA 2019 03

所需依賴(lài)

    <!-- servlet -->
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>javax.servlet-api</artifactId>
      <version>3.1.0</version>
      <scope>provided</scope>
    </dependency>
    
    <!--引入cglib依賴(lài)包-->
    <dependency>
      <groupId>cglib</groupId>
      <artifactId>cglib</artifactId>
      <version>2.1_2</version>
    </dependency>

tomcat插件

   <plugin>
        <groupId>org.apache.tomcat.maven</groupId>
        <artifactId>tomcat7-maven-plugin</artifactId>
        <version>2.2</version>
        <configuration>
          <port>8080</port>
          <path>/</path>
        </configuration>
      </plugin>

這里我們使用一個(gè)StartApplication類(lèi)來(lái)表示當(dāng)前的頂級(jí)包下的啟動(dòng)類(lèi),相當(dāng)于SpringBoot里的Main方法所在的類(lèi)(目的僅僅是指定包,也可以在xml里面配置包名)

在Web.xml里面進(jìn)行配置一下這個(gè)啟動(dòng)類(lèi)

<!DOCTYPE web-app PUBLIC
        "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
        "http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
    <display-name>tomcat啟動(dòng)時(shí)候啟動(dòng)IOC容器</display-name>
    <listener>
        <!-- 啟動(dòng)容器-->
        <!--      注解實(shí)現(xiàn)-->
        <listener-class>com.udeam.edu.factory.impl.AnnotationBeanFactory</listener-class>
        <!--    xml實(shí)現(xiàn)ioc-->
        <!--    <listener-class>com.udeam.edu.factory.impl.ClassPathXmlBeanFactory</listener-class>-->
    </listener>
</web-app>

定義BeanFactory接口類(lèi)

/**
 * 底層BeanFactory工廠接口
 * @author Pilgrim
 */
public interface BeanFactory {

    /**
     * 存儲(chǔ)bean單例
     */
    public final static Map<String, Object> IOC_MAP = new HashMap<>();


    /**
     * 對(duì)外提供獲取bean接口
     *
     * @param id
     * @return bean對(duì)象
     */
    public Object getBean(String id);


    /**
     * 根據(jù)類(lèi)型對(duì)外提供獲取bean示例
     *
     * @param classz
     * @return bean
     */
    public Object getBean(Class<?> classz);

    /**
     * 獲取容器中所有的bean名字
     *
     * @return
     */
    public Object getAllBeanName();
}

抽象類(lèi)AbstractBeanFactory擴(kuò)展一些屬性

public abstract class AbstractBeanFactory implements BeanFactory {

    /**
     * 存儲(chǔ)bean單例
     */
    public final static Map<String, Object> IOC_MAP = new HashMap<>();

    /**
     * 容器執(zhí)行一次 標(biāo)識(shí)
     */
    public static boolean isTag = false;

    public static final String CLASS_STR = ".class";

}

然后編寫(xiě)bean工廠實(shí)現(xiàn)類(lèi)AnnotationBeanFactory

定義初始化方法
initBeanFactory(String packageName);

初始化方法包含以下方法

文件掃描路徑
    /**
     * 遞歸處理路徑下文件夾是否包含文件夾,如不包含則獲取當(dāng)前類(lèi)的權(quán)限定命名存入set中
     *
     * @param packName
     * @param classNameSet
     * @param path
     */
    public static void parseFilePackName(String packName, Set<String> classNameSet, String path) 
bean實(shí)例化方法
 private void setBean();
bean依賴(lài)注入方法
 beanAutoWired()
事務(wù)處理注解方法
doScanTransactional()
/**
 * 注解方式 實(shí)現(xiàn) Bean工廠
 *
 * @author Pilgrim
 */
public class AnnotationBeanFactory extends AbstractBeanFactory {

    /**
     * 2  注解 + 掃描包 方式實(shí)現(xiàn) ioc 容器
     * tomcat啟動(dòng)的時(shí)候去初始化容器
     */
    public AnnotationBeanFactory() {
            if (isTag) {
            return;
        }
        try {
            String packageName = StartApplication.class.getPackage().getName();
            //掃描啟動(dòng)類(lèi)的包名
            System.out.println("------------------- [容器]正在初始化 ------------ ");
            System.out.println(String.format("------------------- 掃描當(dāng)前包是%s  ------------ ", packageName));
            initBeanFactory(packageName);
            System.out.println("------------------- [容器]初始化完成 ------------ ");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        isTag = true;
    }


}


包掃描

遞歸掃描包下的文件
com.xxx.xxx 包名需要轉(zhuǎn)換成磁盤(pán)目錄 c:/xxx/xx這樣的形式

獲取包名

   String packageName = StartApplication.class.getPackage().getName();

轉(zhuǎn)換包名以及掃描包得到所有的class文件名


        if (Objects.isNull(packName) || packName.length() == 0) {
            throw new RuntimeException("無(wú)效的包路徑");
        }
        packName = packName.replace(".", File.separator);
        URL resource = AnnotationBeanFactory.class.getClassLoader().getResource(packName);
        String path = resource.getPath();
        //解析中文
        String filePath = URLDecoder.decode(path, "UTF-8");

遞歸處理

    /**
     * 遞歸處理路徑下文件夾是否包含文件夾,如不包含則獲取當(dāng)前類(lèi)的權(quán)限定命名存入set中
     *
     * @param packName
     * @param classNameSet
     * @param path
     */
    public static void parseFilePackName(String packName, Set<String> classNameSet, String path) {

        File packNamePath = new File(path);

        if (!packNamePath.isDirectory() || !packNamePath.exists()) {
            return;
        }
        //遞歸路徑下所有文件和文件夾
        for (File file : packNamePath.listFiles()) {
            boolean directory = file.isDirectory();
            String classNamePath = packName + File.separator + file.getName().replace(File.separator, ".");
            if (directory) {
                parseFilePackName(classNamePath, classNameSet, file.getPath());
            } else if (file.isFile() && file.getName().endsWith(CLASS_STR)) {
                //存入set
                classNameSet.add(classNamePath.replace(File.separator, ".").replace(CLASS_STR, ""));
            }
        }

    }

bean實(shí)例化

得到所有的java文件名,然后去實(shí)例化bean

判斷是否包含我們剛才自定義的注解

    private void setBean() {
        stringSet.forEach(x -> {
            try {
                //排除指定包 servlet 類(lèi)不能被實(shí)例化 這兒排除
                if (!x.contains("servlet")) {
                    Class<?> aClassz = Class.forName(x);
                    serviceAnnotation(aClassz);
                    repositoryAnnotation(aClassz);
                }
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InstantiationException e) {
                e.printStackTrace();
            }
        });
    }

判斷是否含有ServiceRepository注解

獲取bean的名字 并且判斷當(dāng)前類(lèi)是否有 Service 注解,如有則存入Ioc 如包含屬性value不為空,則設(shè)置value屬性為bean的key

    public void serviceAnnotation(Class aClass1) throws InstantiationException, IllegalAccessException {
        Service annotation = (Service) aClass1.getAnnotation(Service.class);
        if (Objects.nonNull(annotation)) {
            setIocNameMap(annotation.value(), aClass1.getSimpleName(), aClass1);
        }
    }

Repository 同理

    public void repositoryAnnotation(Class aClass) throws InstantiationException, IllegalAccessException {
        Repository annotation = (Repository) aClass.getAnnotation(Repository.class);
        if (Objects.nonNull(annotation)) {
            setIocNameMap(annotation.value(), aClass.getSimpleName(), aClass);
        }
    }

獲取bean的name setIocNameMap方法然后實(shí)例化bean加入到容器
這兒判斷一下是否是單例bean 這兒的單例指的是是否已經(jīng)有一個(gè)bean了

    public void setIocNameMap(String value, String className, Class clasz) throws IllegalAccessException, InstantiationException {
        String iocNameString = value;
        Object beanDefinition =  clasz.newInstance() ;
        if (value.length() > 0) {
            if (IOC_MAP.containsKey(value)) {
                throw new RuntimeException("the named" + className + ",  had one ... ");
            }
        } else {
            //默認(rèn)設(shè)置bean首字母小寫(xiě)的
            iocNameString = getIocNameString(className);
            if (IOC_MAP.containsKey(iocNameString)) {
                throw new RuntimeException("the named  " + className + ",  had one ... ");
            }
        }

        // 根據(jù)父接口類(lèi)型注入
        Class<?>[] interfaces = clasz.getInterfaces();
        if (interfaces != null) {
            for (Class<?> anInterface : interfaces) {
                IOC_MAP.put(anInterface.getSimpleName(), beanDefinition);
            }
        }
        IOC_MAP.put(iocNameString, beanDefinition);
    }

設(shè)置首字母小寫(xiě)

   public static String getIocNameString(String className) {
        return (String.valueOf(className.toCharArray()[0])).toLowerCase() + className.substring(1, className.length());
    }

依賴(lài)注入

依賴(lài)注入方法,獲取成員變量上有 Autowired 注解的字段,然后根據(jù)當(dāng)前類(lèi)類(lèi)型去自動(dòng)裝配

    public static void beanAutoWired() throws ClassNotFoundException {
        //獲取成員變量上有 Autowired 注解的字段,然后根據(jù)當(dāng)前類(lèi)類(lèi)型去自動(dòng)裝配
        for (Map.Entry<String, Object> stringObjectEntry : IOC_MAP.entrySet()) {

            Object beanDefinition = stringObjectEntry.getValue();
            Class<?> aClass = beanDefinition.getClass();
            Field[] declaredFields = aClass.getDeclaredFields();

            if (Objects.isNull(declaredFields) && declaredFields.length == 0) {
                continue;
            }

            for (Field field : declaredFields) {
                //字段含有 Autowired 注解的需要被自動(dòng)裝配對(duì)象
                Autowired autowired = field.getAnnotation(Autowired.class);

                if (Objects.nonNull(autowired)) {
                    //根據(jù)當(dāng)前key獲取需要注入示例對(duì)象
                    //先根據(jù)名字注入,如果名字獲取不到,再根據(jù)類(lèi)型去注入
                    String beanName = autowired.name();

                    if (StringUtils.isEmpty(beanName)) {
                        beanName = field.getType().getSimpleName();
                    }

                    //反射設(shè)置值
                    try {
                        field.setAccessible(true);
                        //自動(dòng)裝配 線程不安全,Spring中默認(rèn)單例
                        field.set(stringObjectEntry.getValue(), IOC_MAP.get(beanName));
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    }
                }
            }
        }

    }

掃描事務(wù)注解

    public void doScanTransactional() throws IllegalAccessException, InstantiationException, ClassNotFoundException {

        for (Map.Entry<String, Object> classBeanDefinitionEntry : IOC_MAP.entrySet()) {
            Object beanDefinition = classBeanDefinitionEntry.getValue();
            //判斷生成代理對(duì)象
            Object proxy = getProxy(beanDefinition);
            if (proxy==null){
                proxy = beanDefinition;
            }
            //更新bean
            IOC_MAP.put(classBeanDefinitionEntry.getKey(), proxy);
        }


    }

判斷選擇哪個(gè)代理實(shí)現(xiàn)方式 根據(jù)是否實(shí)現(xiàn)接口

    public Object getProxy(Object aClass) {
        Object jdkProxy = null;

        Transactional annotation =  aClass.getClass().getDeclaredAnnotation(Transactional.class);
            if (Objects.nonNull(annotation)) {
                //有接口使用jdk動(dòng)態(tài)代理
                 if (aClass.getClass().getInterfaces() == null || aClass.getClass().getInterfaces().length <= 0) {

                    //cglib動(dòng)態(tài)代理
                    jdkProxy = ProxyFactory.getCglibProxy(aClass);
                } else {
                    /*for (Class anInterface : aClass.getClass().getInterfaces()) {
                        System.out.println(anInterface.getSimpleName());
                    }*/
                    jdkProxy = ProxyFactory.getJdkProxy(aClass);
                }
        }
      return jdkProxy;

    }

代理對(duì)象實(shí)現(xiàn) 以及方法執(zhí)行前后處理事務(wù)

/**
 * 代理類(lèi)工廠
 *
 * @author Pilgrim
 */
public class ProxyFactory {

    /**
     * 事務(wù)管理器
     */
  private final TransferServiceManager t = TransferServiceManager.get();

    /**
     * Jdk動(dòng)態(tài)代理
     *
     * @param obj 被代理的對(duì)象
     * @return 返回代理對(duì)象
     */
    public static Object getJdkProxy(Object obj) {

        Object o = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), obj.getClass().getInterfaces(), new InvocationHandler() {
            @Override
            public Object invoke(Object o, Method method, Object[] objects) throws Throwable {

                Object invoke = null;
                try {
                    // 開(kāi)啟事務(wù)(關(guān)閉事務(wù)的自動(dòng)提交)
                    TransferServiceManager.get().start();
                    invoke = method.invoke(obj, objects);
                    // 提交事務(wù)
                    TransferServiceManager.get().commit();
                } catch (Exception e) {
                    e.printStackTrace();
                    // 回滾事務(wù)
                    TransferServiceManager.get().rowback();
                    throw e;
                }

                return invoke;
            }
        });

        return o;

    }


    /**
     * cglib動(dòng)態(tài)代理
     *
     * @param object 被代理的對(duì)象
     * @return 返回代理對(duì)象
     */
    public static Object getCglibProxy(Object object) {

        //生成代理對(duì)象
        return Enhancer.create(object.getClass(), new MethodInterceptor() {

            @Override
            public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                Object result = null;
                try {

                    //開(kāi)啟事務(wù)
                    TransferServiceManager.get().start();

                    result = method.invoke(object, objects);

                    //提交事務(wù)
                    TransferServiceManager.get().commit();
                } catch (Exception e) {
                    //回滾事務(wù)
                    TransferServiceManager.get().rowback();
                    throw e;
                }
                return result;

            }
        });

    }

初始化方法步驟

    /**
     * 初始化bean
     * 1 遞歸掃描包獲取類(lèi)權(quán)限定命名
     * 2 實(shí)例化bean
     * 3 依賴(lài)注入
     * 4 掃描事務(wù)注解 生成代理對(duì)象
     *
     * @param packName
     * @throws UnsupportedEncodingException
     */
    public void initBeanFactory(String packName) throws UnsupportedEncodingException, InstantiationException, IllegalAccessException, ClassNotFoundException {

        if (Objects.isNull(packName) || packName.length() == 0) {
            throw new RuntimeException("無(wú)效的包路徑");
        }
        packName = packName.replace(".", File.separator);
        URL resource = AnnotationBeanFactory.class.getClassLoader().getResource(packName);
        String path = resource.getPath();
        //解析中文
        String filePath = URLDecoder.decode(path, "UTF-8");

        //解析包成java權(quán)限定命名com
        parseFilePackName(packName, stringSet, filePath);
        //實(shí)例化bean
        setBean();

        //System.out.println(String.format("獲取到的bean : %s ", IOC_MAP));

        //自動(dòng)裝配
        beanAutoWired();

        //掃描事務(wù)注解
        doScanTransactional();

    }

對(duì)外提供getBean(方法)

實(shí)現(xiàn)getBean方法

    @Override
    //根據(jù)id名獲取
    public Object getBean(String id) {
        if (Objects.nonNull(id) && id.length() > 0) {
            Object beanDefinition = IOC_MAP.get(id);
            return beanDefinition;
        }
        return null;
    }


    @Override
    //根據(jù)類(lèi)型獲取
    public Object getBean(Class<?> aClass) {
        if (Objects.isNull(aClass)) {
            return null;
        }
        return IOC_MAP.get(aClass.getSimpleName());
    }

    @Override
    public Object getAllBeanName() {
        return IOC_MAP.keySet();
    }

測(cè)試

啟動(dòng)tomcat可以看到啟動(dòng)成功,bean實(shí)例化完成


在這里插入圖片描述

在servlet init方法里可以調(diào)用看一下

    private TransferService transferService  ;

    @Override
    public void init() throws ServletException {
        BeanFactory  beanFactory = new AnnotationBeanFactory();
        transferService= (TransferService)beanFactory.getBean("transferServiceImpl");
        TransferService transferService2  = (TransferService) beanFactory.getBean(TransferService.class);
        super.init();
    }

debug可以看到 根據(jù)類(lèi)型還是id都可以獲取到代理之后的bean


在這里插入圖片描述
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市预麸,隨后出現(xiàn)的幾起案子吏祸,更是在濱河造成了極大的恐慌,老刑警劉巖贡翘,帶你破解...
    沈念sama閱讀 216,544評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件鸣驱,死亡現(xiàn)場(chǎng)離奇詭異蝠咆,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)递胧,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,430評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén)缎脾,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)遗菠,“玉大人华蜒,你說(shuō)我怎么就攤上這事“认玻” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,764評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵譬涡,是天一觀的道長(zhǎng)涡匀。 經(jīng)常有香客問(wèn)我溉知,道長(zhǎng),這世上最難降的妖魔是什么舌劳? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,193評(píng)論 1 292
  • 正文 為了忘掉前任蒿囤,我火速辦了婚禮,結(jié)果婚禮上材诽,老公的妹妹穿的比我還像新娘恒傻。我一直安慰自己,他們只是感情好睁枕,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,216評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布外遇。 她就那樣靜靜地躺著,像睡著了一般诡渴。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上妄辩,一...
    開(kāi)封第一講書(shū)人閱讀 51,182評(píng)論 1 299
  • 那天眼耀,我揣著相機(jī)與錄音佩憾,去河邊找鬼。 笑死澈吨,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的寄摆。 我是一名探鬼主播,決...
    沈念sama閱讀 40,063評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼桑阶,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼蚣录!你這毒婦竟也來(lái)了眷篇?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 38,917評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤虐杯,失蹤者是張志新(化名)和其女友劉穎昧港,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體达舒,經(jīng)...
    沈念sama閱讀 45,329評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,543評(píng)論 2 332
  • 正文 我和宋清朗相戀三年昨登,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了贯底。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,722評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖尿褪,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情顿仇,我是刑警寧澤摆马,帶...
    沈念sama閱讀 35,425評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站述呐,受9級(jí)特大地震影響蕉毯,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜代虾,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,019評(píng)論 3 326
  • 文/蒙蒙 一棉磨、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧乘瓤,春花似錦、人聲如沸斟赚。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,671評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)交掏。三九已至刃鳄,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間叔锐,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,825評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工讨盒, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留步责,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,729評(píng)論 2 368
  • 正文 我出身青樓遂鹊,卻偏偏與公主長(zhǎng)得像蔗包,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子气忠,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,614評(píng)論 2 353

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