• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>
            posts - 319, comments - 22, trackbacks - 0, articles - 11
              C++博客 :: 首頁 :: 新隨筆 :: 聯(lián)系 :: 聚合  :: 管理

            其實(shí)最為重要的一點(diǎn),就是App的類型:
            Qt5 中包括:
            QObject -> QCoreApplication -> QGuiApplication -> QApplication

            一般,QML的程序使用QGuiApplication,如果使用QGuiApplication的話,那么QtWidget的相關(guān)類就不能運(yùn)行正常了。
            需要使用QApplication類來處理,這樣,QtWidget的相關(guān)類就可以使用了。
            特別是我們對(duì)標(biāo)準(zhǔn)對(duì)話框(QFileDialog\ QFontDialog \ QColorDialog)等等


            下面上代碼:


            main.cpp
            =====================================================================
            //![0]
            #include "piechart.h"
            #include <QtQuick/QQuickView>
            #include <QGuiApplication>
            #include <QApplication>

            int main(int argc, char *argv[])
            {
             QApplication app(argc, argv);

             qmlRegisterType<PieChart>("Charts", 1, 0, "PieChart");

             QQuickView view;
             view.setResizeMode(QQuickView::SizeRootObjectToView);
             view.setSource(QUrl::fromLocalFile("app.qml"));
             view.show();
             return app.exec();
            }
            //![0]

            =======================================================================================
            PieChart.h
            =======================================================================================
            #ifndef PIECHART_H
            #define PIECHART_H

            #include <QtQuick/QQuickPaintedItem>
            #include <QColor>

            //![0]
            class PieChart : public QQuickPaintedItem
            {
            //![0]
             Q_OBJECT
             Q_PROPERTY(QString name READ name WRITE setName)
             Q_PROPERTY(QColor color READ color WRITE setColor)

            //![1]
            public:
            //![1]

             PieChart(QQuickItem *parent = 0);

             QString name() const;
             void setName(const QString &name);

             QColor color() const;
             void setColor(const QColor &color);

             void paint(QPainter *painter);

            //![2]
             Q_INVOKABLE void clearChart();

            signals:
             void chartCleared();
            //![2]

            private:
             QString m_name;
             QColor m_color;

            //![3]
            };
            //![3]

            #endif


            ========================================================================================
            PieChart.cpp
            ========================================================================================
            #include "piechart.h"
            #include <QPainter>

            #include <QFileDialog>

            PieChart::PieChart(QQuickItem *parent)
             : QQuickPaintedItem(parent)
            {
            }

            QString PieChart::name() const
            {
             return m_name;
            }

            void PieChart::setName(const QString &name)
            {
             m_name = name;
            }

            QColor PieChart::color() const
            {
             return m_color;
            }

            void PieChart::setColor(const QColor &color)
            {
             m_color = color;
            }

            void PieChart::paint(QPainter *painter)
            {
             QPen pen(m_color, 2);
             painter->setPen(pen);
             painter->setRenderHints(QPainter::HighQualityAntialiasing, true);
             painter->drawPie(boundingRect(), 90 * 16, 290 * 16);
            }

            //![0]
            void PieChart::clearChart()
            {
             QString fileName = QFileDialog::getOpenFileName(0,
             tr("Open Image"), "", tr("Image Files (*.png *.jpg *.bmp)"));
             setColor(QColor(Qt::transparent));
             update();

             emit chartCleared();
            }
            //![0]




            ========================================================================================
            app.qml
            ========================================================================================
             
            //![0]
            import Charts 1.0
            import QtQuick 2.0

            Item {
             width: 300; height: 200

             PieChart {
             id: aPieChart
             anchors.centerIn: parent
             width: 100; height: 100
             color: "red"

             onChartCleared: console.log("The chart has been cleared")
             }

             MouseArea {
             anchors.fill: parent
             onClicked: aPieChart.clearChart()
             }

             Text {
             anchors { bottom: parent.bottom; horizontalCenter: parent.horizontalCenter; bottomMargin: 20 }
             text: "Click anywhere to clear the chart"
             }
            }
            //![0]


            posted @ 2012-10-06 08:10 RTY 閱讀(2381) | 評(píng)論 (0)編輯 收藏

                 摘要: Dynamic Library Design Guidelines Dynamic libraries, in addition to grouping common functionality, help reduce an app’s launch time. However, when designed improperly, dynamic libraries can deg...  閱讀全文

            posted @ 2012-09-13 08:58 RTY 閱讀(1087) | 評(píng)論 (0)編輯 收藏


            http://www.111cn.net/phper/python/41845.htm
            Linux服務(wù)器有CentOS、Fedora等,都預(yù)先安裝了Python,版本從2.4到2.5不等,而Windows類型的服務(wù)器也多數(shù)安裝了Python,因此只要在本機(jī)寫好一個(gè)腳本,上傳到對(duì)應(yīng)機(jī)器,在運(yùn)行時(shí)修改參數(shù)即可。

            Python操作文件和文件夾使用的是os庫,下面的代碼中主要用到了幾個(gè)函數(shù):

            os.listdir:列出目錄下的文件和文件夾

            os.path.join:拼接得到一個(gè)文件/文件夾的全路徑

            os.path.isfile:判斷是否是文件

            os.path.splitext:從名稱中取出一個(gè)子部分

             

            下面是目錄操作的代碼

             代碼如下復(fù)制代碼
            def search(folder, filter, allfile):
                folders = os.listdir(folder)
                for name in folders:
                    curname = os.path.join(folder, name)
                    isfile = os.path.isfile(curname)
                    if isfile:
                        ext = os.path.splitext(curname)[1]
                        count = filter.count(ext)
                        if count>0:
                            cur = myfile()
                            cur.name = curname
                            allfile.append(cur)
                    else:
                        search(curname, filter, allfile)
                return allfile

            在返回文件的各種信息時(shí),使用自定義類allfile來保存文件的信息,在程序中只用到了文件的全路徑,如果需要同時(shí)記錄文件的大小、時(shí)間、類型等信息,可以仿照代碼進(jìn)行擴(kuò)充。

             代碼如下復(fù)制代碼

            class myfile:
                def __init__(self):
                    self.name = ""

            得到存儲(chǔ)文件信息的數(shù)組后,還可以將其另存成xml格式,下面是代碼,在使用時(shí),需要從Document中導(dǎo)入xml.dom.minidom

            下面是保存為xml的代碼

             代碼如下復(fù)制代碼

            def generate(allfile, xml):
                doc = Document()

                root = doc.createElement("root")
                doc.appendChild(root)

                for myfile in allfile:
                    file = doc.createElement("file")
                    root.appendChild(file)

                    name = doc.createElement("name")
                    file.appendChild(name)
                    namevalue = doc.createTextNode(myfile.name)
                    name.appendChild(namevalue)

                print doc.toprettyxml(indent="")
                f = open(xml, 'a+')
                f.write(doc.toprettyxml(indent=""))
                f.close()

            執(zhí)行的代碼如下

             代碼如下復(fù)制代碼

            if __name__ == '__main__':
                folder = "/usr/local/apache/htdocs"
                filter = [".html",".htm",".php"]
                allfile = []
                allfile = search(folder, filter, allfile)
                len = len(allfile)
                print "found: " + str(len) + " files"

                xml = "folder.xml"
                generate(allfile, xml)

            在Linux命令行狀態(tài)下,執(zhí)行Python filesearch.py,便可以生成名為folder.xml的文件。

            如果要在Windows中運(yùn)行該程序,需要把folder變量改成Windows下的格式,例如c:\apache2htdocs,然后執(zhí)行c:python25python.exe filesearch.py(這里假設(shè)python的安裝目錄是c:python25)

            posted @ 2012-07-12 13:30 RTY 閱讀(520) | 評(píng)論 (0)編輯 收藏

            http://qtnode.net/wiki/Qt4_with_Xcode

            Setting up the Xcode IDE for developing Qt applications for Mac OS X is relatively simple. There are three options, GNU Make, Carbon, and Xcode makespec. Using the Xcode makespec is the simplest way to get started, but it seems to have problems with certain setups.

            Contents

             [hide]

            Using the Xcode makespec

            If you don't mind using the console to set up your project, creating a new Xcode project can be done automatically by qmake.

            Create the project bundle

            1. Create a folder for your project. (You can switch the order of this step with the next.)
            2. Open a console such as Terminal.app or an xterm using X11.app.
            3. Change directories to the project folder using the cd command.
            4. Create a .pro file. Calling qmake -project in an empty folder will not produce a suitable pro file, but touch myproject.pro will.
            5. Execute the command qmake -spec macx-xcode.

            From here you can close the console and open the .xcode bundle created by qmake or you can use the open command to launch it from the command line.

            Known Issues

            These issues are as of Qt/Mac 4.1.3, all experienced using OS X 10.3.9 and Xcode 1.5.

            • Adding files to the Xcode project does not automatically add them to the .pro file. The .pro file can be edited within the project.
            • Sometimes, adding a header file to the project will cause the build process to fail in dependency checking. There should be a workaround but I have not found it yet.
            • Xcode will sometimes emit spurious internal errors about the build settings. I'm not sure if this is due a bug in qmake, a bug in Xcode, or misconfiguration of my build environment. It is also possible that qmake generates valid Xcode 2.x project files that aren't completely backwards-compatible with Xcode 1.x.

            As a GNU Make project

            The solution most similar to the traditional build process is to use GNU Make. This uses the traditional build tools and is quicker to set up, but ultimately is less convenient than the other methods.

            Create a new GNU Make project

            1. From File | New Project, select "GNU Make".

            Add a qmake target

            1. From Project | New Target, select "External Target".
            2. Name the new target "qmake".
            3. Under the new target's settings, under Custom Build Command, specify the full path to the qmake binary in "Build Tool".
            4. Erase the contents of the "Arguments" field.

            Manage your .pro file

            If you want to maintain your own .pro file:

            1. From File | New File, select "Empty File in Project".
            2. Name the file with a .pro extension.

            If you want to use qmake -project to maintain your .pro file:

            1. Duplicate the "qmake" target. Name the new target "qmake -project".
            2. Under the new target's settings, under Custom Build Command, set the "Arguments" field to "-project".

            Building your project

            1. If using the qmake -project method, select "qmake -project" as your active project and click Build to update your .pro file.
            2. Select "qmake" as your active project and click Build to run qmake to generate your Makefile.
            3. Select your main target and click Build to run make to compile your application.

            As a Carbon project

            Setting up a Qt application as a Carbon project requires more work but gives you better control over the build process and integrates qmake more comfortably into the Xcode toolchain when compared to the GNU Make method. This method produces a project similar to the one created by using the Xcode makespec, but has been deprecated in favor of the more automatic approach.

            Create a new Carbon project

            1. From File | New Project, select "Carbon application".
            2. Delete the main.c and .pch file automatically generated by the template. (Feel free to replace them with a main.cpp file.)
            • Deleting the prefix header is optional; you can use it if you want but it's not portable. If you choose to use it, take out the Carbon include provided by default.

            Add Qt frameworks

            1. Control+click or right-click on "External Frameworks and Libraries" in your project's file tree. Select Add | Existing Frameworks.
            2. Find and add QtCore.framework.
            3. Repeat to add QtGui.framework and any other frameworks or libraries needed by your application.

            Add a Shell Script build phase

            1. From Project | New Build Phase, select "New Shell Script Build Phase".
            2. Set the script to:
              /path/to/qt4/bin/qmake 
            make mocables rm MocLinkList for i in `ls moc_*.cpp | sed 's/\\.cpp/\\.o/'`; do make ${i} || exit 1 echo ${i} >> MocLinkList doneBe sure to replace /path/to/qt4/bin/qmake with the real path to the qmake binary.
            1. If you intend to use Qt Designer, add make compiler_uic_make_all after make mocables.
            2. If you wish to allow qmake to automatically maintain your .pro file, add /path/to/qt4/bin/qmake -project as the first line.
            3. Expand the active target in your project's file tree and drag the Shell Script Files step up to be the first step of the build sequence.

            Configure the compiler and linker

            1. From the Project menu, select "Edit Active Target".
            2. In the Build tab, find "Other Linker Flags" and set it to "-filelist MocLinkList".
            3. Find "Prefix Header" and clear it.
            4. Find "Precompile Prefix Header" and uncheck it.
            • Of course, if you chose to use the prefix header, don't clear it. You may or may not wish to precompile it; consult Xcode's documentation.

            Building your project

            At this point you can use all of the Xcode build tools directly with no special steps.

            Using Qt Designer

            Unfortunately, while Xcode allows you to choose external editors for various file types, it seems to lack a way to specify new file types. If you could specify a new file type (i.e. "sourcecode.ui"), it would be a simple matter to associate it with Designer.

            This doesn't mean you're out of luck, though. You can add .ui files to your project just like any other file, and if you control+click or right-click the file, selecting "Open with Finder" will launch Designer with the selected file.

            posted @ 2012-06-06 14:57 RTY 閱讀(2023) | 評(píng)論 (0)編輯 收藏

            "Qt internal error: qt_menu.nib could not be loaded. The .nib file should be placed in QtGui.framework/Versions/Current/Resources/ or in the resources directory of your applicaiton bundle."

            posted @ 2012-06-06 14:55 RTY 閱讀(1724) | 評(píng)論 (0)編輯 收藏

                 摘要: http://keys.iteye.com/blog/1117190個(gè)人學(xué)習(xí)筆記 NSString --實(shí)例化方法-------------- NSString *str = [[NSString alloc] init]; NSString *str = [[[NSString alloc] init] autorelease];  &...  閱讀全文

            posted @ 2012-05-29 21:59 RTY 閱讀(713) | 評(píng)論 (0)編輯 收藏

            http://www.cocoachina.com/b/?p=237

            plist文件是標(biāo)準(zhǔn)的xml文件,在cocoa中可以很簡單地使用。這里介紹一下使用方法:

            以下代碼在Mac和iPhone中均適用。

            寫入plist文件:

            1. NSMutableDictionary* dict = [ [ NSMutableDictionary alloc ] initWithContentsOfFile:@"/Sample.plist" ];
            2. [ dict setObject:@"Yes" forKey:@"RestartSpringBoard" ];
            3. [ dict writeToFile:@"/Sample.plist" atomically:YES ];

             

            讀取plist文件:

            1. NSMutableDictionary* dict =  [ [ NSMutableDictionary alloc ] initWithContentsOfFile:@"/Sample.plist" ];
            2. NSStringobject = [ dict objectForKey:@"RestartSpringBoard" ];

             

            posted @ 2012-05-27 09:09 RTY 閱讀(1089) | 評(píng)論 (0)編輯 收藏

            OSGi開發(fā)起步(Getting Started with OSGi)-1第一個(gè)OSGi模塊
            原文出自: http://neilbartlett.name/blog/osgi-articles/ - Getting Started with OSGi

            http://hi.baidu.com/tinawhisper/home

            本文系翻譯文章,但并未嚴(yán)格按照原文翻譯。

            1. 第一個(gè)OSGi模塊
            本文期望展示OSGi開發(fā)環(huán)境的簡潔性,因此這里沒有使用eclipse開發(fā)環(huán)境。本文只使用文本編輯器和基本的命令行工具進(jìn)行OSGi程序開發(fā)。
            本文的第一個(gè)例子相對(duì)其他示例有些長,這是因?yàn)槲覀冃枰⒁粋€(gè)基本的工作環(huán)境。在開始之前,我們需要一個(gè)可以運(yùn)行的OSGi框架。現(xiàn)在我們可以在三種開源框架中進(jìn)行選擇:Apache Felix,Knopflerfish和Equinox。本文所有編寫的代碼對(duì)以上三個(gè)框架來說都是可用的,只是在使得這些代碼運(yùn)行的指令上有所區(qū)別。本文選擇eclipse使用的Equinox。首先需要找到eclipse安裝目錄下的org.eclipse.osgi_3.2.1.R32x_v20060919.jar并復(fù)制此文件到一個(gè)空目錄(文件中的版本號(hào)會(huì)根據(jù)安裝eclipse的不同而不同)。為了使命令行參數(shù)短一些,把剛才復(fù)制過來的jar文件更名為equinox.jar。現(xiàn)在就可以在命令行中定位到剛才那個(gè)空目錄并執(zhí)行這個(gè)命令:
            > java -jar equinox.jar -console
            幾秒鐘后,命令窗口中會(huì)出現(xiàn)osgi>提示。如果是這樣,就說明OSGi已經(jīng)運(yùn)行起來了(意外總會(huì)發(fā)生,比如你沒有安裝java!)。
            osgi>命令行提示允許我們運(yùn)行Equinox中的命令來進(jìn)行整個(gè)框架的控制。可以在提示符后輸入help獲得所有命令的列表。經(jīng)常使用的一個(gè)命令是ss。ss表示簡要狀態(tài)(short status)的意思。ss會(huì)列出當(dāng)前已經(jīng)安裝到框架中所有模塊(bundle)和它們狀態(tài)的列表。在OSGi中bundle表示一個(gè)模塊,這與eclipse中的插件(plug-ins)具有等同的概念。
            輸入ss后,Equinox輸出為:
            Framework is launched.
            id State Bundle
            0 ACTIVE system.bundle_3.2.1.R32x_v20060919
            以上輸出告訴我們當(dāng)前只安裝了一個(gè)已經(jīng)啟動(dòng)的模塊:系統(tǒng)模塊(System bundle)。此模塊是OSGi的一個(gè)總是安裝并處于活動(dòng)狀態(tài)的特殊模塊,它代表了框架本身。
            現(xiàn)在我們開始編寫一個(gè)模塊。在相同的目錄下,創(chuàng)建一個(gè)名稱為HelloActivator.java的文件并復(fù)制以下代碼到文件中:
            import org.osgi.framework.*;
            public class HelloActivator implements BundleActivator {
            public void start(BundleContext context) { System.out.println("Hello Eclipse!"); }
            public void stop(BundleContext context) { System.out.println("Goodbye Eclipse!"); }
            }
            一個(gè)模塊還需要一個(gè)元文件(manifest file)對(duì)它的各種元信息進(jìn)行聲明:如模塊的名稱,版本等信息。所以現(xiàn)在還需要?jiǎng)?chuàng)建一個(gè)名稱為HelloWorld.mf的文件并復(fù)制下文的文本到此文件中。特別需要注意的是要在此文本文件的最后保留一個(gè)空白行,否則打包命令jar在打包時(shí)會(huì)截短文件(最后一行丟失)。這樣就會(huì)使得生產(chǎn)的jar包中的元文件缺失一行,導(dǎo)致jar文件讀取和運(yùn)行失敗。
            Manifest-Version: 1.0
            Bundle-Name: HelloWorld
            Bundle-Activator: HelloActivator
            Bundle-SymbolicName: HelloWorld
            Bundle-Version: 1.0.0
            Import-Package: org.osgi.framework
            現(xiàn)在另外打開一個(gè)命令行窗口使用如下命令構(gòu)建一個(gè)jar文件:
            > javac -classpath equinox.jar HelloActivator.java
            > jar -cfm HelloWorld.jar HelloWorld.mf HelloActivator.class
            回到原先的OSGi控制臺(tái),輸入install file:HelloWorld.jar。控制臺(tái)會(huì)輸出"Bundle id is 1" ,接著輸入ss命令就可以得到如下輸出信息:
            Framework is launched.
            id State Bundle
            0 ACTIVE system.bundle_3.2.1.R32x_v20060919
            1 INSTALLED HelloWorld_1.0.0
            剛才開發(fā)的HelloWorld模塊安裝到了OSGi框架中,但此模塊還沒有運(yùn)行起來。現(xiàn)在輸入命令start 1。命令中的1是安裝HelloWorld模塊完成后,OSGi賦值給此模塊的ID號(hào)。當(dāng)運(yùn)行啟動(dòng)命令后,控制臺(tái)顯示消息"Hello Eclipse!"。接著輸入stop 1就會(huì)得到消息"Goodbye Eclipse!"。
            這些命令都做了些什么事情?上面的代碼實(shí)現(xiàn)了一個(gè)BundleActivator接口,這個(gè)接口用于允許框架通知我們一些重要的生命期事件。當(dāng)模塊啟動(dòng)時(shí),框架調(diào)用start方法,而如果模塊停止時(shí),框架則調(diào)用stop方法。還有一個(gè)要說明的是元文件中有一行聲明"Bundle-Activator: HelloActivator"就是用于通知框架在一個(gè)模塊中那個(gè)類是啟動(dòng)類(activator)。通常我們使用一個(gè)完整的類名稱,但在此示例中為了簡單而只使用的缺省包名。
            術(shù)語表:
            Bundle: 模塊
            Activator:啟動(dòng)器
            Component:組件
            Service:服務(wù)
            Sevice Register:服務(wù)注冊(cè)表
            Declarative Service: 服務(wù)聲明,簡稱DS
            OSGi Framework: OSGi框架或者框架
            manifest file: 元文件

            =========================================================================================
            OSGi開發(fā)起步(Getting Started with OSGi)-2與OSGi框架進(jìn)行交互
            原文出自: http://neilbartlett.name/blog/osgi-articles/ - Getting Started with OSGi

            本文系翻譯文章,但并未嚴(yán)格按照原文翻譯。

            2. 與OSGi框架進(jìn)行交互

            上一節(jié)舉了一個(gè)簡單的示例模塊HelloWorld:啟動(dòng)和停止時(shí)輸出消息的模塊。該模塊通過實(shí)現(xiàn)BundleActivator接口定義的start和stop函數(shù)方法完成了這些消息的輸出。如果回過頭來仔細(xì)的研究start和stop方法的定義,我們可以發(fā)現(xiàn)函數(shù)定義了一個(gè)BundleContext類型的參數(shù)。在本節(jié)描述參數(shù)BundleContext以及它的用法。
            BundleContext是OSGi框架提供給一個(gè)模塊的'通行票據(jù)'。當(dāng)模塊需要與框架進(jìn)行交互和通信時(shí),就可以使用BundleContext。實(shí)際上,這也是一個(gè)模塊可以唯一與OSGi框架進(jìn)行交互的渠道。OSGi框架在每個(gè)模塊啟動(dòng)時(shí)會(huì)通過此模塊的BundleActivator派送一個(gè)票據(jù):BundleContext。
            如果上一節(jié)的OSGi控制臺(tái)還在運(yùn)行狀態(tài),下面的工作就不需要重啟控制臺(tái)。如果控制臺(tái)關(guān)閉了就需要使用下面的命令啟動(dòng)它。
            > java -jar equinox.jar -console
            輸入ss后就會(huì)發(fā)現(xiàn)上次安裝過的HelloWorld模塊。即便是OSGi框架關(guān)閉重啟了之后,HelloWorld模塊也會(huì)保持上節(jié)的狀態(tài)。這主要是因?yàn)镺SGi框架進(jìn)行了模塊的持久化存儲(chǔ)處理。
            在這一節(jié)我們完成一個(gè)查找并卸載HelloWorld模塊的模塊。在OSGi控制臺(tái)可以使用uninstall命令簡單的完成這項(xiàng)任務(wù)。但是這一節(jié)希望通過OSGiAPI編碼的方式實(shí)現(xiàn)此任務(wù)。
            創(chuàng)建一個(gè)名稱為HelloWorldKiller.java的文件,輸入下面的代碼:
            import org.osgi.framework.*;
            public class HelloWorldKiller implements BundleActivator {
            public void start(BundleContext context) {
            System.out.println("HelloWorldKiller searching...");
            Bundle[] bundles = context.getBundles();
            for(int i=0; i<bundles.length; i++) {
            if("HelloWorld".equals(bundles[i]
            .getSymbolicName())) {
            try { System.out.println("Hello World found, " + "destroying!"); bundles[i].uninstall(); return; } catch (BundleException e) { System.err.println("Failed: " + e.getMessage()); }
            }
            }
            System.out.println("Hello World bundle not found");
            }
            public void stop(BundleContext context) { System.out.println("HelloWorldKiller shutting down"); }
            }
            接著創(chuàng)建此模塊的元文件,注意文件最后要留一個(gè)空白行。在元文件中輸入以下內(nèi)容:
            Manifest-Version: 1.0
            Bundle-Name: HelloWorldKiller
            Bundle-Activator: HelloWorldKiller
            Bundle-SymbolicName: HelloWorldKiller
            Bundle-Version: 1.0.0
            Import-Package: org.osgi.framework
            編譯和構(gòu)建模塊對(duì)應(yīng)的jar文件:
            > javac -classpath equinox.jar HelloWorldKiller.java
            >jar -cfm HelloWorldKiller.jar HelloWorldKiller.mf HelloWorldKiller.class
            在控制臺(tái)安裝構(gòu)建完成的新模塊:install file:HelloWorldKiller.jar。然后輸入ss。模塊的狀態(tài)類似以下列表:
            id State Bundle
            0 ACTIVE system.bundle_3.2.1.R32x_v20060919
            1 ACTIVE HelloWorld_1.0.0
            2 INSTALLED HelloWorldKiller_1.0.0
            接著啟動(dòng)我們的新模塊HelloWorldKiller:start 2。其輸出信息為:
            HelloWorldKiller searching...
            Hello World found, destroying!
            Goodbye Eclipse!
            注意最后一行是先前那個(gè)HelloWorld模塊的輸出信息。當(dāng)這個(gè)模塊處于運(yùn)行狀態(tài)并且啟動(dòng)HelloWorldKiller時(shí),HelloWorld模塊被停止并進(jìn)行了卸載。這種操作觸發(fā)了HelloWorld模塊啟動(dòng)器的stop方法。
            輸入命令ss就會(huì)發(fā)現(xiàn)HelloWorld模塊消失了。
            id State Bundle
            0 ACTIVE system.bundle_3.2.1.R32x_v20060919
            2 ACTIVE HelloWorldKiller_1.0.0
            這里的問題是:安全性如何保證?好像任何模塊都可以卸載其他模塊。實(shí)際上OSGi定義了一個(gè)復(fù)雜的安全層次機(jī)制。這個(gè)安全機(jī)制提供了對(duì)模塊與框架交互的精確控制。這樣就可以把卸載模塊的權(quán)限限制在某些特定的管理模塊。重要的是OSGi進(jìn)行安全控制基本上是一個(gè)配置過程,所以在這個(gè)講解系列里我們關(guān)注點(diǎn)主要放在編碼上。
            在開始下節(jié)講解之前,我們可以了解一下BundleContext接口,看看此接口提供了哪些方法可供使用。比如,我們可以使用installBundle方法安裝一個(gè)模塊到OSGi中。或者也可以獲取當(dāng)前所有安裝的模塊列表并打印這些模塊最近一次修改的日期和時(shí)間。具體的方法可以參考OSGi R4的API javadoc .
            術(shù)語表:
            Bundle: 模塊
            Activator:啟動(dòng)器
            Component:組件
            Service:服務(wù)
            Sevice Register:服務(wù)注冊(cè)表
            Declarative Service: 服務(wù)聲明,簡稱DS
            OSGi Framework: OSGi框架或者框架
            manifest file: 元文件

            =========================================================================
            OSGi開發(fā)起步(Getting Started with OSGi)-3 模塊間依賴
            3. 模塊間依賴
            上兩節(jié)展示了模塊的啟動(dòng)和停止以及模塊與系統(tǒng)框架的交互方式。但是問題是模塊真正的用途是什么呢?
            模塊是功能集合。模塊使我們能夠把龐大紛雜的項(xiàng)目劃分成可管理的單元。這些單元可以方便的載入到OSGi運(yùn)行環(huán)境。問題是,不管我們喜歡還是不喜歡,模塊幾乎總是依賴于其他模塊。對(duì)jar文件來說,從來沒有一個(gè)可靠的方式來指定不同jar之間的依賴關(guān)系(注意,jar元文件中的類路徑也不是一個(gè)可靠的方法)。因此你永遠(yuǎn)不能真正的確定: Jar中的代碼會(huì)正常工作?還是會(huì)在運(yùn)行時(shí)失控拋出一個(gè)ClassNotFoundException異常?
            OSGi以簡潔的方式解決了這個(gè)問題。僅僅這樣說還不如通過功能展示來的更有說服力些。所以讓我們馬上開始編碼來做到這點(diǎn)。
            不幸的是直至現(xiàn)在,我們一直使用默認(rèn)包的方式進(jìn)行類的開發(fā)。這種方式不適應(yīng)更復(fù)雜問題的編碼,所以現(xiàn)在需要使用java包的形式進(jìn)行編碼開發(fā)和功能劃分。首先以一個(gè)非常簡單的JavaBean類開始,復(fù)制以下代碼到文件osgitut /movies/ Movie.java :
            package osgitut.movies;
            public class Movie {
            private final String title;
            private final String director;
            public Movie(String title, String director) { this.title = title; this.director = director; }
            public String getTitle() { return title; }
            public String getDirector() { return director; }
            }
            現(xiàn)在接著在此包內(nèi)創(chuàng)建一個(gè)接口。復(fù)制以下代碼到文件osgitut /movies/ MovieFinder.java :
            package osgitut.movies;
            public interface MovieFinder { Movie[] findAll(); }
            然后把這兩個(gè)類封裝為一個(gè)模塊。當(dāng)然,這個(gè)模塊十分簡單而且用途也不大。但是對(duì)于本節(jié)探討的內(nèi)容已經(jīng)足夠了。同前面一樣,模塊還必要有一個(gè)對(duì)應(yīng)的元文件MoviesInterface.mf:
            Manifest-Version: 1.0
            Bundle-ManifestVersion: 2
            Bundle-Name: Movies Interface
            Bundle-SymbolicName: MoviesInterface
            Bundle-Version: 1.0.0
            Export-Package: osgitut.movies;version="1.0.0"
            文件中多了一行:Export-Package。這個(gè)屬性表明包osgitut.movies從這個(gè)模塊中導(dǎo)出。而問題是java的jar文件中所有的東西都是導(dǎo)出的。但是我們難道不想只導(dǎo)出包中的部分代碼而保持其他部分只在包內(nèi)部可見嗎?我們當(dāng)然可以使用private或者protected限制類的訪問范圍,而這樣一來同一個(gè)jar中其他包也不能訪問這些類了。所以O(shè)SGi有效地引進(jìn)了一種新的代碼保護(hù)級(jí)別:如果模塊中的包名沒有在Export-Package頭中列出,那么此包就只能在這個(gè)模塊中才能訪問到。
            此外,我們還在導(dǎo)出包名稱后掛接了軟件版本號(hào)。在下一個(gè)階段的講解中我們會(huì)發(fā)現(xiàn)這十分重要。當(dāng)然,這也不是絕對(duì)有必要提供一個(gè)版本。但如果你不指定版本號(hào)時(shí),OSGi將自動(dòng)為導(dǎo)出的包指定版本" 0.0.0 "。始終為導(dǎo)出的包指定明確的版本是一個(gè)優(yōu)秀的實(shí)踐經(jīng)驗(yàn)。
            現(xiàn)在構(gòu)建此模塊:
            > javac osgitut/movies/Movie.java osgitut/movies/MovieFinder.java
            > jar -cfm MoviesInterface.jar MoviesInterface.mf osgitut/movies/*.class
            完成此項(xiàng)工作后我們不要急于安裝此模塊。在這之前我們還需要構(gòu)建此模塊的一個(gè)依賴模塊。即一個(gè)實(shí)現(xiàn)了MovieFinder接口的具體實(shí)現(xiàn)類:osgitut/movies/impl/BasicMovieFinderImpl.java :
            package osgitut.movies.impl;
            import osgitut.movies.*;
            public class BasicMovieFinderImpl implements MovieFinder {
            private static final Movie[] MOVIES = new Movie[] { new Movie("The Godfather", "Francis Ford Coppola"), new Movie("Spirited Away", "Hayao Miyazaki") };
            public Movie[] findAll() { return MOVIES; }
            }
            還需要一個(gè)元文件:BasicMovieFinder.mf。
            Manifest-Version: 1.0
            Bundle-ManifestVersion: 2
            Bundle-Name: Basic Movie Finder
            Bundle-SymbolicName: BasicMovieFinder
            Bundle-Version: 1.0.0
            Import-Package: osgitut.movies;version="[1.0.0,2.0.0)"
            注意,在這里我們導(dǎo)入了一個(gè)由其他模塊導(dǎo)出的包osgitut.movies。而且還同時(shí)為此導(dǎo)入包設(shè)置了版本范圍。框架在運(yùn)行時(shí)刻使用此版本范圍匹配一個(gè)適合的導(dǎo)出包。OSGi表達(dá)版本范圍使用的是通用的數(shù)學(xué)式語法:方括號(hào)表示包含,圓括號(hào)表示排除。在上面這個(gè)示例元文件中我們使用這個(gè)語法有效的指定了一個(gè)1.x的版本。
            對(duì)本節(jié)示例來說,在導(dǎo)入部分加入版本限制并不是必需的。但這是一個(gè)通用的實(shí)踐準(zhǔn)則。
            現(xiàn)在按照以下命令構(gòu)建第二個(gè)模塊:
            > javac -classpath MoviesInterface.jar osgitut/movies/impl/BasicMovieFinderImpl.java
            > jar -cfm BasicMovieFinder.jar BasicMovieFinder.mf osgitut/movies/impl/*.class
            最后我們可以準(zhǔn)備在OSGi環(huán)境中試驗(yàn)這兩個(gè)模塊。第一步就是安裝BasicMovieFinder模塊并運(yùn)行ss命令。這時(shí)此模塊的狀態(tài)顯示為INSTALLED。
            id State Bundle
            0 ACTIVE org.eclipse.osgi_3.3.0.v20070208
            4 INSTALLED BasicMovieFinder_1.0.0
            (說明,你的模塊列表可能與上面這個(gè)示例列表存在一定的差異。實(shí)際上,模塊id號(hào)會(huì)根據(jù)你上次安裝和卸載HelloWorld模塊的次數(shù)而確定。因此下面示例中的id號(hào)要根據(jù)你運(yùn)行環(huán)境中相應(yīng)的id進(jìn)行替換。)
            INSTALLED表示框架載入了這個(gè)模塊,但還沒有完成此模塊依賴關(guān)系的解析。一個(gè)讓框架執(zhí)行模塊依賴解析的命令是refresh。這里輸入refresh 4然后是ss命令就可以看到:
            id State Bundle
            0 ACTIVE org.eclipse.osgi_3.3.0.v20070208
            4 INSTALLED BasicMovieFinder_1.0.0
            這個(gè)模塊沒有解析成功并仍然處于INSTALLED狀態(tài)。這個(gè)問題出在我們并沒有安裝其依賴的那個(gè)包含Movie類和MovieFinder接口的模塊。如果想確認(rèn)是不是這個(gè)問題,在框架控制臺(tái)輸入diag 4以獲取此模塊的調(diào)試信息:
            file:BasicMovieFinder.jar [4]
            Missing imported package osgitut.movies_[1.0.0,2.0.0).
            看來確實(shí)是因?yàn)榭蚣苓\(yùn)行環(huán)境沒有任何可供導(dǎo)出的osgitut.movies包。那么現(xiàn)在就安裝MoviesInterface.jar模塊并運(yùn)行ss。大致的輸出結(jié)果為:
            id State Bundle
            0 ACTIVE org.eclipse.osgi_3.3.0.v20070208
            4 INSTALLED BasicMovieFinder_1.0.0
            5 INSTALLED MoviesInterface_1.0.0
            最后一步就是通知框架重新執(zhí)行BasicMovieFinder模塊的依賴解析。依舊使用refresh 4命令。輸出的結(jié)果為:
            id State Bundle
            0 ACTIVE org.eclipse.osgi_3.3.0.v20070208
            4 RESOLVED BasicMovieFinder_1.0.0
            5 RESOLVED MoviesInterface_1.0.0
            BasicMovieFinder模塊現(xiàn)在處于已解析狀態(tài)。這是一個(gè)基本步驟。因?yàn)橹挥刑幱谝呀馕龅哪K才能啟動(dòng)。
            需要注意的是通常情況下并不需要使用本節(jié)展示的手工方式進(jìn)行模塊依賴解析。一般來說模塊會(huì)在它們需要的時(shí)候自動(dòng)進(jìn)行解析。例如,我們可以注意到MoviesInterface模塊也處理已解析狀態(tài),而我們并沒有針對(duì)此模塊顯示的執(zhí)行refresh命令。
            下一節(jié)我們進(jìn)行OSGi服務(wù)開發(fā)。

            =======================================================================================
            OSGi開發(fā)起步(Getting Started with OSGi)-4注冊(cè)一個(gè)服務(wù)
            4 注冊(cè)一個(gè)服務(wù)
            我們終于可以開始OSGi服務(wù)的講解了。在我看來,服務(wù)層是OSGi中最具特點(diǎn)的部分。在接下來的幾節(jié)你就會(huì)充分體會(huì)到這一點(diǎn)。
            上節(jié)講解的是MovieFinder接口的例子。這個(gè)接口被MovieLister用來查找電影。這個(gè)例子是一個(gè)Ioc(控制反轉(zhuǎn))的例子。
            MovieLister并不太關(guān)心原始電影數(shù)據(jù)的來源,所以我們使用了MovieFinder接口隱藏了這些細(xì)節(jié)。關(guān)鍵在于我們可以使用另外一種實(shí)現(xiàn)替代現(xiàn)有的MovieFinder:比如從數(shù)據(jù)庫或者亞馬遜web服務(wù)進(jìn)行電影查詢。而MovieLister只依賴與接口而不是具體的實(shí)現(xiàn)。
            雖然不錯(cuò),但是有些時(shí)候我們實(shí)際上還是要把一個(gè)MovieFinder的具體實(shí)現(xiàn)明確指定給MovieLister。我們是用一個(gè)外部容器把這個(gè)具體實(shí)現(xiàn)"推"給了MovieLister,而不是讓MovieLister自己調(diào)用一個(gè)查找方法發(fā)現(xiàn)這個(gè)具體實(shí)現(xiàn)對(duì)象。
            這就是IoC術(shù)語的由來。現(xiàn)在存在大量的這類外部容器,如PicoContainer,HiveMind,Spring以及EJB3.0。但是直至現(xiàn)在這些容器都有一個(gè)基本限制:它們都是靜態(tài)的。一旦一個(gè)MovieFinder指定給了MovieLister,兩者在整個(gè)JVM虛擬機(jī)生命周期內(nèi)都一直關(guān)聯(lián)。
            OSGi支持具有動(dòng)態(tài)特性的IoC模式。OSGi支持動(dòng)態(tài)的提供給MovieLister一個(gè)MovieFinder的實(shí)現(xiàn)然后移除它們。這樣我們就能在一個(gè)支持文本電影數(shù)據(jù)查詢的應(yīng)用程序和一個(gè)依賴亞馬遜web服務(wù)電影數(shù)據(jù)查詢的應(yīng)用程序之間進(jìn)行熱切換。
            OSGi服務(wù)層用于支持以上特性。我們要做的只是在服務(wù)注冊(cè)表中注冊(cè)MovieFinder為其中的一個(gè)服務(wù)就能支持熱切換特性。這可是相當(dāng)簡單啊。同樣的MovieLister的功能也可以由一個(gè)MovieLister服務(wù)提供。一個(gè)服務(wù)與一個(gè)普通的java對(duì)象(POJO)沒有什么不同,它只是使用java接口名進(jìn)行了服務(wù)注冊(cè)而已。
            在本節(jié)我們就看看如何在注冊(cè)表中注冊(cè)服務(wù)。接下來就試試怎樣編碼從注冊(cè)表獲取服務(wù)并提供給一個(gè)MovieLister。
            在上節(jié)代碼基礎(chǔ)上增加一個(gè)BasicMovieFinder。不需要更改任何現(xiàn)有的類,只是在增加一個(gè)啟動(dòng)器類。下面就是這個(gè)啟動(dòng)類osgitut/movies/impl/BasicMovieFinderActivator.java:
            package osgitut.movies.impl;
            import org.osgi.framework.*;
            import osgitut.movies.*;
            import java.util.Properties;
            import java.util.Dictionary;
            public class BasicMovieFinderActivator implements BundleActivator {
            private ServiceRegistration registration;
            public void start(BundleContext context) { MovieFinder finder = new BasicMovieFinderImpl(); Dictionary props = new Properties(); props.put("category", "misc"); registration = context.registerService( MovieFinder.class.getName(), finder, props); }
            public void stop(BundleContext context) { registration.unregister(); }
            }
            接下來就是修改BasicMovieFinder.mf文件:
            Manifest-Version: 1.0
            Bundle-ManifestVersion: 2
            Bundle-Name: Basic Movie Finder
            Bundle-SymbolicName: BasicMovieFinder
            Bundle-Version: 1.0.0
            Bundle-Activator: osgitut.movies.impl.BasicMovieFinderActivator
            Import-Package: org.osgi.framework,
            osgitut.movies;version="[1.0.0,2.0.0)"
            在這個(gè)元文件中相對(duì)上節(jié)變化了兩行。第一個(gè)增加了Bundle-Activator行。此行描述當(dāng)前模塊使用的啟動(dòng)模塊(以前我們并沒有使用)。此外還在導(dǎo)入包行中增加了org.osgi.framework。本節(jié)以前版本的模塊并沒有與框架進(jìn)行交互,因此也就不需要導(dǎo)入OSGiAPI相關(guān)的包。
            現(xiàn)在重新構(gòu)建BasicMovieFinder.jar。
            > javac -classpath equinox.jar:MoviesInterface.jar osgitut/movies/impl/*.java
            > jar cfm BasicMovieFinder.jar BasicMovieFinder.mf osgitut/movies/impl/*.class
            回到OSGi控制臺(tái),上節(jié)安裝的BasicMovieFinder.jar還在框架中運(yùn)行。因此要更新這個(gè)模塊,運(yùn)行update N命令,N是這個(gè)模塊的ID(使用ss可以得到此ID)。接著使用start N命令啟動(dòng)此模塊。接著我們看到什么-基本上也沒看到些什么。
            實(shí)際上,模塊已經(jīng)在OSGi服務(wù)注冊(cè)表中注冊(cè)了,但是并沒有其他人在另外一端要使用此服務(wù)。因此這個(gè)服務(wù)的注冊(cè)過程并沒有產(chǎn)生任何可視效果。如果我們想向自己保證我們的代碼確實(shí)運(yùn)行了,也大可不必掘地三尺。執(zhí)行下面這個(gè)命令就可以了:
            services (objectClass=*MovieFinder)
            我們會(huì)看到下面的輸出:
            {osgitut.movies.MovieFinder}={category=misc, service.id=22}
            Registered by bundle: file:BasicMovieFinder.jar [4]
            No bundles using service.
            看到了,服務(wù)的確注冊(cè)了。在下節(jié)我們就講解如何查找這個(gè)服務(wù)并在另外模塊中使用此服務(wù)。

            =============================================================================
            OSGi開發(fā)起步(Getting Started with OSGi)-5使用一個(gè)服務(wù)
            5. 使用一個(gè)服務(wù):Consuming a Service
            上節(jié)講解了如何注冊(cè)一個(gè)服務(wù)。現(xiàn)在就是在一個(gè)模塊中查找和使用這些服務(wù)的時(shí)候了。
            與上節(jié)一樣,我們還是使用電影搜索的例子。我們已經(jīng)構(gòu)建了一個(gè)MovieFinder服務(wù)并在服務(wù)注冊(cè)表中進(jìn)行了服務(wù)注冊(cè)。現(xiàn)狀我們需要構(gòu)建一個(gè)MovieLister來使用這個(gè)MovieFinder服務(wù)搜索某個(gè)導(dǎo)演執(zhí)導(dǎo)的影片。這里假設(shè)MovieLister也是一個(gè)服務(wù),也可以被其他諸如GUI程序使用。問題是,OSGi服務(wù)是動(dòng)態(tài)的:它們可能有效也可能處理不可使用狀態(tài)。這也就是說有時(shí)存在需要使用MovieFinder服務(wù)而它卻正好不能使用的情況。
            那么當(dāng)MovieFinder不可用的時(shí)候MovieLister怎么辦?顯然,調(diào)用MovieFinder是MovieLister能有效運(yùn)作的核心部分。實(shí)際上我們也只有以下幾種選擇:

            • 產(chǎn)生錯(cuò)誤。如返回null或者拋出一個(gè)異常。
            • 等待。
            • 不讓這種情況出現(xiàn)。
              在本文我們選擇比較簡單的前兩種選擇。The third option may not even make any sense to you yet, but hopefully it will after we look at some of the implications of the first two.
              首先我們需要定義MovieLister服務(wù)的接口。復(fù)制以下內(nèi)容到文件osgitut/movies/MovieLister.java:
              package osgitut.movies;
              import java.util.List;
              public interface MovieLister { List listByDirector(String name); }
              然后是文件osgitut/movies/impl/MovieListerImpl.java :
              package osgitut.movies.impl;
              import java.util.*;
              import osgitut.movies.*;
              import org.osgi.framework.*;
              import org.osgi.util.tracker.ServiceTracker;
              public class MovieListerImpl implements MovieLister {
              private final ServiceTracker finderTrack;
              public MovieListerImpl(ServiceTracker finderTrack) { this.finderTrack = finderTrack; }
              public List listByDirector(String name)
              Unknown macro: { MovieFinder finder = (MovieFinder) finderTrack.getService(); if(finder == null) { return null; } else { return doSearch(name, finder); } }

              private List doSearch(String name, MovieFinder finder) {
              Movie[] movies = finder.findAll();
              List result = new LinkedList();
              for (int i = 0; i < movies.length; i++)
              Unknown macro: { if(movies[i].getDirector().indexOf(name) > -1) { result.add(movies[i]); } }

              return result;
              }
              }
              上面的代碼可能是至今我們最長的一段示例代碼了。這里面有些什么呢?第一,實(shí)際的影片搜索邏輯分離到了doSearch(string, MovieFinder)方法中。此方法幫助我們隔離了搜索代碼與OSGi代碼。雖然,這里制定的搜索辦法并不是很高效,但對(duì)于一個(gè)以示例為目的的程序已經(jīng)足夠了:實(shí)際上只是實(shí)現(xiàn)了一個(gè)只有兩部影片的數(shù)據(jù)庫。
              第二部分是listByDirector(String name)方法。這個(gè)方法使用了一個(gè)ServiceTracker對(duì)象從服務(wù)注冊(cè)表獲取一個(gè)MovieFinder。ServiceTracker是一個(gè)十分有用的類。它封裝和抽象了一些難于使用的OSGi底層API。無論如何我們還是要檢查需要的服務(wù)是否實(shí)際可用。此服務(wù)假定在MovieLister構(gòu)造函數(shù)中把ServiceTracker傳遞過啦。
              注意,你可能在其他部分看到一些沒有使用ServiceTracker從服務(wù)注冊(cè)表查詢服務(wù)的代碼。例如,使用BundleContext的getServiceReference和GetSevice方法。但這樣一來就需要編寫一些更復(fù)雜的代碼并需要仔細(xì)進(jìn)行一些清理工作。在我看來,使用底層API并不能得到太多的好處,反倒是會(huì)引起許多問題。最好的選擇還是使用ServiceTracker。
              在一個(gè)模塊的啟動(dòng)器中是創(chuàng)建ServiceTracker的最佳地點(diǎn)。復(fù)制以下代碼到文件osgitut/movies/impl/MovieListerActivator.java:
              package osgitut.movies.impl;
              import java.util.*;
              import org.osgi.framework.*;
              import org.osgi.util.tracker.ServiceTracker;
              import osgitut.movies.*;
              public class MovieListerActivator implements BundleActivator {
              private ServiceTracker finderTracker;
              private ServiceRegistration listerReg;
              public void start(BundleContext context) throws Exception { // Create and open the MovieFinder ServiceTracker finderTracker = new ServiceTracker(context, MovieFinder.class.getName(), null); finderTracker.open(); // Create the MovieLister and register as a service MovieLister lister = new MovieListerImpl(finderTracker); listerReg = context.registerService(MovieLister.class.getName(), lister, null); // Execute the sample search doSampleSearch(lister); }
              public void stop(BundleContext context) throws Exception { // Unregister the MovieLister service listerReg.unregister(); // Close the MovieFinder ServiceTracker finderTracker.close(); }
              private void doSampleSearch(MovieLister lister) {
              List movies = lister.listByDirector("Miyazaki");
              if(movies == null) { System.err.println("Could not retrieve movie list"); } else
              Unknown macro: { for (Iterator it = movies.iterator(); it.hasNext();) { Movie movie = (Movie) it.next(); System.out.println("Title: " + movie.getTitle()); } }

              }
              }
              現(xiàn)在這個(gè)啟動(dòng)器可以啟動(dòng)服務(wù)查找感興趣的影片了。首先,在start方法中創(chuàng)建了一個(gè)ServiceTracker對(duì)象。這個(gè)對(duì)象由之前寫的MovieLister使用。接著就打開ServiceTracker并設(shè)置此對(duì)象跟蹤注冊(cè)表中的MovieFinder服務(wù)實(shí)例。然后創(chuàng)建MovieListerImpl對(duì)象并使用MovieLister為名稱在注冊(cè)表中注冊(cè)一個(gè)服務(wù)。最后,為了證明服務(wù)確實(shí)運(yùn)行,在啟動(dòng)模塊時(shí)啟動(dòng)器使用MovieLister執(zhí)行了一個(gè)簡單的搜索并輸出了搜索結(jié)果。
              到此,我們可以使用前幾節(jié)的方法構(gòu)建和安裝我們?cè)O(shè)計(jì)的這些模塊。要記得創(chuàng)建元文件,并在元文件中正確的設(shè)置Bundle-Activator為osgitut.movies.impl.MovieListerActivator。還有就是在導(dǎo)入包中包含以下三個(gè)包:org.osgi.framework , org.osgi.util.tracker and osgitut.movies。
              一旦把MovieLister.jar安裝到了OSGi框架運(yùn)行環(huán)境,就可以啟動(dòng)這個(gè)模塊。根據(jù)BasicMovieFinder模塊是否處于運(yùn)行狀態(tài),此時(shí)OSGi控制臺(tái)會(huì)輸出不同的消息。
              如果沒有運(yùn)行,會(huì)輸出:
              osgi> start 2
              Could not retrieve movie list
              如果處于運(yùn)行狀態(tài)則會(huì)輸出:
              osgi> start 2
              Title: Spirited Away
              在停止和啟動(dòng)模塊的時(shí)候,以上消息都會(huì)在控制臺(tái)出現(xiàn)。還記得我們可以在服務(wù)處于不可用狀態(tài)是選擇等待嗎?使用上面的代碼,只需要做簡單的修改:在MovieListerImpl的第16行中使用ServiceTracker的waitForService(5000)代替getService(),同時(shí)增加一個(gè)處理InterruptedException的try/catch塊。
              這會(huì)導(dǎo)致listByDirector()方法阻塞5000毫秒等待MovieFinder服務(wù)可用。如果這時(shí)剛好MovieFinder服務(wù)安裝并且可以使用,這個(gè)方法就可以立即獲取到這個(gè)服務(wù)。
              通常并不建議以這樣的方式掛起一個(gè)線程。在實(shí)踐中,這樣做是相對(duì)危險(xiǎn)的。因?yàn)閘istByDirector方法實(shí)際是由模塊啟動(dòng)器中的start方法調(diào)用的。而模塊啟動(dòng)器start方法又是由一個(gè)框架線程調(diào)用。啟動(dòng)器必須快速返回,因?yàn)楫?dāng)前一個(gè)模塊啟動(dòng)是還需要執(zhí)行許多其他的工作。實(shí)際上在最糟糕的情況下這種等待的做法可能導(dǎo)致一個(gè)死鎖。因?yàn)槲覀冞M(jìn)入了一個(gè)框架對(duì)象的同步區(qū),而這個(gè)同步區(qū)可能已經(jīng)被其他現(xiàn)場鎖定。通常的建議是不要在模塊啟動(dòng)器的start方法或者其他任何框架直接調(diào)用的代碼中執(zhí)行任何長時(shí)間或者阻斷性的操作。

            =================================================================
            OSGi開發(fā)起步(Getting Started with OSGi)-6(1)動(dòng)態(tài)服務(wù)跟蹤
            6 動(dòng)態(tài)服務(wù)跟蹤:Dynamic Service Tracking
            上節(jié)講解了如何使用一個(gè)服務(wù):MovieLister使用MovieFinder查找由某個(gè)導(dǎo)演指導(dǎo)的影片。同時(shí)還涉及了處理OSGi服務(wù)動(dòng)態(tài)特性的各種策略,特別介紹了MovieLister找不到一個(gè)有效的MovieFinder服務(wù)時(shí)的處理辦法。
            還有一種上節(jié)沒有涉及的情況是:如何處理同時(shí)有多個(gè)MovieFinder服務(wù)可用的情況?因?yàn)槿魏文K都可以使用MovieFinder接口注冊(cè)一個(gè)服務(wù),并且對(duì)于服務(wù)注冊(cè)機(jī)制來說任何模塊都是同等對(duì)待的。
            我們可以簡單的忽略這個(gè)問題,這也是上節(jié)代碼的實(shí)際處理邏輯。通過調(diào)用ServiceTracker上的getService()方法,我們選擇由服務(wù)注冊(cè)表任意提供一個(gè)MovieFinder服務(wù)。有一些參數(shù)可以調(diào)整注冊(cè)表的選擇策略(比如在服務(wù)注冊(cè)時(shí)設(shè)置的SERVICE_RANKKING屬性),但是作為一個(gè)服務(wù)的使用者我們并沒有參與這個(gè)服務(wù)選擇過程。并且實(shí)際上盡量少的控制并不是一件壞事,因?yàn)槲覀儜?yīng)該可以使用任何一個(gè)MovieFinder服務(wù)。這也是使用接口的原因。
            從另外一方面來說,在一些情況下注冊(cè)和使用多個(gè)服務(wù)也大有用途,例如,如果有多個(gè)有效的MovieFinder服務(wù),這就意味這存在多個(gè)影片數(shù)據(jù)來源可供MovieLister使用。如果可以通過使用所有的這些服務(wù)進(jìn)行影片查找,我們就可以獲得更大的搜索網(wǎng)絡(luò)空間并提供給用戶更好的搜索結(jié)果。
            另一個(gè)問題是上節(jié)遺留下來的:在沒有任何一個(gè)MovieFinder服務(wù)可用時(shí),MovieLister應(yīng)該如何正確處理?上節(jié)的處理邏輯是在服務(wù)無效時(shí)簡單的在listByDirector調(diào)用時(shí)返回null。But what if we made it impossible for methods on MovieLister to be called when the MovieFinder isn't present?
            MovieLister與MovieFinder一樣是一個(gè)服務(wù)。如果MovieFinder服務(wù)消失了,MovieLister服務(wù)是不是也應(yīng)該消失?也就是說,我們希望MovieLister與MovieFinder之間有一個(gè)一對(duì)多的依賴關(guān)系。本文的最后一節(jié),我們介紹零對(duì)一依賴關(guān)系。
            首先使用下面的代碼修改MovieListerImpl類:
            package osgitut.movies.impl;
            import java.util.*;
            import osgitut.movies.*;
            public class MovieListerImpl implements MovieLister {

            private Collection finders =
            Collections.synchronizedCollection(new ArrayList());

            protected void bindFinder(MovieFinder finder) { finders.add(finder); System.out.println("MovieLister: added a finder"); }

            protected void unbindFinder(MovieFinder finder) { finders.remove(finder); System.out.println("MovieLister: removed a finder"); }

            public List listByDirector(String director) {
            MovieFinder[] finderArray = (MovieFinder[])
            finders.toArray(new MovieFinder[finders.size()]);
            List result = new LinkedList();
            for(int j=0; j<finderArray.length; j++) {
            Movie[] all = finderArray[j].findAll(); 
            for(int i=0; i<all.length; i++) {
            if(director.equals(all[i].getDirector())) { result.add(all[i]); }
            }
            }
            return result;
            }

            }
            上面的代碼移除了MovieListerImpl上所有的OSGi依賴。MovieListerImpl現(xiàn)在是一個(gè)純POJO了。當(dāng)然,它還需要其他對(duì)象協(xié)助跟蹤MovieFinder服務(wù)并通過bindFinder方法提供此服務(wù)。因此我們還需要?jiǎng)?chuàng)建一個(gè)新的java文件:osgitut/movies/impl/MovieFinderTracker.java。
            package osgitut.movies.impl;
            import org.osgi.framework.*;
            import org.osgi.util.tracker.*;
            import osgitut.movies.*;
            public class MovieFinderTracker extends ServiceTracker {

            private final MovieListerImpl lister = new MovieListerImpl();
            private int finderCount = 0;
            private ServiceRegistration registration = null;

            public MovieFinderTracker(BundleContext context) { super(context, MovieFinder.class.getName(), null); }

            private boolean registering = false;
            public Object addingService(ServiceReference reference) {
            MovieFinder finder = (MovieFinder) context.getService(reference);
            lister.bindFinder(finder);
            synchronized(this) { finderCount ++; if (registering) return finder; registering = (finderCount == 1); if (!registering) return finder; }
            ServiceRegistration reg = context.registerService(
            MovieLister.class.getName(), lister, null);
            synchronized(this) { registering = false; registration = reg; }
            return finder;
            }
            public void removedService(ServiceReference reference, Object service) {
            MovieFinder finder = (MovieFinder) service;
            lister.unbindFinder(finder);
            context.ungetService(reference);
            ServiceRegistration needsUnregistration = null;
            synchronized(this) {
            finderCount --;
            if (finderCount == 0) { needsUnregistration = registration; registration = null; }
            }
            if(needsUnregistration != null) { needsUnregistration.unregister(); }
            }
            }

            ==========================================================================================
            OSGi開發(fā)起步(Getting Started with OSGi)-6(2)動(dòng)態(tài)服務(wù)跟蹤
            上面這個(gè)類覆蓋了上節(jié)討論的ServiceTracker類,并且定制了服務(wù)有效和失效時(shí)ServiceTracker的行為。具體來說就是,當(dāng)一個(gè)增加MovieFinder服務(wù)時(shí)調(diào)用addingSevice方法,當(dāng)刪除一個(gè)MovieFinder服務(wù)時(shí)調(diào)用removedService方法。此外還有一個(gè)modifiedService方法,只不過在本節(jié)處理邏輯中不需要而沒有覆蓋。
            有必要仔細(xì)看看這兩個(gè)方法中的處理邏輯。首先,在addingSevice方法中傳入的參數(shù)是ServiceReference而不是一個(gè)實(shí)際服務(wù)實(shí)現(xiàn)對(duì)象。ServiceReference是一個(gè)輕量級(jí)的句柄對(duì)象。它可以作為一個(gè)參數(shù)任意的進(jìn)行傳輸,并可以用來獲取實(shí)際服務(wù)的屬性。比如,服務(wù)在進(jìn)行服務(wù)注冊(cè)時(shí)傳入的屬性集。實(shí)際上,使用一個(gè)ServiceReference對(duì)象并不會(huì)導(dǎo)致OSGi框架增加實(shí)際服務(wù)的引用計(jì)數(shù)。你可以認(rèn)為ServiceReference承擔(dān)了類似java反射API中WeakReference類的角色。
            在addingSevice方法中首先是通過ServiceReference獲取一個(gè)實(shí)際的MovieFinder服務(wù)對(duì)象。這又要使用BundleContext:任何與OSGi框架的交互都是通過BundleContext接口進(jìn)行的。幸運(yùn)的是,ServiceTracker定義了一個(gè)我們可以直接使用的類型為BundleContext的保護(hù)成員context。
            接下來我們使用bindFinder方法把獲取到的MovieFinder服務(wù)綁定到MovieListerImpl。然后就把MovieListerImpl也注冊(cè)為一個(gè)使用MovieLister接口的服務(wù)。注意,我們只在MovieListerImpl沒有注冊(cè)為服務(wù)的情況下才進(jìn)行服務(wù)注冊(cè)。這是因?yàn)椋覀儸F(xiàn)在希望一個(gè)MovieLister使用多個(gè)MovieFinder服務(wù)。
            最后,addingSevice方法返回一個(gè)Object。問題是,我們到底要返回一個(gè)什么對(duì)象呢?實(shí)際上,ServiceTracker并不關(guān)心addingSevice返回什么對(duì)象。addingSevice可以返回任何我們需要的對(duì)象。重點(diǎn)是,addingSevice返回的對(duì)象會(huì)在調(diào)用modifiedService或者removedService的時(shí)候重新傳回。這也就是在removedService方法第一行見到的直接對(duì)object進(jìn)行的MovieFinder類型轉(zhuǎn)換。接著就使用此對(duì)象進(jìn)行它與MovieLister的解綁,同時(shí)我們根據(jù)可以使用的MovieFinder服務(wù)是否為零進(jìn)行MovieLister服務(wù)的反注冊(cè)。
            一般說來,任何在addingSevice中的行為都應(yīng)該在removedService中進(jìn)行狀態(tài)清理。因此,我們應(yīng)該在addingSevice中返回任何有助于我們需要在removedService中進(jìn)行清理工作的對(duì)象。返回的可以是一個(gè)HashMap中的鍵值,也可以是一個(gè)ServiceRegistration對(duì)象,或者是一個(gè)實(shí)際的服務(wù)對(duì)象。
            在removedService的最后一步,我們解綁了在addingSevice中綁定的服務(wù)。這一點(diǎn)十分重要,這個(gè)操作使得服務(wù)注冊(cè)表減少服務(wù)使用計(jì)數(shù)值,從而可以使計(jì)數(shù)值為零進(jìn)行服務(wù)釋放。
            現(xiàn)狀我們還需要一個(gè)模塊啟動(dòng)器osgitut/movies/impl/TrackingMovieListerActivator.java:
            package osgitut.movies.impl;
            import org.osgi.framework.*;
            public class TrackingMovieListerActivator implements BundleActivator {
            private MovieFinderTracker tracker;
            public void start(BundleContext context) { tracker = new MovieFinderTracker(context); tracker.open(); }
            public void stop(BundleContext context) { tracker.close(); }
            }
            不要忘記TrackingMovieLister.mf:
            Manifest-Version: 1.0
            Bundle-ManifestVersion: 2
            Bundle-Name: Tracking Movie Lister
            Bundle-SymbolicName: TrackingMovieLister
            Bundle-Version: 1.0.0
            Bundle-Activator: osgitut.movies.impl.TrackingMovieListerActivator
            Import-Package: org.osgi.framework,
            org.osgi.util.tracker,
            osgitut.movies;version="[1.0.0,2.0.0)"
            按照前幾節(jié)的步驟進(jìn)行模塊的構(gòu)建和部署。完成后,執(zhí)行以下的步驟驗(yàn)證運(yùn)行邏輯是否正確:

            • 啟動(dòng) BasicMovieFinder和TrackingMovieLister。檢查是否出現(xiàn)"MovieLister:added a finder"消息。
            • 停止 BasicMovieFinder。檢查是否出現(xiàn)"MovieLister: removed a finder"消息。
            • 執(zhí)行service命令,檢查是否MovieLister服務(wù)已經(jīng)反注冊(cè)。
              本節(jié)內(nèi)容展示的是一個(gè)十分強(qiáng)大的技術(shù)。我們把一個(gè)服務(wù)的生命周期與另一個(gè)服務(wù)(實(shí)際上是多個(gè))的生命周期綁定。進(jìn)一步,我們可以綁定MovieLister和第三個(gè)服務(wù)并可以依此類推。這樣我們就構(gòu)建了一個(gè)服務(wù)依賴圖,這種依賴圖與其他一些靜態(tài)IOC容器構(gòu)建的beans圖不同。OSGi的服務(wù)依賴圖更健壯,self-healing并具有自動(dòng)調(diào)整能力。
              另一方面,這一節(jié)的代碼還存在一些問題。MovieFinderTracker和TrackingMovieListerActivator類會(huì)形成整個(gè)系統(tǒng)負(fù)載的瓶頸。當(dāng)我們打算進(jìn)行這個(gè)系統(tǒng)的擴(kuò)展時(shí),我們還不得不重復(fù)進(jìn)行這些基本雷同的編碼工作。因此在下一節(jié)我們講解如何使用幾行XML語句完成這些重復(fù)和繁瑣的工作。
              在下一節(jié)不再在一個(gè)目錄下使用命令行構(gòu)建我們的系統(tǒng)。本文的目標(biāo)是展示OSGi是一個(gè)簡單而強(qiáng)大的框架,但并不妨礙我們使用更高效的方式比如eclipse的IDE完成這些工作。

            ==================================================================================================
            OSGi開發(fā)起步(Getting Started with OSGi)-7服務(wù)聲明介紹
            7 服務(wù)聲明介紹:Introducing Declarative Services
            服務(wù)聲明(簡稱DS)規(guī)范是OSGi最新的內(nèi)容之一。增加此服務(wù)規(guī)范是為了在多個(gè)模塊之間有效協(xié)調(diào)服務(wù)。這種服務(wù)協(xié)調(diào)工作本身并不復(fù)雜(如上節(jié)我們展示的那樣),但是這項(xiàng)工作總是面臨那些惱人的代碼。而且具體實(shí)現(xiàn)時(shí)還必須注意處理各種線程問題。總的來說,模塊服務(wù)協(xié)作這件工作是一件極易出錯(cuò)的工作。
            最早試圖解決服務(wù)協(xié)作問題的是一個(gè)稱為Service Binder的工具。這個(gè)工具由Humberto Cervantes 和 Richard Hall開發(fā)。Service Binder設(shè)計(jì)了自動(dòng)服務(wù)依賴管理特性。這個(gè)特性允許開發(fā)者專注于服務(wù)。服務(wù)之間的協(xié)作和依賴通過聲明實(shí)現(xiàn),而所有的聲明都使用XML完成。
            服務(wù)聲明規(guī)范是由Service Binder發(fā)展而來,并成為了OSGi4.0標(biāo)準(zhǔn)的一部分。
            前面幾節(jié)的示例都是使用的命令行完成所有的編碼、配置、構(gòu)建和運(yùn)行工作。從這節(jié)開始則使用EclipseSDK。需要明確的是,本文中介紹的任何技術(shù)和方法并不依賴于Eclipse。盡管Eclipse可以很好的幫助我們完成很多工作,但同樣的我們也可以使用NetBeans,Intellij或者vi來完成這些工作。
            首先我們需要下載Equinox的服務(wù)聲明實(shí)現(xiàn)包。可以在Equinox的下載頁面下載最新的org.eclipse.equniox.ds_x.x.x_xxxx.jar。下載完成后,把下載的jar復(fù)制到Eclipse的plugins目錄,并重新啟動(dòng)Eclipse。
            現(xiàn)在重新創(chuàng)建一個(gè)模塊。在Eclipse中使用插件工程向?qū)В?br />l 在主菜單中選擇File->New->Project。
            l 選擇Plug-in Project并點(diǎn)擊Next。
            l 輸入工程的名稱:SampleExporter。
            l 在最下面的"This plug-in is targeted to run with"文字下選擇"OSGi framework"以及下拉框中的"standard"。這個(gè)是個(gè)基本步驟:這一步可以防止我們使用OSGi框架實(shí)現(xiàn)之外的特性。
            l 選擇"Next"。在向?qū)э@示的下一個(gè)對(duì)話框中選擇"Generate an activator..."。如果選擇,點(diǎn)擊"Finish"完成創(chuàng)建工作。
            我們現(xiàn)在生成了一個(gè)空的模塊工程。接下來的工作就是在這個(gè)工程中增加代碼。為了使得示例盡量簡單,我們使用在每個(gè)java運(yùn)行環(huán)境中都存在的java.lang.Runnable接口提供服務(wù)。現(xiàn)在我們可以使用Eclipse的復(fù)制代碼特性創(chuàng)建包和源文件。復(fù)制下面的代碼,在Eclipse中選擇SampleExporter工程的src目錄,并執(zhí)行Edit->Paste。
            package org.example.ds;
            public class SampleRunnable implements Runnable {
            public void run() { System.out.println("Hello from SampleRunnable"); }
            }
            接下來我們需要?jiǎng)?chuàng)建一個(gè)XML文件。使用這個(gè)文件聲明SampleRunnable是一個(gè)服務(wù)。在工程的頂級(jí)目錄下創(chuàng)建一個(gè)名稱為OSGI-INF的目錄,新建一個(gè)名稱為samplerrunnable.xml的文件然后復(fù)制下面的文本到文件中。
            <?xml version="1.0"?>
            <component name="samplerunnable">
            <implementation class="org.example.ds.SampleRunnable"/>
            <service>
            <provide interface="java.lang.Runnable"/>
            </service>
            </component>
            這是一個(gè)最簡單的DS聲明。這個(gè)聲明表示有一個(gè)名稱為"samplerunnable"的組件,這個(gè)組件使用接口java.lang.Runnable向服務(wù)注冊(cè)器提供一個(gè)服務(wù)。同時(shí)還說明了這個(gè)模塊是由org.example.da.SampleRunnable類實(shí)現(xiàn)的。
            最后就是要告知服務(wù)聲明runtime這個(gè)XML文件的位置。通知是通過在組件的MANIFEST.MF文件中增加一項(xiàng)說明實(shí)現(xiàn)的。在Eclipse中打開元文件頁面,定位到"MANIFEST.MF"文件窗格。在這個(gè)文件內(nèi)容的最后增加一行:
            Sevice-Component: OSGI-INF/samplerunnable.xml
            然后保存文件。在進(jìn)行下一步之前,我們可以運(yùn)行Equinox檢查一下我們的模塊是否可以正常運(yùn)行。在Run菜單中選擇"Run...",當(dāng)運(yùn)行對(duì)話框打開后選擇左邊樹中的"Equniox OSGi Framework"并點(diǎn)擊New按鈕。如果你是一個(gè)熟練的Eclipse用戶但又沒有做過OSGi開發(fā),這個(gè)對(duì)話框相對(duì)來說有些復(fù)雜(或者奇怪)。第一個(gè)標(biāo)識(shí)為Bundles的窗格允許我們選擇哪些Bundles是運(yùn)行環(huán)境需要包含的,是否需要啟動(dòng)等。為了使用一個(gè)簡化的運(yùn)行環(huán)境,取消當(dāng)前所有選中的Bundles,并重新選擇以下的Bundles:
            SampleExporter (underneath Workspace)
            org.eclipse.equinox.ds (underneath Target Platform)
            除此之外我們還有一些依賴的Bundles,這可以使用Add Required Bundles功能把這些依賴的Bundles添加進(jìn)來。最好把"Validate bundles automatically prior to launching"也選中。最后,點(diǎn)擊Run運(yùn)行這個(gè)工程。一段時(shí)間后osgi>提示符就出現(xiàn)在Eclipse控制臺(tái)。OSGi框架運(yùn)行起來了,我們可以使用前幾節(jié)的命令與框架進(jìn)行交互。
            我們開發(fā)的服務(wù)注冊(cè)了沒有?使用services命令檢查一下。在命令的輸出結(jié)果中,我們會(huì)發(fā)現(xiàn)類似的信息:
            {java.lang.Runnable}={component.name=samplerunnable, component.id=1, service.id=22}
            Registered by bundle: initial@reference:file:..../SampleExporter/ [5]
            No bundles using service.
            看來服務(wù)的確注冊(cè)了。需要指出的是:服務(wù)是由我們的模塊注冊(cè)的,而不是由聲明服務(wù)模塊注冊(cè)的,實(shí)際上,In fact DS has registered it on behalf of our bundle。還需要注意的是,服務(wù)的使用者并不需要為了使用這個(gè)服務(wù)而做什么特別的處理,它甚至不必知道我們使用了服務(wù)聲明。這些服務(wù)使用者也可以使用DS或者直接使用OSGi代碼來訪問服務(wù)。
            還有一點(diǎn)就是在上面的模塊代碼中并沒有出現(xiàn)與OSGi標(biāo)準(zhǔn)相關(guān)的java代碼。也就是說,本節(jié)我們編寫的是一個(gè)POJO類。這也是DS的一個(gè)主要特點(diǎn)。

            =================================================================================================================
            OSGi開發(fā)起步(Getting Started with OSGi)-8(1)服務(wù)聲明和依賴

            8服務(wù)聲明和依賴:Declarative Services and Dependencies
            上節(jié)對(duì)DS做了初步的介紹。本節(jié)介紹如何使用DS聲明的服務(wù)。上節(jié)我們使用java.lang.Runnable接口使用DS注冊(cè)了一個(gè)服務(wù),本節(jié)介紹如何創(chuàng)建一個(gè)依賴于這個(gè)服務(wù)的組件。
            正如介紹的那樣,DS規(guī)范使得開發(fā)者只需關(guān)注應(yīng)用自身的邏輯,避免如前幾節(jié)需要編寫的那些OSGi服務(wù)"粘合"代碼。使用DS,我們只要簡單的編寫代碼就可以了。但在之間編碼之前,我們還得創(chuàng)建一個(gè)Eclipse工程。使用上節(jié)的工程創(chuàng)建步驟創(chuàng)建一個(gè)名稱為SampleImporter的工程。
            復(fù)制下面的代碼并粘貼到新建工程的src目錄:
            package org.example.ds;
            import org.eclipse.osgi.framework.console.CommandInterpreter;
            import org.eclipse.osgi.framework.console.CommandProvider;
            public class SampleCommandProvider1 implements CommandProvider {

            private Runnable runnable;

            public synchronized void setRunnable(Runnable r) { runnable = r; }
            public synchronized void unsetRunnable(Runnable r) { runnable = null; }
            public synchronized void _run(CommandInterpreter ci) {
            if(runnable != null) { runnable.run(); } else { ci.println("Error, no Runnable available"); }
            }
            public String getHelp() { return "\trun - execute a Runnable service"; }
            }
            這個(gè)類實(shí)現(xiàn)了CommandProvider接口。啟動(dòng)Equinox后在osgi>提示符可以執(zhí)行一系列的命令。CommandProvider接口用于擴(kuò)充這些命令。編寫CommandProvider的原因是為了提供一種方便的交互式測試代碼方式。在IBM developerWorks上Chirs Aniszczyk的一篇文章詳細(xì)討論了CommandProvider。
            在上面的代碼中我們注意到?jīng)]有任何OSGi方法調(diào)用,實(shí)際上我們并不需要從org.osgi.*導(dǎo)入任何類。我們依賴的那個(gè)服務(wù)(在本示例中是一個(gè)java.lang.Runnable實(shí)例)是通過setRunnable方法提供的,并且通過unsetRunnable移除。我們可以認(rèn)為這是某種形式的依賴注射。
            另外兩個(gè)方法getHelp和_run是CommandProvider接口定義的實(shí)現(xiàn)方法。_run方法名的下劃線有些滑稽,但這也只是Equinox控制臺(tái)API的一項(xiàng)odd特性,與OSGi的DS無關(guān)。在Equinox控制臺(tái)中規(guī)定具有下劃線的方法名稱是一個(gè)控制臺(tái)命令。因此定義一個(gè)_run方法就相當(dāng)于在Equinox控制臺(tái)中增加了一個(gè)run命令。另外一個(gè)需要注意的情況是,在上面這個(gè)類的實(shí)現(xiàn)代碼中仔細(xì)的處理了runnable屬性的線程安全特性。由于OSGi本質(zhì)上是多線程的,因此在OSGi中線程安全是十分重要的。Frankly我們應(yīng)該總是編寫線程安全的代碼。
            和以前一樣,我們還是需要一個(gè)包含DS聲明的XML文件。復(fù)制下面的文本到這個(gè)插件工程的OSGI-INF/commandprovider1.xml文件中:
            <?xml version="1.0"?>
            <component name="commandprovider1">
            <implementation class="org.example.ds.SampleCommandProvider1"/>
            <service>
            <provide interface="org.eclipse.osgi.framework.console.CommandProvider"/>
            </service>
            <reference name="RUNNABLE"
            interface="java.lang.Runnable"
            bind="setRunnable"
            unbind="unsetRunnable"
            cardinality="0..1"
            policy="dynamic"/>
            </component>
            編寫完成了XML文件后還要把下面這行加入到bundle元文件的最后:
            Service-Component: OSGI-INF/commandprovider1.xml

            ===========================================================================================================
            OSGi開發(fā)起步(Getting Started with OSGi)-8(2)服務(wù)聲明和依賴

            在XML文件中有兩個(gè)我們已經(jīng)介紹過的元素項(xiàng):implementation和service。Implementation提供了組件的實(shí)現(xiàn)類,而service則告知DS把這個(gè)組件注冊(cè)為一個(gè)服務(wù)。這個(gè)示例中的服務(wù)使用CommandProvider接口注冊(cè)。這個(gè)接口使得Equinox控制臺(tái)知曉這個(gè)CommandProvider的存在。
            文件中還有一個(gè)以前沒有介紹的reference元素項(xiàng)。reference用于向DS聲明組件與一個(gè)服務(wù)之間的依賴關(guān)系。其中的name屬性為任意字符串。名稱屬性用于對(duì)依賴進(jìn)行命名(我們并不需要關(guān)心它的用途-實(shí)際上只是一個(gè)可讀性的標(biāo)識(shí))。本示例中使用了組件依賴的接口名稱來命名。bind屬性指定一個(gè)實(shí)現(xiàn)類的方法名。當(dāng)一個(gè)服務(wù)可用時(shí),也就是一個(gè)Runnable的服務(wù)在服務(wù)注冊(cè)表中注冊(cè)時(shí),DS就會(huì)調(diào)用此方法。DS使用這個(gè)方法傳入這個(gè)新服務(wù)對(duì)象的引用提供給我們的組件使用。同樣的,unbind屬性是當(dāng)一個(gè)服務(wù)不可用時(shí),DS回調(diào)的方法。
            Cardinality是一個(gè)能顯示DS真正能力的屬性。這個(gè)屬性控制服務(wù)的依賴是可選的還是強(qiáng)制的、單依賴還是多依賴。這個(gè)屬性可能的值為:
            0..1: optional and singular, "zero or one"
            1..1: mandatory and singular, "exactly one"
            0..n: optional and multiple, "zero to many"
            1..n: mandatory and multiple, "one to many" or "at least one"
            在這個(gè)示例中我們使用的是0.1。也就是能cope那些不可用的服務(wù)。回頭看以下_run方法中的代碼,我們就會(huì)發(fā)現(xiàn)為了處理這種情況而進(jìn)行的null檢查代碼。
            現(xiàn)在還是來看看這個(gè)模塊運(yùn)行的情況。如果上節(jié)安裝的SampleExporter還有效,在osgi控制臺(tái)輸入run命令會(huì)得到如下輸出:
            Hello from SampleRunnable
            這證實(shí)了我們已經(jīng)成功將上節(jié)編寫的Runnable服務(wù)成功導(dǎo)入。接著運(yùn)行stop命令停止SampleExporter模塊。再運(yùn)行run命令得到的輸出是:
            Error,no Runnable available
            這個(gè)輸出表明DS發(fā)現(xiàn)了Runnable服務(wù)已經(jīng)停止并調(diào)用了unsetRunnable方法。
            再看一下cardinality屬性,如果把它的值改為1.1(改可選為強(qiáng)制)會(huì)怎樣?更改這個(gè)屬性后重新運(yùn)行Equinox。在SampleExporter啟動(dòng)后運(yùn)行run命令會(huì)得到與前面相同的結(jié)果。但是當(dāng)停止SampleExporter后運(yùn)行run,OSGi輸出的是一個(gè)與前面不同的錯(cuò)誤消息。實(shí)際上,Equinox控制臺(tái)輸出的是一個(gè)幫助消息。這個(gè)消息是對(duì)一個(gè)不可識(shí)別命令的標(biāo)準(zhǔn)錯(cuò)誤輸出消息。這也就是說我們這個(gè)命令執(zhí)行服務(wù)已經(jīng)被DS反注冊(cè)了。當(dāng)一個(gè)組件的強(qiáng)制依賴不能滿足是,DS強(qiáng)制停止這個(gè)組件并反注冊(cè)組件提供的任何服務(wù)。這就是Equinox不能識(shí)別run命令的原因。
            如此方便的服務(wù)裝配是選擇DS的最佳原因。還記得使用ServiceTracker時(shí),我們處理這種情況時(shí)不得不編寫的那些代碼嗎?
            還有一個(gè)值得關(guān)注的屬性是ploicy。這個(gè)屬性的值定義為static和dynamic之一。這兩個(gè)狀態(tài)值用于表明組件是否支持服務(wù)的動(dòng)態(tài)切換。如果不支持,DS就沒有必要每次在目標(biāo)服務(wù)變化后停止原組件并創(chuàng)建一個(gè)新組件實(shí)例。停止后新建這個(gè)過程可是一個(gè)重負(fù)載性的過程,所以我們的建議最好還是盡可能的編碼一個(gè)支持動(dòng)態(tài)切換的組件。不過DS的這個(gè)屬性缺省是靜態(tài)的,因此在組件開發(fā)時(shí)還得手動(dòng)的調(diào)整此屬性為動(dòng)態(tài)。
            上面我們?cè)嚵?.1和1.1,但是沒有講解多對(duì)多(0.n)的依賴。在服務(wù)注冊(cè)表中不會(huì)僅僅只有一個(gè)Runnable服務(wù)注冊(cè)。如果我們只是需要綁定其中的一個(gè)服務(wù),那么我們所綁定的將是其中的任意一個(gè)服務(wù)。看來我們還需要一個(gè)runall命令用來運(yùn)行服務(wù)注冊(cè)表中已經(jīng)注冊(cè)的所有Runnable服務(wù)。
            但就對(duì)上面那個(gè)組件來說,我們把cardinality屬性更改為"0.n"時(shí)會(huì)出現(xiàn)什么情況?其實(shí),更改后這個(gè)服務(wù)還是可以運(yùn)行:只不過setRunnable不會(huì)只調(diào)用一次,DS會(huì)在每個(gè)Runnable服務(wù)注冊(cè)是調(diào)用此方法一次。問題是多次調(diào)用會(huì)導(dǎo)致上面組件處理邏輯的混亂。因此為應(yīng)對(duì)更通常的0.n的情況,就不能只是使用單個(gè)的Runnable成員,而是需要一個(gè)Runnable集合成員。下面是根據(jù)以上分析修改的類,把這個(gè)類的代碼復(fù)制到工程的src目錄下:
            package org.example.ds;
            import java.util.*;
            import org.eclipse.osgi.framework.console.CommandInterpreter;
            import org.eclipse.osgi.framework.console.CommandProvider;
            public class SampleCommandProvider2 implements CommandProvider {
            private List<Runnable> runnables =
            Collections.synchronizedList(new ArrayList<Runnable>());

            public void addRunnable(Runnable r) { runnables.add(r); }
            public void removeRunnable(Runnable r) { runnables.remove(r); }
            public void _runall(CommandInterpreter ci) {
            synchronized(runnables) {
            for(Runnable r : runnables) { r.run(); }
            }
            }
            public String getHelp() { return "\trunall - Run all registered Runnables"; }
            }
            再創(chuàng)建OSGI-INF/commandprovider2.xml:
            <?xml version="1.0"?>
            <component name="commandprovider2">
            <implementation class="org.example.ds.SampleCommandProvider2"/>
            <service>
            <provide interface="org.eclipse.osgi.framework.console.CommandProvider"/>
            </service>
            <reference name="RUNNABLE"
            interface="java.lang.Runnable"
            bind="addRunnable"
            unbind="removeRunnable"
            cardinality="0..n"
            policy="dynamic"/>
            </component>
            最后是把這個(gè)文件按照如下方式加到元文件中:
            Service-Component: OSGI-INF/commandprovider1.xml,
            OSGI-INF/commandprovider2.xml
            上面的XML文件中的DS聲明與以前的基本一樣,只是修改了bind和unbind方法的名稱,以及cardinality的值(0.n)。現(xiàn)在就可以試著運(yùn)行這個(gè)新的runall命令看看結(jié)果如何啦。欣賞完輸出結(jié)果后,還可以試著更改cardinality為1.n看看有什么不一樣的事情發(fā)生!
            本節(jié)是這個(gè)講解的最后一節(jié)。通過這幾節(jié)的講解我們發(fā)現(xiàn)OSGi并不神秘并且相當(dāng)易用。接下來的事情就是不斷的開發(fā)和使用它。不過,我還是強(qiáng)烈的建議在開始和進(jìn)行過程中一定認(rèn)真的學(xué)習(xí)一下OSGi R4 core和service規(guī)范。

            =======================================================================================

            posted @ 2012-05-15 17:07 RTY 閱讀(3138) | 評(píng)論 (1)編輯 收藏

            什么叫IOC(編程術(shù)語)
            IoC就是Inversion of Control,控制反轉(zhuǎn)。在Java開發(fā)中,IoC意味著將你設(shè)計(jì)好的類交給系統(tǒng)去控制,而不是在你的類內(nèi)部控制。這稱為控制反轉(zhuǎn)。
            下面我們以幾個(gè)例子來說明什么是IoC
            假設(shè)我們要設(shè)計(jì)一個(gè)Girl和一個(gè)Boy類,其中Girl有kiss方法,即Girl想要Kiss一個(gè)Boy。那么,我們的問題是,Girl如何能夠認(rèn)識(shí)這個(gè)Boy?
            在我們中國,常見的MM與GG的認(rèn)識(shí)方式有以下幾種 
            1 青梅竹馬; 2 親友介紹; 3 父母包辦 
            那么哪一種才是最好呢?
            青梅竹馬:Girl從小就知道自己的Boy。
            public class Girl { 
                void kiss(){ 
                     Boy boy = new Boy(); 
                } 
            }
            然而從開始就創(chuàng)建的Boy缺點(diǎn)就是無法在更換。并且要負(fù)責(zé)Boy的整個(gè)生命周期。如果我們的Girl想要換一個(gè)怎么辦?(嚴(yán)重不支持Girl經(jīng)常更換Boy,#_#)
            親友介紹:由中間人負(fù)責(zé)提供Boy來見面
            public class Girl { 
                 void kiss(){ 
                     Boy boy = BoyFactory.createBoy(); 
                 } 
            }
            親友介紹,固然是好。如果不滿意,盡管另外換一個(gè)好了。但是,親友BoyFactory經(jīng)常是以Singleton的形式出現(xiàn),不然就是,存在于Globals,無處不在,無處不能。實(shí)在是太繁瑣了一點(diǎn),不夠靈活。我為什么一定要這個(gè)親友摻和進(jìn)來呢?為什么一定要付給她介紹費(fèi)呢?萬一最好的朋友愛上了我的男朋友呢?
            父母包辦:一切交給父母,自己不用費(fèi)吹灰之力,只需要等著Kiss就好了。
            public class Girl { 
                 void kiss(Boy boy){ 
                     // kiss boy 
                     boy.kiss(); 
                 } 
            }
            Well,這是對(duì)Girl最好的方法,只要想辦法賄賂了Girl的父母,并把Boy交給他。那么我們就可以輕松的和Girl來Kiss了。看來幾千年傳統(tǒng)的父母之命還真是有用哦。至少Boy和Girl不用自己瞎忙乎了。
            這就是IOC,將對(duì)象的創(chuàng)建和獲取提取到外部。由外部容器提供需要的組件。 
            我們知道好萊塢原則:“Do not call us, we will call you.” 意思就是,You, girlie, do not call the boy. We will feed you a boy。
            我們還應(yīng)該知道依賴倒轉(zhuǎn)原則即 Dependence Inversion Princinple,DIP
            Eric Gamma說,要面向抽象編程。面向接口編程是面向?qū)ο蟮暮诵摹?/span>
            組件應(yīng)該分為兩部分,即 Service, 所提供功能的聲明 Implementation, Service的實(shí)現(xiàn)
            好處是:多實(shí)現(xiàn)可以任意切換,防止 “everything depends on everything” 問題.即具體依賴于具體。 
            所以,我們的Boy應(yīng)該是實(shí)現(xiàn)Kissable接口。這樣一旦Girl不想kiss可惡的Boy的話,還可以kiss可愛的kitten和慈祥的grandmother。
            二、IOC的type
             
            IoC的Type指的是Girl得到Boy的幾種不同方式。我們逐一來說明。
            IOC type 0:不用IOC 
            public class Girl implements Servicable { 
                 private Kissable kissable; 
                 public Girl() { 
                     kissable = new Boy(); 
                 } 
                 public void kissYourKissable() { 
                     kissable.kiss(); 
                 } 
            }
            Girl自己建立自己的Boy,很難更換,很難共享給別人,只能單獨(dú)使用,并負(fù)責(zé)完全的生命周期。
            IOC type 1,先看代碼:代碼
            public class Girl implements Servicable { 
                Kissable kissable; 
                 public void service(ServiceManager mgr) { 
                     kissable = (Kissable) mgr.lookup(“kissable”); 
                } 
                 public void kissYourKissable() { 
                     kissable.kiss(); 
                 } 
            }
            這種情況出現(xiàn)于Avalon Framework。一個(gè)組件實(shí)現(xiàn)了Servicable接口,就必須實(shí)現(xiàn)service方法,并傳入一個(gè)ServiceManager。其中會(huì)含有需要的其它組件。只需要在service方法中初始化需要的Boy。
            另外,J2EE中從Context取得對(duì)象也屬于type 1。它依賴于配置文件。
            IOC type 2:
            public class Girl { 
                 private Kissable kissable; 
                 public void setKissable(Kissable kissable) { 
                     this.kissable = kissable; 
                } 
                public void kissYourKissable() { 
                     kissable.kiss(); 
                } 
            }
            Type 2出現(xiàn)于Spring Framework,是通過JavaBean的set方法來將需要的Boy傳遞給Girl。它必須依賴于配置文件。
            IOC type 3:
            public class Girl { 
                private Kissable kissable; 
                public Girl(Kissable kissable) { 
                     this.kissable = kissable; 
                } 
                 public void kissYourKissable() { 
                     kissable.kiss(); 
                } 
            }
            這就是PicoContainer的組件 。通過構(gòu)造函數(shù)傳遞Boy給Girl 
            PicoContainer container = new DefaultPicoContainer(); 
            container.registerComponentImplementation(Boy.class); 
            container.registerComponentImplementation(Girl.class); 
            Girl girl = (Girl) container.getComponentInstance(Girl.class); 
            girl.kissYourKissable();
            參考資料 
            1 http://www.picocontainer.org/presentations/JavaPolis2003.ppt 
              http://www.picocontainer.org/presentations/JavaPolis2003.pdf 
            2 DIP, Robert C Martin, Bob大叔的優(yōu)秀論文 
               http://www.objectmentor.com/resources/articles/dip.pdf 
            3 Dependency Injection 依賴注射,Matrin Fowler對(duì)DIP的擴(kuò)展 
               http://www.martinfowler.com/articles/injection.html 
            4 IOC框架 
            PicoContainer 優(yōu)秀的IOC框架 
            http://picocontainer.org/ 
            Avalon 
            http://avalon.apache.org/ 
            Spring Framework 
            http://www.springframework.org/ 
            HiveMind 
            http://jakarta.apache.org/commons/hivemind 
            分類: C#

            什么叫IOC(編程術(shù)語




            posted @ 2012-05-15 13:11 RTY 閱讀(817) | 評(píng)論 (0)編輯 收藏

            Shared by eric 
            mac osx的dyld是挺煩的

            所謂dylib,就是bsd風(fēng)格的動(dòng)態(tài)庫。基本可以認(rèn)為等價(jià)于windows的dll和linux的so。mac基于bsd,所以也使用的是dylib。

            如果你需要引用一個(gè)第三方的dylib,在xcode下編譯出cocoa程序,在本機(jī)上運(yùn)行是不會(huì)出問題的。但是發(fā)布出來,給其他用戶用,就可能出問題。因?yàn)橛脩舨灰欢ㄓ羞@個(gè)庫。

            這個(gè)問題給我造成了相當(dāng)?shù)睦_,我到現(xiàn)在也沒找到正規(guī)的方法。但是我確實(shí)解決了這個(gè)問題,雖然方法不一定正宗。不管怎么說,寫下來,如果暫時(shí)沒有更好的辦法,那么先這樣做。如果誰有更好的辦法,也請(qǐng)一定不吝留言或郵件給我

            我的辦法是這樣的:


            1 otool -L yourapp.app/Contents/MacOS/yourapp

            這一步的意思是對(duì)你編譯出的app使用otool命令,以便獲得依賴哪些dylib的信息。注意這個(gè)路徑。cocoa的app在命令行下表現(xiàn)為目錄。所有相關(guān)的東西都在里面。
            結(jié)果如下所示:
            yourapp.app/Contents/MacOS/yourapp:
            /System/Library/Frameworks/Cocoa.framework/Versions/A/Cocoa (compatibility version 1.0.0, current version 12.0.0)
            /usr/local/lib/lib01.dylib (compatibility version 0.0.0, current version 0.1.0)
            /usr/local/lib/lib02.dylib (compatibility version 0.0.0, current version 0.1.0)
            /usr/local/lib/lib03.dylib (compatibility version 0.0.0, current version 0.1.0)
            /usr/lib/libgcc_s.1.dylib (compatibility version 1.0.0, current version 1.0.0)
            /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 111.0.0)
            /usr/lib/libobjc.A.dylib (compatibility version 1.0.0, current version 227.0.0)
            /System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation (compatibility version 150.0.0, current version 476.0.0)
            /System/Library/Frameworks/AppKit.framework/Versions/C/AppKit (compatibility version 45.0.0, current version 949.0.0)
            /System/Library/Frameworks/Foundation.framework/Versions/C/Foundation (compatibility version 300.0.0, current version 677.12.0)

            注意我標(biāo)紅的地方。假如lib01,lib02,lib03是本程序引用的第三方庫,那么在這個(gè)程序里面,他們的引用地址是位于/usr/local/lib上的。這是開發(fā)機(jī)上的安裝情況。而使用這個(gè)程序的客戶機(jī)未必安裝這些東西,所以程序就要出錯(cuò)。

            顯然,我們需要做2件事。a 把這些庫附帶在app上 b 把他們的引用地址修改到正確的位置。

            2 mkdir yourapp.app/Contents/dylib

            在編譯出來的app中,創(chuàng)建dylib目錄

            然后把所有需要的庫復(fù)制過去

            cp /usr/local/lib/lib01.dylib yourapp.app/Contents/dylib/
            cp /usr/local/lib/lib02.dylib yourapp.app/Contents/dylib/
            cp /usr/local/lib/lib03.dylib yourapp.app/Contents/dylib/

            3 install_name_tool -change /usr/local/lib/lib01.dylib @loader_path/../dylib/lib01.dylib "yourapp.app/Contents/MacOS/yourapp"

            install_name_tool 是蘋果提供的用來修改dylib安裝名稱的命令。這個(gè)命令執(zhí)行之后,再用otool -L 就可以看到變化了

            yourapp.app/Contents/MacOS/yourapp:
            /System/Library/Frameworks/Cocoa.framework/Versions/A/Cocoa (compatibility version 1.0.0, current version 12.0.0)
            @loader_path/../dylib/lib01.dylib (compatibility version 0.0.0, current version 0.1.0)
            /usr/local/lib/lib02.dylib (compatibility version 0.0.0, current version 0.1.0)
            /usr/local/lib/lib03.dylib (compatibility version 0.0.0, current version 0.1.0)
            /usr/lib/libgcc_s.1.dylib (compatibility version 1.0.0, current version 1.0.0)
            /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 111.0.0)
            /usr/lib/libobjc.A.dylib (compatibility version 1.0.0, current version 227.0.0)
            /System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation (compatibility version 150.0.0, current version 476.0.0)
            /System/Library/Frameworks/AppKit.framework/Versions/C/AppKit (compatibility version 45.0.0, current version 949.0.0)
            /System/Library/Frameworks/Foundation.framework/Versions/C/Foundation (compatibility version 300.0.0, current version 677.12.0)

            注意標(biāo)紅的位置。已經(jīng)變化了。@loader_path 指的是應(yīng)用程序運(yùn)行的位置,也就是yourapp.app/Contents/MacOS/yourapp,所以要用一個(gè)..,以便定位到第2步創(chuàng)建的dylib目錄。
            重復(fù)這個(gè)命令,修改lib02,lib03


            4 otool -L yourapp.app/Contents/dylib/*.dylib

            繼續(xù)用otool 來檢查dylib下面使用的第三方庫是否還有其他依賴,install_name是否正確,重復(fù)1,2,3的步驟,把所需要的dylib復(fù)制過來,修改install_name。

            如果都改對(duì)了,那么這個(gè)app就附帶上了dylib,可以在其他機(jī)器上正確運(yùn)行了,不用非要尋找/usr/local/lib下面的庫了。


            剛才我們修改的結(jié)果是一個(gè)build的結(jié)果。當(dāng)然,每次build都這么折騰一下很麻煩。所以繼續(xù)這樣做:

            1 前面的步驟得到了一個(gè)完整的dylib目錄。把這個(gè)dylib復(fù)制一份備用。比如放在你的xcode項(xiàng)目下面。

            2 編寫一個(gè)腳本:

            mkdir "$TARGET_BUILD_DIR/$PRODUCT_NAME.app/Contents/dylib"
            cp -f /your/path/to/xcode_project_name/dylib/*.dylib "$TARGET_BUILD_DIR/$PRODUCT_NAME.app/Contents/dylib/"

            install_name_tool -change /usr/local/lib/lib01.dylib @loader_path/../dylib/lib01.dylib "$TARGET_BUILD_DIR/$PRODUCT_NAME.app/Contents/MacOS/$PRODUCT_NAME"
            (用這個(gè)格式重復(fù)前面對(duì)app使用過的dylib)

            3 在xcode中,展開targets節(jié)點(diǎn),右鍵點(diǎn)工程名稱,在菜單中選Add->New Build Phasa->New Run Script Build Phasa,在打開的對(duì)話框里面,把剛才的腳本貼進(jìn)去。如圖所示。

            這個(gè)腳本會(huì)在build之后自動(dòng)運(yùn)行。不過我這里有個(gè)奇怪的問題,如果Shell里面寫了/bin/sh,會(huì)報(bào)告找不到這個(gè)文件(實(shí)際上存在),而讓shell為空,反而可以正確的運(yùn)行shell命令。

            經(jīng)過這些處理,每次編譯出來的app就可以拿到其他機(jī)器上運(yùn)行了。可真夠麻煩的...

            posted @ 2012-05-07 23:25 RTY 閱讀(1025) | 評(píng)論 (0)編輯 收藏

            僅列出標(biāo)題
            共31頁: 1 2 3 4 5 6 7 8 9 Last 
            亚洲午夜无码久久久久小说| 亚洲国产精品成人久久| 久久九九亚洲精品| 国产AV影片久久久久久| 久久人人超碰精品CAOPOREN| 日本欧美久久久久免费播放网| 久久水蜜桃亚洲av无码精品麻豆| 精品国产乱码久久久久久1区2区| 国产精品VIDEOSSEX久久发布 | 蜜桃麻豆www久久| 久久精品国产一区二区三区| 中文字幕久久久久人妻| 国产成人精品久久综合 | 久久精品无码免费不卡| 国内精品伊人久久久久777| 97久久精品人人做人人爽| 日韩欧美亚洲综合久久| 久久99久久成人免费播放| 热re99久久6国产精品免费| 青青草原综合久久大伊人导航| 东京热TOKYO综合久久精品| 国产69精品久久久久观看软件| 中文字幕成人精品久久不卡 | 国产成人精品久久亚洲| 久久综合九色综合网站| 欧美亚洲国产精品久久| 久久久精品日本一区二区三区 | 欧美激情精品久久久久久久九九九 | 久久99精品久久久久婷婷| 亚洲伊人久久综合中文成人网| 国产精品亚洲美女久久久| 青青热久久综合网伊人| 久久国产色AV免费观看| 青青草原精品99久久精品66| 色婷婷久久综合中文久久蜜桃av| 久久99久国产麻精品66| 国产精品成人久久久| 亚洲va久久久久| 老色鬼久久亚洲AV综合| 久久久久亚洲AV成人网人人网站 | 色婷婷综合久久久久中文|