一、契子
  很早以前就开始构思可动态部署的Web应用,模块化应用无疑是一种趋势,Portal应用可谓是一个小革新,它的功能引起了很多人的注意,OSGi 无疑会为这带来本质上的升级。
二、目标
这篇blog中的例子从JPetStoreOsgi衍生,通过扩展(修改)Spring mvc中的某些对象,实现模块的动态部署,当然,这只是很简单的案例,不过足以达到我的预期目标:有2个非常简单的模块module1和module2,它们都有自己的Spring mvc配置文件,可以在运行时简单的通过OSGi控制台,安装它们,并完成它们各自的功能。
三、准备工作
[点击这里下载 DynamicModule 工程包] 
由于整个Workspace太大,所以仅仅只是把更新的5个Bundle的Project上传了,先 下载JPetStoreOsgi ,然后将所有关于JPetStore的Project删除,导入这5个Project
四、Spring MVC
目前还没有用于OSGi环境的MVC框架,所以选用Spring MVC做为演示框架
org.phrancol.osgi.demo.mvc.springmvc 是整个应用的MVC Bundle,以下简称 MVCBundle
    - org.phrancol.osgi.demo.mvc.springmvc.core.HandlerRegister 
   public interface HandlerRegister public interface HandlerRegister { {
   
   /** *//** /** *//**
  * 当bundle的ApplicationContext生成后,获取HandlerMapping,并注册 * 当bundle的ApplicationContext生成后,获取HandlerMapping,并注册
  * @param context Spring为Bundle生成的ApplicationContext * @param context Spring为Bundle生成的ApplicationContext
  * @param bundle * @param bundle
  */ */
  public void registerHandler(ApplicationContext context, Bundle bundle); public void registerHandler(ApplicationContext context, Bundle bundle);
   
   /** *//** /** *//**
  * 当Bundle被停止或是卸载的时候,注销这个bundle的HandlerMapping * 当Bundle被停止或是卸载的时候,注销这个bundle的HandlerMapping
  * 当然这个功能没有实现(它可以实现),因为他不属于演示范围 * 当然这个功能没有实现(它可以实现),因为他不属于演示范围
  * @param bundle * @param bundle
  */ */
  public void unRegisterHandler(Bundle bundle); public void unRegisterHandler(Bundle bundle);
  
  } }
 
- 扩展DispatcherServlet - org.phrancol.osgi.demo.mvc.springmvc.core.OsgiDispatcherServlet
 同时,它还充当一个HandlerMapping注册管理器的角色,通过一个BundleHandlerMappingManager来管理bundle的HandlerMapping,包括动态添加/删除等,它会重写DispatcherServlet 的getHandler方法,从BundleHandlerMappingManager获取Handler.....这里的代码比较简单,一看就能明白。BundleHandlerMappingManager只是一个Map的简单操作,代码省略
  public class OsgiDispatcherServlet extends DispatcherServlet implements public class OsgiDispatcherServlet extends DispatcherServlet implements
   HandlerRegister HandlerRegister { {
  
  private static final Log log = LogFactory private static final Log log = LogFactory
  .getLog(OsgiDispatcherServlet.class); .getLog(OsgiDispatcherServlet.class);
   
   /**//* HandlerMapping管理对象 */ /**//* HandlerMapping管理对象 */
  private BundleHandlerMappingManager bundleHandlerMappingManager; private BundleHandlerMappingManager bundleHandlerMappingManager;
  
  private BundleContext bundleContext; private BundleContext bundleContext;
  
   public OsgiDispatcherServlet(BundleContext bundleContext) public OsgiDispatcherServlet(BundleContext bundleContext) { {
  this.bundleContext = bundleContext; this.bundleContext = bundleContext;
  this.bundleHandlerMappingManager = new BundleHandlerMappingManager(); this.bundleHandlerMappingManager = new BundleHandlerMappingManager();
  } }
  
  protected WebApplicationContext createWebApplicationContext( protected WebApplicationContext createWebApplicationContext(
   WebApplicationContext parent) throws BeansException WebApplicationContext parent) throws BeansException { {
  ClassLoader contextClassLoader = Thread.currentThread() ClassLoader contextClassLoader = Thread.currentThread()
  .getContextClassLoader(); .getContextClassLoader();
   try try { {
  ClassLoader cl = BundleDelegatingClassLoader ClassLoader cl = BundleDelegatingClassLoader
  .createBundleClassLoaderFor(bundleContext.getBundle(), .createBundleClassLoaderFor(bundleContext.getBundle(),
  getClass().getClassLoader()); getClass().getClassLoader());
  Thread.currentThread().setContextClassLoader(cl); Thread.currentThread().setContextClassLoader(cl);
  LocalBundleContext.setContext(bundleContext); LocalBundleContext.setContext(bundleContext);
  
  ConfigurableWebApplicationContext wac = new OSGiXmlWebApplicationContext( ConfigurableWebApplicationContext wac = new OSGiXmlWebApplicationContext(
  bundleContext); bundleContext);
  wac.setParent(parent); wac.setParent(parent);
  wac.setServletContext(getServletContext()); wac.setServletContext(getServletContext());
  wac.setServletConfig(getServletConfig()); wac.setServletConfig(getServletConfig());
  wac.setNamespace(getNamespace()); wac.setNamespace(getNamespace());
   if (getContextConfigLocation() != null) if (getContextConfigLocation() != null) { {
  wac wac
  .setConfigLocations(StringUtils .setConfigLocations(StringUtils
  .tokenizeToStringArray( .tokenizeToStringArray(
  getContextConfigLocation(), getContextConfigLocation(),
  ConfigurableWebApplicationContext.CONFIG_LOCATION_DELIMITERS)); ConfigurableWebApplicationContext.CONFIG_LOCATION_DELIMITERS));
  } }
  wac.addApplicationListener(this); wac.addApplicationListener(this);
  wac.refresh(); wac.refresh();
  return wac; return wac;
   } finally } finally { {
  Thread.currentThread().setContextClassLoader(contextClassLoader); Thread.currentThread().setContextClassLoader(contextClassLoader);
  } }
  } }
  
   /** *//** /** *//**
  * 重写这个方法是很有必要的 * 重写这个方法是很有必要的
  */ */
  protected HandlerExecutionChain getHandler(HttpServletRequest request, protected HandlerExecutionChain getHandler(HttpServletRequest request,
   boolean cache) throws Exception boolean cache) throws Exception { {
   
  HandlerExecutionChain handler = (HandlerExecutionChain) request HandlerExecutionChain handler = (HandlerExecutionChain) request
  .getAttribute(HANDLER_EXECUTION_CHAIN_ATTRIBUTE); .getAttribute(HANDLER_EXECUTION_CHAIN_ATTRIBUTE);
   if (handler != null) if (handler != null) { {
   if (!cache) if (!cache) { {
  request.removeAttribute(HANDLER_EXECUTION_CHAIN_ATTRIBUTE); request.removeAttribute(HANDLER_EXECUTION_CHAIN_ATTRIBUTE);
  } }
  return handler; return handler;
  } }
  
  for (Iterator _it = this.bundleHandlerMappingManager for (Iterator _it = this.bundleHandlerMappingManager
   .getBundlesHandlerMapping().values().iterator(); _it.hasNext();) .getBundlesHandlerMapping().values().iterator(); _it.hasNext();) { {
  List _handlerMappings = (List) _it.next(); List _handlerMappings = (List) _it.next();
  
   for (Iterator it = _handlerMappings.iterator(); it.hasNext();) for (Iterator it = _handlerMappings.iterator(); it.hasNext();) { {
   
  HandlerMapping hm = (HandlerMapping) it.next(); HandlerMapping hm = (HandlerMapping) it.next();
   if (logger.isDebugEnabled()) if (logger.isDebugEnabled()) { {
  logger.debug("Testing handler map [" + hm logger.debug("Testing handler map [" + hm
  + "] in OsgiDispatcherServlet with name '" + "] in OsgiDispatcherServlet with name '"
  + getServletName() + "'"); + getServletName() + "'");
  } }
  handler = hm.getHandler(request); handler = hm.getHandler(request);
   if (handler != null) if (handler != null) { {
   if (cache) if (cache) { {
  request.setAttribute(HANDLER_EXECUTION_CHAIN_ATTRIBUTE, request.setAttribute(HANDLER_EXECUTION_CHAIN_ATTRIBUTE,
  handler); handler);
  } }
  return handler; return handler;
  } }
  } }
  } }
  return null; return null;
  } }
  
   /** *//** /** *//**
  * 这个功能实现起来有点牵强,但是以演示为主,一笑而过 * 这个功能实现起来有点牵强,但是以演示为主,一笑而过
  */ */
  protected View resolveViewName(String viewName, Map model, Locale locale, protected View resolveViewName(String viewName, Map model, Locale locale,
   HttpServletRequest request) throws Exception HttpServletRequest request) throws Exception { {
  long bundleId = this.bundleHandlerMappingManager.getBundleId(request); long bundleId = this.bundleHandlerMappingManager.getBundleId(request);
  Bundle bundle = this.bundleContext.getBundle(bundleId); Bundle bundle = this.bundleContext.getBundle(bundleId);
  ViewResolver viewResolver = new OsgiInternalResourceViewResolver( ViewResolver viewResolver = new OsgiInternalResourceViewResolver(
  bundle, getWebApplicationContext(), viewName); bundle, getWebApplicationContext(), viewName);
  View view = viewResolver.resolveViewName(viewName, locale); View view = viewResolver.resolveViewName(viewName, locale);
  return view; return view;
  } }
  
   public void registerHandler(ApplicationContext context, Bundle bundle) public void registerHandler(ApplicationContext context, Bundle bundle) { {
  Map matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors( Map matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(
  context, HandlerMapping.class, true, false); context, HandlerMapping.class, true, false);
   if (!matchingBeans.isEmpty()) if (!matchingBeans.isEmpty()) { {
  List _list = new ArrayList(matchingBeans.values()); List _list = new ArrayList(matchingBeans.values());
  String bundleId = new Long(bundle.getBundleId()).toString(); String bundleId = new Long(bundle.getBundleId()).toString();
  this.bundleHandlerMappingManager.registerHandlerMapping(bundleId, this.bundleHandlerMappingManager.registerHandlerMapping(bundleId,
  _list); _list);
  } }
  } }
   
   public void unRegisterHandler(Bundle bundle) public void unRegisterHandler(Bundle bundle) { {
  String bundleId = new Long(bundle.getBundleId()).toString(); String bundleId = new Long(bundle.getBundleId()).toString();
  this.bundleHandlerMappingManager.unRegisterHandlerMapping(bundleId); this.bundleHandlerMappingManager.unRegisterHandlerMapping(bundleId);
  } }
  } }
 
- 扩展InternalResourceViewResolver - org.phrancol.osgi.demo.mvc.springmvc.core.OsgiInternalResourceViewResolver
 为了方便,这部份的代码写得有些不地道(演示为主~),重写getPrefix()方法,主要是为了获取jsp文件
  public class OsgiInternalResourceViewResolver extends public class OsgiInternalResourceViewResolver extends
   InternalResourceViewResolver InternalResourceViewResolver { {
   
  private static final Log log = LogFactory.getLog(OsgiInternalResourceViewResolver.class); private static final Log log = LogFactory.getLog(OsgiInternalResourceViewResolver.class);
   
  private static final String PREFIX = "/web/jsp/spring/"; private static final String PREFIX = "/web/jsp/spring/";
   
  private static final String SUFFIX = ".jsp"; private static final String SUFFIX = ".jsp";
   
  private String viewName; private String viewName;
   
  private Bundle bundle; private Bundle bundle;
   
   public OsgiInternalResourceViewResolver(Bundle bundle, ApplicationContext applicationContext , String viewName) public OsgiInternalResourceViewResolver(Bundle bundle, ApplicationContext applicationContext , String viewName) { {
  this.bundle = bundle; this.bundle = bundle;
  setPrefix(PREFIX); setPrefix(PREFIX);
  setSuffix(SUFFIX); setSuffix(SUFFIX);
  setViewClass(new JstlView().getClass()); setViewClass(new JstlView().getClass());
  setApplicationContext(applicationContext); setApplicationContext(applicationContext);
   
  this.bundle = bundle; this.bundle = bundle;
  this.viewName = viewName; this.viewName = viewName;
   
  } }
   
   protected String getPrefix() protected String getPrefix() { {
  String _prefix= "/"+bundle.getSymbolicName()+PREFIX; String _prefix= "/"+bundle.getSymbolicName()+PREFIX;
  return _prefix; return _prefix;
  } }
  
  } }
 
- MVCBundle需要设置一个Activator,用于将OsgiDispatcherServlet注册为OSGi Service
   public void start(BundleContext bundleContext) throws Exception public void start(BundleContext bundleContext) throws Exception { {
  DispatcherServlet ds = new OsgiDispatcherServlet(bundleContext); DispatcherServlet ds = new OsgiDispatcherServlet(bundleContext);
  bundleContext.registerService(DispatcherServlet.class.getName(), ds, bundleContext.registerService(DispatcherServlet.class.getName(), ds,
  null); null);
  } }
 
- MVCBundle中的SpringmvcHttpServiceRegister还是需要的,它需要生成一个所谓的容器Context
   public class SpringmvcHttpServiceRegister implements HttpServiceRegister public class SpringmvcHttpServiceRegister implements HttpServiceRegister { {
  public void serviceRegister(BundleContext context, public void serviceRegister(BundleContext context,
   ApplicationContext bundleApplicationContext) ApplicationContext bundleApplicationContext) { {
   try try { {
  
  ServiceReference sr = context.getServiceReference(HttpService.class ServiceReference sr = context.getServiceReference(HttpService.class
  .getName()); .getName());
  HttpService httpService = (HttpService) context.getService(sr); HttpService httpService = (HttpService) context.getService(sr);
  HttpContext defaultContext = httpService.createDefaultHttpContext(); HttpContext defaultContext = httpService.createDefaultHttpContext();
  Dictionary<String, String> initparams = new Hashtable<String, String>(); Dictionary<String, String> initparams = new Hashtable<String, String>();
  initparams.put("load-on-startup", "1"); initparams.put("load-on-startup", "1");
   /** *//**/ /** *//**/
  ContextLoaderServlet contextloaderListener = new BundleContextLoaderServlet( ContextLoaderServlet contextloaderListener = new BundleContextLoaderServlet(
  context, bundleApplicationContext); context, bundleApplicationContext);
  httpService.registerServlet("/initContext", contextloaderListener, httpService.registerServlet("/initContext", contextloaderListener,
  initparams, defaultContext); initparams, defaultContext);
  /**/ /**/
  DispatcherServlet dispatcherServlet = (DispatcherServlet) context DispatcherServlet dispatcherServlet = (DispatcherServlet) context
  .getService(context .getService(context
  .getServiceReference(DispatcherServlet.class .getServiceReference(DispatcherServlet.class
  .getName())); .getName()));
   /**//* 这里给了 DispatcherServlet 一个空的配置文件,可以节省好多代码*/ /**//* 这里给了 DispatcherServlet 一个空的配置文件,可以节省好多代码*/
  dispatcherServlet dispatcherServlet
  .setContextConfigLocation("META-INF/dispatcher/DynamicModule-servlet.xml"); .setContextConfigLocation("META-INF/dispatcher/DynamicModule-servlet.xml");
  initparams = new Hashtable<String, String>(); initparams = new Hashtable<String, String>();
  initparams.put("servlet-name", "DynamicModule"); initparams.put("servlet-name", "DynamicModule");
  initparams.put("load-on-startup", "2"); initparams.put("load-on-startup", "2");
  httpService.registerServlet("/*.do", dispatcherServlet, initparams, httpService.registerServlet("/*.do", dispatcherServlet, initparams,
  defaultContext); defaultContext);
   } catch (Exception e) } catch (Exception e) { {
  e.printStackTrace(System.out); e.printStackTrace(System.out);
  } }
  } }
  } }
通过以上工作,Spring MVC就被简单的改造完了......当然他仅仅只是能实现我所要演示的功能
五、模块
新建一个模块bundle - org.phrancol.osgi.demo.mvc.springmvc.module2  ,Bundle-SymbolicName设置为module2
先看看它的bean配置
 <beans>
<beans>

 <bean id="module2Register"
    <bean id="module2Register"
 class="org.phrancol.osgi.demo.mvc.util.BundleServiceRegister">
        class="org.phrancol.osgi.demo.mvc.util.BundleServiceRegister">
 <constructor-arg>
        <constructor-arg>
 <bean
            <bean
 class="org.phrancol.osgi.demo.mvc.springmvc.module2.SpringmvcHttpServiceRegister" />
                class="org.phrancol.osgi.demo.mvc.springmvc.module2.SpringmvcHttpServiceRegister" />
 </constructor-arg>
        </constructor-arg>
 </bean>
    </bean>

 <!-- ========================= DEFINITIONS OF PUBLIC CONTROLLERS ========================= -->
    <!-- ========================= DEFINITIONS OF PUBLIC CONTROLLERS ========================= -->

 <bean id="module2HandlerMapping"
    <bean id="module2HandlerMapping"
 class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping" />
        class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping" />

 <bean name="/DynamicModule/module2.do"
    <bean name="/DynamicModule/module2.do"
 class="org.phrancol.osgi.demo.mvc.springmvc.module2.TheSecondModuleController">
        class="org.phrancol.osgi.demo.mvc.springmvc.module2.TheSecondModuleController">
 </bean>
    </bean>

 </beans>
</beans> 
也使用了一个SpringmvcHttpServiceRegister,它就是用来注册这个bundle 中的jsp和资源的

 public class SpringmvcHttpServiceRegister implements HttpServiceRegister
public class SpringmvcHttpServiceRegister implements HttpServiceRegister  {
{
 public void serviceRegister(BundleContext context,
    public void serviceRegister(BundleContext context,

 ApplicationContext bundleApplicationContext)
            ApplicationContext bundleApplicationContext)  {
{

 try
        try  {
{

 ServiceReference sr = context.getServiceReference(HttpService.class
            ServiceReference sr = context.getServiceReference(HttpService.class
 .getName());
                    .getName());

 /**//* 在上一个例子中,HttpContext的用法不对,这个用法才是正确的 */
            /**//* 在上一个例子中,HttpContext的用法不对,这个用法才是正确的 */
 HttpService httpService = (HttpService) context.getService(sr);
            HttpService httpService = (HttpService) context.getService(sr);
 HttpContext defaultContext = httpService.createDefaultHttpContext();
            HttpContext defaultContext = httpService.createDefaultHttpContext();
 httpService.registerResources("/module2", "/module2",
            httpService.registerResources("/module2", "/module2",
 defaultContext);
                    defaultContext);

 /**//*
            /**//*
 * 这个JspServlet对象中的参数"module2/web",可以理解为 The root path of module
             * 这个JspServlet对象中的参数"module2/web",可以理解为 The root path of module
 * application,它是干什么用的,请参考它的JavaDoc,建议从Eclipse的CVS中准备一份Equinox的源代码
             * application,它是干什么用的,请参考它的JavaDoc,建议从Eclipse的CVS中准备一份Equinox的源代码
 */
             */
 JspServlet jspServlet = new JspServlet(context.getBundle(),
            JspServlet jspServlet = new JspServlet(context.getBundle(),
 "/module2/web");
                    "/module2/web");
 httpService.registerServlet("/module2/*.jsp", jspServlet, null,
            httpService.registerServlet("/module2/*.jsp", jspServlet, null,
 defaultContext);
                    defaultContext);

 HandlerRegister dispatcherServlet = (HandlerRegister) context
            HandlerRegister dispatcherServlet = (HandlerRegister) context
 .getService(context
                    .getService(context
 .getServiceReference(DispatcherServlet.class
                            .getServiceReference(DispatcherServlet.class
 .getName()));
                                    .getName()));
 dispatcherServlet.registerHandler(bundleApplicationContext, context
            dispatcherServlet.registerHandler(bundleApplicationContext, context
 .getBundle());
                    .getBundle());


 } catch (Exception e)
        } catch (Exception e)  {
{
 e.printStackTrace(System.out);
            e.printStackTrace(System.out);
 }
        }
 }
    }
 }
}来看看org.phrancol.osgi.demo.mvc.springmvc.module2.TheSecondModuleController ,只有很简单的一个输出

 public class TheSecondModuleController implements Controller
public class TheSecondModuleController implements Controller  {
{
 
    
 private static final String VIEWSTRING = "Hello, this is the second module !";
    private static final String VIEWSTRING = "Hello, this is the second module !";

 public ModelAndView handleRequest(HttpServletRequest request,
    public ModelAndView handleRequest(HttpServletRequest request,

 HttpServletResponse response) throws Exception
            HttpServletResponse response) throws Exception  {
{
 Map model = new HashMap();
        Map model = new HashMap();
 model.put("viewString", VIEWSTRING);
        model.put("viewString", VIEWSTRING);
 ModelAndView mv = new ModelAndView("Success", model);
        ModelAndView mv = new ModelAndView("Success", model);
 return mv;
        return mv;
 }
    }

 }
}目录结构也有一点变化 /module1/web/jsp/spring/ *.jsp
模块1和模块2是一样的
 
六、运行
将模块二导出为bundle jar包,放到C盘根目录下,启动这个应用(当然不要启动modure2),在浏览器看看module1的运行情况
/module1.gif)
现在安装一下module2
/module2_install.gif)
试着访问一下module2
/module2_404.gif)
404,正常,启动一下这个bundle再看看
/module2.gif)
显示出来了,现在可以动态的操作这2个模块了......
七、扩展
通过这个演示,可以领略到OSGi带给我们的一小部分功能,做一些扩展看看
1.  当然是各种框架的支持。
2.  强大的bundle资源库
3.  绝对动态的部署框架,可以通过UI界面来操作。
4.  可以从URL来安装bundle,  install http://www.domain.com/sampleBundle.jar ,如果是这样的,服务网关就能体现出来了,你提供一个服务框架,别人可以通过你的框架运行自己的服务。
5.   个人猜测,它将取代Portal的运行模式
6.  ..........
八、结束语
OSGi在Web应用中还有很长的路要走,它到底会发展成什么样子,就目前的功能还真不好推测。
现在不管是MVC还是持久层都还没有框架对OSGi的支持,我个人准备用业余时间研究一下这方面,顺便也可以练练手,希望传说中的强人能开发这样的框架并不吝开源~
九、相关资源
就我目前能找到的一些资源,列出如下:
Struts2有一个OSGi的插件,但是我看了看,并不能达到预期效果,不过可以看一看
http://cwiki.apache.org/S2PLUGINS/osgi-plugin.html
在持久层方面,db4o似乎有这个打算,不做评论
http://www.db4o.com/osgi/
另外它的合作伙伴prosyst已经开发出了一个基于Equinox的OSGi Server,还有个专业版,好像要收费,所以也就没下载,不知道是个什么样子。
http://www.prosyst.com/
	
posted on 2007-11-01 15:09 
Phrancol Yang 阅读(6293) 
评论(5)  编辑  收藏  所属分类: 
OSGI