YCOE

You Can't stOp mE!

导航

[原创]DWR与OSGi的整合

Posted on 2007-09-17 22:35  YCOE  阅读(1969)  评论(3编辑  收藏  举报

  最近一个项目中用了OSGi,此时OSGi还没有引起足够多人的重视,无论在国内还是国外,所以在开发过程中遇到的问题只能自己慢慢去找。但是,特别想感谢BlueDavy,是他的OpenDoc带我进入了OSGi的世界。
  先说下我的项目中遇到的困难吧,首先我选择了使用HTTP作为应用的View层。OSGi对HTTP的支持还很小,就连最近版本的org.eclipse.equinox.http_1.0.1在HTTP服务的支持上都还有很多还没有实现的,就像Servlet2.4中的编码、过滤器、监听器......还有好多方法。只能自己实现,其实我们可以考虑使用Bridge的,但出项目性质考虑,还是使用了OSGi直接支持的HTTP服务。至于org.eclipse.equinox.http中的一些方法的实现我有空再写吧。
  好了,搞定了HTTP服务,现在就想用回我们熟悉的Spring、Hibernate。这在别人的文章里都有写了,特别是在JavaEye的OSGi专栏里已经有人实验过了。呵呵,就差DWR。听说BlueDavy也在用OSGi+DWR,但也没有看到有进一步的资料。
  DWR与OSGi的整合其实挺简单的,这要归功于DWR的作者设计时的思路。在网上,一直没有人提到DWR可以有多个配置文件的,更可以在web.xml里面进行参数的设置,之前我也不知道,这两天算是把它的源代码看了一遍。要整合DWR,还得先知道它初始化的原理:
  DWR在第一次启动时运行DwrServlet中的init()方法,首先初始化一个Continer。Continer可以在web.xml中设定,这样如果必要的话可以加入自己的Continer,如果没有,则使用默认的DefaultContainer。ContainerUtil为Continer提供了很多的方法,就像Map对象一样。add....(...)、get(...)方法
   完成Continer初始化后,就开始加载配置文件。这里会加载几个配置文件
        首先会加载dwr.jar包里面的dwr.xml文件,这个文件里面定义了基本类型的转换,比如String、Date、Collection....还有一些Creator。比如new、null、Spring...这样的设计有一个好处,就是在以后的扩展中可以很轻松地将一个新的Creator加进去。
        接着,加载程序configureFromInitParams会去查找ServletConfig里,即是web.xml中DwrServlet中有没有以"config"开关的Name,有话就提取出它的Value,并且以"\n"符把它分割,分别加载。这段代码如下:

        Enumeration en = servletConfig.getInitParameterNames();
        
boolean foundConfig = false;
        
while (en.hasMoreElements())
        
{
            String name 
= (String) en.nextElement();
            String value 
= servletConfig.getInitParameter(name);

            
// if the init param starts with "config" then try to load it
            if (name.startsWith(INIT_CONFIG))
            
{
                foundConfig 
= true;

                StringTokenizer st 
= new StringTokenizer(value, "\n,");
                
while (st.hasMoreTokens())
                
{
                    String fileName 
= st.nextToken().trim();
                    DwrXmlConfigurator local 
= new DwrXmlConfigurator();
                    local.setServletResourceName(fileName);
                    local.configure(container);
                }

            }

            
else if (name.equals(INIT_CUSTOM_CONFIGURATOR))
            
{
                foundConfig 
= true;

                
try
                
{
                    Configurator configurator 
= (Configurator) LocalUtil.classNewInstance(INIT_CUSTOM_CONFIGURATOR, value, Configurator.class);
                    configurator.configure(container);
                    log.debug(
"Loaded config from: " + value);
                }

                
catch (Exception ex)
                
{
                    log.warn(
"Failed to start custom configurator", ex);
                }

            }

        }


  也就是说我们可以在web.xml中进行以下配置,以加载多个dwr.xml文件:

    <servlet>
        
<servlet-name>dwr-invoker</servlet-name>
        
<servlet-class>uk.ltd.getahead.dwr.DWRServlet</servlet-class>
        
<init-param>
            
<param-name>config</param-name>
            
<param-value>
                dwrConfig1.xml
                dwrConfig2.xml
                .
            
</param-value>
        
</init-param>
        
<init-param>
            
<param-name>debug</param-name>
            
<param-value>true</param-value>
        
</init-param>
    
</servlet>

    
<servlet-mapping>
        
<servlet-name>dwr-invoker</servlet-name>
        
<url-pattern>/dwr/*</url-pattern>
    
</servlet-mapping>

  其实这一段代码就是我们所需要的,在OSGi里面,可以在每个Bundle里面放置自己的dwr.xml文件,以配置自己的HTTP中的DWR方法。
        好了,让我们开始OSGi的DWR之旅吧。
  新建一个Plug-in项目。初始化,可以把它称为DwrServer。继续初始化......
  在Eclipse时里面开始的话,让它生成Activator.java文件,它是Bundle级的Listener。代码如下:
        

package com.ycoe.dwr;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
import java.net.URLConnection;
import java.util.Hashtable;

import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.parsers.ParserConfigurationException;

import org.directwebremoting.Container;
import org.directwebremoting.WebContextFactory;
import org.directwebremoting.WebContextFactory.WebContextBuilder;
import org.directwebremoting.extend.ServerLoadMonitor;
import org.directwebremoting.impl.ContainerUtil;
import org.directwebremoting.impl.DefaultContainer;
import org.directwebremoting.impl.StartupUtil;
import org.directwebremoting.servlet.UrlProcessor;
import org.directwebremoting.util.Logger;
import org.directwebremoting.util.ServletLoggingOutput;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceEvent;
import org.osgi.framework.ServiceListener;
import org.osgi.framework.ServiceReference;
import org.osgi.service.http.HttpService;
import org.osgi.util.tracker.ServiceTracker;
import org.xml.sax.SAXException;

public class Activator extends HttpServlet implements BundleActivator,
        ServiceListener 
{

    
private BundleContext bc;

    
private ServiceReference ref;

    
private Servlet servlet;


    
/**
     * dwr container
     
*/

    
private DefaultContainer container;

    
/**
     * 用于储存http的WebContext对象
     
*/

    
private WebContextBuilder webContextBuilder;

    
/**
     * 日志
     
*/

    
public static final Logger log = Logger.getLogger(Activator.class);

    
/**
     * 取得DWR里的Container
     * 
@return container
     
*/

    
public Container getContainer() {
        
return container;
    }


    
/*
     * (non-Javadoc)
     * 
     * @see org.osgi.framework.BundleActivator#start(org.osgi.framework.BundleContext)
     
*/

    
public void start(BundleContext context) throws Exception {

        bc 
= context;
        registerServlet();
        context.addServiceListener(
this"(objectClass="
                
+ HttpService.class.getName() + ")");

        
try {
            
// 初始化DWR,让Bundle启动时就去加载dwr配置,其实这可以放在其它地方做的
            
//有人建议不要在bundle启动时加载太多东西,会影响启动速度
            
//也是,但根据项目需求喽
            URL url = new URL("HTTP""127.0.0.1"80"/ajax/about");
            URLConnection c 
= url.openConnection();
            c.connect();
            
//以下代码用于测试,在应用时可以注释掉
            BufferedReader in = new BufferedReader(new InputStreamReader(c
                    .getInputStream()));
            String line 
= null;
            StringBuffer content 
= new StringBuffer();
            
while ((line = in.readLine()) != null{// line为返回值,这就可以判断是否成功、
                content.append(line);
            }

            log.info(content.toString());
            in.close();
            in 
= null;
            url 
= null;
        }
 catch (Exception e) {
        }

    }


    
/*
     * 
     * @see org.osgi.framework.BundleActivator#stop(org.osgi.framework.BundleContext)
     
*/

    
public void stop(BundleContext context) throws Exception {
        
try {
            unregisterServlet();
        }
 catch (Throwable t) {
            t.printStackTrace();
        }

        
//这里有待完善,因为在这个Bundle停止时我们应该删除这个Bundle加载到Continer里的信息
        servlet = null;
        bc 
= null;
        ref 
= null;
    }


    
/*
     * 注册Web应用
     
*/

    
private void registerServlet() {
        
if (ref == null{
            ref 
= bc.getServiceReference(HttpService.class.getName());
        }

        
if (ref != null{
            
try {
                HttpService http 
= (HttpService) bc.getService(ref);
                
//这里把dwr的请求都定义为以/ajax开头
                http.registerServlet("/ajax"new Activator(), nullnull);
            }
 catch (Exception e) {
                e.printStackTrace();
            }

        }

    }


    
public void serviceChanged(ServiceEvent event) {
        
// TODO Auto-generated method stub
        
//这里可以再添加Bundle变化时的动作
    }


    
/*
     * 卸载Web应用
     
*/

    
private void unregisterServlet() {
        
if (ref != null{
            
try {
                HttpService http 
= (HttpService) bc.getService(ref);
                http.unregister(
"/ajax");
            }
 catch (Exception e) {
                e.printStackTrace();
            }

        }

    }


    @Override
    
public void init(ServletConfig servletConfig) throws ServletException {
        
super.init(servletConfig);
        ServletContext servletContext 
= servletConfig.getServletContext();
        
try {
            
//取得一个Container
            container = ContainerUtil.createDefaultContainer(servletConfig);
            
//初始化Container
            ContainerUtil.setupDefaultContainer(container, servletConfig);
            
//取得一个webContextBuilder,用于保存Servlet中的状态
            webContextBuilder = StartupUtil.initWebContext(servletConfig,
                    servletContext, container);
            StartupUtil.initServerContext(servletConfig, servletContext,
                    container);
            ContainerUtil.prepareForWebContextFilter(servletContext,
                    servletConfig, container, webContextBuilder, 
this);
            
//这里是加载各个bundle里的dwr配置文件
            DwrLoader.loadDwrConfig(container);
            ContainerUtil.publishContainer(container, servletConfig);
        }
 catch (ExceptionInInitializerError ex) {
            log.fatal(
"ExceptionInInitializerError. Nested exception:", ex
                    .getException());
            
throw new ServletException(ex);
        }
 catch (Exception ex) {
            log.fatal(
"DwrServlet.init() failed", ex);
            
throw new ServletException(ex);
        }
 finally {
            
if (webContextBuilder != null{
                webContextBuilder.unset();
            }

            ServletLoggingOutput.unsetExecutionContext();
        }

    }


    
public void doPost(HttpServletRequest request, HttpServletResponse response)
            
throws ServletException, IOException {
        
try {
            webContextBuilder.set(request, response, getServletConfig(),
                    getServletContext(), container);
            ServletLoggingOutput.setExecutionContext(
this);
            UrlProcessor processor 
= (UrlProcessor) container
                    .getBean(UrlProcessor.
class.getName());
            processor.handle(request, response);
        }
 finally {
            webContextBuilder.unset();
            ServletLoggingOutput.unsetExecutionContext();
        }

    }


    
public void doGet(HttpServletRequest request, HttpServletResponse response)
            
throws ServletException, IOException {
        doPost(request, response);
    }


    
public void destroy() {
        shutdown();
        
super.destroy();
    }


    
public void shutdown() {
        ServerLoadMonitor monitor 
= (ServerLoadMonitor) container
                .getBean(ServerLoadMonitor.
class.getName());
        monitor.shutdown();
    }

}

  如果看过dwr源代码的人也许会发现,这个类和DwrServlet非常地像,的确,我是拷那里的。为什么不用它原来的呢?后面会写原因。。。
  好了,接下来,是实现加载每个Bundle里的dwr配置的文件类DwrLoader.java了

package com.ycoe.dwr;

import java.io.IOException;
import java.util.Dictionary;

import javax.xml.parsers.ParserConfigurationException;

import org.directwebremoting.impl.DefaultContainer;
import org.directwebremoting.impl.DwrXmlConfigurator;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.xml.sax.SAXException;
import com.ycoe.core.SystemContext;

public class DwrLoader {
    
/**
     * 权限配置文件
     
*/

    
private static final String DWR_CONFIG_FILE = "DWR-AJAX";

    @SuppressWarnings(
"unchecked")
    
public static void loadDwrConfig(
            DefaultContainer container) 
throws IOException, ParserConfigurationException, SAXException{
        BundleContext context 
= SystemContext.getUsysContext().getContext();
        Bundle[] bundles 
= context.getBundles();
        
for (Bundle bundle : bundles) {
            Dictionary headers 
= bundle.getHeaders();
            String config 
= (String) headers.get(DWR_CONFIG_FILE);
            
// 如果配置文件存在
            if (null != config) {
                String[] configXmls 
= config.split(",");
                
for (String configXml : configXmls) {
                    DwrXmlConfigurator system 
= new DwrXmlConfigurator();
                    system.setClassResourceName(configXml);
                    system.configure(container);
                }

            }

        }

    }

}

  完成!
        然而会发现出错了。接下来的内容我在回复中写好了。See you...