Chinaunix首页 | 论坛 | 博客
  • 博客访问: 638280
  • 博文数量: 125
  • 博客积分: 8703
  • 博客等级: 中将
  • 技术积分: 1102
  • 用 户 组: 普通用户
  • 注册时间: 2010-03-10 17:48
文章分类

全部博文(125)

文章存档

2012年(2)

2011年(3)

2010年(11)

2009年(1)

2008年(12)

2007年(58)

2006年(38)

分类: Java

2007-12-06 10:22:10


  一. 引言

  一般地,企业软件产品都要求在客户端具有定制能力,而且当客户必须修改核心产品的配置来引入他们自己的定制时一般都要求进行更新操作。借助于易于扩展和可升级的高度模块化的软件,插件技术能够提供针对这种典型场所下的完美解决方案。

  注释1-什么是插件呢?一个插件是使用什么样的代码构成的?

  在众多的定义当中,我认为最好的定义当属Eclipse工程中所定义的:插件是一种代码贡献,它能够把代码添加到一个系统中的众所周知的扩展点处。也 就是说,一个插件是一个良好定义的代码包(例如一个jar文件或目录),它提供足够的配置能力来实现在系统中的一个特定的众所周知的位置插入和激活自身。

  插件本身还可以定义另外的其它插件能够扩展的扩展点。一个扩展点定义了一个语言接口(该插件将提供它的一个实现)和使用该被发现的插件的组件。一个扩展点能够接受被动态地发现和在运行时刻配置的插件。

  借助于一种拥有清晰定义的扩展点的插件环境,核心产品可以自由升级,而且插件本身可以根据独立的计划发行和升级。例如,借助于我的开源 Classpath助理工程(基于Eclipse的插件框架),我可以按常规来升级我的Eclipse,而且还可以轻松地发行我自己的插件的更新版本。

  特别对于Java开发者来说,与现有J2EE组件(参考"注释2-J2EE组件不是插件吗?")相比,插件提供了一种更好的升级技术。可以设想你的许 多EJB是由不同的开发小组构建的;然后,在了解它们能够良好工作的情况下,就可以把它们整合到一个应用程序中。一个插件架构应该是允许进行这种级别的组 件化的。

  注释2-J2EE组件不是插件吗?

  是的,J2EE组件,例如EJB和Servlet,都不是插件。尽管它们都具有一定程度的"可插入性"(这是指,你能够交换一个EJB或 Servlet实现),但是配置它们并不那么清晰明快,而且它们缺乏一个插件所具有的容易的升级能力。例如,Servlet无法把代码与配置结合到一起。 因此,尽管你能够在其自己的jar文件中打包一个servlet实现;但是,此时你往往需要修改web.xml以便servlet容器能够识别它。

  乍看上去,EJB似乎更象插件-它们包含提供有关自己信息的发布描述符。然而,EJB也不是插件,因为,典型情况下,它们都要求外部配置(一种在 EAR的application.xml中的引用);并且,典型地,它们在其各自的发布描述符中进行彼此参考。这两种特征都使一个EJB无法成为"插件式 可发布的"。

  借助于流行的Spring框架的BeanFactoryPostProcessor接口,开发者可以轻松地创建一个轻量级插件框架。本文正是想讨论如何实现这一点;同时,还要向你展示一个使用轻量级插件的工作示例。

  二. 准备你的插件平台

  在你的平台能够支持可插入的组件前,它需要满足下列两个标准:

  · 组件必须是自发现的。你已经了解到J2EE组件不能成为真正插件的准确理由。典型情况下,你应该找到一个需要升级的外部配置文件以便该平台能够感知新的代码。

  · 组件必须包含足够信息以便在应用程序内部集成或配置其本身。

  如果你仅是添加一些不需要与系统进行协作的代码(也就是说,松耦合的),那么自动发现就是很简单的。真正的挑战是结合有紧密集成的自发现。

  三. Spring中的自发现功能

  事实证明,Spring实际上为支持插件开发作了比较好的准备。Spring已经能够在若干种bean上下文文件中存储配置,并且它使得自发现配置文 件非常简单。例如,下面的Spring语句自动发现以ctx.xml结尾的存在于classpath的META-INF/services目录下的任何文 件:

<import resource="classpath*:META-INF/services/*.ctx.xml" />

  这种现成的功能正是当构建轻量级插件框架时你要利用的一个特色。

  注意,Spring并不关心它自己的代码自动发现功能。这通常不是一个问题,因为大多数J2EE容器都提供一个lib目录,存放于这个目录下的任何 jar文件将被自动地添加到classpath中。这意味着,如果你想以jar文件形式捐献你的代码的话,那么在任何一种J2EE容器中实现自发现都会是 相当容易的事情。

  在一个应用程序服务器外,使用例如ant这样的工具来实现jar文件的自发现也是非常容易的。下列的Apache Ant XML以一种与一个应用程序服务器类似的方式检测所有的存在于lib目录下的jar文件:

<path id="classpath">
<fileset dir="${basedir}/lib">
<include name="**/*.jar"/>
</fileset>
</path>
<target name="start.server" description="launches the server process">
<java classname="platform.bootstrap.Server">
<classpath refid="classpath" />
</java>
</target>


  因此,尽管Spring并不直接支持自发现功能,但是通过使用标准技术,你仍然可以使你的代码容易地实现自发现。这一点,与Spring的能够自动检测配置的能力相结合,就可以使你既能够实现代码捐献的目的也能够使你的代码在系统中被发现和激活。

  四. 在Spring中实现自配置

  你需要进一步实现的是,使插件具有自配置能力。尽管Spring并不直接支持这种功能,但是,借助于它提供的一些工具,实现这一目标也是相当直接的。 实现自配置的关键部分是BeanFactoryPostProcessor,这是一个Spring调用的接口(该调用应该是在所有配置被发现和加载到一个 内存描述之后,但在创建实际的对象之前发生)。

  通过使用BeanFactoryPostProcessor,你可以动态地把所有的bean组合到一起而不必修改原始的文件系统配置。下列代码是我的 BeanFactoryPostProcessor实现的核心部分:PluginBeanFactoryPostProcessor(下载源码中提供了完 整的类):

private String extensionBeanName;//经由spring设置(在此没有显示setter)
private String propertyName;//经由spring设置(在此没有显示setter)
private String pluginBeanName;//经由spring设置(在此没有显示setter)
/*
*(非Javadoc)
*@请参考BeanFactoryPostProcessor#postProcessBeanFactory(ConfigurableListableBeanFactory)
*/
public void postProcessBeanFactory(
ConfigurableListableBeanFactory beanFactory)
throws BeansException {
//找到我们希望修改的bean定义
BeanDefinition beanDef =
beanFactory.getBeanDefinition(extensionBeanName);

//在该bean定义中,查找它的属性并且发现我们将修改的具体属性。
MutablePropertyValues propValues = beanDef.getPropertyValues();
if ( !propValues.contains(propertyName))
throw new IllegalArgumentException("Cannot find property " +
propertyName + " in bean " + extensionBeanName);
PropertyValue pv = propValues.getPropertyValue(propertyName);

//取出值定义(在我们的情况下,我们仅支持列表风格属性的更新)
Object prop = pv.getValue();
if ( !(prop instanceof List))
throw new IllegalArgumentException("Property " + propertyName +
" in extension bean " +
extensionBeanName +
" is not an instanceof List.");
//把我们的bean参考添加到列表中。当Spring创建对象
// 并且把它们绑定到一起时,我们的bean现在准备好了.
List l = (List) pv.getValue();
l.add(new RuntimeBeanReference(pluginBeanName));
}


  下面展示了配置在Spring中看上去的样子。首先,在你的核心工程中定义扩展点-它是example.craps.Table的一个实例,其中它的两个属性(dice,players)配置以空列表。这是标准的Spring用法:

<beans>
<bean id="extension-point.craps.table"
class="example.craps.Table"
init-method="init">
<property name="dice">
<list>
</list>
</property>
<property name="players">
<list>
</list>
</property>
</bean>
</beans>

  现在,你可以使用插件类连同它的Spring上下文(这将是自发现的)打包一个jar文件,并且你可以拥有一个类似如下的配置:

<beans>
<bean id="real-dice" class="example.craps.RealDice" />
<bean class="platform.spring.PluginBeanFactoryPostProcessor">
<property name="extensionBeanName"
value="extension-point.craps.table" />
<property name="propertyName" value="dice" />
<property name="pluginBeanName" value="real-dice" />
</bean>
</beans>

  在这个Spring配置中定义了一个example.craps.RealDice的实例,然后,它定义你的 PluginBeanFactoryPostProcessor(它被配置以找到extension-point.craps.table bean)。这一实例还会把你的real-dice bean添加到craps表的dice属性中。

  注意,这是本文中真正的焦点所在。这个到Spring的小扩展就是编写基于插件的组件的所有要求。注意,如果你删除这个包含该Spring上下文的 jar文件,那么,你还要从extension-point.craps.table bean中"分离"你的bean。然后,把该jar添加回去,并且把它自己绑定到系统中的适当位置。

  五. 使用轻量级插件进行开发

  我常常吃惊于大多数的架构师团队极少地考虑开发者能否容易地使用他们的框架。其实,EJB就是一种具有学术式优点的极好的例子,但是,其实践中的开发 缺点使其变得极为昂贵。所以,我认为,当选用一种框架实现典型的编码/构建/调试工作时,先了解一下该框架具有什么样的负荷能力和影响是非常重要的。

  从这种角度来看,轻量级插件则是相当"无痛苦"的。你可以把每一个插件作为它自己的简单地依赖于核心产品的jar的可构建工程。这在一种类似于 Eclipse这样的工具(在其中,核心产品具有其自己的Java工程并且每一种插件也都有其自己的)中是很容易建模的。你仅需要一个最终的装配工程-它 依赖于核心产品和包括的各种插件工程。通过使装配工程依附于核心和插件工程,你的classpath会被自动地正确构建。本文的下载源码中提供了一个类似 这样的工程。记住,你可以为每一种客户创建一个装配工程,从而允许你把不同的插件与不同的客户相匹配。这种方式与Eclipse恰好吻合-允许在调试期间 的增长式编译和代码热交换;这使你的开发进程相当灵活-不必要加入完全妨碍Eclipse的本机Java支持的构建步骤。

  六. 一切都是插件吗?

  Eclipse的一个根本特征是,一切都是插件(请参考注释3:Eclipse插件比较)。从系统的初始启动到Java开发环境,再到在线帮助系统, 每一种捐献代码(即使不是Java代码的代码)都以一种插件形式存在。这种方式具有其优点,但是它规定了一种工业插件开发环境-具有完整的工具,例如管理 组件、调试器支持,等等。幸好,Eclipse提供了这些功能,但是,具有这种级别支持的服务器端框架并不存在(据我所知)。

  注释3-Eclipse插件比较

  比较于Eclipse插件,我一直把该插件称作是轻量级的,但是你可能疑惑:凭什么说它们是轻量级的?其实,我使用术语"轻量级"术语主要是强调,实现一种基于插件的架构的主要优点是相当轻快和简单的。

  Eclipse工程基于一种具有工业强度的插件架构。因此,我认为,把稍微扩展Spring框架功能的插件架构与一种具有丰富特征的插件实现进行比较是很有价值的。

  多个类加载器支持

  Eclipse工程具有一种复杂的类加载模型,这区别于(但非完全不同于)一种应用程序服务器的使用类加载器层次的方式。既然Eclipse鼓励第三方进行插件开发,那么很可能存在具体类的命名和版本冲突问题。

  通过不支持同一个类的多个版本,轻量级方法则可以完全避免这个问题。例如,对于我所工作的应用程序来说,这就是一种合理的约束,因为我们主要使用插件来提供一种可信的升级功能。我们只是或多或少控制我们想使用哪些版本和jar文件;因此,我们不需要多个类加载器支持。

  Manifest和其它Meta信息

  Eclipse插件提供了一种详细的manifest-它负责不仅提供有关一个插件扩展了哪些扩展点的信息而且还提供有关它如何依赖于其它插件的信 息。在运行时刻,你可以浏览该插件仓库以发现插件并且遍历它们的依赖性。Eclipse鼓励使用一种"懒惰式"插件加载模型。当实现一个扩展点时,你必须 显式地查找扩展它的那个插件;并且,典型地,你仅加载你需要的那些插件。这种方案减少了启动时间并且能够防止因加载不用的对象而造成资源浪费。

  Meta信息也是很重要的,Eclipse可以使用它来强制实现你的声明。Eclipse能够通知你有关丢失的相关性信息,告诉你使用相同的扩展点时何时你有太多或太少的插件,等等。

  借助于轻量级插件,你不必拥有一个正常的manifest,它允许你以编程方式存取你依赖的内容,而且所有的你的插件在运行时刻加载,只是你必须自己来进行任何类型的检查。

  如果不使用一种显式的依赖性列表,那么,你必须或者把所有你对于第三方的依赖性打包到你的插件jar中或者假定一种第三方类的基本集合总是位于客户环境中。如果你忘记一些东西,那么你就会遇到典型的挑战-确定丢失了哪些jar文件。

  在实践中,我经常需要决定是否要构建第三方库(如果它是顾客特定的jar,那么,典型情况下,我都把它们嵌入到这个插件jar中;如果我使用一种标准 的开源包,例如Jakarta commons,那么我会经常把它添加到核心应用程序中)。当轻量级过于"轻量级"并且你不能以一种ad-hoc方式来管理这些类型的依赖性时,作好调用 判断确实更为重要。

  至于其它的manifest相关数据,你可以通过提供一种轻量级的manifest来扩展PluginBeanFactoryPostProcessor.java以便跟踪具有插件的bean。你还可以使用该信息来强制实施一些约束规则。

  在服务器端开发中,构建的EJB、JSP/Servlet等组件并不是以真正插件的形式出现的,它们都要求真正的工作以便定义和归档一个扩展点。因此,把一切都当作一个插件可能会增加大量工作,因为大多数J2EE工程师可能对此不太熟悉。

  我总是试图把插件作为一种工具来实现特定领域的定制目的。同样地,利用Spring创建轻量级的与你的现有应用程序和技术无缝接合的插件就成为极其紧迫的任务。注意,在大部分情况下,你的应用程序通常是一种比较独立的Spring/J2EE应用程序。

  另外,你还应该熟悉一些可选择的插件框架。特别是那些经常在[url][/url]上发布的 Java插件框架工程。我从来没有使用这些框架来确定是否它能够与Spring良好协作以及你的应用程序需要花多大代价来采纳它。但是,如果基于 Spring的其它插件不太适合你的口味的话,那么这些框架可能是你的一个不错的选择。
阅读(2320) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~