一、契子
很早以前就开始构思可动态部署的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 void registerHandler(ApplicationContext context, 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 HandlerRegister { private static final Log log = LogFactory .getLog(OsgiDispatcherServlet.class); private BundleHandlerMappingManager bundleHandlerMappingManager; private BundleContext bundleContext; public OsgiDispatcherServlet(BundleContext bundleContext) { this.bundleContext = bundleContext; this.bundleHandlerMappingManager = new BundleHandlerMappingManager(); } protected WebApplicationContext createWebApplicationContext( WebApplicationContext parent) throws BeansException { ClassLoader contextClassLoader = Thread.currentThread() .getContextClassLoader(); try { ClassLoader cl = BundleDelegatingClassLoader .createBundleClassLoaderFor(bundleContext.getBundle(), getClass().getClassLoader()); Thread.currentThread().setContextClassLoader(cl); LocalBundleContext.setContext(bundleContext); ConfigurableWebApplicationContext wac = new OSGiXmlWebApplicationContext( bundleContext); wac.setParent(parent); wac.setServletContext(getServletContext()); wac.setServletConfig(getServletConfig()); wac.setNamespace(getNamespace()); if (getContextConfigLocation() != null) { wac .setConfigLocations(StringUtils .tokenizeToStringArray( getContextConfigLocation(), ConfigurableWebApplicationContext.CONFIG_LOCATION_DELIMITERS)); } wac.addApplicationListener(this); wac.refresh(); return wac; } finally { Thread.currentThread().setContextClassLoader(contextClassLoader); } } protected HandlerExecutionChain getHandler(HttpServletRequest request, boolean cache) throws Exception { HandlerExecutionChain handler = (HandlerExecutionChain) request .getAttribute(HANDLER_EXECUTION_CHAIN_ATTRIBUTE); if (handler != null) { if (!cache) { request.removeAttribute(HANDLER_EXECUTION_CHAIN_ATTRIBUTE); } return handler; } for (Iterator _it = this.bundleHandlerMappingManager .getBundlesHandlerMapping().values().iterator(); _it.hasNext();) { List _handlerMappings = (List) _it.next(); for (Iterator it = _handlerMappings.iterator(); it.hasNext();) { HandlerMapping hm = (HandlerMapping) it.next(); if (logger.isDebugEnabled()) { logger.debug("Testing handler map [" + hm + "] in OsgiDispatcherServlet with name '" + getServletName() + "'"); } handler = hm.getHandler(request); if (handler != null) { if (cache) { request.setAttribute(HANDLER_EXECUTION_CHAIN_ATTRIBUTE, handler); } return handler; } } } return null; } protected View resolveViewName(String viewName, Map model, Locale locale, HttpServletRequest request) throws Exception { long bundleId = this.bundleHandlerMappingManager.getBundleId(request); Bundle bundle = this.bundleContext.getBundle(bundleId); ViewResolver viewResolver = new OsgiInternalResourceViewResolver( bundle, getWebApplicationContext(), viewName); View view = viewResolver.resolveViewName(viewName, locale); return view; } public void registerHandler(ApplicationContext context, Bundle bundle) { Map matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors( context, HandlerMapping.class, true, false); if (!matchingBeans.isEmpty()) { List _list = new ArrayList(matchingBeans.values()); String bundleId = new Long(bundle.getBundleId()).toString(); this.bundleHandlerMappingManager.registerHandlerMapping(bundleId, _list); } } public void unRegisterHandler(Bundle bundle){ String bundleId = new Long(bundle.getBundleId()).toString(); this.bundleHandlerMappingManager.unRegisterHandlerMapping(bundleId); } } -
扩展InternalResourceViewResolver - org.phrancol.osgi.demo.mvc.springmvc.core.OsgiInternalResourceViewResolver
为了方便,这部份的代码写得有些不地道(演示为主~),重写getPrefix()方法,主要是为了获取jsp文件
public class OsgiInternalResourceViewResolver extends InternalResourceViewResolver { private static final Log log = LogFactory.getLog(OsgiInternalResourceViewResolver.class); private static final String PREFIX = "/web/jsp/spring/"; private static final String SUFFIX = ".jsp"; private String viewName; private Bundle bundle; public OsgiInternalResourceViewResolver(Bundle bundle, ApplicationContext applicationContext , String viewName){ this.bundle = bundle; setPrefix(PREFIX); setSuffix(SUFFIX); setViewClass(new JstlView().getClass()); setApplicationContext(applicationContext); this.bundle = bundle; this.viewName = viewName; } protected String getPrefix() { String _prefix= "/"+bundle.getSymbolicName()+PREFIX; return _prefix; } } - MVCBundle需要设置一个Activator,用于将OsgiDispatcherServlet注册为OSGi Service public void start(BundleContext bundleContext) throws Exception { DispatcherServlet ds = new OsgiDispatcherServlet(bundleContext); bundleContext.registerService(DispatcherServlet.class.getName(), ds, null); }
- MVCBundle中的SpringmvcHttpServiceRegister还是需要的,它需要生成一个所谓的容器Context public class SpringmvcHttpServiceRegister implements HttpServiceRegister { public void serviceRegister(BundleContext context, ApplicationContext bundleApplicationContext) { try { ServiceReference sr = context.getServiceReference(HttpService.class .getName()); HttpService httpService = (HttpService) context.getService(sr); HttpContext defaultContext = httpService.createDefaultHttpContext(); Dictionary<String, String> initparams = new Hashtable<String, String>(); initparams.put("load-on-startup", "1"); DispatcherServlet dispatcherServlet = (DispatcherServlet) context .getService(context .getServiceReference(DispatcherServlet.class .getName())); dispatcherServlet .setContextConfigLocation("META-INF/dispatcher/DynamicModule-servlet.xml"); initparams = new Hashtable<String, String>(); initparams.put("servlet-name", "DynamicModule"); initparams.put("load-on-startup", "2"); httpService.registerServlet(" HttpService httpService = (HttpService) context.getService(sr); HttpContext defaultContext = httpService.createDefaultHttpContext(); httpService.registerResources("/module2", "/module2", defaultContext); JspServlet jspServlet = new JspServlet(context.getBundle(), "/module2/web"); httpService.registerServlet("/module2/*.jsp", jspServlet, null, defaultContext); HandlerRegister dispatcherServlet = (HandlerRegister) context .getService(context .getServiceReference(DispatcherServlet.class .getName())); dispatcherServlet.registerHandler(bundleApplicationContext, context .getBundle()); } catch (Exception e) { e.printStackTrace(System.out); } }
}
来看看org.phrancol.osgi.demo.mvc.springmvc.module2.TheSecondModuleController ,只有很简单的一个输出
public class TheSecondModuleController implements Controller { private static final String VIEWSTRING = "Hello, this is the second module !"; public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception { Map model = new HashMap(); model.put("viewString", VIEWSTRING); ModelAndView mv = new ModelAndView("Success", model); return mv; }}
目录结构也有一点变化 /module1/web/jsp/spring/ *.jsp
模块1和模块2是一样的
六、运行
将模块二导出为bundle jar包,放到C盘根目录下,启动这个应用(当然不要启动modure2),在浏览器看看module1的运行情况 现在安装一下module2 试着访问一下module2 404,正常,启动一下这个bundle再看看 显示出来了,现在可以动态的操作这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/