實時發(fā)布-嵌入式OSGi的應用

場景

單機應用已經越來越不能符合目前越來越復雜的產品需求了。即使是小型應用,至少也需要部署2臺以上的服務器做集群。且應用必須24小時對外服務,可用性得達到n個9前翎。這就對發(fā)布有了更高的要求。

也就催生了灰度發(fā)布這樣的發(fā)布過程畅涂。而即使是這樣港华,還是需要經歷大致如下的發(fā)布過程:

  • 下載代碼
  • 打包
  • 停止服務器
  • 部署
  • 啟動服務器

而業(yè)界一直詬病JVM的啟動速度,再加上如果項目比較大毅戈,編譯過程比較長,發(fā)布機器比較多愤惰,那么做一次完整的發(fā)布可能需要幾個小時苇经。萬一中途出了問題,要回退宦言,又要幾個小時扇单。

是否可以解決這樣的問題呢?而OSGi恰是一個不錯的選擇!

OSGi

OSGi是一個優(yōu)雅奠旺、完整和動態(tài)的組件模型蜘澜,提供了完整的模塊化運行環(huán)境。

應用程序(稱為bundle)無需重新引導可以被遠程安裝响疚、啟動鄙信、升級和卸載。

其主要應用在嵌入式開發(fā)中忿晕,而在JavaSE和JavaEE方面則少有建樹装诡。其最著名的使用就是eclipse了。究其原因主要有:

  • 增加開發(fā)難度:需要開發(fā)人員更關心模塊的劃分践盼,處理模塊與模塊之間的依賴關系(模塊間的導入導出),這是一個好的方面鸦采,但是
  • 沒有完善的工具:模塊間的依賴關系需要開發(fā)人員手動處理(有相應的工具,但不能百分百處理依賴關系)咕幻。
  • 額外的運行環(huán)境:應用(bundle)需要運行在實現了OSGi規(guī)范的容器內渔伯,導致了模塊間的依賴關系需要在運行時才能驗證是否有問題。也就是說無法在編譯期驗證模塊間的關系肄程。同時也增加了測試及調試的難度锣吼。

可以看出选浑,OSGi的主要缺點是開發(fā)較繁瑣。而針對前面所提到的問題吐限,OSGi解決了如下幾個問題:

  • 項目模塊化鲜侥,對于項目的更新與發(fā)布不再需要發(fā)布整個項目,只需要發(fā)布需要更新的模塊即可诸典。提高了編譯打包的速度描函。
  • OSGi可在運行時的對bundle進行安裝、啟動狐粱、升級和卸載舀寓。提高了部署的速度。(這里就需要吐槽一下eclipse了肌蜻,它是基于osgi的互墓,但是每次安裝插件都要重啟是要搞哪樣?蒋搜!)
  • 支持多版本發(fā)布豆挽。在OSGi容器內可發(fā)布相同應用的不同版本

OSGi是如何做到這些的呢?其實OSGi實現了一套自身的ClassLoader帮哈,具體可見此文

OSGi容器

目前OSGi容器主要有Knopflerfish, Apache Felix, Equinox, Spring DM。其具體比較請見此文

以及其上的一些應用咖刃,方便在OSGi上進行開發(fā),比如Karaf,ServiceMix等憾筏。

OSGi的使用方式

OSGi容器有兩種使用方式:

  • 作為容器使用:

      OSGi容器作為外層氧腰,所有的應用均部署在OSGi容器內容贝。那么所有的應用都需要bundle化斤富,但是上面說了,bundle化不是一個方便的過程满力。
      且OSGi在非嵌入式領域并不是很流行,雖然之前業(yè)界一直在推廣刻帚,但最終效果并不理想,Spring最后也放棄了對OGSi的支持顷歌。
      所以當你的應用較大時,bundle化會是一個比較大的絆腳石眯漩。
    
  • 嵌入式使用

      基于上面的原因赦抖,我們可以將OSGi容器作為嵌入式容器使用队萤,即基本的模塊在OSGi外部運行浮禾,也就不需要bundle化了,
      變動比較頻繁的模塊部署到OSGi容器內,使用OSGi便利的部署機制旁钧。
      比如:項目中依賴的Spring,Mybatis等jar包可以在OSGi容器外部署,而業(yè)務模塊則部署到OSGi容器內
    

Felix安裝

這里使用felix作為OSGi容器來演示嵌入式OSGi的使用寄猩!felix可到Apache網站下載!

felix目錄結構如下:

-bin:felix.jar路徑,其實felix只需要這個jar包就可以運行了
-bundle:部署的bundle目錄,如果你有需要部署的bundle剪况,將其拷貝到此目錄下译断,啟動felix時會自動部署
-cache:bundle緩存目錄
-conf:配置文件目錄
-doc:相關文檔

在根目錄運行如下命令即可啟動

java -jar bin/felix.jar
  • bundle目錄下默認有四個bundle,提供了類似命令行功能翎蹈。啟動時自動部署了荤堪〕窝簦可以輸入lb,來查看已安裝的bundle
g! lb
START LEVEL 1
   ID|State      |Level|Name
    0|Active     |    0|System Bundle (5.4.0)|5.4.0
    1|Active     |    1|Apache Felix Bundle Repository (2.0.6)|2.0.6
    2|Active     |    1|Apache Felix Gogo Command (0.16.0)|0.16.0
    3|Active     |    1|Apache Felix Gogo Runtime (0.16.2)|0.16.2
    4|Active     |    1|Apache Felix Gogo Shell (0.10.0)|0.10.0
g!

這是普通的使用felix的方式肮塞。不做過多介紹枕赵。主要介紹嵌入式Felix的應用。

嵌入啟動Felix

創(chuàng)建Maven項目

  • 首先創(chuàng)建一個普通的Maven項目
  • 在pom.xml中添加felix依賴
<dependencies>
    <dependency>
        <groupId>org.apache.felix</groupId>
        <artifactId>org.apache.felix.main</artifactId>
        <version>5.4.0</version>
    </dependency>
</dependencies>

編寫啟動類

  • 啟動felix的核心代碼如下
FrameworkFactory factory = getFrameworkFactory();
m_fwk = factory.newFramework(configProps);
m_fwk.init();
AutoProcessor.process(configProps, m_fwk.getBundleContext());
m_fwk.start();
m_fwk.waitForStop(0);
System.exit(0);
  • getFrameworkFactory()方法通過jdk6的ServiceLoader來加載FrameworkFactory實現副瀑,并實例化返回糠睡,具體代碼如下
  • 通過configProps來構建Framework狈孔,configProps是Map<String, String>類型均抽,里面為felix及osgi相關配置,具體配置后面介紹
  • 初始化Framework
  • 配置屬性和發(fā)布bundle
  • 啟動Framework
//getFrameworkFactory()方法實現代碼
private static FrameworkFactory getFrameworkFactory() throws Exception {
    URL url = Main.class.getClassLoader().getResource(
                                                        "META-INF/services/org.osgi.framework.launch.FrameworkFactory");
    if (url != null) {
        BufferedReader br = new BufferedReader(new InputStreamReader(url.openStream()));
        try {
            for (String s = br.readLine(); s != null; s = br.readLine()) {
                s = s.trim();
                // Try to load first non-empty, non-commented line.
                if ((s.length() > 0) && (s.charAt(0) != '#')) {
                    return (FrameworkFactory) Class.forName(s).newInstance();
                }
            }
        } finally {
            if (br != null)
                br.close();
        }
    }
    throw new Exception("Could not find framework factory.");
}

Felix屬性

框架屬性

  • org.osgi.framework.executionenvironment - osgi執(zhí)行JVM環(huán)境,不必特殊設置
  • org.osgi.framework.storage - bundle緩存的完整路徑惋鹅,可使用下面的felix.cache.rootdir作為前綴拼接
  • felix.cache.rootdir - bundle緩存的root地址
  • org.osgi.framework.storage.clean - 是否需要刷新bundle緩存,"none"或"onFirstInit"武鲁,默認為"none"
  • felix.cache.filelimit - 限制bundle緩存數量洞坑,默認是0,即無限制
  • felix.cache.locking - 是否開啟鎖,限制并發(fā)訪問锅尘,默認開啟
  • felix.cache.bufsize - 設置緩存的緩沖區(qū)大小藤违,默認4096
  • org.osgi.framework.system.packages - 以逗號隔開的包名议街,來確定哪些包通過系統bundle來加載特漩,就是lb命令列出的第一個bundle
  • org.osgi.framework.system.packages.extra - 和org.osgi.framework.system.packages功能相同涂身,放額外的包
  • org.osgi.framework.bootdelegation - 以逗號隔開的包(支持模糊匹配,上面兩個屬性不支持)悍抑,委托給父ClassLoader加載(由org.osgi.framework.bundle.parent定義)搜骡,OSGi容器內的bundle不需要Import即可使用此包。OSGi不建議使用此屬性摸吠,破壞了模塊化
  • org.osgi.framework.bundle.parent - 指明哪個ClassLoader將用來加載bootdelegation屬性所指定的包寸痢。boot表示啟動的根ClassLoader,app表示應用ClassLoader,ext表示ExtClassLoader,framework表示容器的ClassLoader,默認是boot
  • felix.bootdelegation.implicit - 配置容器是否要判斷哪些包是否是delegate的,默認開啟
  • felix.systembundle.activators - 用來配置系統Bundle的啟動器對象献烦,這個配置只能通過類來配置巩那,不能通過配置文件即横,因為設置的值是個對象實例
  • felix.log.logger - 設置一個org.apache.felix.framework.Logger實例东囚,同樣只能通過類來配置
  • felix.log.level - 日志級別(1 = error, 2 = warning, 3 = information, and 4 = debug). 默認為1
  • org.osgi.framework.startlevel.beginning - 框架啟動級別舔庶,默認為1
  • felix.startlevel.bundle - bundle啟動級別,默認為1
  • felix.service.urlhandlers - 是否開啟URL Handler瞧甩,默認開啟肚逸。開啟后會調用URL.setURLStreamHandlerFactory()和URLConnection.setContentHandlerFactory()

啟動屬性

  • felix.auto.deploy.dir - 配置自動部署bundle的目錄朦促,默認為當前目錄下的bundle目錄
  • felix.auto.deploy.action - 使用一個以逗號隔開的字符串配置在auto-deploy目錄中的bundle所要執(zhí)行的動作,包括install, update, start和uninstall。如果沒有配置箩退,或者配置出錯戴涝,則auto-deploy目錄中的bundle將不會做任何動作
  • felix.auto.install.<n> - 空格隔開的bundle URL,<n>是啟動級別郑什,當啟動級別低于felix.startlevel.bundle設置的值,則自動安裝
  • felix.auto.start.<n> - 空格隔開的bundle URL兜粘,<n>是啟動級別剃法,當啟動級別低于felix.startlevel.bundle設置的值贷洲,則自動安裝并啟動
  • felix.shutdown.hook - 配置是否需要一個關閉鉤子优构,來進行關閉時的清理工作。默認為true

與Felix交互

構建bundle

  • 創(chuàng)建Maven項目彪腔,在pom.xml配置如下插件,打包方式為bundle
<packaging>bundle</packaging>

...

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.felix</groupId>
            <artifactId>maven-bundle-plugin</artifactId>
            <version>2.5.3</version>
            <extensions>true</extensions>
            <configuration>
                <instructions>
                    <Bundle-Name>demo</Bundle-Name>
                    <Bundle-SymbolicName>demo</Bundle-SymbolicName>
                    <Implementation-Title>demo</Implementation-Title>
                    <Implementation-Version>1.0.0</Implementation-Version>
                    <Export-Package></Export-Package>
                    <Import-Package></Import-Package>
                    <Bundle-Activator>org.embedosgi.activator.Activator</Bundle-Activator>
                </instructions>
            </configuration>
        </plugin>
    </plugins>
</build>
  • Export-Package和Import-Package為空,說明此bundle不對外導出任何包盲厌,也不導入任何包
  • Bundle-Activator定義了一個啟動器org.embedosgi.activator.Activator,它包含了在OSGi容器啟動此bundle時需要做的處理,代碼如下
package org.embedosgi.activator;

import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;

import java.util.Dictionary;
import java.util.Hashtable;

/**
 * Created by wangyifan on 2015/11/9.
 */
public class Activator implements BundleActivator {
    public void start(BundleContext bundleContext) throws Exception {
        Dictionary<String,String> dict = new Hashtable<String, String>();
        String version = bundleContext.getBundle().getVersion().toString();
        dict.put("version",version);
        Object bean = Class.forName("org.embedosgi.demo.impl.HelloImpl").newInstance();
        bundleContext.registerService("org.embedosgi.demo.Hello", bean, dict);
        System.out.println("Reg Hello Service End!" + version);
    }

    public void stop(BundleContext bundleContext) throws Exception {

    }
}

其主要作用就是將HelloImpl對象對外發(fā)布為Hello服務,并設置版本號為自身bundle的版本號阀湿,即在pom.xml中設置的Version

Hello和HelloImpl很簡單

package org.embedosgi.demo;

/**
 * Created by wangyifan on 2015/11/9.
 */
public interface Hello {
    String say(String name);
}

package org.embedosgi.demo.impl;
import org.embedosgi.demo.Hello;

/**
 * Created by wangyifan on 2015/11/9.
 */
public class HelloImpl implements Hello {
    public String say(String name) {
        return "Hello " + name;
    }
}

通過maven的package命令打包,即可打包成一個bundle

本地部署與debug

打包成bundle后灾挨,一般情況下你需要把bundle發(fā)布到OSGi容器內去部署劳澄,這里就是發(fā)布到felix中莫矗。而目前我們使用了內嵌式的felix,可直接在本地部署食磕。方便調試彬伦。

  • 首先,將上面的Felix啟動應用打包搂橙,發(fā)布到本地maven倉庫中
  • 在bundle項目中添加依賴
<!--這是我在上面創(chuàng)建的Felix啟動項目的依賴配置区转,請根據自己的項目做修改-->
<dependencies>
    <dependency>
        <groupId>com.ivan.osgi</groupId>
        <artifactId>osgi</artifactId>
        <version>1.0-SNAPSHOT</version>
    </dependency>
</dependencies>
  • 將felix中的conf目錄拷貝到bundle應用的根目錄

  • 新建一個Application啟動

  • 設置Main Class為Felix啟動應用中的Main類,我這里是org.embedosgi.main.Main

  • 在VM options中添加-Dfelix.auto.start.2=file:/E:/code/embedosgi/demo/target/demo-1.0.0.jar

      上面的配置可參考前面的屬性說明!這里路徑指到bundle應用的打包路徑蜻韭。
    
  • 添加一個運行前的mvn package動作

  • 最后運行

這樣的話,每次運行時都會打包這個bundle俯画,然后自動將其部署到了Felix容器中了泡仗。且支持debug

外部類獲取OSGi服務

這里測試如何在org.embedosgi.main.Main中調用bundle中發(fā)布的Hello服務沮焕。

其實很簡單辣辫,OSGi通過BundleContext來管理bundle,在上面發(fā)布服務的時候你也看到了姐浮,也是通過BundleContext來發(fā)布服務的卖鲤。

而Framework提供了獲取BundleContext的方法getBundleContext(),只要獲取到BundleContext就可以獲取服務了区匣。相關代碼如下:

BundleContext context = m_fwk.getBundleContext();
String filter = "(&(objectClass=org.embedosgi.demo.Hello)(version=1.0.0))";
Filter f = context.createFilter(filter);
ServiceTracker serviceTracker = new ServiceTracker(context, f, null);
serviceTracker.open();
Object o = serviceTracker.getService();
Class clz = o.getClass();
System.out.println(this.getClass().getClassLoader() + " | " + clz.getClassLoader());
Method method = clz.getDeclaredMethod("say", String.class);
System.out.println(method.invoke(o, "Ivan"));
  • 獲取BundleContext
  • 構建LDAP Filter語法字符串.LDAP語法請見此處
  • 根據Filter語法字符串創(chuàng)建Filter
  • 通過BundleContext和Filter構建ServiceTracker欺旧,此類可以通過Filter在Context中查找到符合條件的Service
  • 打開ServiceTracker彻坛,必要操作
  • 獲取service
  • 后面就是通過反射調用了

內部bundle調用外部類

我們在Felix中啟動項目中新增一個類HostHello

package org.embedosgi.host;

/**
 * Created by wangyifan on 2015/11/9.
 */
public class HostHello {
    public String name(){
        return "HostName";
    }
}

只是簡單的返回一個字符串

在Bundle項目中,修改HelloImpl類來獲取這個類

package org.embedosgi.demo.impl;
import org.embedosgi.demo.Hello;
import org.embedosgi.host.HostHello;

/**
 * Created by wangyifan on 2015/11/9.
 */
public class HelloImpl implements Hello {
    public String say(String name) {
        return "Hello " + name + new HostHello().name();
    }
}

如何能使HelloImpl調用到HostHello的name方法呢?

其實有兩個方法可以實現!

  • 配置org.osgi.framework.system.packages或org.osgi.framework.system.packages.extra
  • 配置org.osgi.framework.bootdelegation和org.osgi.framework.bundle.parent

我們先看第一個方法:

只需要在conf/config.properties中配置

org.osgi.framework.system.packages=org.embedosgi.host
或者
org.osgi.framework.system.packages.extra=org.embedosgi.host

然后修改Bundle項目的pom.xml文件

<plugin>
    <groupId>org.apache.felix</groupId>
    <artifactId>maven-bundle-plugin</artifactId>
    <version>2.5.3</version>
    <extensions>true</extensions>
    <configuration>
        <instructions>
            <Bundle-Name>demo</Bundle-Name>
            <Bundle-SymbolicName>demo</Bundle-SymbolicName>
            <Implementation-Title>demo</Implementation-Title>
            <Implementation-Version>1.0.0</Implementation-Version>
            <Export-Package></Export-Package>
            <Import-Package>*</Import-Package>
            <Bundle-Activator>org.embedosgi.activator.Activator</Bundle-Activator>
        </instructions>
    </configuration>
</plugin>

*號表示自動生成需要的導入包,你也可以將Import-Package標簽刪除疙渣,默認就是自動導入

也就是說,通過系統Bundle導出了org.embedosgi.host這個包啦租,然后在Bundle項目中導入了這個包。這樣就可以在Bundle中調用了

第二種方法

配置

org.osgi.framework.bootdelegation=sun.*,com.sun.*,org.osgi.framework,org.osgi.framework.*,org.embedosgi.host
org.osgi.framework.bundle.parent=app

這里的意思是所有以sun,com.sun,org.osgi.framework和org.embedosgi開頭的包都通過AppClassLoader加載,加載后對所有bundle可見嘉蕾。

Bundle項目不需要做任何導入導出荆针!

ClassLoader結構圖

第二種方式的ClassLoader結構圖如下:

HelloImpl在遇到HostHello類時棱貌,發(fā)現配置了org.osgi.framework.bootdelegation今魔,那么直接委托給AppClassLoader來加載错森。

可以稍微修改下代碼,打印出ClassLaoder即可得到!

package org.embedosgi.demo.impl;
import org.embedosgi.demo.Hello;
import org.embedosgi.host.HostHello;

/**
 * Created by wangyifan on 2015/11/9.
 */
public class HelloImpl implements Hello {
    public String say(String name) {
        System.out.println("HostHello ClassLoader = " + HostHello.class.getClassLoader());
        return "Hello " + name + new HostHello().name();
    }
}

打印結果

HostHello ClassLoader = sun.misc.Launcher$AppClassLoader@610f7612

多版本

OSGi支持多版本發(fā)布袁波,即可以在一個OSGi容器內發(fā)布多個不同版本的應用瓦阐。比如這里我們有一個demo-1.0.0.jar的應用蜗侈。

我們可以對HelloImpl稍做修改,發(fā)布一個demo-1.0.1.jar的版本睡蟋。兩個版本可以并存。調用時只需要通過LDAP過濾即可戳杀。

總結

本文介紹了

  • 如何嵌入式的啟動一個OSGi容器
  • 如何與OSGi bundle進行交互
  • 多版本

通過如上內容该面,我們可以將應用中基礎的部分固化,而業(yè)務代碼動態(tài)化豺瘤,來加快代碼的迭代速度吆倦。

同時也可實現如服務框架听诸,結合MVVM模式坐求,可實現易擴展的Web應用。想象空間還是很大的晌梨。

最后給出項目代碼

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末桥嗤,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子仔蝌,更是在濱河造成了極大的恐慌泛领,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,539評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件敛惊,死亡現場離奇詭異渊鞋,居然都是意外死亡,警方通過查閱死者的電腦和手機瞧挤,發(fā)現死者居然都...
    沈念sama閱讀 93,594評論 3 396
  • 文/潘曉璐 我一進店門锡宋,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人特恬,你說我怎么就攤上這事执俩。” “怎么了癌刽?”我有些...
    開封第一講書人閱讀 165,871評論 0 356
  • 文/不壞的土叔 我叫張陵役首,是天一觀的道長。 經常有香客問我显拜,道長衡奥,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,963評論 1 295
  • 正文 為了忘掉前任远荠,我火速辦了婚禮矮固,結果婚禮上,老公的妹妹穿的比我還像新娘矮台。我一直安慰自己乏屯,他們只是感情好根时,可當我...
    茶點故事閱讀 67,984評論 6 393
  • 文/花漫 我一把揭開白布茂浮。 她就那樣靜靜地躺著耍休,像睡著了一般。 火紅的嫁衣襯著肌膚如雪觅彰。 梳的紋絲不亂的頭發(fā)上含友,一...
    開封第一講書人閱讀 51,763評論 1 307
  • 那天替裆,我揣著相機與錄音,去河邊找鬼窘问。 笑死辆童,一個胖子當著我的面吹牛,可吹牛的內容都是我干的惠赫。 我是一名探鬼主播把鉴,決...
    沈念sama閱讀 40,468評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼儿咱!你這毒婦竟也來了庭砍?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,357評論 0 276
  • 序言:老撾萬榮一對情侶失蹤混埠,失蹤者是張志新(化名)和其女友劉穎怠缸,沒想到半個月后,有當地人在樹林里發(fā)現了一具尸體钳宪,經...
    沈念sama閱讀 45,850評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡揭北,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,002評論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現自己被綠了吏颖。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片搔体。...
    茶點故事閱讀 40,144評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖侦高,靈堂內的尸體忽然破棺而出嫉柴,到底是詐尸還是另有隱情,我是刑警寧澤奉呛,帶...
    沈念sama閱讀 35,823評論 5 346
  • 正文 年R本政府宣布计螺,位于F島的核電站,受9級特大地震影響瞧壮,放射性物質發(fā)生泄漏登馒。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,483評論 3 331
  • 文/蒙蒙 一咆槽、第九天 我趴在偏房一處隱蔽的房頂上張望陈轿。 院中可真熱鬧,春花似錦、人聲如沸麦射。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,026評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽潜秋。三九已至蛔琅,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間峻呛,已是汗流浹背罗售。 一陣腳步聲響...
    開封第一講書人閱讀 33,150評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留钩述,地道東北人寨躁。 一個月前我還...
    沈念sama閱讀 48,415評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像牙勘,于是被迫代替她去往敵國和親职恳。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,092評論 2 355

推薦閱讀更多精彩內容

  • Spring Cloud為開發(fā)人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理谜悟,服務發(fā)現话肖,斷路器北秽,智...
    卡卡羅2017閱讀 134,672評論 18 139
  • Spring Boot 參考指南 介紹 轉載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 46,827評論 6 342
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,190評論 25 707
  • 最可怕的往往是最真實的葡幸,于是蒙蔽自我會在不經意間變成我們的首選。有趣的是贺氓,有時人們往往會想方設法地蒙住自己的雙眼蔚叨,...
    亦以為心閱讀 2,080評論 0 1
  • 無標題文章11111111111111111111111111vvvvvvvvvvvvvvvvvvvvvvvvvv...
    guozhao1985閱讀 231評論 0 1