`
YiSingQ
  • 浏览: 88144 次
  • 性别: Icon_minigender_1
  • 来自: 广州
社区版块
存档分类
最新评论

模块化Java:静态模块化

阅读更多
    模块化是大型Java系统的一个重要特征。在这些项目中构建脚本和项目通常被划分为多个模块,以便改进构建过程,但是在运行时却很少考虑划分模块的问题。
在“模块化Java”系列文章的第二篇里,我们将讨论静态模块化(static modularity)。内容包括如何创建bundle、将其安装到OSG引擎以及怎样建立bundle之间的版本依赖。在下一篇文章中,我们将讨论动态模块化(dynamic modularity)并展示bundle如何对其他bundle作出响应。

    在上篇文章《模块化Java简介》 中讲到,Java在开发时把package作为模块化单元,部署时把JAR文件作为模块化单元。可是尽管像Maven这样的构建工具能够在编译时保证 package和JAR的特定组合,但这些依赖在运行时classpath下仍可能出现不一致的情况。为了解决这一问题,模块可以声明其依赖需求,这样, 在运行时就可以在执行之前进行依赖检查。

    OSGi是一个Java的运行时动态模块系统。OSGi规范描述了OSGi运行时的工作行为方式;当前版本是OSGi R4.2(Infoq曾经报导过)。

    一个OSGi模块(也称为bundle)就是一个普通的JAR文件,但在其MANIFEST.MF中带有附加信息。一个bundle的manifest必须至少包含如下内容:

Bundle-ManifestVersion:对OSGi R4 bundle来说必须是2(OSGi R3 bundle则默认为1)
Bundle-SymbolicName:bundle的文本标识符,通常以反向域名的形式出现,如com.infoq,并且往往对应了包含其中的package
Bundle-Version:major.minor.micro.qualifier形式的版本号,前三个元素是数字(缺省是0),qualifier则是文本(缺省是空字符串)

创建一个bundle
最简单的bundle必须在manifest文件中包含如下内容:

Bundle-ManifestVersion: 2
Bundle-SymbolicName: com.infoq.minimal
Bundle-Version: 1.0.0


创建bundle并没有什么可稀奇的,那么让我们创建一个带activator的bundle吧。下面是OSGi特定的代码片段,在bundle启动时被调用,有点像是bundle的main方法。

package com.infoq;
import org.osgi.framework.*;
public class ExampleActivator implements BundleActivator {
  public void start(BundleContext context) {
    System.out.println("Started");
  }
  public void stop(BundleContext context) {
    System.out.println("Stopped");
  }
}


为了让OSGi知道哪个类是activator,我们需要在manifest中加入额外的信息项:

Bundle-Activator: com.infoq.ExampleActivator
Import-Package: org.osgi.framework


Bundle-Activator声明了在bundle启动时要实例化并调用其start()方法的类;类似的,在bundle停止时将调用该类的stop()方法。

那么Import-Package又是干什么的?每个bundle都需要在manifest中定义其依赖,以便在运行时判断所有必需代码是否可用。在本例 中,ExampleActivator依赖于org.osgi.framework包中的BundleContext;如果我们不在manifext中声 明该依赖,在运行时就会碰到NoClassDefFoundError错误。

下载OSGi引擎
要编译并测试我们的bundle,需要一个OSGi引擎。对OSGi R4.2,下面罗列了几个可用的开源引擎。你也可以下载Reference API来编译(这样可以确保没有用到任何平台特定特性);可是,要运行bundle,还是需要一个OSGi引擎。以下引擎都可供选择:

Equinox

许可 Eclipse Public License
文献 http://www.eclipse.org/equinox/
下载 org.eclipse.osgi_3.5.0.v20090520.jar
评注
该org.eclipse.osgi bundle包含了框架、运行时和shell,是全部合一的。它的文件名也最长,用tab补全(或改名为equinox.jar)可以解决这一问题。要启动console,在命令行输入java -jar org.eclipse.osgi_3.5.0.v20090520.jar -console即可。
框架 org.eclipse.osgi_3.5.0.v20090520.jar

Felix

许可 Apache License
文献 http://felix.apache.org/
下载 Felix Framework Distribution 2.0.0
评注
这是所见遵守规范最严格的OSGi引擎,还被用在GlassFish及许多其他开源产品中。运行时需要在命令行输入java -jar bin/felix.jar而不是 java -jar felix.jar,因为启动时它要从当前目录查找bundles 路径。
框架 bin/felix.jar

Knopflerfish

许可 Knopflerfish License (BSD-esque)
文献 http://www.knopflerfish.org/
下载 knopflerfish_fullbin_osgi_2.3.3.jar
评注
该JAR是一个自解压zip文件;刚开始你必须运行java -jar进行解压。不要下载“bin_osgi”,它无法启动。
框架 knopflerfish.org/osgi/framework.jar

另外还有更小的定位于嵌入设备的OSGi R3运行时可用(比如Concierge),但本系列文章只关注OSGi R4。

编译并运行bundle
获得framework.jar之后,把OSGi加入classpath并编译上面的例子,然后将其打包成JAR:

javac -cp framework.jar com/infoq/*/*.java

jar cfm example.jar MANIFEST.MF com


每种引擎都有shell,命令也相似(但不相同)。为了便于练习,让我们看看如何获得这些引擎并使之运行、安装和启/停bundle。

一旦引擎启动并运行起来,你就可以安装bundle(由 file:// URL来定位)了,然后可以使用安装bundle所返回的bundle id,启动或停止该bundle。

尽管所有的shell工作起来大都一样,但命令之间还是有容易混淆的细微差别。有两个统一console的项目(Pax Shell,Posh)和运行器(Pax Runner)可以利用;OSGi RFC 132则是一个正在进行中的提案,试图标准化command shell。另外,Apache Karaf可以运行在Equinox或Felix之上,提供统一的shell以及其他特性。尽管使用这些项目或工具进行实际部署是可取的,但本系列文章还是关注于普通的OSGi框架实现。

如果你启动了OSGi框架,你应该能够安装上面所讲的com.infoq.minimal-1.0.0.jar(你还可以用链接地址及install命令直接从网站上进行安装)。每次安装bundle,引擎都会打印出该bundle的数字ID。

在安装好bundle之前不可能知道bundle的ID是多少,这取决于系统中其它bundle的ID分配情况;但是你可以使用适当的命令罗列出已安装的bundle将其找出来。

依赖
迄今为止,我们只有一个bundle。模块化的一个好处是可以把系统分解为多个小模块,在此过程中,减小应用的复杂性。从某种程度上,Java的 package已经做到了这一点:例如,一个common包有独立的client和server包,他们都依赖于该common包。但是Jetty最近的例子(client意外地依赖于server)表明做到这一点并不总是很容易。实际上,有些由OSGi给项目带来的好处纯粹就是强制模块间的模块化约束。

模块化的另一个好处是把'public'包从非public包中分离出来。Java的编译时系统允许隐藏非public类(在特定包中是可见的),但是不支持更大程度的灵活性。然而在OSGi模块中,你可以选择哪些包是exported(输出)的,这就意味着没有输出的包对其他模块是不可见的。

让我们继续开发一个功能,用来初始化URI Templates(与Restlet中 使用的一样)。因为该功能可重用,我们想将其放在一个单独模块中,让使用它的客户端依赖于它。(通常,bundle不适合这么细粒度的用法,但是其可用于 说明工作原理)。该功能将根据一个模板(比如http://www.amazon.{tld}/dp/{isbn}/)和一个包含有 tld=com,isbn=1411609255的Map,产生出URL http://www.amazon.com/dp/1411609255/(这么做的一个原因是,如果Amazon URL模式发生了变化,我们能够改变该模板,尽管好的URI是不会改变的)。

为了提供一个在不同实现之间切换的简单方法,我们将提供一个接口和一个工厂。这会让我们看到在提供功能的同时实现是怎样对client隐藏的。代码(对应几个源文件)如下:

package com.infoq.templater.api;
import java.util.*;
public interface ITemplater {
  public String template(String uri, Map data);
}
// ---
package com.infoq.templater.api;
import com.infoq.templater.internal.*;
public class TemplaterFactory {
  public static ITemplater getTemplater() {
    return new Templater();
  }
}
// ---
package com.infoq.templater.internal;
import com.infoq.templater.api.*;
import java.util.*;
public class Templater implements ITemplater {
  public String template(String uri, Map data) {
    String[] elements = uri.split("\\{|\\}");
    StringBuffer buf = new StringBuffer();
    for(int i=0;i

该实现隐藏在com.infoq.templater.internal包中,而public API则位于com.infoq.templater.api包中。这就给了我们巨大的灵活性,如果需要,以后可以修改实现以提供更加有效的手 段。(internal包名是约定俗成,你可以起其他名字)。

为了让其他bundle能够访问该public API,我们需要将其export(输出)。我们的manifest如下:

Bundle-ManifestVersion: 2
Bundle-SymbolicName: com.infoq.templater
Bundle-Version: 1.0.0
Export-Package: com.infoq.templater.api


创建一个client bundle
我们现在可以创建一个使用templater的client。利用上面的例子创建一个activator,其start()方法如下:

package com.infoq.amazon;
import org.osgi.framework.*;
import com.infoq.templater.api.*;
import java.util.*;
public class Client implements BundleActivator {
  public void start(BundleContext context) {
    Map data = new HashMap();
    data.put("tld", "co.uk"); // or "com" or "de" or ...
    data.put("isbn", "1411609255"); // or "1586033115" or ...
    System.out.println( "Starting\n" + 
        TemplaterFactory.getTemplater().template(
        "http://www.amazon.{tld}/dp/{isbn}/", data));
  }
  public void stop(BundleContext context) {
  }
}

我们需要在manifest中显式输入templater API,否则我们的bundle无法编译。用Import-Package或Require-Bundle都可指定依赖。前者可以让我们单独输入包;后者 则将隐式输入该bundle中所有输出包。(多个包及bundles可以用逗号分开)。

Bundle-ManifestVersion: 2
Bundle-SymbolicName: com.infoq.amazon
Bundle-Version: 1.0.0
Bundle-Activator: com.infoq.amazon.Client
Import-Package: org.osgi.framework
Require-Bundle: com.infoq.templater


注意在前面的例子中,我们已经使用了Import-Package来输入org.osgi.framework。在这个例子中,我们将演示 Require-Bundle的用法,其使用了Bundle-SymbolicName。当然,用Import-Package: org.osgi.framework, com.infoq.templater.api可以达到相同的效果。

不管如何声明依赖于templater的bundle,我们都只能访问单一的输出包com.infoq.templater。尽管client可以通过 TemplaterFactory.getTemplater()来访问templater,但是我们不能直接从internal包中访问该类。这样我们 在未来就可以在不影响client的情况下改变templater类的实现。

测试该系统
任何OSGi应用实际上都是一组bundle。在本例中,我们需要编译并把bundle打包为JAR(如前面所述),启动OSGi引擎,安装两个bundle。下面是在Equinox中进行操作的例子:

java -jar org.eclipse.osgi_* -console
osgi> install file:///tmp/com.infoq.templater-1.0.0.jar
Bundle id is 1
osgi> install file:///tmp/com.infoq.amazon-1.0.0.jar
Bundle id is 2
osgi> start 2
Starting http://www.amazon.co.uk/dp/1411609255

Amazon client bundle现在已经启动了;当其启动时,它先用(硬编码的)给定值为我们初始化URI template。然后在该bundle启动过程中打印出来以确定其已正常工作。当然,一个真正的系统不会这么死板;但是Templater服务可以用于 任何其他应用(例如,产生一个基于Web的应用中的链接)。将来,我们将能够在OSGi环境中查看Web应用。

带版本的依赖
本文最后要指出的是目前我们所谈的依赖都没有版本;更确切的说,我们可以使用任意版本。两个bundle整体及各个包都可以有版本,增大minor号通常 意味着增加了新特性(但保持向后兼容)。以org.osgi.framework包为例,OSGi R4.1中是1.4.0,OSGi R4.2中是1.5.0。(顺便提一句,这是bundle版本和销售版本保持分离的很好理由,Scala语言已经这么做了)。

要声明依赖处于一个特定版本,我们必须在Import-Package或Require-Bundle中来表达。比如,我们可以指定Require- Bundle: com.infoq.templater;bundle-version="1.0.0"以表示工作所需的最低版本为1.0.0。类似的,我们可以用 Import-Package: com.infoq.templater.api;version="1.0.0"做相同的事情 —— 但是要记住package与bundle版本是完全分开的。如果你不指定版本,缺省为0.0.0,因此,除非指定了相应的Export-Package: com.infoq.templater.api;version="1.0.0"否则该输入不会被解析。

还可以指定一个版本范围。例如,按惯例OSGi版本的major号增大表示向后兼容发生改变,因此我们可能想将其限制在1.x的范围内。我们可以通过 (bundle-)version="[1.0,2.0)"的方式来表达这一依赖约束。该例中,[表示‘包含’,而)表示‘不包含’。换句话说,‘从 1.0到2.0但不包括2.0’。实际上,将一个依赖约束表达为‘1.0’与“[1.0,∞)”意思是一样的——换句话说,比1.0版高都可以。

尽管这些内容超出了本文的范围,但是在一个OSGi系统中,一个bundle同时有两个版本也是可能的。如果你有一个老client依赖于1.0版本 API,同时又一个新client依赖于2.0版本API,这就非常有用了。只要每个bundle的依赖是一致的(换句话说,一个bundle不能直接或 间接同时输入1.0和2.0)那么应用程序将工作良好。作为读者练习,你可以创建一个使用泛型的2.0版的Templater API,然后让一个client依赖于1.x,而另一个依赖于2.x。

总结
在本文中,我们探讨了开源OSGi引擎Equinox、Felix和Knopflerfish,并且创建了两个有依赖关系的bundle。我们还谈到了带版本的依赖。截止目前,模块化还是静态的;我们还没有涉及到OSGi的动态本质。在下一篇文章中我们将涉及这一内容。

本文所讲例子的可安装bundle罗列如下(包含源代码):
com.infoq.minimal-1.0.0.jar
com.infoq.templater-1.0.0.jar
com.infoq.amazon-1.0.0.jar


查看英文原文:Modular Java: Static Modularity。

-----------------------------转载自:InfoQ-------------------------------
  • 大小: 46.7 KB
分享到:
评论

相关推荐

    java 模块化 开发

    在 Java 开发中,模块化可以分为静态模块化(static modularity)和动态模块化(dynamic modularity)。 一、静态模块化(static modularity) 静态模块化是指在编译时对模块的依赖关系进行检查和管理。Java 中的...

    java静态文件处理

    在Java编程语言中,处理静态文件是一项常见的任务,特别是在构建Web应用程序时。静态文件通常指的是HTML、CSS、JavaScript、图片等非动态内容,这些文件在服务器端不需要额外的处理即可发送给客户端。本篇文章将深入...

    java 静态页面开发

    它可以通过`<script>`标签引入,也可以使用模块化工具如RequireJS或Webpack进行组织。 5. **URL映射**:在Java中,即使处理静态资源,也需要配置Web服务器或Servlet容器来正确处理URL请求。例如,在web.xml中配置一...

    JAVA:抽象类和接口

    ### JAVA:抽象类和接口 #### 一、概念与特性 **抽象类**与**接口**作为Java语言中实现抽象的两种主要手段,在面向对象编程中扮演着...理解和区分这两者之间的差异有助于编写出更加模块化、可扩展和易于维护的代码。

    页面静态化之真静态

    页面静态化是Web开发中的一种优化策略,主要目的是提高网站的加载速度和用户体验,同时也有助于搜索引擎优化(SEO)。真静态,顾名思义,就是将动态网页转化为静态HTML文件,用户请求时直接返回这些预生成的HTML,而...

    Java静态分析Java代码检查.docx

    Java静态分析和Java代码检查是软件开发中至关重要的步骤,主要目的是在代码执行之前发现潜在的错误和不规范之处,从而提升代码质量和可维护性。Parasoft Jtest是一款强大的工具,专门针对Java应用程序,提供了静态...

    java 3 模块化与抽象编程-1649236423580.ppt

    Java 通过包(package)、类(class)以及接口(interface)来实现模块化。包是命名空间,可以防止类名冲突,同时提供了一种组织相关类的方式。类是模块化的基础单元,它封装了数据和操作这些数据的方法。接口则定义...

    jsp静态化和伪静态化

    在JSP中,实现伪静态化通常需要借助Web服务器(如Apache、Nginx)或应用服务器(如Tomcat)的URL重写模块,通过配置规则将看似静态的URL映射到实际的动态处理程序。 总的来说,静态化和伪静态化都是优化Web性能的...

    NotWhatItSeems:静态装饰模块

    在Java编程语言中,"NotWhatItSeems:静态装饰模块"可能指的是使用静态导入(static import)和装饰器模式(Decorator Pattern)相结合的一种技术或实践。这两种特性都是Java中的重要概念,它们各自有着独特的用途,...

    java/jsp网站实现伪静态

    - **缓存策略**:静态化的URL可以利用浏览器缓存和CDN服务,提高页面访问速度。 - **维护和更新**:当动态内容更改时,确保伪静态URL能够正确映射到新的内容,避免出现404错误。 - **URL一致性**:保持内部链接和...

    bach-library-template::musical_notes:模块化Java库模板

    **模块化Java库模板—— Bach Library Template** 在Java开发中,模块化是一个重要的设计原则,它有助于提升代码的可读性、可维护性和可重用性。`Bach Library Template`是一个专为Java开发者设计的模块化库模板,...

    工一说javaweb之smbms实战静态资源.zip

    - Spring MVC:Spring框架的一个模块,提供了一种MVC实现,简化了Java Web开发。 - Struts、Hibernate等:其他流行的Java Web框架,Struts处理控制器逻辑,Hibernate处理数据持久化。 6. **数据库交互**: - ...

    动态页面静态化汇总--页面静态化方案

    在静态化过程中,URLRewrite可以将动态URL转换为伪静态格式,使得用户和搜索引擎看到的是看似静态的URL,而实际上这些请求仍被转发到动态处理程序。通过这种方式,可以提升用户体验,同时利于SEO优化。 4. **JSP+...

    java 面向对象扩展编程

    总的来说,Java中的静态类、内部类和匿名类都是面向对象编程的重要组成部分,它们提供了灵活性和代码复用,帮助开发者构建更加模块化和易于维护的程序。了解并熟练运用这些特性,将有助于提升Java编程能力。

    java用户管理模块

    Java用户管理模块是一个常见且重要的系统组件,尤其对于初学者来说,它提供了了解Web开发基础,尤其是JSP(JavaServer Pages)技术的良好实践机会。在这个模块中,开发者通常会实现用户注册、登录、权限控制、密码...

    JAVA公共资源模块的设计与开发(源代码+论文).zip

    Java公共资源模块的设计与开发是一个重要的主题,特别是在大型企业级应用中,它对于代码复用、模块化和系统可维护性有着显著的影响。本资源包包含了关于这个主题的源代码和相关论文,旨在帮助学习者深入理解和实践...

    JAVA对象模块.rar

    Java对象模块是Java编程中的核心概念,它代表了程序中的数据和行为的组合。在Java中,对象是类的实例,而类则是对象的蓝图。理解这一模块的关键在于掌握类、对象、属性和方法等基本概念。 1. 类(Class): 类是...

    使用内部和匿名类优化Java代码

    使用内部类和匿名类可以优化Java代码,提高代码的复用性和模块化。然而,过度使用或不恰当使用这些特性可能导致代码过于复杂,难以理解和维护。因此,应谨慎选择何时使用内部类和匿名类,确保它们的使用符合设计原则...

    达内 java云笔记 初始化 静态页面

    【Java云笔记初始化静态页面详解】 Java云笔记的初始化阶段是构建一个可运行的Web应用程序的关键步骤,这里的“初始化”通常指的是创建一个基础框架,包括前端界面和后端逻辑的初步设定。在这个场景中,我们关注的...

    Java开源博客系统框架B3log

    Solo的架构设计体现了模块化和组件化的思想。它将博客的各个功能如用户管理、文章发布、评论系统等划分为独立的模块,这样的设计有利于代码的复用和维护。同时,Solo还支持插件机制,允许开发者根据需要添加自定义...

Global site tag (gtag.js) - Google Analytics