(转)OSGI+Spring+Hibernate+...完美解决方案[非SpringDM] 2008-08-08

OSGI+Spring+Hibernate+...完美解决方案[非SpringDM]2008-08-08

导论

最近,我做一个项目需要编写Eclipse的插件。我想在Eclipse插件中使用Spring和Hibernate。但却遇到了巨大的问题。

按照Spring组织的提示,我使用SpringDM1.02编写Spring程序(后来用SpringDM1.1,SpringDM又叫SpringOSGI)。但是总是遇到种种问题。特别是Spring管理下的Hibernate,总是无法找到Hibernate的配置文件。

到了后来,连Eclipse也挂了。每次重设Eclipse的目标插件集合的时候,Eclipse都会死掉!终于,我再也无法忍受SpringDM的折磨了!我告诉自己,是时候反思了!终于,我发现了SpringDM根本就是一个完全、彻底失败,无用的废物!所有的问题都是SpringDM造成的!而不是SpringDM所说的,OSGI,Hibernate等等其他软件造成的!我终于发现了OSGI+Spring+Hibernate+...完美解决方案。

问题的根源—ClassLoader

OSGI提供了自己的ClassLoader。每一个OSGI的插件,都有一个独立的ClassLoader被使用。这样就实现了各个插件的独立性。一个JVM上可以运行无数个OSGI插件,每一个OSGI插件都是独立的,除非发布为OSGI服务,或者发布自己Package。SpringDM的工作原理是这样子的:(我猜想的)

1.SpringDM是一个OSGI插件。作为一个一般的插件进行部署。

2.SpringDM一旦启动,就会探查所有其他Active状态的插件。

◦MANIFEST.MF中没有Spring的头,如果有,执行该配置。

◦如果MANIFEST.MF中没有Spring的头,探查META-INF/spring/目录下有没有.xml文件,如果有,它也是一个SpringDM项目。SpringDM探查其他插件的资源,应该使用的是这些插件的BundleContext对象来实现的。

另外,SpringDM应该注册了OSGI的事件,这样,其他插件启动,关闭,都会通知SpringDM。如果SpringDM发现一个插件是SpringDM插件。就导入该插件中的Spring配置文件,创建ApplicationContext。然后,把该ApplicationContext作为OSGI服务发布。如果插件中需要显式的getBean()方法获得对象,就使用它来获得。

SpringDM的问题

SpringDM的DM----动态管理,这个设计似乎很巧妙!刚开始我也被SpringDM的这一“强大功能”所折服。但是,实际上,SpringDM的所有问题根源,就在这里!

SpringDM实际上是在SpringDM这个插件中为所有Spring插件创建ApplicationContext的!显然,此时使用的ClassLoader是SpringDM这个插件的ClassLoader,而不是实际的Spring项目的插件的ClassLoader。并且,当前线程中的ClassLoader,也是SpringDM插件的ClassLoader。这样,就产生了问题。如,Hibernate,它内部使用当前线程中的ClassLoader来载入配置文件。

Thread.currentThread().getContextClassLoader()

绝大部分现有Java代码都使用这样的代码来获得ClassLoader来载入资源,动态创建类。现在,使用了SpringDM,所有这些代码的ClassLoader都会出现问题!都无法找到资源!

SpringDM的谎言

SpringDM的官方文档,把这些问题的责任推给了Hibernate和OSGI。它说,由于OSGI环境下ClassLoader的特殊性,因此使用Thread.currentThread().getContextClassLoader()得到的ClassLoader都是不正确的。应该使用BundleActivator接口的实现类的Classloader来得到正确的ClassLoader。而Hibernate这样的设计时未考虑OSGI环境的类库,在OSGI环境中出错是不可避免的。因此,Hibernate应该修改代码,以适应OSGI环境。而我上了Hibernate网站,没有看到HibernateOSGI项目。我看到有不少朋友在网上写了一些补丁代码,让Hibernate在SpringDM环境下正确运行。看来,我们还应该忙着修改数以亿行计的Java代码,把获得ClassLoader的代码修改掉,否则在SpringDM的OSGI环境下就会报错!

如果真是如此,OSGI真的没有存在的价值了!

事实

但是,事实并非如此!

如果不使用SpringDM,直接在OSGI插件中使用Hibernate,或者Thread.currentThread().getContextClassLoader(),都可以正确载入资源。因为,OSGI插件使用的ClassLoader是可以直接载入OSGI插件的所有资源的。并且,当前线程中的ClassLoader,也是当前OSGI插件使用的ClassLoader。因此,SpringDM在说谎!SpringDM的动态创建ApplicationContext的机制是一系列问题的源泉。

OSGI+Spring+Hibernate+...完美解决方案[非SpringDM]

只要不使用SpringDM的动态创建ApplicationContext机制,就可以避免上述种种问题。试一下在BundleActivator接口中直接使用newClassPathXmlApplicationContext()的方式创建ApplicationContext。结果失败了!通过Debug,我发现是Spring自己的一些特殊处理方式使它在OSGI环境下失败。然后,我研究了SpringDM1.1的API,找到了在插件中直接创建ApplicationContext的方法。

需要的环境:Spring的jar包,还有SpringDM1.1的core和extender的jar包。以及相应的依赖jar包。这些Jar包,可以放在插件中。也可以发布为一个OSGI插件,让所有需要创建ApplicationContext的插件依赖它。

源代码

ConfigableClassPathDefaultConfigurationScanner

/**

*

*/

packagenet.sf.oxmled.springOsgiNoDM;

importorg.osgi.framework.Bundle;

importorg.springframework.osgi.extender.support.scanning.DefaultConfigurationScanner;

importorg.springframework.osgi.io.OsgiBundleResource;

/**

*@[email protected]

*

*/

publicclassConfigableClassPathDefaultConfigurationScannerextends

DefaultConfigurationScanner{

privateString[]configFiles=null;

@Override

publicString[]getConfigurations(Bundlebundle){

String[]myConfigFiles=newString[configFiles.length];

for(inti=0;i<configFiles.length;i++){

StringconfigFile=this.getConfigFiles()[i];

if(configFile.indexOf(":")==-1){

if(configFile.indexOf("/")!=0){

configFile="/"+configFile;

}

configFile="classpath:"+configFile;

}

myConfigFiles[i]=configFile;

}

this.setConfigFiles(myConfigFiles);

returnmyConfigFiles;

}

publicString[]getConfigFiles(){

returnconfigFiles;

}

publicvoidsetConfigFiles(String[]configFiles){

this.configFiles=configFiles;

}

/**

*

*/

publicConfigableClassPathDefaultConfigurationScanner(){

//TODOAuto-generatedconstructorstub

}

publicConfigableClassPathDefaultConfigurationScanner(String[]configFiles){

//TODOAuto-generatedconstructorstub

this.configFiles=configFiles;

}

/**

*@paramargs

*/

publicstaticvoidmain(String[]args){

//TODOAuto-generatedmethodstub

}

}

StandAloneOsgiBundleXmlApplicationContext

/**

*

*/

packagenet.sf.oxmled.springOsgiNoDM;

importorg.springframework.context.ApplicationContext;

importorg.springframework.osgi.context.support.OsgiBundleXmlApplicationContext;

/**

*@[email protected]

*

*/

publicclassStandAloneOsgiBundleXmlApplicationContextextends

OsgiBundleXmlApplicationContext{

privateString[]customConfigLocations=newString[]{};

@Override

publicString[]getDefaultConfigLocations(){

returnthis.getCustomConfigLocations();

}

/**

*

*/

publicStandAloneOsgiBundleXmlApplicationContext(){

//TODOAuto-generatedconstructorstub

}

/**

*@paramparent

*/

publicStandAloneOsgiBundleXmlApplicationContext(ApplicationContextparent){

super(parent);

//TODOAuto-generatedconstructorstub

}

/**

*@paramconfigLocations

*/

publicStandAloneOsgiBundleXmlApplicationContext(String[]configLocations){

super(configLocations);

this.setCustomConfigLocations(configLocations);

//TODOAuto-generatedconstructorstub

}

/**

*@paramconfigLocations

*@paramparent

*/

publicStandAloneOsgiBundleXmlApplicationContext(String[]configLocations,

ApplicationContextparent){

super(configLocations,parent);

this.setCustomConfigLocations(configLocations);

//TODOAuto-generatedconstructorstub

}

/**

*@paramargs

*/

publicstaticvoidmain(String[]args){

//TODOAuto-generatedmethodstub

}

publicString[]getCustomConfigLocations(){

returncustomConfigLocations;

}

publicvoidsetCustomConfigLocations(String[]customConfigLocations){

this.customConfigLocations=customConfigLocations;

}

}

StandAloneOsgiApplicationContextCreator

/**

*

*/

packagenet.sf.oxmled.springOsgiNoDM;

importorg.apache.commons.logging.Log;

importorg.apache.commons.logging.LogFactory;

importorg.osgi.framework.Bundle;

importorg.osgi.framework.BundleContext;

importorg.springframework.osgi.context.DelegatedExecutionOsgiBundleApplicationContext;

importorg.springframework.osgi.context.support.OsgiBundleXmlApplicationContext;

importorg.springframework.osgi.extender.OsgiApplicationContextCreator;

importorg.springframework.osgi.extender.support.ApplicationContextConfiguration;

importorg.springframework.osgi.extender.support.scanning.ConfigurationScanner;

importorg.springframework.osgi.util.OsgiStringUtils;

importorg.springframework.util.ObjectUtils;

/**

*@[email protected]

*

*/

publicclassStandAloneOsgiApplicationContextCreatorimplements

OsgiApplicationContextCreator{

/**logger*/

privatestaticfinalLoglog=LogFactory.getLog(StandAloneOsgiApplicationContextCreator.class);

privatebooleanpublishContextAsService=true;

privateConfigurationScannerconfigurationScanner;

privateString[]configFiles=null;

publicString[]getConfigFiles(){

returnconfigFiles;

}

publicvoidsetConfigFiles(String[]configFiles){

this.configFiles=configFiles;

}

/**

*

*/

publicStandAloneOsgiApplicationContextCreator(){

}

publicStandAloneOsgiApplicationContextCreator(String[]configFiles){

this.setConfigFiles(configFiles);

}

/*(non-Javadoc)

*@seeorg.springframework.osgi.extender.OsgiApplicationContextCreator#createApplicationContext(org.osgi.framework.BundleContext)

*/

publicDelegatedExecutionOsgiBundleApplicationContextcreateApplicationContext(

BundleContextbundleContext)throwsException{

//TODOAuto-generatedmethodstub

Bundlebundle=bundleContext.getBundle();

this.configurationScanner=newConfigableClassPathDefaultConfigurationScanner(configFiles);

ApplicationContextConfigurationconfig=newApplicationContextConfiguration(bundle,configurationScanner);

if(log.isTraceEnabled())

log.trace("Createdconfiguration"+config+"forbundle"

+OsgiStringUtils.nullSafeNameAndSymName(bundle));

//it'snotaspringbundle,ignoreit

log.info("Discoveredconfigurations"+ObjectUtils.nullSafeToString(config.getConfigurationLocations())

+"inbundle["+OsgiStringUtils.nullSafeNameAndSymName(bundle)+"]");

String[]strs=config.getConfigurationLocations();

DelegatedExecutionOsgiBundleApplicationContextsdoac=newOsgiBundleXmlApplicationContext(

config.getConfigurationLocations());

sdoac.setBundleContext(bundleContext);

sdoac.setPublishContextAsService(this.isPublishContextAsService());

sdoac.refresh();

returnsdoac;

}

/**

*@paramargs

*/

publicstaticvoidmain(String[]args){

//TODOAuto-generatedmethodstub

}

publicbooleanisPublishContextAsService(){

returnpublishContextAsService;

}

publicvoidsetPublishContextAsService(booleanpublishContextAsService){

this.publishContextAsService=publishContextAsService;

}

}

OSGI插件中创建ApplicationContext的演示

/**

*

*@[email protected]

*

*/

publicclassActivatorimplementsBundleActivator{

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

privateLoglog=LogFactory.getLog(this.getClass());

privateDelegatedExecutionOsgiBundleApplicationContextapplicationContext;

publicDelegatedExecutionOsgiBundleApplicationContextgetApplicationContext(){

returnapplicationContext;

}

/*

*(non-Javadoc)

*

*@seeorg.osgi.framework.BundleActivator#start(org.osgi.framework.BundleContext)

*/

publicvoidstart(BundleContextcontext){

System.out.println("com.withub.gis.client.serviceStartup!!");

String[]configFiles=newString[]{"spring/bean.xml",

"/spring/client.xml","classpath:spring/osgi.xml"};

StandAloneOsgiApplicationContextCreatorstandAloneOsgiApplicationContextCreator=newStandAloneOsgiApplicationContextCreator(

configFiles);

try{

this.applicationContext=standAloneOsgiApplicationContextCreator

.createApplicationContext(context);

applicationContext.refresh();

}catch(Exceptione1){

//TODOAuto-generatedcatchblock

e1.printStackTrace();

}

System.out.println("SpringApplicationContextStartup!!");

}

/*

*(non-Javadoc)

*

*@seeorg.osgi.framework.BundleActivator#stop(org.osgi.framework.BundleContext)

*/

publicvoidstop(BundleContextcontext)throwsException{

System.out.println("com.withub.gis.client.serviceShutdown!!");

}

publicClassLoadergetClassLoader(){

returnclassLoader;

}

publicvoidsetClassLoader(ClassLoaderclassLoader){

this.classLoader=classLoader;

}

}

解释

1.上面的演示代码中,我是用spring目录下的文件作为Spring的配置文件。

我不建议你在META-INF/spring/目录下编写spring配置文件。否则,如果你的插件的部署环境中有SPringDM存在,那么就会创建2个ApplicationContext。

2.现在,你的Spring配置文件中如果使用了Hibernate,也仍然是正确无误的!

目前,使用的spring配置文件不能使用Ant风格的通配符。如果有哪位朋友对Spring源代码有研究,希望能加上这个功能。我还有项目要完成,目前只能做到这个程度了。我的代码使用了SPringDM提供的API。这是一个简单的办法。因为Spring内部的源代码太多,太复杂了。我现在的这个办法是最简单的。由于我使用了SPringDM的代码,因此,你应该了解SPringDM的一些资源搜索的策略。

OSGiSearchStrategyPrefixExplanation

ClassSpaceclasspath:Searchesthebundleclassloader(thebundle,allimportedpackagesandrequiredbundles).Forcesthebundletoberesolved.ThismethodhassimilarsemanticstoBundle#getResource(String)ClassSpaceclasspath*:Searchesthebundleclassloader(thebundleandallimportedpackagesandrequiredbundles).Forcesthebundletoberesolved.ThismethodhassimilarsemanticstoBundle#getResources(String)JARFile(orJarSpace)osgibundlejar:Searchesonlythebundlejar.Provideslow-levelaccesswithoutBundlesandApplicationContextsSpringDynamicModules(1.1.0)8OSGiSearchStrategyPrefixExplanationrequiringthebundletoberesolved.BundleSpaceosgibundle:Searchesthebundlejaranditsattachedfragments(ifthereareany).Doesnotcreateaclassloaderorforcethebundletoberesolved.

如果提供的SPring资源没有前缀,我就会为你增加classpath:前缀。经过试验,只有classpath:前缀的资源才可以被正确导入。

如,下面的配置

<context:property-placeholderlocation="spring/jdbc.properties"/>

在一般的Spring程序中是可用的。(当然,spring目录下有jdbc.properties这个文件)但是在OSGI环境下,就会报错,告诉我找不到spring/jdbc.properties这个资源。必须加上classpath:前缀,

<context:property-placeholderlocation="classpath:/spring/jdbc.properties"/>

现在就正确了!

相关推荐