基于Eclipse开拓轻量级Spring插件
副标题#e#
摘要 本文先容如安在Eclipse中操作Spring框架作为一个平台来建设轻量级的可以或许与你的现有J2EE应用措施无缝集成的插件。
一.引言
一般地,企业软件产物都要求在客户端具有定制本领,并且当客户必需修改焦点产物的设置来引入他们本身的定制时一般都要求举办更新操纵。借助于易于扩展和可进级的高度模块化的软件,插件技能可以或许提供针对这种典范场合下的完美办理方案。
注释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组件不能成为真正插件的精确来由。典范环境下,你应该找到一个需要进级的外部设置文件以便该平台可以或许感知新的代码。
组件必需包括足够信息以便在应用措施内部集成或设置其自己。
假如你仅是添加一些不需要与系统举办协作的代码(也就是说,松耦合的),那么自动发明就是很简朴的。真正的挑战是团结有细麋集成的自发明。
#p#副标题#e#
三.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文件:
#p#分页标题#e#
<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添加归去,而且把它本身绑定到系统中的适当位置。
五.利用轻量级插件举办开拓
#p#分页标题#e#
我经常受惊于大大都的架构师团队少少地思量开拓者可否容易地利用他们的框架。其实,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方法来打点这些范例的依赖性时,作好挪用判定确实更为重要。
#p#分页标题#e#
至于其它的manifest相关数据,你可以通过提供一种轻量级的manifest来扩展PluginBeanFactoryPostProcessor.java以便跟踪具有插件的bean。你还可以利用该信息来强制实施一些约束法则。
在处事器端开拓中,构建的EJB、JSP/Servlet等组件并不是以真正插件的形式呈现的,它们都要求真正的事情以便界说和归档一个扩展点。因此,把一切都看成一个插件大概会增加大量事情,因为大大都J2EE工程师大概对此不太熟悉。
我老是试图把插件作为一种东西来实现特定规模的定制目标。同样地,操作Spring建设轻量级的与你的现有应用措施和技能无缝接合的插件就成为极其紧要的任务。留意,在大部门环境下,你的应用措施凡是是一种较量独立的Spring/J2EE应用措施。
别的,你还应该熟悉一些可选择的插件框架。出格是那些常常在http://jpf.sourceforge.net/上宣布的Java插件框架工程。我从来没有利用这些框架来确定是否它可以或许与Spring精采协作以及你的应用措施需要花多大价钱来采用它。可是,假如基于Spring的其它插件不太适合你的口胃的话,那么这些框架大概是你的一个不错的选择。