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; } }
这样就大功告成了!