随笔 - 1  文章 - 37  trackbacks - 0
<2024年12月>
24252627282930
1234567
891011121314
15161718192021
22232425262728
2930311234

留言簿(16)

随笔分类

随笔档案

文章分类

文章档案

test

搜索

  •  

最新评论

一、契子

  很早以前就开始构思可动态部署的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/
posted on 2007-11-01 15:09 Phrancol Yang 阅读(6274) 评论(5)  编辑  收藏 所属分类: OSGI

FeedBack:
# re: 构建模块化的动态Web应用(演示版) 2007-12-03 14:47 ky
petStoreOsgi的包下不到啊,是不是链接失效了?Thanks!  回复  更多评论
  
# re: 构建模块化的动态Web应用(演示版) 2007-12-11 09:54 Phrancol Yang
可以下载啊,不要用 Firefox 访问那个下载页面  回复  更多评论
  
# re: 构建模块化的动态Web应用(演示版) 2008-02-14 17:51 s
启动出现错误org.springframework.ejb.config.JeeNamespaceHandler not found
是什么原因,谢谢!  回复  更多评论
  
# re: 构建模块化的动态Web应用(演示版) 2008-02-15 09:14 Phrancol Yang
@s
http://forum.springframework.org/showthread.php?t=33474
看看这个,如果还不能解决你的问题,请将问题发详细一些~  回复  更多评论
  
# re: 构建模块化的动态Web应用(演示版) 2008-07-11 18:04 亚龙
太好了,帮你顶!!  回复  更多评论
  

只有注册用户登录后才能发表评论。


网站导航: