自己的osgi收藏(比较详尽的osgi学习文档)五与hibernate的集成

来源:互联网 发布:linux中切换输入法 编辑:程序博客网 时间:2024/05/16 00:45

 

l        解决和hibernate的集成

传统方式下,hibernate通过hibernate.cfg.xml中的mapping resource来加载归入sessionfactory中的管理得po,加载过程中,hibernate通过hibernate类所在的classloader加载mapping resource中配置的hbm映射文件,并通过cglibhbm映射文件中指定的class生成proxy.

基于osgi的系统后,产生了以下两个冲突:

1.     po分散到不同的工程了,同时要保持模块的封装性,就不能所有人都修改hibernate模块里的hibernate.cfg.xml文件。

2.     封装hibernate的模块的classloader无法加载到其他模块的po对象。

   解决以上冲突时基于osgi使用hibernate的关键,需要解决的就是如何将其他模块的po注册到封装hibernate的模块中,及hibernateconfiguration如何加载其他模块的po和映射文件。方案如下:

 增加扩展点方式可以实现。之后需要的是把class加入到hibernateconfiguration中,osgi中每个bundle都是独立的classloader,hibernate所在的bundle无法加载到扩展点中classname所对应的类,,但在实现扩展点的模块中创建提供扩展的类可以实现。

这是我们目前实现的方式,通过标准的Eclipse扩展点与扩展机制,我们在Hibernate插件中plugin.xml配置文件中声明下述扩展点:

 

<extension-point id="org.opengoss.database.domain.object" name="domainObject"/>

 

在模型对象插件中声明扩展,例如:

<extension point="org.opengoss.database.domain.object">

<domainObject class="org.opengoss.alarm.core.Alarm"/>

</extension>

Hibernate插件的启动中,用代码配置生成SessionFactory,代码如下:

 

props.put("uid", "Hibernate:SessionFactory"); public void start(BundleContext context) throws Exception {

Configuration configuration = new Configuration().configure(new File(

"./etc/org.opengoss.database.hibernate/hibernate.cfg.xml"));

Class[] domainClasses = getDomainClasses();

for (Class domainClass : domainClasses) {

 

configuration.addClass(domainClass);

 

}

 

sessionFactory = configuration.buildSessionFactory();

Dictionarynew Hashtable

props.put("scope", "APPLICATION");

 

 

registration = context.registerService( SessionFactory.class.getName(), sessionFactory, props);

}

 

private Class[] getDomainClasses() throws Exception {

List domainClasses = new ArrayList();

 

IExtensionPoint point = registry .getExtensionPoint(IConstants.DOMAIN_OBJECT_EXTENSION_POINT);

 

IExtension[] extensions = point.getExtensions();

 

for (IExtension extension : extensions) {

IConfigurationElement[] elements = extension .getConfigurationElements();

 

 

for (IConfigurationElement configurationElement : elements) {

 

Bundle bundle = pluginContext.getBundleBySymbolId(extension .getNamespaceIdentifier());

 

Class domainClass =  bundle.loadClass(configurationElement .getAttribute("class"));

 

domainClasses.add(domainClass);

 

}

 

}

return domainClasses.toArray(new Class[domainClasses.size()]);

}注意:Hibernate内部的类加载机制实在无法令人满意,尽管我们在这种方式中已经加载所有的模型类对象,但Hibernate内部仍然会调用Class.forName()去试图加载。所以,我们不得不在其自描述文件(MANIFEST.MF) 中加入描述:

DynamicImport-Package: *

 

u       再这里通过一个基于osgi加入hibernate支持的具体事例来分析开发具体细节与流程:

(基于一个图书查询的实现,关于一些bundle的配置(hibernate,spring,mysql等)

 

l        准备Hibernate Bundle

在运行时hibernate需要如下的库:(本文档实例中包的具体版本根据需求导入)

asm.jar

asm-attrs.jar

cglib-2.1.3.jar

dom4j-1.6.1.jar

antlr-2.7.6.jar

这些库都可以在hibernate下载包中的lib目录里找到。为了简单起见,我们把这些库放到hibernate Bundle里,而不把每个库打成单独的bundle

因为在执行过程中会出现了一个错误:

java.lang.NoClassDefFoundError:org/hibernate/proxy/HibernateProxy

修改getClassLoader方法,原因是加载位置不对,增加如下蓝色字体的代码:

public ClassLoader getClassLoader() {

    ClassLoader t = classLoader;

   if(t==null){

      t=this.getClass().getClassLoader();

       }

       if (t == null) {

           t = getDefaultClassLoader();

       }

       if (t == null) {

           t = getClass().getClassLoader();

       }

       if (t == null) {

           t = Thread.currentThread().getContextClassLoader();

       }

       if (t == null) {

           throw new IllegalStateException("Cannot determine classloader");

       }

       return t;

   }

这个文件我们单独编译放在bundle的根目录里,MANIFEST.MF文件的内容如下:

Manifest-Version: 1.0

Bundle-ManifestVersion: 2

Bundle-Name: Hibernate Plug-in

Bundle-SymbolicName: hibernate

Bundle-Version: 3.2.5

Bundle-ClassPath: lib/asm.jar,

 lib/asm-attrs.jar,

 lib/cglib-2.1.3.jar,

 lib/dom4j-1.6.1.jar,

 lib/hibernate3.jar,

 lib/antlr-2.7.6.jar,

  .

DynamicImport-Package: *

Export-Package: org.hibernate,

 org.hibernate.action,(详见eclipse中配置基于OSGiwebx项目——加入Hibernate支持)

Import-Package: javax.transaction;version="1.1.0",

 org.apache.commons.collections;version="3.2.0",

 org.apache.commons.logging;version="1.0.4"

 

l        MySQL驱动Bundle

下载mysql的驱动文件,在MENIFEST.MF文件中增加如下内容,把jar文件打包成bundle

Manifest-Version: 1.0

Bundle-ManifestVersion: 2

Bundle-Name: Driver Plug-in

Bundle-SymbolicName: com.mysql.jdbc.Driver

Bundle-Version: 1.0.0

Export-Package: com.mysql.jdbc,

 com.mysql.jdbc.exceptions,

 com.mysql.jdbc.integration.c3p0,

 com.mysql.jdbc.integration.jboss,

 com.mysql.jdbc.jdbc2.optional,

 com.mysql.jdbc.log,

 com.mysql.jdbc.profiler,

 com.mysql.jdbc.util,

 org.gjt.mm.mysql

 

l        修改webx Bundle

MENIFEST.MF中增加如下的内容:

Require-Bundle: org.springframework.bundle.spring.core,

 org.springframework.bundle.spring.beans,

 org.springframework.bundle.spring.orm,

 org.springframework.bundle.spring.tx,

 org.apache.struts,

 hibernate,

 org.apache.log4j

Import-Package: javax.servlet,

 javax.servlet.http,

 javax.transaction;version="1.1.0"

其中需要依赖的spring bundle:

spring-beans-2.5.1.jar

spring-core-2.5.1.jar

spring-orm-2.5.1.jar

spring-tx-2.5.1.jar

l        运行时需要的bundle

系统要运行时需要如下的bundle,如果在运行过程中遇到问题,可以参考下面内容:

javax.servlet_2.4.0.v200706111738

javax.servlet.jsp_2.0.0.v200706191603

org.apache.struts_1.0.0

org.apache.commons.digester_1.0.0

org.apache.log4j_1.0.0

org.apache.commons.lang_1.0.0

org.apache.commons.logging_1.0.4.v200706111724

………………………………….详见转文原址,便于调试错误。

l        增加book.hibernate Bundle

问题

默认情况下Hibernate通过hibernate.cfg.xml中的mapping resource来加载归入SessionFactory中管理的PO,在加载时Hibernate通过Hibernate类所在的classloader加载Mapping resource中配置的hbm映射文件,并通过cglibhbm映射文件中指定的class生成proxy

改为基于OSGi后产生冲突的地方就在于PO分散到不同的工程中去了,同时要保持模块化的封装性,就不能所有人都来修改封装Hibernate模块里的hibernate.cfg.xml;另外一个冲突点在于封装Hibernate的模块的classloader无法加载到位于其他模块的PO对象,只要解决了这两个冲突,基于OSGi使用Hibernate的问题就解决了。

解决办法

1增加扩展点

要解决这两个冲突,需要解决的就是如何将其他模块的PO注册到封装Hibernate的模块中,以及HibernateConfiguration如何加载其他模块的PO和映射文件。

基于OSGi的可扩展性,要将其他模块的PO注册到封装Hibernate的模块中,通过扩展点方式即可实现,首先来设计此扩展点,我们注意到HibernateConfiguration.addClass加载class的方式可达到和mapping resource完全一样的效果,扩展点中需要的仅为poclassname

增加扩展点schema文件hibernateExtension.exsd,通过可视化编辑器增加po,类型为String,修改后的文件主要内容是:

        ……

        <sequence minOccurs="1" maxOccurs="unbounded">

           <element ref="po" minOccurs="1" maxOccurs="unbounded"/>

        </sequence>

  ……

  <element name="po">

     <complexType>

        <attribute name="class" type="string" use="required">

           <annotation>

              <documentation>

              </documentation>

           </annotation>

        </attribute>

     </complexType>

  </element>

  ……

然后增加扩展点的定义文件plugin.xml内容为

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

<plugin>

   <extension-point id="hibernateExtension" name="book.hibernate.extension" schema="schema/hibernateExtension.exsd"/>

</plugin>

2 检测扩展点,实现我们的SessionFactoryBean

我们继承SpringLocalSessionFactoryBean类,为了监听bundle注册的改变,我们实现IRegistryChangeListener接口,如下:

public class MySessionFactory extends LocalSessionFactoryBean

        implements IRegistryChangeListener

在设置注册对象时,我们获取到扩展点:

        public void setRegistry(IExtensionRegistry registry) {

               logger.info("************setRegistry");

               registry.addRegistryChangeListener(this, "book.hibernate");

               this.registry = registry;

               IExtension[] extensions = registry.getExtensionPoint(

                               "book.hibernate.hibernateExtension").getExtensions();

               for (int i = 0; i < extensions.length; i++) {

                       poExtensions.add(extensions[i]);

               }

        }

获取到扩展点的实现后,需要的就是把class加入到HibernateConfiguration中, OSGi中每个Bundle都是独立的ClassLoader,那也就是说Hibernate所在的这个Bundle是无法加载到扩展点中className对应的类的,所幸的是IExtensionIConfigurationElement提供了初始化扩展中class的方法,也就是说可以实现在扩展点的模块中直接创建提供扩展的模块中的类,实现的方法是:

        private void addHibernateConfig()

                       throws HibernateException {

               logger.info("************addHibernateConfig");

               Class poClass = null;

               try {

        for (Iterator iter = poExtensions.iterator(); iter.hasNext();) {

     IExtension extension = (IExtension) iter.next()

IConfigurationElement[] elements = extension.getConfigurationElements()

for (int j = 0; j < elements.length; j++) {

                                      logger.info("************elements:"+elements[j]);

poClass = elements[j].createExecutableExtension("class")

                                                     .getClass();

  configuration.addClass(poClass);

                               }

                       }

               } catch (Throwable t) {

                       String message = "Hibernate config isn't add.";

                       logger.error(message, t);

                       throw new HibernateException(message + t);

               }

               logger.info("************addHibernateConfig OK");

        }

那么什么时候应用这些扩展点呢?我们通过重载LocalSessionFactoryBeanpostProcessConfiguration方法,这个时候我们把扩展点中的类的实例加入SessionFactory的管理范围内:

        protected void postProcessConfiguration(Configuration config)

                       throws HibernateException {

               this.configuration = config;

               this.addHibernateConfig();

        }

上面的实现方式解决了静态时Hibernate加载其他模块中的PO初始化SessionFactory的问题,但记住要保持好基于OSGi的系统的动态性的特征,要保持动态化,就得监听Hibernate模块这个扩展点的扩展实现的变化情况,当系统中增加了新的需要归入SessionFactory管理的PO时,需要动态的加入到目前的SessionFactory中,当已有的PO从系统中删除时,也需要动态的将其从目前的SessionFactory中删除,不过HibernateSessionFactory是不支持动态的删除和增加其中管理的PO的,当发生变化时,只能重新初始化SessionFactory了。所以我们实现了IRegistryChangeListener中的registryChanged方法:

public void registryChanged(IRegistryChangeEvent event) {

        IExtensionDelta[] deltas = event.getExtensionDeltas("book.hibernate","hibernateExtension");

    if (deltas.length == 0)

                       return;

    for (int i = 0; i < deltas.length; i++) {

                       switch (deltas[i].getKind()) {

                      case IExtensionDelta.ADDED:

                               poExtensions.add(deltas[i].getExtension());

                             break;

                       case IExtensionDelta.REMOVED:

                               poExtensions.remove(deltas[i].getExtension());

                               break;

                       default:

                               break;

                       }

               }

               refreshSessionFactory();

        }

按照上面的步骤完成后,Hibernate会根据映射文件中的classname去实例化该Class,而不是直接使用我们通过Configuration.addClass时传递给HibernateClass,直接使用Class.forName的方法实例化扩展出的PO会出错误,因为Hibernate类所在的ClassLoader和扩展的类所在的ClassLoader并不同,碰到这样的情况,就得使用OSGi提供给BundleDynamicImport-Package了,使用此属性OSGi就可以在运行期动态的为这个Bundle获取其他Bundle ExportPackage,使用这种方法的话要求PO所在的BundlePOPackage Export,打开BundleMANIFEST.MF文件,在其中加上这一行:

DynamicImport-Package: *

 

l        配置文件

bundle需要如下配置文件:

 hibernate.properties

hibernate.dialect=org.hibernate.dialect.MySQLDialect

hibernate.show_sql=false

hibernate.connection.release_mode=after_transaction

jdbc.properties

jdbc.driverClassName=com.mysql.jdbc.Driver

jdbc.url=jdbc:mysql://localhost:3306/book

jdbc.username=root

jdbc.password=root

Spring的配置文件

   <osgi:reference id="iExtensionRegistry" interface="org.eclipse.core.runtime.IExtensionRegistry"/>

<bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">

       <property name="locations">

           <list>

               <value>config/jdbc.properties</value>

               <value>config/hibernate.properties</value>

           </list>

       </property>

   </bean>

  <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">

       <property name="driverClass" value="${jdbc.driverClassName}" />

       <property name="jdbcUrl" value="${jdbc.url}" />

       <property name="user" value="${jdbc.username}" />

       <property name="password" value="${jdbc.password}" />

       <property name="initialPoolSize" value="20" />

       <property name="minPoolSize" value="20" />

       <property name="maxPoolSize" value="50" />

       <property name="acquireIncrement" value="5" />

       <property name="maxIdleTime" value="10" />

       <property name="maxStatements" value="0" />

       <property name="testConnectionOnCheckin" value="true" />

       <property name="testConnectionOnCheckout" value="true" />

       <property name="idleConnectionTestPeriod" value="30" />

   </bean>

<bean id="sessionFactory" class="book.hibernate.MySessionFactory">

       <property name="dataSource" ref="dataSource" />

       <property name="configLocations">

           <list></list>

       </property>

       <property name="hibernateProperties">

           <props>

  <prop key="hibernate.dialect">${hibernate.dialect}</prop>

  <prop key="hibernate.show_sql">${hibernate.show_sql}</prop>

<propkey="hibernate.connection.release_mode">${hibernate.connection.release_mode}</prop></props>

</property><property name="registry" ref="iExtensionRegistry" />

   </bean>

<osgi:service id="factory" ref="sessionFactory" interface="org.hibernate.SessionFactory"/>

l        结构类图

l        其它内容

bundle里需要c3p0-0.9.1.jar,为了简单起见,我把它放到bundle里了,没有把它单独作为一个bundle,另外需要引入一些package,整个MANIFEST.MF文件如下:

Manifest-Version: 1.0

Spring-Context: config/springBeans.xml

Bundle-ManifestVersion: 2

Bundle-Name: book.hibernate Plug-in

Bundle-SymbolicName: book.hibernate;singleton:=true

Bundle-Version: 1.0.0

Export-Package: book.hibernate.dao

DynamicImport-Package: *

Bundle-ClassPath: lib/c3p0-0.9.1.jar,

 .

Import-Package: com.mysql.jdbc,

 com.sitechasia.webx.core.dao,

 com.sitechasia.webx.core.dao.hibernate3,

 com.sitechasia.webx.core.support,

 javax.transaction;version="1.1.0",

 org.apache.commons.logging;version="1.0.4",

 org.eclipse.core.runtime,

 org.hibernate,

 org.hibernate.cfg,

 org.osgi.framework;version="1.4.0",

 org.springframework.beans.factory;version="2.5.1",

 …………………………………………………

l        修改book.service bundle

加入daomodelvo

webx例子中的这三个源代码目录复制到book.service bundle源代码目录中,并且把hbm的两个配置文件复制到model目录中。

增加hibernate扩展

扩展的定义plugin.xml如下:

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

<plugin>

   <extension point="book.hibernate.hibernateExtension">

      <po class="book.model.CategoryDO"/>

   </extension>

</plugin>

Spring配置文件

bundle中需要引用book.hibernate中定义的sessionFactory,在定义的两个daobean里都要引用sessionFactory,并且把这两个bean发布为服务,供book.service使用

<osgi:reference id="sessionFactory" interface="org.hibernate.SessionFactory"/>

 

<bean id="categoryDao" class="com.sitechasia.webx.book.dao.impl.CategoryDaoImpl">

    <property name="sessionFactory" ref="sessionFactory"/>

</bean>

 

<bean id="bookDao" class="com.sitechasia.webx.book.dao.impl.BookDaoImpl">

    <property name="sessionFactory" ref="sessionFactory"/>

</bean>

l        其它

bundle需要暴露三个packagebook.modelbook.servicebook.vo,具体的MANIFEST.MF的配置如下:

Manifest-Version: 1.0

Spring-Context: config/springBeans.xml

Bundle-ManifestVersion: 2

Bundle-Name: book.service Plug-in

Bundle-SymbolicName: book.service;singleton:=true

Bundle-Version: 1.0.0

Export-Package: book.model,

 book.service,

 book.vo

Require-Bundle: book.hibernate

Import-Package: com.sitechasia.webx.core.model,

 com.sitechasia.webx.core.service,

 com.sitechasia.webx.core.service.impl,

 com.sitechasia.webx.core.support,

 org.aopalliance.aop;version="1.0.0",

 org.apache.commons.logging;version="1.0.4"

 

 

 

原创粉丝点击