在eclipse中配置基于OSGi的webx项目——加入Hibernate支持
From Tuscany中文社区
本文是在eclipse中配置基于OSGi的webx项目一文的延续部分,主要说明在加入Spring、Struts之后,再加入Hibernate的支持。
目录 |
[编辑] 环境准备
[编辑] 准备JTA Bundle
下载jta.jar文件1.1版本,因为Spring需要的最小版本是1.04,在文件MANIFEST.MF中增加如下内容:
Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: Jta Plug-in Bundle-SymbolicName: jta Bundle-Version: 1.1.0 Export-Package: javax.transaction;version="1.1.0", javax.transaction.xa;version="1.1.0"
把修改后的jar文件放到eclipse\plugin目录里面。
[编辑] 准备Hibernate Bundle
我们这里使用的是hibernate的版本是3.2.5.ga,在运行时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
这个错误对于延迟加载而言会有不小的影响,因此我们需要追查此错误的原因,根据错误堆栈信息,可追查出是cglib中的AbstractClassGenerator.create方法执行时出现了错误,找出AbstractClassGenerator的源码,跟踪后发现是在此类中无法找到HibernateProxy,是不是有些奇怪呢?仔细来分析下,为什么明明在同一个Bundle下的两个jar,却加载不到呢?问题必然还是出在 classloader上,仔细看create方法,会看到其中有个getClassLoader(),跟踪调试看getClassLoader()就会发现,当为Bulletin生成Proxy时,这个时候的ClassLoader变成了Bulletin所在Bundle的ClassLoader了,不用说,用这个ClassLoader去加载HibernateProxy自然是加载不到了,找到了问题的根源,我们修改AbstractClassGenerator类,把它的ClassLoader改为使用当前类所在的ClassLoader,修改它的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, org.hibernate.bytecode, org.hibernate.bytecode.cglib, org.hibernate.bytecode.javassist, org.hibernate.bytecode.util, org.hibernate.cache, org.hibernate.cache.entry, org.hibernate.cfg, org.hibernate.classic, org.hibernate.collection, org.hibernate.connection, org.hibernate.context, org.hibernate.criterion, org.hibernate.dialect, org.hibernate.dialect.function, org.hibernate.dialect.lock, org.hibernate.engine, org.hibernate.engine.loading, org.hibernate.engine.query, org.hibernate.engine.query.sql, org.hibernate.engine.transaction, org.hibernate.event, org.hibernate.event.def, org.hibernate.exception, org.hibernate.hql, org.hibernate.hql.antlr, org.hibernate.hql.ast, org.hibernate.hql.ast.exec, org.hibernate.hql.ast.tree, org.hibernate.hql.ast.util, org.hibernate.hql.classic, org.hibernate.id, org.hibernate.id.enhanced, org.hibernate.id.insert, org.hibernate.impl, org.hibernate.intercept, org.hibernate.intercept.cglib, org.hibernate.intercept.javassist, org.hibernate.jdbc, org.hibernate.jmx, org.hibernate.loader, org.hibernate.loader.collection, org.hibernate.loader.criteria, org.hibernate.loader.custom, org.hibernate.loader.custom.sql, org.hibernate.loader.entity, org.hibernate.loader.hql, org.hibernate.lob, org.hibernate.mapping, org.hibernate.metadata, org.hibernate.param, org.hibernate.persister, org.hibernate.persister.collection, org.hibernate.persister.entity, org.hibernate.pretty, org.hibernate.property, org.hibernate.proxy, org.hibernate.proxy.dom4j, org.hibernate.proxy.map, org.hibernate.proxy.pojo, org.hibernate.proxy.pojo.cglib, org.hibernate.proxy.pojo.javassist, org.hibernate.secure, org.hibernate.sql, org.hibernate.stat, org.hibernate.tool.hbm2ddl, org.hibernate.tool.instrument, org.hibernate.tool.instrument.cglib, org.hibernate.tool.instrument.javassist, org.hibernate.transaction, org.hibernate.transform, org.hibernate.tuple, org.hibernate.tuple.component, org.hibernate.tuple.entity, org.hibernate.type, org.hibernate.usertype, org.hibernate.util Import-Package: javax.transaction;version="1.1.0", org.apache.commons.collections;version="3.2.0", org.apache.commons.logging;version="1.0.4"
你也可以从这里下载bundle文件。
[编辑] 准备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
[编辑] 修改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
[编辑] 运行时需要的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 org.apache.jasper_5.5.17.v200706111724 org.apache.commons.el_1.0.0.v200706111724 org.mortbay.jetty_5.1.11.v200706111724 org.eclipse.core.runtime_3.3.100.v20070530 org.eclipse.core.jobs_3.3.1.R33x_v20070709 org.eclipse.core.contenttype_3.2.100.v20070319 org.eclipse.osgi_3.3.1.R33x_v20070828 org.eclipse.osgi.services_3.1.200.v20070605 org.eclipse.osgi.util_3.1.200.v20070605 org.eclipse.equinox.http.servlet_1.0.1.R33x_v20070816 org.eclipse.equinox.http.jetty_1.0.1.R33x_v20070816 org.eclipse.equinox.http.helper_1.0.0.200801161414 org.eclipse.equinox.jsp.jasper_1.0.1.R33x_v20070816 org.eclipse.equinox.common_3.3.0.v20070426 org.eclipse.equinox.registry_3.3.1.R33x_v20070802 org.eclipse.equinox.app_1.0.1.R33x_v20070828 org.eclipse.equinox.preferences_3.2.100.v20070522 org.springframework.bundle.spring.core_2.5.1 org.springframework.bundle.spring.beans_2.5.1 org.springframework.bundle.spring.context_2.5.1 org.springframework.bundle.spring.aop_2.5.1 org.springframework.bundle.spring.tx_2.5.1 org.springframework.bundle.spring.orm_2.5.1 org.springframework.bundle.spring.jdbc_2.5.1 org.springframework.bundle.osgi.io_1.0.0.rc2 org.springframework.bundle.osgi.core_1.0.0.rc2 org.springframework.bundle.osgi.extender_1.0.0.rc2 org.springframework.osgi.commons-beanutils.osgi_1.7.0.SNAPSHOT org.springframework.osgi.commons-collections.osgi_3.2.0.SNAPSHOT org.springframework.osgi.aopalliance.osgi_1.0.0.SNAPSHOT
[编辑] 增加book.hibernate Bundle
[编辑] 问题
默认情况下Hibernate通过hibernate.cfg.xml中的mapping resource来加载归入SessionFactory中管理的PO,在加载时Hibernate通过Hibernate类所在的classloader加载Mapping resource中配置的hbm映射文件,并通过cglib将hbm映射文件中指定的class生成proxy。
改为基于OSGi后产生冲突的地方就在于PO分散到不同的工程中去了,同时要保持模块化的封装性,就不能所有人都来修改封装Hibernate模块里的hibernate.cfg.xml;另外一个冲突点在于封装Hibernate的模块的classloader无法加载到位于其他模块的PO对象,只要解决了这两个冲突,基于OSGi使用Hibernate的问题就解决了。
[编辑] 解决办法
[编辑] 增加扩展点
要解决这两个冲突,需要解决的就是如何将其他模块的PO注册到封装Hibernate的模块中,以及Hibernate的Configuration如何加载其他模块的PO和映射文件。
基于OSGi的可扩展性,要将其他模块的PO注册到封装Hibernate的模块中,通过扩展点方式即可实现,首先来设计此扩展点,我们注意到Hibernate的Configuration.addClass加载class的方式可达到和mapping resource完全一样的效果,扩展点中需要的仅为po的classname。
增加扩展点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>
[编辑] 检测扩展点,实现我们的SessionFactoryBean
我们继承Spring的LocalSessionFactoryBean类,为了监听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加入到Hibernate的Configuration中, OSGi中每个Bundle都是独立的ClassLoader,那也就是说Hibernate所在的这个Bundle是无法加载到扩展点中className对应的类的,所幸的是IExtension的IConfigurationElement提供了初始化扩展中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");
}
那么什么时候应用这些扩展点呢?我们通过重载LocalSessionFactoryBean的postProcessConfiguration方法,这个时候我们把扩展点中的类的实例加入SessionFactory的管理范围内:
protected void postProcessConfiguration(Configuration config)
throws HibernateException {
this.configuration = config;
this.addHibernateConfig();
}
上面的实现方式解决了静态时Hibernate加载其他模块中的PO初始化SessionFactory的问题,但记住要保持好基于OSGi的系统的动态性的特征,要保持动态化,就得监听Hibernate模块这个扩展点的扩展实现的变化情况,当系统中增加了新的需要归入SessionFactory管理的PO时,需要动态的加入到目前的SessionFactory中,当已有的PO从系统中删除时,也需要动态的将其从目前的SessionFactory中删除,不过Hibernate的SessionFactory是不支持动态的删除和增加其中管理的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时传递给Hibernate的Class,直接使用Class.forName的方法实例化扩展出的PO会出错误,因为Hibernate类所在的ClassLoader和扩展的类所在的ClassLoader并不同,碰到这样的情况,就得使用OSGi提供给Bundle的DynamicImport-Package了,使用此属性OSGi就可以在运行期动态的为这个Bundle获取其他Bundle Export的Package,使用这种方法的话要求PO所在的Bundle将PO的Package Export,打开Bundle的MANIFEST.MF文件,在其中加上这一行:
DynamicImport-Package: *
[编辑] 配置文件
本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>
<prop key="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"/>
[编辑] 结构类图
[编辑] 其它内容
此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", org.springframework.dao.support;version="2.5.1", org.springframework.orm.hibernate3;version="2.5.1", org.springframework.orm.hibernate3.support;version="2.5.1", org.springframework.osgi.context;version="1.0.0.rc2"
[编辑] 修改book.service bundle
[编辑] 加入dao、model和vo
把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,在定义的两个dao的bean里都要引用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>
[编辑] 其它
此bundle需要暴露三个package:book.model、book.service和book.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"
[编辑] 尾声
本文还有一些问题没有解决,比如关于事务问题、其它需要完善的内容等,需要进一步探讨。


