3.实现步骤
有了以上的分析和设计思路,我们接下来详细描述实现过程及需要注意的地方。
3.1扩展Tomcat,集成OSGi平台
Step1.创建java项目com.dinstone.tomcat.osgi,创建OsgiLifecycleListener类:
package com.dinstone.tomcat.osgi;
imp<wbr>ort</wbr> java.util.logging.Logger;
imp<wbr>ort</wbr> org.apache.catalina.Lifecycle; imp<wbr>ort</wbr> org.apache.catalina.LifecycleEvent; imp<wbr>ort</wbr> org.apache.catalina.LifecycleListener;
imp<wbr>ort</wbr> com.dinstone.osgi.OsgiServices;
publicclass OsgiLifecycleListener implements LifecycleListener {
privatestatic Logger log = Logger.getLogger(OsgiLifecycleListener.class .getName());
privatestatic OsgiLifecycleListener listener = null;
/**theosgiTypedefaultvalueis'Equixox'.*/ private String osgiType = "Equinox";
private OsgiContent osgiContent = null;
public OsgiLifecycleListener() { }
publicstatic OsgiLifecycleListener getInstance() { returnlistener; }
@Override publicvoid lifecycleEvent(LifecycleEvent event) { if (Lifecycle.INIT_EVENT.equals(event.getType())) { log.info("The osgi content is initialized. Using osgi content:" + osgiType); try { initContent(); } catch (Exception e) { e.printStackTrace(); } } elseif (Lifecycle.START_EVENT.equals(event.getType())) { try { log.info("Starting osgi service."); osgiContent.start(); } catch (Exception e) { e.printStackTrace(); log.info("Starting the osgi content occured error. " + e.getMessage()); } } elseif (Lifecycle.STOP_EVENT.equals(event.getType())) { try { log.info("Stopping osgi service."); osgiContent.stop(); } catch (Exception e) { e.printStackTrace(); log.info("Stopping the osgi content occured error. " + e.getMessage()); } } }
privatevoid initContent() throws Exception { listener = this; osgiContent = OsgiContentFactory.getInstance().getOsgiContent(osgiType); }
public String getOsgiType() { returnosgiType; }
publicvoid setOsgiType(String osgiType) { this.osgiType = osgiType; }
public OsgiServices getOsgiServices() { returnosgiContent; }
publicvoid setOsgiContent(OsgiContent osgiContent) { this.osgiContent = osgiContent; } }
|
Step2.打开${Tomcat_Home}/conf/server.xml.。${Tomcat_Home}为Tomcat安装目录,下同。
添加红色部分:
<Server port="8005" shutdown="SHUTDOWN"> <!--APR library loader. Documentation at /docs/apr.html --> <Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on" /> <!--Initialize Jasper prior to webapps are loaded. Documentation at /docs/jasper-howto.html --> <Listener className="org.apache.catalina.core.JasperListener" /> <!-- JMX Support for the Tomcat server. Documentation at /docs/non-existent.html --> <Listener className="org.apache.catalina.mbeans.ServerLifecycleListener" /> <Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" /> <!-- OSGi support for the Tomcat server --> <Listener className="com.dinstone.tomcat.osgi.OsgiLifecycleListener" osgiType="felix"/> …
|
Step3. 打开${Tomcat_Home}/conf/catalina.properties。修改红色部分:
# # # List of comma-separated paths defining the contents of the "common" # classloader. Prefixes should be used to define what is the repository type. # Path may be relative to the CATALINA_HOME or CATALINA_BASE path or absolute. # If left as blank,the JVM system loader will be used as Catalina's "common" # loader. # Examples: # "foo": Add this folder as a class repository # "foo/*.jar": Add all the JARs of the specified folder as class # repositories # "foo/bar.jar": Add bar.jar as a class repository # ${catalina.home}/osgi/equinox/plugins,${catalina.home}/osgi/equinox/plugins/*.jar, common.loader=${catalina.home}/lib,${catalina.home}/lib/*.jar,${catalina.home}/osgi/felix/bin/*.jar |
Step4.构建equinox环境。
新建目录:${Tomcat_Home}/osgi/equinox/plugins/,将org.eclipse.osgi_3.3.2.R33x_v20080105.jar放于该目录下。
Step5.构建felix环境。
新建目录:${Tomcat_Home}/osgi/felix/,将下载的felix-1.6.0.zip解压到该目录。最终的目录结构如图:
Step5.创建服务接口:
package com.dinstone.osgi;
publicinterface OsgiServices {
public Object getOSGiService(String serviceName);
public Class<?> getBundleClass(String bundleName, String className) throws ClassNotFoundException; } |
3.2 发布OSGi服务到JNDI
Step6.创建资源工厂类:
package com.dinstone.tomcat.osgi;
imp<wbr>ort java.util.Enumeration;</wbr> imp<wbr>ort java.util.Hashtable;</wbr> imp<wbr>ort java.util.logging.Logger;</wbr>
imp<wbr>ort javax.naming.Context;</wbr> imp<wbr>ort javax.naming.Name;</wbr> imp<wbr>ort javax.naming.RefAddr;</wbr> imp<wbr>ort javax.naming.Reference;</wbr> imp<wbr>ort javax.naming.spi.ObjectFactory;</wbr>
imp<wbr>ort com.dinstone.osgi.OsgiServices;</wbr>
public class OsgiServicesFactory implements ObjectFactory {
private static Logger log = Logger.getLogger(OsgiServicesFactory.class .getName());
private OsgiServices osgiServices;
@Override public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment) throws Exception {
// Customize the bean properties from our attributes Reference ref = (Reference) obj; Enumeration<RefAddr> addrs = ref.getAll(); while (addrs.hasMoreElements()) { RefAddr addr = addrs.nextElement(); String attrName = addr.getType(); String value = (String) addr.getContent(); log.info("the attribute is (" + attrName + " == " + value); }
initContext(); return osgiServices; }
private void initContext() { if (osgiServices == null) { OsgiLifecycleListener osgilcl = OsgiLifecycleListener.getInstance(); osgiServices = osgilcl.getOsgiServices(); } }
} |
Step7.打开${Tomcat_Home}/conf/context.xml。添加以下内容:
<Resource name="osgi/services" auth="Container" type="com.dinstone.osgi.OsgiServices" factory="com.dinstone.tomcat.osgi.OsgiServicesFactory" /> |
说明:
1. OsgiLifecycleListener为单例对象,主要功能为根据配置信息osgiType来加载不同的OSGi平台,根据事件类型来启动和停止OSGi平台。
2. osgiType必须有get/set方法,Tomcat会注入配置信息。
3.将com.dinstone.tomcat.osgi工程编译打包成com.dinstone.tomcat.osgi_1.12.4.jar,将jar放于${Tomcat_Home}/lib目录下。
4.step7中的配置方式意味着所有的应用都可以引用"osgi/services"资源。另外一种方式可以在web应用的发布文件中配置,具体参见其它相关文档。
3.3 Web应用引用JNDI资源
Web应用为了引用JNDI资源,需要使用java的反射机制来调用资源服务。首先我们建立web端得JNDI资源应用API。
Step1.创建java项目com.dinsotne.web.osgi,创建JndiOsgiServicesFactory类,负责在JNDI中查找OsgiServices服务。
package com.dinsotne.web.osgi;
imp<wbr>ort javax.naming.Context;</wbr> imp<wbr>ort javax.naming.InitialContext;</wbr> imp<wbr>ort javax.naming.NamingException;</wbr>
imp<wbr>ort com.dinstone.osgi.OsgiServices;</wbr>
public class JndiOsgiServicesFactory implements OsgiServicesFactory {
/** JNDI prefix used in a J2EE container */ private static final String CONTAINER_PREFIX = "java:comp/env/";
private String jndiName;
public String getJndiName() { return jndiName; }
public void setJndiName(String jndiName) { this.jndiName = jndiName; }
public OsgiServices getOsgiServices() { return (OsgiServices) lookup(getJndiName()); }
private Object lookup(String jndiName) { String convertedName = convertJndiName(jndiName); Object jndiObject = null; try { Context context = new InitialContext(); jndiObject = context.lookup(convertedName); } catch (NamingException e) { throw new IllegalServiceException( "The JNDI OSGi services name is error.", e); } catch (Exception e) { throw new IllegalServiceException( "The JNDI OSGi services can not be initialed.", e); }
return jndiObject; }
private String convertJndiName(String jndiName) { if (!jndiName.startsWith(CONTAINER_PREFIX) && jndiName.indexOf(':') == -1) { jndiName = CONTAINER_PREFIX + jndiName; } return jndiName; } } |
Step2.在web应用的web.xml中添加如下内容:
<resource-env-ref> <description>osgi services</description> <resource-env-ref-name>osgi/services</resource-env-ref-name> <resource-env-ref-type> com.dinstone.osgi.OsgiServices </resource-env-ref-type> </resource-env-ref> |
3.4 Web应用调用OSGi服务
Step3.有了OsgiServices服务后,我们创建OsgiServiceFactory类,负责获取OSGi平台的动态服务。
package com.dinsotne.web.osgi;
imp<wbr>ort</wbr> com.dinstone.osgi.OsgiServices;
publicclass OsgiServiceFactory {
private OsgiServices services;
public OsgiServiceFactory(OsgiServices services) { this.services = services; }
public OsgiServiceFactory() { }
public <T> T getOsgiService(Class<T> serviceType, String serviceName) { OsgiServiceInvocationHandler handler = new OsgiServiceInvocationHandler( services, serviceName); return JavaProxyObjectFactory.getProxyObject(serviceType, handler); }
public OsgiServices getServices() { returnservices; }
publicvoid setServices(OsgiServices services) { this.services = services; }
} |
Step4.为了方便Web端得调用,我们创建了类OsgiServiceFacade。
package com.dinsotne.web.osgi;
imp<wbr>ort</wbr> com.dinstone.osgi.OsgiServices;
publicclass OsgiServiceFacade {
publicstatic <T> T getOsgiService(String jndiName, Class<T> serviceType, String serviceName) { JndiOsgiServicesFactory factory = new JndiOsgiServicesFactory(); factory.setJndiName(jndiName);
OsgiServices services = factory.getOsgiServices(); OsgiServiceFactory sf = new OsgiServiceFactory(services); return sf.getOsgiService(serviceType, serviceName); } } |
Step5.Web调用示例。
publicstatic String getUserName(String id) { try { IUserService service = OsgiServiceFacade.getOsgiService( "osgi/services", IUserService.class, IUserService.class .getName());
return service.getUserName(id); } catch (IllegalArgumentException e) { e.printStackTrace(); e.printStackTrace(); }
returnnull; } |
说明:
1.以上的代码应用了java代理和反射技术,其它的代码参见源码。
2. OsgiServiceFactory在获取OSGi平台服务时,使用了java代理。读者可能会疑问,为什么Datasource资源服务的引用就不必使用反射,而我们的OSGi服务就需要使用反射啊?这个都是java的类加载机制惹得祸。对于Datasource资源,它的类型是javax.sql.DataSource,为系统类,且运行在Tomcat中的web应用都使用Tomcat容器的类加载器加载这个类,故web应用中的javax.sql.DataSource跟Tomcat加载的是同一类。但是,对于OSGi服务类,该类由OSGi容器的类加载器加载,而我们的web应用是不能使用该类加载器加载该类的,故只能通过反射来调用服务了。
3.将项目工程com.dinsotne.web.osgi导出打包:com.dinsotne.web.osgi_1.12.0.jar。