最近,我重新发现了我遗忘的JBoss Fuse功能,并且我认为其他人可能会从此提醒中受益 。
这篇文章将重点放在JBoss Fuse和Fabric8上,但所有正在寻找最小侵入性方法来为其静态配置文件添加一定程度的动态支持的开发人员也可能会对此感兴趣。
在OSGi和Fabric8中进行动态配置的想法
OSGi框架因其类加载行为而经常被人们记住。 但其中一部分还定义了框架必须实现的其他概念和功能。 其中之一是ConfigAdmin 。
ConfigAdmin是一项服务,用于定义逻辑上绑定到部署单元的一组外部化的属性文件。
此外部属性文件的生命周期与OSGi捆绑软件生命周期链接: 如果您修改外部属性文件,则将通知您的捆绑软件 。 根据您对捆绑软件进行编码的方式,您可以决定对通知做出反应,并以编程方式或通过不同的帮助程序框架(例如,蓝图) 来调用使用新配置的代码。
这种机制既方便又强大,并且所有使用OSGi的开发人员都熟悉它。
Fabric8建立在ConfigAdmin的思想之上,并对其进行了扩展 。
通过其配置功能,Fabric8定义了概要文件的概念,该概要文件封装了部署单元和配置。 它在普通OSGi的基础上增加了一些功能,并且允许管理任何种类的部署单元,不仅是OSGi捆绑软件,还有任何种类的配置或静态文件。
如果查看官方文档,则会找到Fabric8层提供的“扩展”列表,并且您会发现它们主要分为两类: Url Handlers和Property Resolvers 。
我建议对本技术感兴趣的每个人都仔细阅读文档。 但是要提供一个简短的摘要和一个简短的示例,请假设您的Fabric概要文件具有使用特定占位符在运行时解析某些值的功能。 例如
# sample url handler usage, ResourceName is a filename relative to the namespace of the containing Profile:
profile:ResourceName
# sample property handler, the value is read at deploy time, from the Apache Zookeeper distributed registry that is published when you run JBoss Fuse
${zk:/fabric/registry/containers/config/ContainerName/Property}
开箱即用有多个处理程序,涵盖了开发人员认为最常见的用例:Zookeeper,配置文件,蓝图,Spring,系统属性,受管端口等。
而且,您可能还想扩展定义自己的扩展的机制:例如,您可能想对存储在某个系统上的性能指标做出反应,您可以编写一个扩展,并使用其语法约定从系统中注入值。
所有这些功能的限制:静态配置文件
我上面介绍的功能令人兴奋且强大,但是它们有一个隐含的限制 : 它们仅适用于.properties文件或Fabric可以识别的文件 。
这意味着,如果您必须管理Fabric Profile,OSGi属性或与之交互的其他特定技术(例如Camel),则可以使用这些功能,但是对于Fabric-Unaware而言 , 它们没有启用 。
想象一下,您有读取.xml
配置文件的自定义代码。 并想象您的代码没有引用任何Fabric对象或服务。
您的代码将按原样处理该.xml
文件。 标记或路径不会有任何魔术替代,因为尽管您在Fabric内部运行,但您并未使用任何直接支持的技术,也未在通知Fabric时可能需要其服务。
要解决此问题,您有3种选择 :
- 您为Fabric 编写了扩展 ,以处理和识别您的静态资源,并将动态替换委派给框架代码。
- 您可以更改部署单元中包含的代码 ,而不是直接消耗静态资源,而是要求Fabric服务为您内插它们
- *您使用
mvel:
网址处理程序(并避免触摸任何其他代码!)
什么是MVEL?
MVEL实际上是一种编程语言 : https : //en.wikipedia.org/wiki/MVEL 。 特别是,它也是脚本语言 ,您可以从源代码直接运行而跳过编译步骤。
实际上,它具有多个特定的特性,可能使其很有趣地嵌入到另一个应用程序中,并可以在运行时用于定义新的行为。 例如,由于所有这些原因,它也是JBoss Drools项目支持的语言之一,可与您可能希望在运行时定义或修改的业务规则一起使用。
为什么对我们有用? 主要有两个原因:
- 它可以很好地用作模板语言
- Fabric8已经有一个
mvel:
url处理程序,它隐式地还充当资源处理程序!
模板语言
模板语言是那些语言系列(通常是领域特定语言),您可以在其中更改按原样阅读的文本的静态部分和将在解析时处理的动态指令 。 我可能以更复杂的方式说出了我上面已经介绍过的相同想法:您可以在文本中包含标记,这些标记将按照特定的约定进行翻译。
这听起来完全像我们上面介绍的处理程序所提供的功能。 有一个重要的区别:虽然那些是上下文特定的处理程序,但MVEL是一种通用技术。 因此,不要指望它对Zookeeper或Fabric配置文件有任何了解,而是希望它能够支持通用编程语言概念,例如循环,代码调用,反射等。
面料支持它!
可在以下位置找到对Fabric中支持的参考: http : //fabric8.io/gitbook/urlHandlers.html
但是,让我添加一个实现该功能的原始代码的一段,因为即使在JBoss Fuse的上下文之外,这也是您可能会发现此方法有趣的部分: https : //github.com/fabric8io/fabric8/blob/1 .x / fabric / fabric-core / src / main / java / io / fabric8 / service / MvelUrlHandler.java#L115-L126
public InputStream getInputStream() throws IOException {
assertValid();
String path = url.getPath();
URL url = new URL(path);
CompiledTemplate compiledTemplate = TemplateCompiler.compileTemplate(url.openStream());
Map<String, Object> data = new HashMap<String, Object>();
Profile overlayProfile = fabricService.get().getCurrentContainer().getOverlayProfile();
data.put(“profile”, Profiles.getEffectiveProfile(fabricService.get(), overlayProfile));
data.put(“runtime”, runtimeProperties.get());
String content = TemplateRuntime.execute(compiledTemplate, data).toString();
return new ByteArrayInputStream(content.getBytes());
}
这里发生了什么事?
首先,由于未在代码段中显示,请记住这是一个url处理程序。 这意味着针对通过特定uri引用的文件触发了行为获取。 在这种情况下,它是mvel:
。 例如,有效路径可能是mvel:jetty.xml
。
要注意的另一个有趣且相对简单的事情是与MVEL解释器的交互。 像大多数模板技术一样,即使是您可以自己实现的最简单的技术,也通常具有:
- 引擎/编译器,这里是
TemplateCompiler
- 包含模板的变量,这里是
url
- 代表上下文的变量,即要向引擎公开的一组变量,此处为
data
将它们放在一起,要求引擎来完成它的工作,这里使用TemplateRuntime.execute(...)
而您在输出中得到的是一个静态String。 不再使用模板说明,而是应用了模板定义的所有逻辑,并最终从上下文中获取了一些附加的输入值。
一个例子
我希望我的解释足够简单,但是可能有一个例子是表达该概念的最佳方法。
让我们使用JBoss Fuse default.profile
包含的jetty.xml
,它是JBoss Fuse不会作为任何特殊文件处理的静态资源,因此它不提供任何替代功能。
我将在这里展示MVEL集成的两个方面:从上下文变量中读取一些值,并展示如何使用编程逻辑(这里只是2个整数的和):
<Property name="jetty.port" default="@{ Integer.valueOf( profile.configurations['org.ops4j.pax.web']['org.osgi.service.http.port'] ) + 10 }"/>
我们正在修改Jetty端口的默认值,其初始值来自“配置文件”上下文变量,该变量是可识别结构的对象,可以访问其余配置:
profile.configurations['org.ops4j.pax.web']['org.osgi.service.http.port']
我们将其从String显式转换为Integer:
Integer.valueOf( ... )
并将静态值10
添加到返回值中:
.. + 10
让我们保存文件,停止我们的fuse
实例。 重新启动它并重新创建测试结构:
# in Fuse CLI shell
shutdown -f
# in bash shell
rm -rf data instances
bin/fuse
# in Fuse CLI shell
fabric:create --wait-for-provisioning
只需等待并监视日志,然后…… 哦,哦。 一个错误! 发生了什么?
这是错误:
2015-10-05 12:00:10,005 | ERROR | pool-7-thread-1 | Activator | 102 - org.ops4j.pax.web.pax-web-runtime - 3.2.5 | Unable to start pax web server: Exception while starting Jetty
java.lang.RuntimeException: Exception while starting Jetty
at org.ops4j.pax.web.service.jetty.internal.JettyServerImpl.start(JettyServerImpl.java:143)[103:org.ops4j.pax.web.pax-web-jetty:3.2.5]
…
Caused by: java.lang.reflect.InvocationTargetException
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)[:1.7.0_76]
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:57)[:1.7.0_76]
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)[:1.7.0_76]
at java.lang.reflect.Constructor.newInstance(Constructor.java:526)[:1.7.0_76]
at org.eclipse.jetty.xml.XmlConfiguration$JettyXmlConfiguration.set(XmlConfiguration.java:572)[96:org.eclipse.jetty.aggregate.jetty-all-server:8.1.17.v20150415]
at org.eclipse.jetty.xml.XmlConfiguration$JettyXmlConfiguration.configure(XmlConfiguration.java:396)[96:org.eclipse.jetty.aggregate.jetty-all-server:8.1.17.v20150415]
…
Caused by: java.lang.NumberFormatException: For input string: “@{profile.configurations[’org.ops4j.pax.web'][‘org.osgi.service.http.port’] + 1}”
at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)[:1.7.0_76]
at java.lang.Integer.parseInt(Integer.java:492)[:1.7.0_76]
at java.lang.Integer.<init>(Integer.java:677)[:1.7.0_76]
… 29 more
如果您注意到该错误消息,则说明我们的模板片段无法转换为Number 。
为什么我们的模板代码片段会在第一个实例中显示? 模板引擎应该完成其工作的一部分,并给我们返回一个静态String,而无需任何对模板指令的引用!
我已经故意向您显示此错误,以坚持我上面描述的概念,但是在初审中可能不会被理解。
Fabric中的MVEL支持被实现为url处理程序。
到目前为止, 我们只是修改了静态资源文件的内容,但没有向Fabric提供任何提示,表明我们希望将该文件作为mvel模板进行处理。
怎么做?
使用正确的uri引用同一文件只是一个问题。
因此,修改文件default.profile/org.ops4j.pax.web.properties
,该文件位于默认Fabric Profile中,您可以在其中定义哪个静态文件包含Jetty配置:
# change it from org.ops4j.pax.web.config.url=profile:jetty.xml to
org.ops4j.pax.web.config.url=mvel:profile:jetty.xml
现在,再次停止实例,删除Fabric配置文件,重新创建Fabric,并注意您的Jetty实例如何正确运行。
我们可以通过以下方式进行检查:
JBossFuse:karaf@root> config:list | grep org.osgi.service.http.port
org.osgi.service.http.port = 8181
从浏览器中,您可以验证是否可以通过端口8191
访问部署在Jetty顶部的Hawtio(JBoss Fuse Web控制台): http:// localhost:8191 / hawtio