查了好些资料,从 2009 年的 opendoc 到 2012 年左右的一些零散例子,无非是用 felix 或者 eclipse 自带的插件环境来测试。于是想了想,不如就从 eclipse 入手吧。
我用的开发工具是 eclipse Neon 3 ,配合 Jdk 8 来使用。填写项目名称后,一路自动生成,选了一个 DecliaritiveService 的样板工程。出于方便测试的目的,我选的运行环境是 eclipse (3.5 or greater) 。明天继续更新~~~
退而求其次,我选择了先搞定一个 DS 的使用案例。首先新建一个插件项目,叫什么不重要。这里主要验证了 DS 构建网页应用需要导入的 package 和运行时需要配置的 OSGi 环境。经过实验、有用的 OSGi 运行时配置如下:
[bundles in import-package]
javax.servlet;version="3.1.0",
javax.servlet.http;version="3.1.0",
org.eclipse.osgi.framework.console;version="1.0.0",
org.osgi.framework;version="1.3.0",
org.osgi.service.component.annotations;version="1.2.0";resolution:=optional,
org.osgi.service.http;version="1.2.1",
org.osgi.util.tracker;version="1.3.1"
[bundles in run configuration]
javax.activation(1.1.0.v201211130549)
javax.annotation(1.2.0.v201602091430)
javax.el(2.2.0.v201303151357)
javax.inject(1.0.0.v20091030)
javax.jws(2.0.0.v201005080400)
javax.mail(1.4.0.v201005080615)
javax.persistence(2.1.0.v201304241213)
javax.servlet(3.1.0.v201410161800)
javax.servlet.jsp(2.2.0.v201112011158)
javax.wsdl(1.5.1.v201012040544)
javax.wsdl(1.6.2.v201012040545)
javax.xml(1.3.4.v201005080400)
javax.xml.rpc(1.1.0.v201209140446)
javax.xml.soap(1.2.0.v201005080501)
javax.xml.stream(1.0.1.v201004272200)
javax.xml.ws(2.1.0.v200902101523)
jaxb-api(2.2.7)
org.apache.felix.gogo.command(0.10.0.v201209301215)
org.apache.felix.gogo.runtime(0.10.0.v201209301036)
org.apache.felix.gogo.shell(0.10.0.v201209301605)
org.eclipse.equinox.console(1.1.200.v20150929-1405)
org.eclipse.equinox.ds(1.4.400.v20160226-2036)
org.eclipse.equinox.http.jetty(3.3.0.v20160324-1850)
org.eclipse.equinox.http.servlet(1.3.1.v20160808-1329)
org.eclipse.equinox.util(1.0.500.v20130404-1337)
org.eclipse.equinox.jetty.continuation(9.3.9.v20160517)
org.eclipse.equinox.jetty.http(9.3.9.v20160517)
org.eclipse.equinox.jetty.io(9.3.9.v20160517)
org.eclipse.equinox.jetty.security(9.3.9.v20160517)
org.eclipse.equinox.jetty.server(9.3.9.v20160517)
org.eclipse.equinox.jetty.servlet(9.3.9.v20160517)
org.eclipse.equinox.jetty.util(9.3.9.v20160517)
org.eclipse.equinox.jetty.webapp(9.3.9.v20160517)
org.eclipse.equinox.jetty.xml(9.3.9.v20160517)
org.eclipse.osgi(3.11.3.v20170209-1843)
org.eclipse.osgi.services(3.5.100.v20160504-1419)
比如说我们要注册一个 Servlet 或者什么别的静态资源。在不使用 Activator 的情况下,首先写一个简单的 Component 类来获取一个 org.osgi.service.http.HttpService 实例。代码如下:
package com.maple.osgi.starter;
import javax.servlet.ServletException;
import org.osgi.service.http.HttpContext;
import org.osgi.service.http.HttpService;
import org.osgi.service.http.NamespaceException;
/**
* demonstrate a binding component with DS files</p>
* that register and un-register the servlets on demand</p>
* @author luobenyu
*
*/
public class Component {
private HttpService httpService;
private String myServletUri = "/demo/test";
private String myStaticPageUri = "/page";
private String myWebPagesFolder = "/webpages";
public void setHttpService(HttpService httpService) {
this.httpService = httpService;
}
public void ungetHttpService(HttpService httpService) {
this.httpService = null;
}
/**
* register servlet within DS files
*/
public void startup(){
try {
HttpContext ctx = this.httpService.createDefaultHttpContext();
// register servlet
this.httpService.registerServlet(this.myServletUri, new DefaultServlet(), null, ctx);
// register static resources
this.httpService.registerResources(this.myStaticPageUri, this.myWebPagesFolder, ctx);
} catch (ServletException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
} catch (NamespaceException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
}
/**
* unregister servlet within DS files
*/
public void shutdown(){
this.httpService.unregister(this.myServletUri);
this.httpService.unregister(this.myWebPagesFolder);
}
}
这里简单定义了 Component 启动和关闭时的动作,主要就是注册和反注册(取消注册)http 资源。接下来我们要让 osgi 认得这个 Component,右键单击 Component 类,选择"new"-> "component definition"。我会事先在项目的根目录创建一个 OSGI-INF 文件夹,component definition 的位置指向这个文件夹、同时把类指向 Component 类。生成之后还有一个问题,要注意 MANIFEST.MF 文件里面,对 的描述不可以重复、否则项目一直启动不来。贴一下用到的 DefaultServlet 类和其他文件:
package com.maple.osgi.starter;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* demonstrate a simple request
* @author luobenyu
*
*/
public class DefaultServlet extends HttpServlet {
/**
*
*/
private static final long serialVersionUID = -8989572162853489148L;
public void doGet(HttpServletRequest request, HttpServletResponse response){
response.setContentType("text/html");
PrintWriter writer = null;
try {
writer = response.getWriter();
writer.println("Hello Default Servlet!");
writer.flush();
} catch (IOException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
} finally {
if (writer != null) {
writer.close();
}
}
}
}
贴一下 OSGI-INF 目录下的 component definition 文件(加什么名字都行,注意事项见 MANIFEST.MF 处):
<?xml version="1.0" encoding="UTF-8"?>
<!-- rename this file as you wish -->
<!-- DO NOT add duplicated reference in 'Service-Component' at MANIFEST.MF -->
<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" name="com.maple.osgi.starter"
activate="startup" deactivate="shutdown">
<!-- works under 'dynamic' policy -->
<reference name="httpService" bind="setHttpService" unbind="ungetHttpService" interface="org.osgi.service.http.HttpService" policy="dynamic" cardinality="1..1" />
<implementation class="com.maple.osgi.starter.Component"/>
</scr:component>
这里主要是 cardinality 属性的使用,默认是 1..1 也就是必须创建、只创建一个。最后是 MANIFEST.MF:
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Starter
Bundle-SymbolicName: com.maple.osgi.starter
Bundle-Version: 1.0.0.qualifier
Bundle-Vendor: MAPLE
Bundle-RequiredExecutionEnvironment: JavaSE-1.8
Service-Component: OSGI-INF/*.xml
Import-Package: javax.servlet;version="3.1.0",
javax.servlet.http;version="3.1.0",
org.eclipse.osgi.framework.console;version="1.0.0",
org.osgi.framework;version="1.3.0",
org.osgi.service.component.annotations;version="1.2.0";resolution:=optional,
org.osgi.service.http;version="1.2.1",
org.osgi.util.tracker;version="1.3.1"
Bundle-ActivationPolicy: lazy
这里的 Service-Component 字段告诉了 osgi 在哪里找这些 component definition,所以我用 * 通配符代表了任意文件。主要是新增或者删除后重建 component definition ,eclipse 都会自动往原来的值后面追加新文件的名字(重复了!),容易导致启动不了。然后右键 "run as"->"OSGi framework",新建一个运行时配置、用上面的依赖来跑,就不用总是启动一大堆无关的 bundle 了。
后面回顾近几年撰写的资料,许多人关心的问题应该是这些(也将继续更新):
- 怎样把 MVC 项目(包含 Servlet 和 Spring 的扩展、web项目必需的一些配置文件)放进一个插件项目?
- 怎样控制 Spring 或者其他使用全局 classloader 的库,顺利转换到 OSGi 插件中运行?
- 怎样保证 Hibernate 或者 Mybatis 等查询框架的 session (插件间)同步问题?
附上参考的一些资料地址:
- IBM Leaving the IBM Web site - United States IBM 讲解 DS 的原理和关键参数(cardinarity)
- java - osgi框架下开发的web应用,可以启动,但是无法访问 - SegmentFault 思否 一个搭建OSGi环境网页应用的帖子
- https://github.com/aritratony/mvc-osgi 一个基于 OSGi 的 SpringMVC 应用