java

位置:IT落伍者 >> java >> 浏览文章

基于Eclipse开发轻量级Spring插件


发布日期:2020年02月18日
 
基于Eclipse开发轻量级Spring插件

摘要 本文介绍如何在Eclipse中利用Spring框架作为一个平台来创建轻量级的能够与你的现有JEE应用程序无缝集成的插件

引言

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

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

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

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

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

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

注释JEE组件不是插件吗?

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

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

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

准备你的插件平台

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

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

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

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

Spring中的自发现功能

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

<import resource=classpath*:METAINF/services/*ctxxml />

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

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

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

<path id=classpath

<fileset dir=${basedir}/lib

<include name=**/*jar/>

</fileset>

</path>

<target name=startserver description=launches the server process

<java classname=platformbootstrapServer

<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 =

beanFactorygetBeanDefinition(extensionBeanName);

//在该bean定义中查找它的属性并且发现我们将修改的具体属性

MutablePropertyValues propValues = beanDefgetPropertyValues();

if ( !propValuescontains(propertyName))

throw new IllegalArgumentException(Cannot find property +

propertyName + in bean + extensionBeanName);

PropertyValue pv = propValuesgetPropertyValue(propertyName);

//取出值定义(在我们的情况下我们仅支持列表风格属性的更新)

Object prop = pvgetValue();

if ( !(prop instanceof List))

throw new IllegalArgumentException(Property + propertyName +

in extension bean +

extensionBeanName +

is not an instanceof List);

//把我们的bean参考添加到列表中当Spring创建对象

// 并且把它们绑定到一起时我们的bean现在准备好了

List l = (List) pvgetValue();

ladd(new RuntimeBeanReference(pluginBeanName));

}

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

<beans>

<bean id=extensionpointcrapstable

class=examplecrapsTable

initmethod=init

<property name=dice

<list>

</list>

</property>

<property name=players

<list>

</list>

</property>

</bean>

</beans>

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

<beans>

<bean id=realdice class=examplecrapsRealDice />

<bean class=platformspringPluginBeanFactoryPostProcessor

<property name=extensionBeanName

value=extensionpointcrapstable />

<property name=propertyName value=dice />

<property name=pluginBeanName value=realdice />

</bean>

</beans>

在这个Spring配置中定义了一个examplecrapsRealDice的实例然后它定义你的PluginBeanFactoryPostProcessor(它被配置以找到extensionpointcrapstable bean)这一实例还会把你的realdice bean添加到craps表的dice属性中

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

使用轻量级插件进行开发

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

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

一切都是插件吗?

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

注释Eclipse插件比较

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

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

多个类加载器支持

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

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

Manifest和其它Meta信息

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

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

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

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

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

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

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

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

另外你还应该熟悉一些可选择的插件框架特别是那些经常在http://jpfsourceforgenet/上发布的Java插件框架工程我从来没有使用这些框架来确定是否它能够与Spring良好协作以及你的应用程序需要花多大代价来采纳它但是如果基于Spring的其它插件不太适合你的口味的话那么这些框架可能是你的一个不错的选择

上一篇:Eclipse 3.2 Java开发新特征全面体验

下一篇:Eclipse中SVN的配置步骤和须知