模式7:Container Independence

 由  YiFei 发布

模式描述

模块应当独立于运行时的容器。

具体描述

过度依赖容器的模块称为重量级模块,它们不能再容器之外执行。一个比较好的重量级技术的示例是EJB,而轻量级框架的迅速出现,如Spring,直接原因就是重量级技术有不少缺陷。不依赖容器的轻量级模块主要有两大显著的优势:
1. 他们可以移植于不同的运行平台。
2. 他们是可测试的。

另外,轻量级模块更加容易去维护,因为它们有模块化关底层服务的内容没有被过度的依赖污染。开发轻量级模块要求容器依赖被抽象化。如下图所示,Client.jar模块独立于runtime.jar,通过abstracttion.jar模块封装容器依赖这种方式来协调Client.jar与运行环境的交互。所以,Client类对runtime.jar或abstracttion.jar的API没有依赖。

http://assets.osgi.com.cn/article/7289355/Container.png

实现变种

外部配置模式可以用来配置模块,这样模块可以在特定的运行环境下正确运行。在这些情况中,外部配置是模式用来达成容器独立的一种模式,因为配置依赖被移到外部配置文件。然后上层框架再去处理模块的配置。然而,外部配置模式与容器独立模式还是有一些区别。外部配置模式是用来配置一个模块,使模块可以运行于不同环境。容器独立模式是用来保证模块是可以移植与不同容器的。

在很多情况中,开发者不需要通过自己织入一些代码来达到容器独立的目的,而是选择一些框架,如Spring。为了达到容器独立这一目标,最常用的技术是依赖注入。许多依赖注入框架也提供重要底层服务来保证模块仍然是容器独立的。

结果

容器独立保证一个模块可以移植于不同运行环境,因为底层行为与模块行为是隔离的。Spring框架就是一个帮助你创建轻量级模块的示例。底层代码被框架所封装,这样导致与底层相关的错误会更小可能性地发生。总的来说,应用代码更容易写、维护和移植于不同环境,开发者需要写的底层代码量也减少很多。

容器独立也增加了测试一个模块的便利性,因为没有容器依赖需要去处理。一个测试模块可以验证模块的行为不需要测试底层代码。

虽然容器的独立在可测试性和可移植性方面提供显著的提升,但是也可能要挑战你去写和维护复杂的底层代码。当一个依赖注入的框架被使用,XML配置文件也会增多。幸运的是,这样的权衡还是值得的。

示例

看两个简单的模块:client.jar和service.jar。这些模块使用OSGi来获得运行时模块化特性的支持。同时下面贴的配置文件是用来帮助我们实现容器的独立。

列表一中所展示的类中,这个service类是被注册为OSGi服务,这个类的start方法是使其他类请求这个类时可以发现这个服务。通过代码,可以看到代码严重依赖OSGi框架,而这也阻止了这段代码被用在OSGi环境之外。

先看重量级容器依赖的例子,如下:

列表一 Heavy Container Dependencies

import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceListener;
import org.osgi.framework.ServiceEvent;
import org.osgi.framework.ServiceRegistration;

public class HelloServiceImpl implements HelloService, BundleActivator {

private ServiceRegistration registration;

public void start(BundleContext context) {
    Properties props = new Properties();
    props.put("Language", "English");
    registration = context.registerService(HelloService.class.getName(), this, props);
}

public void stop(BundleContext context) {

}
public String sayHello() {
    return "Hello World!! ";
}

public String sayGoodbye() {
    return "Goodbye World!!";
}
}

任何客户使用这个被注册成OSGi服务时都需要使用OSGi框架。下面的客户端代码展示了如何去客户端去与OSGi框架交互来获得这一服务的引用。

列表二 Heavy Client import com.extensiblejava.hello.service.HelloService;

import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceReference;
import org.osgi.util.tracker.ServiceTracker;

public class HelloConsumer implements BundleActivator {

private ServiceTracker helloWorldTracker;
private HelloService helloService;

public void setService(HelloService helloService) {
    this.helloService = helloService;
}

public void removeService() {
    this.helloService = null;
}

public void start(BundleContext context) throws Exception {
    helloWorldTracker = new ServiceTracker(context, HelloService.class.getName(), null);
    helloWorldTracker.open();
    HelloService hello = (HelloService) helloWorldTracker.getService();

    if (hello == null) {
        System.out.println("Hello service unavailable on HelloConsumer start");
    } else {
        System.out.println(hello.sayHello());
    }
}
public void stop(BundleContext context) {
    HelloService hello = (HelloService) helloWorldTracker.getService();
    if (hello == null) {
        System.out.println("Hello service unavailable on HelloConsumer stop");
    } else {
        System.out.println(hello.sayGoodbye());
    }

    helloWorldTracker.close();
}

}

这段重量级的代码阻止它被使用在OSGi环境之外,也不方便使用单元测试。为了保持一样的模块结构,如下所示,我们利用Spring DM来去除所有对OSGi的依赖。列表三和列表四展示了升级版的使用了Spring DM的代码,当然我们需要部署Spring DM模块到OSGi的运行环境中,还要引入相应的配置文件,如列表五所示。

列表三 Independent HelloServiceImpl Class

package com.extensiblejava.hello.service.impl;

import java.util.Properties;
import com.extensiblejava.hello.service.HelloService;

public class HelloServiceImpl implements HelloService {

public String sayHello() {
    return "Hello OSGi Spring World!! ";
}

public String sayGoodbye() {
    return "Goodbye OSGi Spring World!!";
}
}

列表四 Independent Consumer Class

package com.extensiblejava.hello.client;

import com.extensiblejava.hello.service.HelloService;

public class HelloConsumer {
private HelloService hello;

public void setHelloService(HelloService hello) {
    this.hello = hello;
}

public void start() throws Exception {
   System.out.println(hello.sayHello());
}

public void stop() throws Exception {
    System.out.println(hello.sayGoodbye());
    // NOTE: The service is automatically released.
}

}

列表五 Spring XML configuration File

<osgi:reference id="helloService" interface="com.extensiblejava.hello.service.HelloService"/>

<bean name="helloConsumer" class="com.extensiblejava.hello.client.HelloConsumer"
    init-method="start" destroy-method="stop">
    <property name="helloService" ref="helloService"/>
 </bean>

总结

容器依赖会导致难以重用和维护的重量级模块。消除这些依赖帮助提升这些模块的可测性。没有容器依赖的模块可以方便地移植于不同的运行环境,而且可以被用在不同的上下文中。依赖注入是一种常用的解决容器依赖的技术。


罗俊杰 2013-06-18 02:04

回复张卫滨: 非常欢迎:) 也欢迎给我们社区投稿,有一些好文章的话,可以发到社区上来,为书的正式出版热热身。

顶(0) 踩(0) 回复

张卫滨 2013-06-17 21:48

《Java应用架构》这本书由华章图书引进,本人翻译,目前已经交稿了,预计8月底出版,到时候可以和出版社商量一下,发一些样章上来<br>ps:这个社区很不错

顶(1) 踩(0) 回复
查看评论