Spring DM与Struts2集成
上一篇文章已经将struts2集成到OSGi环境中了,但要在struts2中使用OSGi的服务还是很麻烦,要自己手动查找服务,而Spring DM则提供了相应的标签来支持查找OSGi服务,所以现在的目标就是让Struts2中的配置文件可以使用Spring DM中定义的Bean。
首先我们要搞清楚SpringDM的工作原理,SpringDM是将每个Bundle下面的/META-INF/Spring/*.xml文件加载进来,创建Spring的上下文,但bundle之间的上下文是隔离的,是存放在org.springframework.osgi.extender.internal.activator.LifecycleManager类中的一个成员变量managedContexts中,此为一个Map,键为Bundle的id,值为bundle对应的Spring上下文,只要我们把bundle对应的Spring上下文找到,要完成在struts2中使用Spring的Bean就容易了。我的做法是将这个map发布成一个服务,然后在Struts的bundle中引用。先将这个Map开放出来。先将org.springframework.osgi.extender以插件的形式导入到工程中来,然后把LifecycleManager的源代码放到src下,在LifecycleManager类中增加方法
public Map<Long, ConfigurableOsgiBundleApplicationContext> getManagedContexts() {
return managedContexts;
}
然后再编写一个自己的类,持有这个Map
/**
* 用于存放Spring DM的上下文环境。
* @author Dream.Lee
* @version 2013-6-6
*/
public class SpringContextHolder {
public SpringContextHolder(
Map<Long, ConfigurableOsgiBundleApplicationContext> bundleContexts) {
super();
this.bundleContexts = bundleContexts;
}
/**
* key为bundle的id,value为bunlde对应的Spring上下文。
*/
private Map<Long, ConfigurableOsgiBundleApplicationContext> bundleContexts;
public Map<Long, ConfigurableOsgiBundleApplicationContext> getBundleContexts() {
return bundleContexts;
}
public void setBundleContexts(
Map<Long, ConfigurableOsgiBundleApplicationContext> bundleContexts) {
this.bundleContexts = bundleContexts;
}
}
然后修改org.springframework.osgi.extender.internal.activator.ContextLoaderListener的start()方法,在最后加上
//将SpringContextHolder发布成服务,在其它bundle中可以通过bundleid来取得spring的上下文。
holderSF=context.registerService(SpringContextHolder.class.getName(), new SpringContextHolder(lifecycleManager.getManagedContexts()), null);
SpringDM的代码已经改造完成,下面是改造Struts2的代码
由于Struts2中并不能在运行过程中取得自己所在的Bundle,所以我们只有将action所在的bundle保存起来,我的办法是,在解析配置文件的时候在package的标签中加上bundleid,然后在addPackage的方法中,有一个全局变量把action所有的bundle保存下来,以namespace.action为键,bundleid为值,相应的代码如下
if ("package".equals(nodeName)) {
if(!configuration.getPackageConfigNames().contains(child.getAttribute("name"))){
PackageConfig cfg = addPackage(child);
//FIXME 将action的全路径名与对应的bundle存储到map中。
if(child.getAttribute("bundleid")!=null&&child.getAttribute("bundleid").length()!=0){
for(Entry<String, ActionConfig> entry:cfg.getActionConfigs().entrySet()){
actionNameMapping.put(cfg.getNamespace()+"."+entry.getValue().getName(), Long.parseLong(child.getAttribute("bundleid")));
}
}
if (cfg.isNeedsRefresh()) {
reloads.add(child);
}
}
}
if ("package".equals(nodeName)) {
PackageConfig cfg = addPackage(child);
//FIXME 将action的全路径名与对应的bundle存储到map中。
if(child.getAttribute("bundleid")!=null&&child.getAttribute("bundleid").length()!=0){
for(Entry<String, ActionConfig> entry:cfg.getActionConfigs().entrySet()){
actionNameMapping.put(cfg.getNamespace()+"."+entry.getValue().getName(), Long.parseLong(child.getAttribute("bundleid")));
}
}
if (cfg.isNeedsRefresh()) {
reloads.add(child);
}
}
就是上一篇文章提供的那个xmlProvider文件中的方法,
然后是修改ObjectFactory这个类,原来的ObjectFactory在从buildAction调用buildBean的时候,虽然namespace和actionname都传给了buildAction,但buildAction并没有把namespace传给buildBean,所以我们要加一个四个参数的buildBean方法,并设置成protected,默认返回null,这样不会影响其它调用。
public Object buildAction(String actionName, String namespace, ActionConfig config, Map<String, Object> extraContext) throws Exception {
//FIXME 先调用增加的四个参数的buildBean方法,如果返回null,再调用原来的buildBean方法
Object retObj=buildBean(actionName, namespace, config.getClassName(), extraContext);
if(retObj==null) retObj=buildBean(config.getClassName(), extraContext);
return retObj;
}
/**
* 用于集成Spring DM,默认实现将直接返回空,这样不影响以前的调用。
* @param actionName
* @param namespace
* @param className
* @param extraContext
* @return
*/
protected Object buildBean(String actionName,String namespace,String className,Map<String, Object> extraContext) throws Exception{
return null;
}
然后增加自己的ObjectFactory实现,这儿是实现我们刚才增加的那个四个参数的buildBean方法
/**
* Struts2集成Spring DM所用的对象工厂。
* @author Dream.Lee
* @version 2013-6-4
*/
public class SpringOSGiObjectFactory extends StrutsObjectFactory {
private static final long serialVersionUID = -6286345242515916560L;
@Override
protected Object buildBean(String actionName, String namespace,
String className, Map<String, Object> extraContext) throws Exception {
Object obj=null;
BundleContext context =Activator.getContext();
ServiceTracker<SpringContextHolder, SpringContextHolder> st=new ServiceTracker<SpringContextHolder, SpringContextHolder>(context,SpringContextHolder.class.getName(),null);
st.open();
SpringContextHolder holder=st.getService();
long bundleid=OSGiXmlConfigurationProvider.actionNameMapping.get(namespace+"."+actionName);
ConfigurableOsgiBundleApplicationContext appcontext=holder.getBundleContexts().get(bundleid);
if(appcontext!=null){
try{
obj=appcontext.getBean(className);
injectInternalBeans(obj);
}catch(Exception e){
buildBean(className, extraContext, true);
}
}
st.close();
return obj;
}
}
这样就大功告成了!