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);
}
}
}