天天看点

可动态部署的web应用

一、契子

  很早以前就开始构思可动态部署的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

  1. org.phrancol.osgi.demo.mvc.springmvc.core.HandlerRegister
    可动态部署的web应用
    public  interface HandlerRegister  {
    可动态部署的web应用
    可动态部署的web应用
    可动态部署的web应用
        public void registerHandler(ApplicationContext context, Bundle bundle);
    可动态部署的web应用
    可动态部署的web应用
    可动态部署的web应用
        public void unRegisterHandler(Bundle bundle);
    可动态部署的web应用
    可动态部署的web应用
    }
  2. 扩展DispatcherServlet - org.phrancol.osgi.demo.mvc.springmvc.core.OsgiDispatcherServlet

    同时,它还充当一个HandlerMapping注册管理器的角色,通过一个BundleHandlerMappingManager来管理bundle的HandlerMapping,包括动态添加/删除等,它会重写DispatcherServlet 的getHandler方法,从BundleHandlerMappingManager获取Handler.....这里的代码比较简单,一看就能明白。BundleHandlerMappingManager只是一个Map的简单操作,代码省略

    可动态部署的web应用
    public  class OsgiDispatcherServlet  extends DispatcherServlet  implements
    可动态部署的web应用
            HandlerRegister  {
    可动态部署的web应用
    可动态部署的web应用
        private static final Log log = LogFactory
    可动态部署的web应用
                .getLog(OsgiDispatcherServlet.class);
    可动态部署的web应用
    可动态部署的web应用
    可动态部署的web应用
        private BundleHandlerMappingManager bundleHandlerMappingManager;
    可动态部署的web应用
    可动态部署的web应用
        private BundleContext bundleContext;
    可动态部署的web应用
    可动态部署的web应用
        public OsgiDispatcherServlet(BundleContext bundleContext) {
    可动态部署的web应用
            this.bundleContext = bundleContext;
    可动态部署的web应用
            this.bundleHandlerMappingManager = new BundleHandlerMappingManager();
    可动态部署的web应用
        }
    可动态部署的web应用
    可动态部署的web应用
        protected WebApplicationContext createWebApplicationContext(
    可动态部署的web应用
                WebApplicationContext parent) throws BeansException {
    可动态部署的web应用
            ClassLoader contextClassLoader = Thread.currentThread()
    可动态部署的web应用
                    .getContextClassLoader();
    可动态部署的web应用
            try {
    可动态部署的web应用
                ClassLoader cl = BundleDelegatingClassLoader
    可动态部署的web应用
                        .createBundleClassLoaderFor(bundleContext.getBundle(),
    可动态部署的web应用
                                getClass().getClassLoader());
    可动态部署的web应用
                Thread.currentThread().setContextClassLoader(cl);
    可动态部署的web应用
                LocalBundleContext.setContext(bundleContext);
    可动态部署的web应用
    可动态部署的web应用
                ConfigurableWebApplicationContext wac = new OSGiXmlWebApplicationContext(
    可动态部署的web应用
                        bundleContext);
    可动态部署的web应用
                wac.setParent(parent);
    可动态部署的web应用
                wac.setServletContext(getServletContext());
    可动态部署的web应用
                wac.setServletConfig(getServletConfig());
    可动态部署的web应用
                wac.setNamespace(getNamespace());
    可动态部署的web应用
                if (getContextConfigLocation() != null) {
    可动态部署的web应用
                    wac
    可动态部署的web应用
                            .setConfigLocations(StringUtils
    可动态部署的web应用
                                    .tokenizeToStringArray(
    可动态部署的web应用
                                            getContextConfigLocation(),
    可动态部署的web应用
                                            ConfigurableWebApplicationContext.CONFIG_LOCATION_DELIMITERS));
    可动态部署的web应用
                }
    可动态部署的web应用
                wac.addApplicationListener(this);
    可动态部署的web应用
                wac.refresh();
    可动态部署的web应用
                return wac;
    可动态部署的web应用
            } finally {
    可动态部署的web应用
                Thread.currentThread().setContextClassLoader(contextClassLoader);
    可动态部署的web应用
            }
    可动态部署的web应用
        }
    可动态部署的web应用
    可动态部署的web应用
    可动态部署的web应用
        protected HandlerExecutionChain getHandler(HttpServletRequest request,
    可动态部署的web应用
                boolean cache) throws Exception {
    可动态部署的web应用
    可动态部署的web应用
            HandlerExecutionChain handler = (HandlerExecutionChain) request
    可动态部署的web应用
                    .getAttribute(HANDLER_EXECUTION_CHAIN_ATTRIBUTE);
    可动态部署的web应用
            if (handler != null) {
    可动态部署的web应用
                if (!cache) {
    可动态部署的web应用
                    request.removeAttribute(HANDLER_EXECUTION_CHAIN_ATTRIBUTE);
    可动态部署的web应用
                }
    可动态部署的web应用
                return handler;
    可动态部署的web应用
            }
    可动态部署的web应用
    可动态部署的web应用
            for (Iterator _it = this.bundleHandlerMappingManager
    可动态部署的web应用
                    .getBundlesHandlerMapping().values().iterator(); _it.hasNext();) {
    可动态部署的web应用
                List _handlerMappings = (List) _it.next();
    可动态部署的web应用
    可动态部署的web应用
                for (Iterator it = _handlerMappings.iterator(); it.hasNext();) {
    可动态部署的web应用
    可动态部署的web应用
                    HandlerMapping hm = (HandlerMapping) it.next();
    可动态部署的web应用
                    if (logger.isDebugEnabled()) {
    可动态部署的web应用
                        logger.debug("Testing handler map [" + hm
    可动态部署的web应用
                                + "] in OsgiDispatcherServlet with name '"
    可动态部署的web应用
                                + getServletName() + "'");
    可动态部署的web应用
                    }
    可动态部署的web应用
                    handler = hm.getHandler(request);
    可动态部署的web应用
                    if (handler != null) {
    可动态部署的web应用
                        if (cache) {
    可动态部署的web应用
                            request.setAttribute(HANDLER_EXECUTION_CHAIN_ATTRIBUTE,
    可动态部署的web应用
                                    handler);
    可动态部署的web应用
                        }
    可动态部署的web应用
                        return handler;
    可动态部署的web应用
                    }
    可动态部署的web应用
                }
    可动态部署的web应用
            }
    可动态部署的web应用
            return null;
    可动态部署的web应用
        }
    可动态部署的web应用
    可动态部署的web应用
    可动态部署的web应用
        protected View resolveViewName(String viewName, Map model, Locale locale,
    可动态部署的web应用
                HttpServletRequest request) throws Exception {
    可动态部署的web应用
            long bundleId = this.bundleHandlerMappingManager.getBundleId(request);
    可动态部署的web应用
            Bundle bundle = this.bundleContext.getBundle(bundleId);
    可动态部署的web应用
            ViewResolver viewResolver = new OsgiInternalResourceViewResolver(
    可动态部署的web应用
                    bundle, getWebApplicationContext(), viewName);
    可动态部署的web应用
            View view = viewResolver.resolveViewName(viewName, locale);
    可动态部署的web应用
            return view;
    可动态部署的web应用
        }
    可动态部署的web应用
    可动态部署的web应用
        public void registerHandler(ApplicationContext context, Bundle bundle) {
    可动态部署的web应用
            Map matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(
    可动态部署的web应用
                    context, HandlerMapping.class, true, false);
    可动态部署的web应用
            if (!matchingBeans.isEmpty()) {
    可动态部署的web应用
                List _list = new ArrayList(matchingBeans.values());
    可动态部署的web应用
                String bundleId = new Long(bundle.getBundleId()).toString();
    可动态部署的web应用
                this.bundleHandlerMappingManager.registerHandlerMapping(bundleId,
    可动态部署的web应用
                        _list);
    可动态部署的web应用
            }
    可动态部署的web应用
        }
    可动态部署的web应用
    可动态部署的web应用
        public void unRegisterHandler(Bundle bundle){
    可动态部署的web应用
            String bundleId = new Long(bundle.getBundleId()).toString();
    可动态部署的web应用
            this.bundleHandlerMappingManager.unRegisterHandlerMapping(bundleId);
    可动态部署的web应用
        }
    可动态部署的web应用
    }
  3. 扩展InternalResourceViewResolver - org.phrancol.osgi.demo.mvc.springmvc.core.OsgiInternalResourceViewResolver

    为了方便,这部份的代码写得有些不地道(演示为主~),重写getPrefix()方法,主要是为了获取jsp文件

    可动态部署的web应用
    public  class OsgiInternalResourceViewResolver  extends
    可动态部署的web应用
            InternalResourceViewResolver  {
    可动态部署的web应用
    可动态部署的web应用
        private static final Log log = LogFactory.getLog(OsgiInternalResourceViewResolver.class);
    可动态部署的web应用
    可动态部署的web应用
        private static final String PREFIX = "/web/jsp/spring/";
    可动态部署的web应用
    可动态部署的web应用
        private static final String SUFFIX = ".jsp";
    可动态部署的web应用
    可动态部署的web应用
        private String viewName;
    可动态部署的web应用
    可动态部署的web应用
        private Bundle bundle;
    可动态部署的web应用
    可动态部署的web应用
        public OsgiInternalResourceViewResolver(Bundle bundle, ApplicationContext applicationContext , String viewName){
    可动态部署的web应用
            this.bundle = bundle;
    可动态部署的web应用
            setPrefix(PREFIX);
    可动态部署的web应用
            setSuffix(SUFFIX);
    可动态部署的web应用
            setViewClass(new JstlView().getClass());
    可动态部署的web应用
            setApplicationContext(applicationContext);
    可动态部署的web应用
    可动态部署的web应用
            this.bundle = bundle;
    可动态部署的web应用
            this.viewName = viewName;
    可动态部署的web应用
    可动态部署的web应用
        }
    可动态部署的web应用
    可动态部署的web应用
        protected String getPrefix() {
    可动态部署的web应用
            String _prefix= "/"+bundle.getSymbolicName()+PREFIX;
    可动态部署的web应用
            return _prefix;
    可动态部署的web应用
        }    
    可动态部署的web应用
    可动态部署的web应用
    }
  4. MVCBundle需要设置一个Activator,用于将OsgiDispatcherServlet注册为OSGi Service
    可动态部署的web应用
    public  void start(BundleContext bundleContext)  throws Exception  {
    可动态部署的web应用
            DispatcherServlet ds = new OsgiDispatcherServlet(bundleContext);
    可动态部署的web应用
            bundleContext.registerService(DispatcherServlet.class.getName(), ds,
    可动态部署的web应用
                    null);
    可动态部署的web应用
        }
  5. MVCBundle中的SpringmvcHttpServiceRegister还是需要的,它需要生成一个所谓的容器Context
    可动态部署的web应用
    public  class SpringmvcHttpServiceRegister  implements HttpServiceRegister  {
    可动态部署的web应用
        public void serviceRegister(BundleContext context,
    可动态部署的web应用
                ApplicationContext bundleApplicationContext) {
    可动态部署的web应用
            try {
    可动态部署的web应用
    可动态部署的web应用
                ServiceReference sr = context.getServiceReference(HttpService.class
    可动态部署的web应用
                        .getName());
    可动态部署的web应用
                HttpService httpService = (HttpService) context.getService(sr);
    可动态部署的web应用
                HttpContext defaultContext = httpService.createDefaultHttpContext();
    可动态部署的web应用
                Dictionary<String, String> initparams = new Hashtable<String, String>();
    可动态部署的web应用
                initparams.put("load-on-startup", "1");
    可动态部署的web应用
    可动态部署的web应用
                DispatcherServlet dispatcherServlet = (DispatcherServlet) context
    可动态部署的web应用
                        .getService(context
    可动态部署的web应用
                                .getServiceReference(DispatcherServlet.class
    可动态部署的web应用
                                        .getName()));
    可动态部署的web应用
    可动态部署的web应用
                dispatcherServlet
    可动态部署的web应用
                        .setContextConfigLocation("META-INF/dispatcher/DynamicModule-servlet.xml");
    可动态部署的web应用
                initparams = new Hashtable<String, String>();
    可动态部署的web应用
                initparams.put("servlet-name", "DynamicModule");
    可动态部署的web应用
                initparams.put("load-on-startup", "2");
    可动态部署的web应用
                httpService.registerServlet("
    可动态部署的web应用
                HttpService httpService = (HttpService) context.getService(sr);
    可动态部署的web应用
                HttpContext defaultContext = httpService.createDefaultHttpContext();
    可动态部署的web应用
                httpService.registerResources("/module2", "/module2",
    可动态部署的web应用
                        defaultContext);
    可动态部署的web应用
    可动态部署的web应用
                JspServlet jspServlet = new JspServlet(context.getBundle(),
    可动态部署的web应用
                        "/module2/web");
    可动态部署的web应用
                httpService.registerServlet("/module2/*.jsp", jspServlet, null,
    可动态部署的web应用
                        defaultContext);
    可动态部署的web应用
    可动态部署的web应用
                HandlerRegister dispatcherServlet = (HandlerRegister) context
    可动态部署的web应用
                        .getService(context
    可动态部署的web应用
                                .getServiceReference(DispatcherServlet.class
    可动态部署的web应用
                                        .getName()));
    可动态部署的web应用
                dispatcherServlet.registerHandler(bundleApplicationContext, context
    可动态部署的web应用
                        .getBundle());
    可动态部署的web应用
    可动态部署的web应用
            } catch (Exception e) {
    可动态部署的web应用
                e.printStackTrace(System.out);
    可动态部署的web应用
            }
    可动态部署的web应用
        }
    可动态部署的web应用

    }

    来看看org.phrancol.osgi.demo.mvc.springmvc.module2.TheSecondModuleController ,只有很简单的一个输出

    可动态部署的web应用
    public  class TheSecondModuleController  implements Controller  {
    可动态部署的web应用
    可动态部署的web应用
        private static final String VIEWSTRING = "Hello, this is the second module !";
    可动态部署的web应用
    可动态部署的web应用
        public ModelAndView handleRequest(HttpServletRequest request,
    可动态部署的web应用
                HttpServletResponse response) throws Exception {
    可动态部署的web应用
            Map model = new HashMap();
    可动态部署的web应用
            model.put("viewString", VIEWSTRING);
    可动态部署的web应用
            ModelAndView mv = new ModelAndView("Success", model);
    可动态部署的web应用
            return mv;
    可动态部署的web应用
        }
    可动态部署的web应用
    可动态部署的web应用

    }

    目录结构也有一点变化 /module1/web/jsp/spring/ *.jsp

    模块1和模块2是一样的

    六、运行

    将模块二导出为bundle jar包,放到C盘根目录下,启动这个应用(当然不要启动modure2),在浏览器看看module1的运行情况
    可动态部署的web应用
    现在安装一下module2
    可动态部署的web应用
    试着访问一下module2
    可动态部署的web应用
    404,正常,启动一下这个bundle再看看
    可动态部署的web应用
    显示出来了,现在可以动态的操作这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/