可动态部署的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
    public  interface HandlerRegister  {
        
        /**
         * 当bundle的ApplicationContext生成后,获取HandlerMapping,并注册
         * 
    @param context Spring为Bundle生成的ApplicationContext
         * 
    @param bundle
         
    */

        public void registerHandler(ApplicationContext context, Bundle bundle);
        
        /**
         * 当Bundle被停止或是卸载的时候,注销这个bundle的HandlerMapping
         * 当然这个功能没有实现(它可以实现),因为他不属于演示范围
         * 
    @param bundle
         
    */

        public void unRegisterHandler(Bundle bundle);

    }

  2. 扩展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);
        
        /* HandlerMapping管理对象 */
        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.classtruefalse);
            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);
        }

    }

  3. 扩展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;
        }
        

    }

  4. MVCBundle需要设置一个Activator,用于将OsgiDispatcherServlet注册为OSGi Service
    public  void start(BundleContext bundleContext)  throws Exception  {
            DispatcherServlet ds = new OsgiDispatcherServlet(bundleContext);
            bundleContext.registerService(DispatcherServlet.class.getName(), ds,
                    null);
        }

  5. 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");
                /**/
                ContextLoaderServlet contextloaderListener = new BundleContextLoaderServlet(
                        context, bundleApplicationContext);
                httpService.registerServlet("/initContext", contextloaderListener,
                        initparams, defaultContext);
                /*
    */

                DispatcherServlet dispatcherServlet = (DispatcherServlet) context
                        .getService(context
                                .getServiceReference(DispatcherServlet.class
                                        .getName()));
                /* 这里给了 DispatcherServlet 一个空的配置文件,可以节省好多代码*/
                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("/*.do", dispatcherServlet, initparams,
                        defaultContext);
            }
     catch (Exception e) {
                e.printStackTrace(System.out);
            }

        }

    }

通过以上工作,Spring MVC就被简单的改造完了......当然他仅仅只是能实现我所要演示的功能

五、模块

新建一个模块bundle - org.phrancol.osgi.demo.mvc.springmvc.module2  ,Bundle-SymbolicName设置为module2
先看看它的bean配置

< beans >

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

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

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

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

</ beans >

 

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

public  class SpringmvcHttpServiceRegister  implements HttpServiceRegister  {
    public void serviceRegister(BundleContext context,
            ApplicationContext bundleApplicationContext) {
        try {

            ServiceReference sr = context.getServiceReference(HttpService.class
                    .getName());
            /* 在上一个例子中,HttpContext的用法不对,这个用法才是正确的 */
            HttpService httpService = (HttpService) context.getService(sr);
            HttpContext defaultContext = httpService.createDefaultHttpContext();
            httpService.registerResources("/module2", "/module2",
                    defaultContext);
            /*
             * 这个JspServlet对象中的参数"module2/web",可以理解为 The root path of module
             * application,它是干什么用的,请参考它的JavaDoc,建议从Eclipse的CVS中准备一份Equinox的源代码
             
*/

            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/
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值