扩展Tomcat支持OSGi应用服务
1.摘要
OSGi的动态服务,包版本管理,安全设施,热插拔等特性吸引了越来越多开发人员的关注,由于OSGi不同于以往的设计理念,在企业级应用中一直没有很大的发挥。不过通过大家的不断努力,OSGi已在企业级服务平台中有所集成。本文站在另一个角度尝试Tomcat服务器下的OSGi服务集成,为web应用提供动态服务,为企业级开发提供参考。
本文需要读者了解以下内容:
l Java编程及一些简单的设计模式
l OSGi的相关知识
l Tomcat服务器的相关知识
2.集成思路
我们知道OSGi R4.0平台中发布的服务为java对象服务,只能在其内部使用,而不能够为外部环境所使用的主要原因是类加载问题。要想为外部环境引用OSGi平台中的服务,我们需要使用java的反射机制来达到目的。
鉴于OSGi平台服务的各种新的让人向往的优势,我们当然希望将其应用于开发和生产环境中。那么如何将OSGi平台服务集成到Tomcat中,并被web应用所使用呢?首先我们需要了解一些基本知识。
2.1 J2EE基础知识
我们知道,J2EE平台的JNDI规范,规定了服务器如何提供JNDI服务,同时也规定了web应用如何使用JNDI服务。举个例子,我们经常在服务器中配置数据库连接池,然后在web应用中使用(不明白的可以参考《Tomcat中数据源的配置及原理》)DataSource对象,而这个DataSource对象就是通过JNDI发布给web应用的。
当然,JNDI不仅仅能够发布DataSource服务对象,它还可以发布其它java服务对象。基于这个原理,我们很自然地会想,能否将OSGi平台的服务对象发布到JNDI呢?答案是肯定的。把OSGi平台的服务对象发布到JNDI的方式有很多种,这里介绍通过扩展Tomcat服务器来集成OSGi平台来实现以上的想法。
2.2 扩展Tomcat,支持OSGi平台
这里我们选择支持以下OSGi平台和Tomcat服务器:
1. Eclipse的OSGi平台实现equinox,版本equinox3.3.2。
2. Apache的OSGi平台实现felix,版本felix1.6.0。
3.Apache的Web应用服务器Tomcat,版本tomcat6.0.18(6.0以上目录结构一致)。
我们选择扩展Tomcat服务器的目的是,希望保持OSGi环境对Tomcat的透明,即OSGi平台的集成不会影响Tomcat服务器的功能。我们需要在Tomcat启动时启动OSGi平台,在Tomcat停止时停止OSGi平台。所以我们将服务器的启动作为扩展点,具体来说,就是在Tomcat的server组件启动时,添加一个关于OSGi平台生命周期的监听器(OsgiLifecycleListener),OsgiLifecycleListener必须实现LifecycleListener接口,这样就可以接受到server组件的事件通知了,根据不同事件,就可以在Tomcat的server组件启动时,启动OSGi平台,在server组件停止时,停止OSGi平台了。
2.3 Tomcat中发布JNDI服务
集成了OSGi平台后,我们希望将OSGi平台中服务发布到JNDI中,而OSGi平台中的服务对象各不相同,且是动态的,如果直接将这些服务发布给JNDI,web的使用将是痛苦的,也将不能获得OSGi动态服务的特性。所以,我们需要抽象一个共同的服务接口(OsgiServices),将该接口发布到JNDI中,而当web应用使用OSGi服务时,通过该接口来查找相应的服务。
为了在Tomcat中发布JNDI资源(OsgiServices),我们创建自己的资源工厂OsgiServicesFactory,OsgiServicesFactory实现了对象工厂(ObjectFactory)。
2.4 集成模型
到目前为止,我们的集成模型可以用图表示如下:
3.实现步骤
有了以上的分析和设计思路,我们接下来详细描述实现过程及需要注意的地方。
3.1扩展Tomcat,集成OSGi平台
Step1.创建java项目com.dinstone.tomcat.osgi,创建OsgiLifecycleListener类:
1 package com.dinstone.tomcat.osgi; 2 3 import java.util.logging.Logger; 4 5 import org.apache.catalina.Lifecycle; 6 import org.apache.catalina.LifecycleEvent; 7 import org.apache.catalina.LifecycleListener; 8 9 import com.dinstone.osgi.OsgiServices; 10 11 public class OsgiLifecycleListener implements LifecycleListener 12 { 13 private static Logger log = Logger.getLogger(OsgiLifecycleListener.class.getName()); 14 private static OsgiLifecycleListener listener = null; 15 16 /** the osgiType default value is 'Equixox'. */ 17 private String osgiType = "Equinox"; 18 private OsgiContent osgiContent = null; 19 20 public OsgiLifecycleListener() 21 { 22 23 } 24 25 public static OsgiLifecycleListener getInstance() 26 { 27 return listener; 28 } 29 30 @Override 31 public void lifecycleEvent(LifecycleEvent event) 32 { 33 if (Lifecycle.INIT_EVENT.equals(event.getType())) 34 { 35 log.info("The osgi content is initialized. Using osgi content:" + osgiType); 36 try 37 { 38 initContent(); 39 } 40 catch (Exception e) 41 { 42 e.printStackTrace(); 43 } 44 } 45 else if (Lifecycle.START_EVENT.equals(event.getType())) 46 { 47 try 48 { 49 log.info("Starting osgi service."); 50 osgiContent.start(); 51 } 52 catch (Exception e) 53 { 54 e.printStackTrace(); 55 log.info("Starting the osgi content occured error. " + e.getMessage()); 56 } 57 } 58 else if (Lifecycle.STOP_EVENT.equals(event.getType())) 59 { 60 try 61 { 62 log.info("Stopping osgi service."); 63 osgiContent.stop(); 64 } 65 catch (Exception e) 66 { 67 e.printStackTrace(); 68 log.info("Stopping the osgi content occured error. " + e.getMessage()); 69 } 70 } 71 } 72 73 private void initContent() throws Exception 74 { 75 listener = this; 76 osgiContent = OsgiContentFactory.getInstance().getOsgiContent(osgiType); 77 } 78 79 public String getOsgiType() 80 { 81 return osgiType; 82 } 83 84 public void setOsgiType(String osgiType) 85 { 86 this.osgiType = osgiType; 87 } 88 89 public OsgiServices getOsgiServices() 90 { 91 return osgiContent; 92 } 93 94 public void setOsgiContent(OsgiContent osgiContent) 95 { 96 this.osgiContent = osgiContent; 97 } 98 }
Step2.打开${Tomcat_Home}/conf/server.xml.。${Tomcat_Home}为Tomcat安装目录,下同。
添加红色部分:
1 <Server port="8005" shutdown="SHUTDOWN"> 2 3 <!--APR library loader. Documentation at /docs/apr.html --> 4 <Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on" /> 5 6 <!--Initialize Jasper prior to webapps are loaded. Documentation at /docs/jasper-howto.html --> 7 <Listener className="org.apache.catalina.core.JasperListener" /> 8 9 <!-- JMX Support for the Tomcat server. Documentation at /docs/non-existent.html --> 10 <Listener className="org.apache.catalina.mbeans.ServerLifecycleListener" /> 11 <Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" /> 12 13 <!-- OSGi support for the Tomcat server --> 14 <Listener className="com.dinstone.tomcat.osgi.OsgiLifecycleListener" osgiType="felix"/>
Step3. 打开${Tomcat_Home}/conf/catalina.properties。修改红色部分:
1 # 2 3 # 4 5 # List of comma-separated paths defining the contents of the "common" 6 7 # classloader. Prefixes should be used to define what is the repository type. 8 9 # Path may be relative to the CATALINA_HOME or CATALINA_BASE path or absolute. 10 11 # If left as blank,the JVM system loader will be used as Catalina's "common" 12 13 # loader. 14 15 # Examples: 16 17 # "foo": Add this folder as a class repository 18 19 # "foo/*.jar": Add all the JARs of the specified folder as class 20 21 # repositories 22 23 # "foo/bar.jar": Add bar.jar as a class repository 24 25 # ${catalina.home}/osgi/equinox/plugins,${catalina.home}/osgi/equinox/plugins/*.jar, 26 27 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; public interface OsgiServices { public Object getOSGiService(String serviceName); public Class<?> getBundleClass(String bundleName, String className)throws ClassNotFoundException; }
3.2 发布OSGi服务到JNDI
Step6.创建资源工厂类:
1 package com.dinstone.tomcat.osgi; 2 3 import java.util.Enumeration; 4 import java.util.Hashtable; 5 import java.util.logging.Logger; 6 7 import javax.naming.Context; 8 import javax.naming.Name; 9 import javax.naming.RefAddr; 10 import javax.naming.Reference; 11 import javax.naming.spi.ObjectFactory; 12 13 import com.dinstone.osgi.OsgiServices; 14 15 public class OsgiServicesFactory implements ObjectFactory 16 { 17 private static Logger log = Logger.getLogger(OsgiServicesFactory.class.getName()); 18 private OsgiServices osgiServices; 19 20 @Override 21 public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment) throws Exception 22 { 23 // Customize the bean properties from our attributes 24 Reference ref = (Reference) obj; 25 26 Enumeration<RefAddr> addrs = ref.getAll(); 27 28 while (addrs.hasMoreElements()) 29 { 30 RefAddr addr = addrs.nextElement(); 31 32 String attrName = addr.getType(); 33 34 String value = (String) addr.getContent(); 35 36 log.info("the attribute is (" + attrName + " == " + value); 37 } 38 39 initContext(); 40 41 return osgiServices; 42 } 43 44 private void initContext() 45 { 46 if (osgiServices == null) 47 { 48 OsgiLifecycleListener osgilcl = OsgiLifecycleListener.getInstance(); 49 osgiServices = osgilcl.getOsgiServices(); 50 } 51 } 52 }
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服务。
1 package com.dinsotne.web.osgi; 2 3 import javax.naming.Context; 4 5 import javax.naming.InitialContext; 6 import javax.naming.NamingException; 7 8 import com.dinstone.osgi.OsgiServices; 9 10 public class JndiOsgiServicesFactory implements OsgiServicesFactory 11 { 12 /** JNDI prefix used in a J2EE container */ 13 private static final String CONTAINER_PREFIX = "java:comp/env/"; 14 private String jndiName; 15 16 public String getJndiName() 17 { 18 return jndiName; 19 } 20 21 public void setJndiName(String jndiName) 22 { 23 this.jndiName = jndiName; 24 } 25 26 public OsgiServices getOsgiServices() 27 { 28 return (OsgiServices) lookup(getJndiName()); 29 } 30 31 private Object lookup(String jndiName) 32 { 33 String convertedName = convertJndiName(jndiName); 34 Object jndiObject = null; 35 36 try 37 { 38 Context context = new InitialContext(); 39 jndiObject = context.lookup(convertedName); 40 } 41 catch (NamingException e) 42 { 43 throw new IllegalServiceException("The JNDI OSGi services name is error.", e); 44 } 45 catch (Exception e) 46 { 47 throw new IllegalServiceException("The JNDI OSGi services can not be initialed.", e); 48 } 49 return jndiObject; 50 } 51 52 private String convertJndiName(String jndiName) 53 { 54 if (!jndiName.startsWith(CONTAINER_PREFIX) && jndiName.indexOf(':') == -1) 55 { 56 jndiName = CONTAINER_PREFIX + jndiName; 57 } 58 return jndiName; 59 } 60 }
Step2.在web应用的web.xml中添加如下内容:
1 <resource-env-ref> 2 <description>osgi services</description> 3 <resource-env-ref-name>osgi/services</resource-env-ref-name> 4 <resource-env-ref-type> 5 com.dinstone.osgi.OsgiServices 6 </resource-env-ref-type> 7 </resource-env-ref>
3.4 Web应用调用OSGi服务
Step3.有了OsgiServices服务后,我们创建OsgiServiceFactory类,负责获取OSGi平台的动态服务。
1 package com.dinsotne.web.osgi; 2 3 import com.dinstone.osgi.OsgiServices; 4 5 public class OsgiServiceFactory 6 { 7 private OsgiServices services; 8 9 public OsgiServiceFactory(OsgiServices services) 10 { 11 this.services = services; 12 } 13 14 public OsgiServiceFactory() 15 { 16 } 17 18 public <T> T getOsgiService(Class<T> serviceType, String serviceName) 19 { 20 OsgiServiceInvocationHandler handler = new OsgiServiceInvocationHandler(services, serviceName); 21 return JavaProxyObjectFactory.getProxyObject(serviceType, handler); 22 } 23 24 public OsgiServices getServices() 25 { 26 return services; 27 } 28 29 public void setServices(OsgiServices services) 30 { 31 this.services = services; 32 } 33 }
Step4.为了方便Web端得调用,我们创建了类OsgiServiceFacade。
1 package com.dinsotne.web.osgi; 2 3 import com.dinstone.osgi.OsgiServices; 4 5 public class OsgiServiceFacade 6 { 7 public static <T> T getOsgiService(String jndiName, Class<T> serviceType, String serviceName) 8 { 9 JndiOsgiServicesFactory factory = new JndiOsgiServicesFactory(); 10 factory.setJndiName(jndiName); 11 12 OsgiServices services = factory.getOsgiServices(); 13 OsgiServiceFactory sf = new OsgiServiceFactory(services); 14 15 return sf.getOsgiService(serviceType, serviceName); 16 } 17 }
Step5.Web调用示例。
1 public static String getUserName(String id) 2 { 3 try 4 { 5 IUserService service = OsgiServiceFacade.getOsgiService("osgi/services",
IUserService.class,
IUserService.class.getName()); 6 return service.getUserName(id); 7 } 8 catch (IllegalArgumentException e) 9 { 10 e.printStackTrace(); 11 } 12 return null; 13 }
说明:
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。
4.测试
通过以上的实现,我们将OSGi平台集成到了Tomcat中,并且明确了web应用如何使用OSGi服务。下面就来创建一个测试的例子,看看OSGi编程模式对web应用开发的影响,同时也测试一下集成的效果。
4.1 创建并发布OSGi服务
Step1.创建插件工程com.dinstone.demo.user,选择standard OSGi framework。创建接口:
1 package com.dinstone.demo.user; 2 3 public interface IUserService 4 { 5 public String getUserName(String id); 6 }
Step2.创建插件工程com.dinstone.demo.user.db,创建类UserServiceImpl实现IUserService接口。
package com.dinstone.demo.user.db; import java.util.logging.Logger; import com.dinstone.demo.user.IUserService; public class UserServiceImpl implements IUserService { private static Logger log = Logger.getLogger(UserServiceImpl.class .getName()); @Override public String getUserName(String id) { log.info("get user name from db"); return "db" + id; } }
Step3.向OSGi平台发布IUserService服务。创建Activator注册服务对象。
package com.dinstone.demo.user.db; import java.util.Properties; import org.osgi.framework.BundleActivator; import org.osgi.framework.BundleContext; import org.osgi.framework.ServiceRegistration; import com.dinstone.demo.user.IUserService; public class Activator implements BundleActivator { private ServiceRegistration serviceReg; /* * (non-Javadoc) * * @see org.osgi.framework.BundleActivator#start(org.osgi.framework.BundleContext) */ public void start(BundleContext context) throws Exception { Properties p = new Properties(); serviceReg = context.registerService(IUserService.class.getName(), new UserServiceImpl(), p); } /* * (non-Javadoc) * * @see org.osgi.framework.BundleActivator#stop(org.osgi.framework.BundleContext) */ public void stop(BundleContext context) throws Exception { serviceReg.unregister(); } }
Step4. 创建插件工程com.dinstone.demo.user.file,创建类UserServiceImpl实现IUserService接口。
package com.dinstone.demo.user.file; import java.util.logging.Logger; import com.dinstone.demo.user.IUserService; public class UserServiceImpl implements IUserService { private static Logger log = Logger.getLogger(UserServiceImpl.class.getName()); @Override public String getUserName(String id) { log.info("get user name from file"); return "file" + id; } }
Step5.向OSGi平台发布IUserService服务。创建Activator注册服务对象。
package com.dinstone.demo.user.file; import java.util.Properties; import org.osgi.framework.BundleActivator; import org.osgi.framework.BundleContext; import org.osgi.framework.ServiceRegistration; import com.dinstone.demo.user.IUserService; public class Activator implements BundleActivator { private ServiceRegistration serviceReg; /* * (non-Javadoc) * * @see org.osgi.framework.BundleActivator#start(org.osgi.framework.BundleContext) */ public void start(BundleContext context) throws Exception { Properties p = new Properties(); serviceReg = context.registerService(IUserService.class.getName(), new UserServiceImpl(), p); } /* * (non-Javadoc) * * @see org.osgi.framework.BundleActivator#stop(org.osgi.framework.BundleContext) */ public void stop(BundleContext context) throws Exception { serviceReg.unregister(); } }
4.2 创建Web应用
Step1.创建动态web工程webDemo,并新建类:UserServiceFacade。
package com.dinsotne.web.demo; import com.dinsotne.web.osgi.OsgiServiceFacade; import com.dinstone.demo.user.IUserService; public class UserServiceFacade { public static 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(); } return null; } }
Step2.创建index.jsp页面。
<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="ISO-8859-1"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <%@page import="com.dinsotne.web.demo.UserServiceFacade"%> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <title>Insert title here</title> </head> <body> User Name is <%=UserServiceFacade.getUserName("001") %> </body> </html>
Step3.修改web.xml文件,添加红色部分。
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5"> <display-name>webDemo</display-name> <welcome-file-list> <welcome-file>index.html</welcome-file> <welcome-file>index.htm</welcome-file> <welcome-file>index.jsp</welcome-file> <welcome-file>default.html</welcome-file> <welcome-file>default.htm</welcome-file> <welcome-file>default.jsp</welcome-file> </welcome-file-list> <resource-env-ref> <description>osgi service</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> </web-app>
说明:
1. 由于UserServiceFacade依赖IUserService类,故需要将com.dinstone.demo.user_1.0.0.jar(参见4.3)添加到lib中。
2. 由于UserServiceFacade依赖OsgiServiceFacade类,故将com.dinsotne.web.osgi_1.12.0.jar(参见3.4说明)添加到lib中。
4.3 发布OSGi Bundle
Step1.依次从插件工程导出插件包:com.dinstone.demo.user_1.0.0.jar,com.dinstone.demo.user.file_1.0.0.jar,com.dinstone.demo.user.db_1.0.0.jar。
Step2.将以上的插件放于${Tomcat_Home}\osgi\felix\bundle目录下。
4.4 发布web应用
Step1.导出web应用webDemo.war。
Step2.将webDemo.war放于${Tomcat_Home}\webapps目录下。
4.5 启动Tomcat并安装OSGi Bundle
Step1.在命令行下启动Tomcat。E:\Cluster\apache-tomcat-6.0.18为我的${Tomcat_Home}。
E:\Cluster\apache-tomcat-6.0.18>bin\startup.bat Using CATALINA_BASE: E:\Cluster\apache-tomcat-6.0.18 Using CATALINA_HOME: E:\Cluster\apache-tomcat-6.0.18 Using CATALINA_TMPDIR: E:\Cluster\apache-tomcat-6.0.18\temp Using JRE_HOME: C:\Program Files\Java\jdk1.6.0_10 2009-8-12 13:21:39 com.dinstone.tomcat.osgi.OsgiLifecycleListener lifecycleEvent 信息: The osgi content is initialized. Using osgi content:felix 2009-8-12 13:21:39 org.apache.coyote.http11.Http11Protocol init 信息: Initializing Coyote HTTP/1.1 on http-8080 2009-8-12 13:21:40 org.apache.coyote.http11.Http11Protocol init 信息: Initializing Coyote HTTP/1.1 on http-8443 2009-8-12 13:21:40 org.apache.catalina.startup.Catalina load 信息: Initialization processed in 1748 ms 2009-8-12 13:21:41 com.dinstone.tomcat.osgi.OsgiLifecycleListener lifecycleEvent 信息: Starting osgi service. 2009-8-12 13:21:41 com.dinstone.tomcat.osgi.felix.FelixContent start 信息: ********************************* 2009-8-12 13:21:41 com.dinstone.tomcat.osgi.felix.FelixContent start 信息: catalina home is E:\Cluster\apache-tomcat-6.0.18 2009-8-12 13:21:41 com.dinstone.tomcat.osgi.felix.FelixContent start 信息: osgi home is E:\Cluster\apache-tomcat-6.0.18\osgi\felix 2009-8-12 13:21:41 com.dinstone.tomcat.osgi.felix.FelixContent start 信息: ******user.dir is E:\Cluster\apache-tomcat-6.0.18 Welcome to Felix. ================= -> 2009-8-12 13:21:42 org.apache.catalina.core.StandardService start 信息: Starting service Catalina 2009-8-12 13:21:42 org.apache.catalina.core.StandardEngine start 信息: Starting Servlet Engine: Apache Tomcat/6.0.18 2009-8-12 13:21:42 org.apache.catalina.loader.WebappLoader start 信息: Dual registration of jndi stream handler: factory already defined 2009-8-12 13:21:44 org.apache.coyote.http11.Http11Protocol start 信息: Starting Coyote HTTP/1.1 on http-8080 2009-8-12 13:21:44 org.apache.coyote.http11.Http11Protocol start 信息: Starting Coyote HTTP/1.1 on http-8443 2009-8-12 13:21:44 org.apache.jk.common.ChannelSocket init 信息: JK: ajp13 listening on /0.0.0.0:8009 2009-8-12 13:21:44 org.apache.jk.server.JkMain start 信息: Jk running ID=0 time=0/47 config=null 2009-8-12 13:21:44 org.apache.catalina.startup.Catalina start 信息: Server startup in 3882 ms -> ps START LEVEL 1 ID State Level Name [ 0] [Active ] [ 0] System Bundle (1.6.0) [ 25] [Active ] [ 1] Apache Felix Shell Service (1.2.0) [ 26] [Active ] [ 1] Apache Felix Shell TUI (1.2.0) [ 27] [Active ] [ 1] Apache Felix Bundle Repository (1.4.0) ->
Step3.访问web应用,http://localhost:8080/webDemo/。由于没有启动OSGi服务,故出现500异常页面,错误原因是没有找到服务。
root cause com.dinsotne.web.osgi.IllegalServiceException: Cann't find out osgi service:com.dinstone.demo.user.IUserService com.dinsotne.web.osgi.OsgiServiceInvocationHandler.invoke(OsgiServiceInvocationHandler.java:30) $Proxy0.getUserName(Unknown Source) com.dinsotne.web.demo.UserServiceFacade.getUserName(UserServiceFacade.java:14) org.apache.jsp.index_jsp._jspService(index_jsp.java:64) org.apache.jasper.runtime.HttpJspBase.service(HttpJspBase.java:70) javax.servlet.http.HttpServlet.service(HttpServlet.java:717) org.apache.jasper.servlet.JspServletWrapper.service(JspServletWrapper.java:374) org.apache.jasper.servlet.JspServlet.serviceJspFile(JspServlet.java:342) org.apache.jasper.servlet.JspServlet.service(JspServlet.java:267) javax.servlet.http.HttpServlet.service(HttpServlet.java:717)
Step4.启动User DB Implement Plug-in服务,激活User模块的DB实现。
->ps START LEVEL 1 ID State Level Name [ 0] [Active ] [ 0] System Bundle (1.6.0) [ 25] [Active ] [ 1] Apache Felix Shell Service (1.2.0) [ 26] [Active ] [ 1] Apache Felix Shell TUI (1.2.0) [ 27] [Active ] [ 1] Apache Felix Bundle Repository (1.4.0) [ 39] [Installed ] [ 1] User Model Interface Plug-in (1.0.0) [ 40] [Installed ] [ 1] User DB Implement Plug-in (1.0.0) [ 41] [Installed ] [ 1] User File Implement Plug-in (1.0.0) -> start 40 -> ps -s START LEVEL 1 ID State Level Symbolic name [ 0] [Active ] [ 0] org.apache.felix.framework (1.6.0) [ 25] [Active ] [ 1] org.apache.felix.shell (1.2.0) [ 26] [Active ] [ 1] org.apache.felix.shell.tui (1.2.0) [ 27] [Active ] [ 1] org.apache.felix.bundlerepository (1.4.0) [ 39] [Resolved ] [ 1] com.dinstone.demo.user (1.0.0) [ 40] [Active ] [ 1] com.dinstone.demo.user.db (1.0.0) [ 41] [Installed ] [ 1] com.dinstone.demo.user.file (1.0.0) ->
访问http://localhost:8080/webDemo/。页面显示:
Step5. 启动User File Implement Plug-in服务,激活User模块的File实现。
-> ps START LEVEL 1 ID State Level Name [ 0] [Active ] [ 0] System Bundle (1.6.0) [ 25] [Active ] [ 1] Apache Felix Shell Service (1.2.0[ 26] [Active ] [ 1] Apache Felix Shell TUI (1.2.0) [ 27] [Active ] [ 1] Apache Felix Bundle Repository (1.4.0) [ 39] [Resolved ] [ 1] User Model Interface Plug-in (1.0.0) [ 40] [Active ] [ 1] User DB Implement Plug-in (1.0.0) [ 41] [Installed ] [ 1] User File Implement Plug-in (1.0.0) -> start 41 -> ps START LEVEL 1 ID State Level Name [ 0] [Active ] [ 0] System Bundle (1.6.0) [ 25] [Active ] [ 1] Apache Felix Shell Service (1.2.0) [ 26] [Active ] [ 1] Apache Felix Shell TUI (1.2.0) [ 27] [Active ] [ 1] Apache Felix Bundle Repository (1.4.0) [ 39] [Resolved ] [ 1] User Model Interface Plug-in (1.0.0) [ 40] [Active ] [ 1] User DB Implement Plug-in (1.0.0) [ 41] [Active ] [ 1] User File Implement Plug-in (1.0.0) ->
访问http://localhost:8080/webDemo/。页面显示:
Step6.现在停止User DB Implement Plug-in服务。
-> ps START LEVEL 1 ID State Level Name [ 0] [Active ] [ 0] System Bundle (1.6.0) [ 25] [Active ] [ 1] Apache Felix Shell Service (1.2.0) [ 26] [Active ] [ 1] Apache Felix Shell TUI (1.2.0) [ 27] [Active ] [ 1] Apache Felix Bundle Repository (1.4.0) [ 39] [Resolved ] [ 1] User Model Interface Plug-in (1.0.0) [ 40] [Active ] [ 1] User DB Implement Plug-in (1.0.0) [ 41] [Active ] [ 1] User File Implement Plug-in (1.0.0) -> stop 40 -> ps START LEVEL 1 ID State Level Name [ 0] [Active ] [ 0] System Bundle (1.6.0) [ 25] [Active ] [ 1] Apache Felix Shell Service (1.2.0) [ 26] [Active ] [ 1] Apache Felix Shell TUI (1.2.0) [ 27] [Active ] [ 1] Apache Felix Bundle Repository (1.4.0) [ 39] [Resolved ] [ 1] User Model Interface Plug-in (1.0.0) [ 40] [Resolved ] [ 1] User DB Implement Plug-in (1.0.0) [ 41] [Active ] [ 1] User File Implement Plug-in (1.0.0) ->
访问http://localhost:8080/webDemo/。页面显示:
4.6 停止Tomcat服务器
5结论
通过以上的测试,我们发现以上的实现基本符合最初的设想:
l OSGi的集成对Tomcat几乎是透明的。
l OSGi的所有优点。
l Web表现和业务逻辑的完全分离。
l 基于模块化服务的编程模型。
同时,我们也发现了一些问题:
l Web层没有支持模块化、可热插拔的编程模型。
l OSGi层的服务日志跟web层的日志分离增加了维护的难度。
l 该集成方式没有经严格测试,虽然已经有产品应用了。