在OSGI(felix)里整合hibernate


早前的時候寫了一篇《在插件里應用hsqldb和hibernate》

但是之前的方法只適用於equinox環境,並不能算是hibernate和osgi的完整整合,昨天終於完成了felix+hibernate的整合方式,現做一個完整記錄。源碼地址如下:

SVN:http://ext-eclipse.googlecode.com/svn

位置在trunck/mos下,主要是三個maven project: mos.hibernate,mos.hibernate.extender,mos.hibernate.manager

還有一個demo: mos.hibernate.demo,該插件並不完善,只提供了基本的使用方式,啟動它需要blueprint配置。

 

先說明下三個主要插件的功能以及實現的思路:

mos.hibernate:

提供hibernate所需要的全部jar包,以后如果有版本變更,全部在這里維護。該插件不依賴於任何業務包。

清單包括(此處默認dom4j已經作為獨立bundle,不計入其中):

<Bundle-ClassPath>.,lib/ejb3-persistence.jar,lib/hibernate-annotations.jar,lib/hibernate-commons-annotations.jar,lib/hibernate-entitymanager.jar,lib/antlr-2.7.6.jar,lib/asm-all-3.3.1.jar,lib/c3p0-0.9.1.jar,lib/cglib-2.2.2.jar,lib/commons-collections-2.1.1.jar,
lib/commons-logging-1.0.4.jar,lib/concurrent-1.3.4.jar,lib/ehcache-1.2.3.jar,lib/hibernate-3.2.7.ga.jar,lib/jta-1.1.jar</Bundle-ClassPath>

osgi整合hibernate最大的問題在於bundle的ClassLoader是分離的,而hibernate大量使用了反射方法,且在它設計本身,是沒有考慮到多ClassLoader的情況的。

在絕大多數代碼里,它使用了Thread.currentThread().getContextClassLoader()這個ClassLoader,於是只能獲取到當前bundle能訪問到的類。

如果是equinox環境,要解決這個問題就相對簡單,eclipse為我們提供了一個buddy策略,參見《RCP Buddy簡易實現》

但是,這對於osgi是破壞性的,它完全忽略了osgi規范定下的訪問級別。

在felix環境下,我們有另外的替代方案:

方案一:

1、fragment bundle

我們可以把數據庫驅動放在一個一個的fragment bundle中,讓它的main bundle設為mos.hibernate,這樣就實現了hibernate沒有依賴於業務插件,也可以相對靈活的配置數據庫

但是明顯的,它有局限性。

2、DynamicImport-Package

這是OSGI規范內部的東西,它的作用是動態的查找可導入的包。

示例:

<DynamicImport-Package>*</DynamicImport-Package>

既然它符合osgi規范,那么,就必然無法動態導入已經依賴了mos.hibernate插件的其他插件中的package。

對於數據庫驅動來說,這不是個問題。

但是對於業務Entity插件來說,這是必然會矛盾的地方。

所以對於業務Entity插件,我們還有另外的處理方式,將在mos.hibernate.manager講解處說明。

 

mos.hibernate.extender:

1、注冊數據庫配置(HbmDatabaseConfig)和映射配置(HbmMappingConfig)

2、監聽bundle的啟動和停止,並解析其MANIFEST.MF文件,獲取<Database-Configuration>,<Mapping-Configuration>的配置信息。

配置格式如下:

<Database-Configuration>[sessionFactoryId];[hibernate.cfg.xml File Path],[sessionFactoryId];[hibernate.cfg.xml File Path]</Database-Configuration>

<Mapping-Configuration>[sessionFactoryId];[Entity class name],[sessionFactoryId];[Entity class name]</Mapping-Configuration>

示例:

<Database-Configuration>hsqldb;/config/hsqldb.cfg.xml</Database-Configuration>
<Mapping-Configuration>hsqldb;mos.hibernate.demo.DemoBean,hsqldb;mos.hibernate.demo.Cat</Mapping-Configuration>

該bundle的實現有點類似於eclipse的擴展點機制。

事實上extender是一種非常值得學習的插件編程方式。

它由兩個部分組成:

1)通過 context.getBundles()遍歷全部已經存在的bundle

2)使用BundleListener,來對BundleEvent.STARTED狀態的bundle注冊,和BundleEvent.STOPPING狀態的bundle反注冊

同時,它提供對MANIFEST.MF文件的解析

bundle.getHeaders()能獲取全部的MF屬性名。

參見mos.hibernate.extender.util.HibernateBundleHelper的實現

 

mos.hibernate.manager:

1、提供數據庫配置(HbmDatabaseConfig)和映射配置(HbmMappingConfig)的定義

2、提供數據庫配置容器的定義以及緩存。

3、提供外部訪問接口DAO(暫未完善)

這個插件的實現難點主要在於,使hibernate能獲取到業務實體的Class實例。

通常的Configuration類的實現里,hibernate是對org.dom4j.Document的解析,獲取類名,然后通過反射來實現獲取實例的。

在osgi環境下,這種方式遇到了困難。

我們來使用另外幾個jar包:

1、hibernate-annotations.jar

2、ejb3-persistence.jar

hibernate-annotations.jar提供一個AnnotationConfiguration

它繼承於Configuration,但是提供了特別的對Class的處理。

如下:

 

@Override
    protected void parseMappingElement(Element subelement, String name) {
        Attribute rsrc = subelement.attribute( "resource" );
        Attribute file = subelement.attribute( "file" );
        Attribute jar = subelement.attribute( "jar" );
        Attribute pckg = subelement.attribute( "package" );
        Attribute clazz = subelement.attribute( "class" );
        if ( rsrc != null ) {
            log.debug( name + "<-" + rsrc );
            addResource( rsrc.getValue() );
        }
        else if ( jar != null ) {
            log.debug( name + "<-" + jar );
            addJar( new File( jar.getValue() ) );
        }
        else if ( file != null ) {
            log.debug( name + "<-" + file );
            addFile( file.getValue() );
        }
        else if ( pckg != null ) {
            log.debug( name + "<-" + pckg );
            addPackage( pckg.getValue() );
        }
        else if ( clazz != null ) {
            log.debug( name + "<-" + clazz ); Class loadedClass = null; try { loadedClass = ReflectHelper.classForName( clazz.getValue() ); } catch (ClassNotFoundException cnf) { throw new MappingException( "Unable to load class declared as <mapping class=\"" + clazz.getValue() + "\"/> in the configuration:", cnf ); } catch (NoClassDefFoundError ncdf) { throw new MappingException( "Unable to load class declared as <mapping class=\"" + clazz.getValue() + "\"/> in the configuration:", ncdf ); } addAnnotatedClass( loadedClass );
        }
        else {
            throw new MappingException( "<mapping> element in configuration specifies no attributes" );
        }
    }

 

注意addAnnotatedClass這個方法:

它的實現是:

public AnnotationConfiguration addAnnotatedClass(Class persistentClass) throws MappingException {
        XClass persistentXClass = reflectionManager.toXClass( persistentClass );
        try {
            annotatedClasses.add( persistentXClass );
            return this;
        }
        catch (MappingException me) {
            log.error( "Could not compile the mapping annotations", me );
            throw me;
        }
    }

 

於是我們可以猜想,被注冊的Class已經維護在了annotatedClasses里。

事實上也是如此。

一個簡單的Annotated實體類的定義如下:

package mos.hibernate.demo;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.TableGenerator;

/**
 * @author caiyu
 * @date 2012-10-11 下午2:41:21
 */
@Entity
@Table(name = "CAT")
public class Cat {
    @Id
    @TableGenerator(name = "CAT", allocationSize = 1)
    @GeneratedValue(strategy = GenerationType.TABLE, generator = "CAT")
    private int id;
    
    @Column(name="name")
    private String name;
    
    @Column(name="color")
    private String color;

    public Cat() {
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }

    public String toString() {
        StringBuffer sb = new StringBuffer();
        sb.append("ID:");
        sb.append(id);
        sb.append("\n");
        sb.append("Name:");
        sb.append(name);
        sb.append("\n");
        sb.append("Color:");
        sb.append(color);
        sb.append("\n");
        return sb.toString();
    }
}

它不再需要另一個單獨的XML文件來定義它的hibernate映射關系,這些映射關系已經被它本身的annotation定義描述出來了。

 

 

要注意的細節問題:

1、dom4j找不到驅動和NoClassDefFoundError問題,這是因為dom4j需要的xerces和xml-apis已經被jdk 1.4以及以后版本引入了,在config.properties配置里添加:

# The following property makes specified packages from the class path
# available to all bundles. You should avoid using this property.

org.osgi.framework.bootdelegation=javax.*,org.*

即是使jre下的javax.*和org.*對osgi的全部bundle可見。

當然也可以將xerces和xml-apis的合適版本插件化,但這樣比較麻煩,且可能會導致類名重復的問題。

2、使用該整合的bundle,需要import如下package:

mos.hibernate.manager,org.hibernate,javax.persistence,org.hibernate.proxy,net.sf.cglib.proxy


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM