9.9.3 Equinox框架嵌入到Web容器中

Eclipse Equinox框架支持被嵌入到Web容器中来使用。可以在servlet容器中启动一个Equinox框架,用来处理servlet请求。通过这种方式,既可以把OSGi技术应用到复杂的Web应用开发中,又可以复用已有的servlet容器的相关资产。这种实现方式是通过一个特殊的servlet来处理请求,并转发给Equinox框架来处理。当在servlet容器中嵌入Equinox框架时,会出现一些与类加载器相关的问题。这是因为servlet容器和OSGi框架内部都采用了比较复杂的类加载器实现,当它们两个在一起共同使用时,会出现无法加载Java类的情况。

以之前提到的计算器程序中的OSGi模块为例,把这些模块嵌入到servlet容器中来运行。在计算器程序的内部实现中,使用脚本语言支持API来进行运算表达式的求值。使用的脚本语言不是Java平台默认支持的JavaScript,而是Ruby。为了能够使用Ruby语言,在下载了JRuby的库之后,将其放在合适的位置以被脚本语言支持API所识别。脚本语言支持API使用线程上下文类加载器来查找不同脚本引擎的实现类。因此JRuby库的jar包需要放在可以被线程上下文类加载器查找到的位置。在一般情况下,只需要把jar包放在OSGi模块的某个目录下,并在清单文件中通过Bundle-ClassPath属性声明即可。不过在这个示例中,除了OSGi框架之外,Web应用的其他部分也需要使用JRuby库,这就要求在Web应用的WEB-INF下的lib目录中也要有JRuby的jar包。在同一个Web应用中包含两个相同的jar包会在后期的维护中带来麻烦。更好的做法是把JRuby的jar包放在Web应用的WEB-INF下的lib目录中,在OSGi模块中引用这个jar包,这要求在OSGi模块运行时的线程上下文类加载器能够加载到servlet容器中lib目录中的jar包。可以在脚本语言支持API的ScriptEngineManager类的对象查找到可用的脚本引擎之前,把线程上下文类加载器设置成加载Web应用的类加载器对象,这样就可以查找到WEB-INF下的lib目录中的jar包。

首先需要获取Web应用的当前类加载器。Web应用的类加载器对象用来加载servlet实现类,只需要通过servlet实现类的getClassLoader方法就可以获取。Equinox框架使用org.eclipse.equinox.servletbridge.BridgeServlet类的对象来接受HTTP请求并转发给OSGi框架中的servlet来处理。为了能够在OSGi模块中使用Web应用的类加载器对象,需要继承BridgeServlet类并提供保存ClassLoader类的对象的功能。代码清单9-18给出了对应实现的代码。在CustomBridgeServlet类的service方法实现中,在处理servlet请求之前,把Web应用的类加载器对象保存在表示HTTP请求的HttpServletRequest类的对象中,以便可以在后续的代码实现中使用。

代码清单9-18 自定义的BridgeServlet类的子类


public class CustomBridgeServlet extends BridgeServlet{

protected void service(HttpServletRequest req, HttpServletResponse resp)throws ServletException, IOException{

req.setAttribute("WebappClassLoader",this.getClass().getClassLoader());

super.service(req, resp);

}

}


代码清单9-19给出了处理表达式计算请求的实际servlet类的代码。在实现中,先从HttpServletRequest类的对象中得到之前保存的Web应用的类加载器对象,接着把线程的上下文类加载器设置为该类加载器对象。经过这样的设置之后,ScriptEngineManager类的对象就可以正确地查找到Ruby语言的脚本执行引擎的实现类。在完成查找之后,需要把当前线程的上下文类加载器恢复为之前的值,以确保不影响后面代码的使用。

代码清单9-19 处理表达式计算请求的servlet的代码


public class CalculatorServlet extends HttpServlet{

public void doGet(HttpServletRequest req, HttpServletResponse resp)throws ServletException, IOException{

Thread currentThread=Thread.currentThread();

ClassLoader oldContextLoader=currentThread.getContextClassLoader();

ClassLoader webappLoader=(ClassLoader)req.getAttribute("WebappClassLoa der");

currentThread.setContextClassLoader(webappLoader);

ScriptEngineManager manager=new ScriptEngineManager();

ScriptEngine engine=manager.getEngineByExtension("rb");

currentThread.setContextClassLoader(oldContextLoader);

if(engine==null){

resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);

return;

}

String expr=req.getParameter("expr");

try{

Object result=engine.eval(expr);

resp.getWriter().write(result.toString());

}catch(ScriptException e){

throw new ServletException(e);

}

}

}