随着公司业务的不断扩展,业务人员向IT部门提出了更多的需求,为了快速响应业务人员的需求,尽快的交付成果,码农们日以继夜的将代码往现有系统中不断的堆叠,直到某一天,码农们发现无法再继续往系统上堆叠新的代码,它们不得不停下了工作,经过一番争吵之后,他们决定将整个系统推翻重建。诸如此类的场景不断的重演,就好比地球自转,周而复始,永不停息。虽然对于某些人来说这或许是一件好事情,因为他们可以利用重建系统的机会尝试新的技术框架和整理凌乱的业务逻辑,但是对于绝大多数人来说,那简直就是噩梦,或许会因此而丢到自己的工作。
OSGi为解决诸如此类的问题提供了很好的解决方案。架构师在设计一个新系统的时候,不可能面面俱到,因为世界上所有的事物都是千变万化的,所以预知未来并不是架构师所必须具备的技能。我们能做的只是尽早的发现问题并解决问题,将风险控制在可控范围内。通过OSGi的模块化设计思想,我们可以最大程度的拆分日益臃肿的系统。通过划分清晰的系统边界,可以将整个系统分而治之,使每个子系统的功能足够的内聚,只开放有限的接口和其他子系统进行交互,从而构建一个分布式的OSGi应用平台。接下去我们通过一个具体的实例来说明在基于OSGi的企业级开发框架中如何的发布远程服务以及调用远程服务。
首先我们需要在common-service-facade Bundle中编写一个接口,该接口定义了供其他系统调用的服务方法。如下所示:
package org.storevm.helloworld.common.service.facade; /** * * @author Administrator * @version $Id: DistributedOsgiService.java, v 0.1 2013-2-27 下午3:06:16 Administrator Exp $ */ public interface DistributedOsgiService { /** * 服务 * @param text * @return */ String getValue(String text); }
在上述代码中,我们定义了getValue方法,它返回String类型的的值,并接收一个String类型的参数。如果其他系统需要调用这个服务,只要引入这个Bundle就可以。
接着我们要暴露出该服务的Package以便其他Bundle可以引入,在common-service-facade Bundle中的MANIFEST.MF文件内加入一行,如下所示:
Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: Helloworld Common Service Facade Bundle Bundle-SymbolicName: helloworld-common-service-facade;singleton:=true Bundle-Version: 1.0.0 Export-Package: org.storevm.helloworld.common.service.facade
粗体字部分就是我们加入的配置。接着我们在common-service-client Bundle中编写实现类,代码如下所示:
package org.storevm.helloworld.service.client; import org.apache.log4j.Logger; import org.springframework.stereotype.Component; import org.storevm.eosgi.core.annotation.Service; import org.storevm.eosgi.core.annotation.ServiceType; import org.storevm.eosgi.core.utils.LogUtils; import org.storevm.helloworld.common.service.facade.DistributedOsgiService; /** * * @author Administrator * @version $Id: DistributedOsgiServiceImpl.java, v 0.1 2013-2-27 下午4:34:05 Administrator Exp $ */ @Component("/distributedOsgiService") @Service(serviceInterface = DistributedOsgiService.class, serviceType = ServiceType.HESSIAN) public class DistributedOsgiServiceImpl implements DistributedOsgiService { private static final Logger LOGGER = Logger.getLogger(DistributedOsgiServiceImpl.class); /** * @see org.storevm.helloworld.common.service.facade.DistributedOsgiService#getValue(java.lang.String) */ @Override public String getValue(String text) { LogUtils.info(LOGGER, "接收到参数, text={0}", text); return "调用了DistributedOsgiService, text=" + text; } }
这里需要关注的是@Service注解,该注解用以发布一个远程服务,远程服务使用的是Hessian框架(另外还支持RMI和HTTP远程服务)。让我们再次的启动Eclipse IDE中的OSGi容器,当你看到如下提示信息时,表示我们的远程服务发布成功了。
(图一)
我们可以验证一下服务是否启动成功,请打开任意浏览器,比如IE。然后输入如下地址:
http://localhost:8660/helloworld/services/distributedOsgiService
按回车,会显示如下页面:
(图二)
红色框中说明了,Hessian只支持POST请求,这证明我们的远程服务发布成功了。开发框架默认的远程服务端口是8660,如果要修改该端口,请打开jetty-config Bundle中的jetty.xml文件,如下图所示:
(图三)
将红色框中的8660修改成自己的端口号,然后重启OSGi容器即可。这个例子比较简单,接下来我们稍微修改一下,让远程服务调用biz-service-impl Bundle中的OSGi服务。首先在biz-service-impl Bundle中定义一个接口,代码如下:
package org.storevm.helloworld.biz.service.impl; /** * * @author Administrator * @version $Id: DistributedOsgiInvokeService.java, v 0.1 2013-2-27 下午5:41:48 Administrator Exp $ */ public interface DistributedOsgiInvokeService { /** * 调用 * @param text * @return */ String invoke(String text); }
然后编写一个实现类,代码如下:
package org.storevm.helloworld.biz.service.impl; import org.storevm.eosgi.core.annotation.OsgiService; /** * * @author Administrator * @version $Id: DistributedOsgiInvokeServiceImpl.java, v 0.1 2013-2-27 下午5:43:28 Administrator Exp $ */ @OsgiService(interfaces = { DistributedOsgiInvokeService.class }) public class DistributedOsgiInvokeServiceImpl implements DistributedOsgiInvokeService { /** * @see org.storevm.helloworld.biz.service.impl.DistributedOsgiInvokeService#invoke(java.lang.String) */ @Override public String invoke(String text) { return "调用了DistributedOsgiInvokeService服务,text=" + text; } }
请注意这里的@OsgiService注解,如果对此注解还不够了解,请看上一篇文章(OSGi Annotations)中的相关内容。接着我们需要将Bean注册到Spring上下文中,如下所示:
<bean id="distributedOsgiInvokeService" class="org.storevm.helloworld.biz.service.impl.DistributedOsgiInvokeServiceImpl"/>
最后我们需要暴露biz-service-impl Bundle中的package,请在MANIFEST.MF文件中添加如下内容:
Export-Package: org.storevm.helloworld.biz.service.impl
OSGi服务发布完成了,然后我们修改DistributedOsgiServiceImpl实现类的代码,如下所示:
package org.storevm.helloworld.service.client; import org.apache.log4j.Logger; import org.springframework.stereotype.Component; import org.storevm.eosgi.core.annotation.Service; import org.storevm.eosgi.core.annotation.ServiceReference; import org.storevm.eosgi.core.annotation.ServiceType; import org.storevm.eosgi.core.utils.LogUtils; import org.storevm.helloworld.biz.service.impl.DistributedOsgiInvokeService; import org.storevm.helloworld.common.service.facade.DistributedOsgiService; /** * * @author Administrator * @version $Id: DistributedOsgiServiceImpl.java, v 0.1 2013-2-27 下午4:34:05 Administrator Exp $ */ @Component("/distributedOsgiService") @Service(serviceInterface = DistributedOsgiService.class, serviceType = ServiceType.HESSIAN) public class DistributedOsgiServiceImpl implements DistributedOsgiService { private static final Logger LOGGER = Logger .getLogger(DistributedOsgiServiceImpl.class); private DistributedOsgiInvokeService distributedOsgiInvokeService; /** * @see org.storevm.helloworld.common.service.facade.DistributedOsgiService#getValue(java.lang.String) */ @Override public String getValue(String text) { LogUtils.info(LOGGER, "接收到参数, text={0}", text); return distributedOsgiInvokeService.invoke(text); } /** * Setter method for property <tt>distributedOsgiInvokeService</tt>. * * @param distributedOsgiInvokeService value to be assigned to property distributedOsgiInvokeService */ @ServiceReference public void setDistributedOsgiInvokeService(DistributedOsgiInvokeService distributedOsgiInvokeService) { this.distributedOsgiInvokeService = distributedOsgiInvokeService; } }
这里需要关注的是如何的用Annotation引入OSGi服务。好了,再次重启Eclipse IDE中的OSGi容器,如果一切正常,将看到如下所示的信息提示:
(图四)
接下去我们将再创建一个新的基于OSGi的企业级开发框架,用来调用Helloworld系统发布的远程服务。关于如何创建基于OSGi的企业级开发框架,请查看《基于 OSGi的企业级开发框架实践——开发框架的创建》中的相关介绍。这里假设你已经新建了另外的一个系统,我们取名为:InvokeHelloworld。如下图所示的是我创建的InvokeHelloworld系统的项目结构:
(图五)
好吧,让我把它run起来,结果报了一大堆异常信息。这是因为我们同时启动了2个OSGi容器,而我们开发框架使用atomikos做为JTA分布式事务支持,因此同时启动2个JTA容器造成log冲突。解决办法是,修改2个系统中任意一个的jta-config Bundle中的jta.properties文件(我修改的是invokeHelloworld系统),修改一下log的路径,不要冲突即可。如下图所示:
(图六)
再次分别重启2个系统,这次不再会有异常报错信息了。
接下去我们在invokeHelloworld系统中的common-service-integration Bundle中编写一个调用Helloworld系统的远程服务的接口(之前的文章中介绍过,common-service-integration的职责是接入其他系统的远程服务,因此将所有调用远程服务的类都写在这个Bundle中)。代码如下:
package org.storevm.invokeHelloworld.common.service.integration; /** * * @author Administrator * @version $Id: InvokeRemoteServiceClient.java, v 0.1 2013-2-27 下午6:25:16 Administrator Exp $ */ public interface InvokeRemoteServiceClient { /** * 远程调用 * @param text * @return */ void invokeRemoteService(); }
在写实现类之前,我们还需要做其他的一些事情,由于InvokeHelloworld系统需要引入Helloworld系统的common-service-facade Bundle,所以我们要先在Helloworld项目根目录下执行Maven的install命令,将所有的jar包安装的本地仓库中,如下所示:
mvn clean install -Dmaven.test.skip=true
然后将common-service-facade JAR包配置到InvokeHelloworld系统的总控pom.xml文件中,如下图所示:
(图七)
在InvokeHelloworld系统中的common-service-integration项目下的pom.xml文件中添加如下图所示的配置信息:
(图八)
最后在InvokeHelloworld项目根目录下执行:
mvn eclipse:eclipse
接着执行:
mvn clean install -Dmaven.test.skip=true
执行成功之后刷新整个项目目录结构。通过《基于 OSGi的企业级开发框架实践——开发框架的创建》中介绍的导入第三方Bundle的方法,将helloworld-common-service-facade Bundle(该Bundle在maven本地仓库目录中,比如我的目录为:D:\repository\org\storevm\helloworld\helloworld-common-service-facade\1.0.0)导入到Eclipse IDE中,如下图所示:
(图九)
好了,现在可以编写调用远程服务的实现类了,如下代码所示:
package org.storevm.invokeHelloworld.common.service.integration; import org.storevm.helloworld.common.service.facade.DistributedOsgiService; /** * * @author Administrator * @version $Id: InvokeRemoteServiceClientImpl.java, v 0.1 2013-2-27 下午6:44:48 Administrator Exp $ */ public class InvokeRemoteServiceClientImpl implements InvokeRemoteServiceClient { private static final Logger LOGGER = Logger.getLogger(InvokeRemoteServiceClient.class); /* 远程服务接口 */ private DistributedOsgiService distributedOsgiService; /** * @see org.storevm.invokeHelloworld.common.service.integration.InvokeRemoteServiceClient#invokeRemoteService() */ @Override public void invokeRemoteService() { String value = distributedOsgiService.getValue("调用方:invokeRemoteService"); LOGGER.info(value); } /** * Setter method for property <tt>distributedOsgiService</tt>. * * @param distributedOsgiService value to be assigned to property distributedOsgiService */ public void setDistributedOsgiService(DistributedOsgiService distributedOsgiService) { this.distributedOsgiService = distributedOsgiService; } }
接下去,我们需要配置远程服务的代理Bean(这里用到的是Spring提供的remote框架),如下所示 (定义在bundle-context.xml文件中):
<!-- 远程服务调用 --> <bean id="distributedOsgiService" class="org.springframework.remoting.caucho.HessianProxyFactoryBean"> <property name="serviceUrl" value="http://localhost:8660/helloworld/services/distributedOsgiService"/> <property name="serviceInterface" value="org.storevm.helloworld.common.service.facade.DistributedOsgiService"/> <property name="overloadEnabled" value="true"/> <property name="readTimeout" value="60000"/> </bean>
其中,serviceUrl:远程服务的URL地址。serviceInterface:远程服务接口类型全限定名称。overloadEnabled:是否允许调用重载的远程服务方法。readTimeout:访问远程服务的超时时间(单位:毫秒)。
为了方便测试,我们需要将InvokeRemoteServiceClientImpl实现类注册到Spring上下文中,配置如下:
<bean class="org.storevm.invokeHelloworld.common.service.integration.InvokeRemoteServiceClientImpl" init-method="invokeRemoteService"/>
好了,重启InvokeHelloworld项目的OSGi容器。如果一切正常,我们会在Console中看到如下的日志信息:
(图十)
从上图我们可以发现,调用Helloworld系统的远程服务成功了,并打印出正确的日志信息。另外你也可以将integration Bundle中的类发布为OSGi服务,这样其他的Bundle就可以执行调用远程服务的方法了,这里我们就不再讨论,有兴趣的同学可以自己尝试一下。
下一篇文章我们将介绍开发框架提供的OSGi集成测试工具,它可以帮助我们在不启动OSGi容器的情况下进行OSGi服务的集成测试。敬请关注!