TL; DR
- 将Java静态调用公开为Karaf Shell本机命令
- 在部署时覆盖OSGi标头
- 在使用OSGi片段部署时间后覆盖OSGi标头
将Java静态调用公开为Karaf Shell本机命令
作为必须与支持人员和客户进行协作的软件工程师的一部分,我经常发现自己需要从无法访问的系统中提取其他信息。 通常适用于所有软件的方法通常是提取日志,调用交互式命令以获得特定的输出,或者在最复杂的情况下,部署一些PoC单元来验证特定行为。
JBoss Fuse和Karaf所基于的平台在公开所有这些数据方面做得非常好。
你有:
- 大量日志并与Log4j集成
- jmx操作的详尽列表(您最终可以使用jolokia通过http调用)
- 大量的Shell命令
但是有时候这还不够。 如果您看过我以前有关如何在JBoss Fuse上使用Byteman的文章,则可以想象所有其他情况:
- 您需要打印代码中未记录或未返回的值
- 您可能需要短路一些逻辑才能命中代码的特定执行分支
- 您想注入根本不存在的代码行
Byteman仍然是一个很好的选择,但是Karaf具有一种可用于运行自定义代码的工具 。
Karaf,允许您直接在其shell中编写代码; 并允许您将这些代码位记录为可以重新调用的宏。 这个宏看起来像一个原生的Karaf shell命令!
让我们看一个我必须实现的真实示例:
验证运行我的JBoss Fuse实例的jvm是否正在按预期解析特定的DNS。
标准JDK具有可用来解析dns名称的方法: InetAddress.gettAllByName(String)
由于该命令非常简单,这意味着它不需要复杂或结构化的输入,我想我可以将其变成易于重用的命令:
# add all public static methods on a java class as commands to the namespace "my_context":
# bundle 0 is because system libs are served by that bundle classloader
addcommand my_context (($.context bundle 0) loadClass java.net.InetAddress)
该时髦的行用以下方式解释:
-
addcommand
是接受新命令的karaf shell功能 -
my_context
是您要附加命令的名称空间/前缀。 就我而言,“ dns”将成为一个好的名称空间。($.context bundle 0)
调用Java代码。 特别是,我们正在调用$.context
实例,该实例是Karaf shell公开的内置实例,用于暴露OSGi框架,其类型为org.apache.felix.framework.BundleContextImpl
,并且正在调用其方法,称为bundle
pass参数0
代表负责加载JDK类的OSGi类加载器的ID。 该调用返回org.apache.felix.framework.Felix
的实例,可用于加载所需的特定类定义,即java.net.InetAddress
。
就像内联注释所说的那样,调用addcommand
公开该类上的所有公共静态方法 。 因此,现在允许我们调用这些方法,尤其是可以解析dns条目的方法:
JBossFuse:karaf@root> my_context:getAllByName "www.google.com"
www.google.com/74.125.232.146
www.google.com/74.125.232.145
www.google.com/74.125.232.148
www.google.com/74.125.232.144
www.google.com/74.125.232.147
www.google.com/2a00:1450:4002:804:0:0:0:1014
此功能在Karaf文档页面上进行了描述。
在部署时覆盖OSGi标头
如果您与Karaf合作,那么您正在使用OSGi,喜欢或讨厌它 。 每个OSGi工作流程中的一个典型步骤是播放(或战斗) OSGi标头 。 如果您完全控制项目,则这可能会或多或少容易,具体取决于部署单元之间的关系。 请参阅Christian Posta的帖子 ,以瞥见一些不明显的例子。
在这些情况下,一种非常典型的情况是必须使用捆绑包,您自己或他人的捆绑包 ,并且捆绑包头不正确 。 最终要做的通常是重新打包该捆绑包,以便您可以更改其MANIFEST
的内容 ,以添加所需的OSGi标头。
Karaf在这方面具有一种设施,称为wrap
协议。 您可能已经知道,这是在Karaf上部署非捆绑jar的快捷方式,但实际上不仅限于此 。
顾名思义,它真正要做的就是包装。 但是它可以同时包裹非捆绑包和捆绑包! 这意味着我们还可以使用它来更改我们将要安装的已打包捆绑包的元数据。
让我们举个例子,再次从现实生活中获得经验。 Apache HttpClient并非完全OSGi友好。 我们可以使用wrap:
协议将其安装在Karaf上,并导出所有软件包 。
JBossFuse:karaf@root> install -s 'mvn:org.apache.httpcomponents/httpclient/4.2.5'
Bundle ID: 257
JBossFuse:karaf@root> exports | grep -i 257257 No active exported packages. This command only works on started bundles, use osgi:headers instead
JBossFuse:karaf@root> install -s 'wrap:mvn:org.apache.httpcomponents/httpclient/\ 4.2.5$Export-Package=*; version=4.2.5'
Bundle ID: 259
JBossFuse:karaf@root> exports | grep -i 259259 org.apache.http.client.entity; version=4.2.5259 org.apache.http.conn.scheme; version=4.2.5259 org.apache.http.conn.params; version=4.2.5259 org.apache.http.cookie.params; version=4.2.5
...
我们可以看到它也适用于普通捆绑包 :
JBossFuse:karaf@root> la -l | grep -i camel-core
[ 142] [Active ] [ ] [ ] [ 50] mvn:org.apache.camel/camel-core/2.12.0.redhat-610379
JBossFuse:karaf@root> install -s 'wrap:mvn:org.apache.camel/camel-core/2.12.0.redhat-610379\
$overwrite=merge&Bundle-SymbolicName=paolo-s-hack&Export-Package=*; version=1.0.1'
Bundle ID: 269JBossFuse:karaf@root> headers 269camel-core (269)
----------------
...Bundle-Vendor = Red Hat, Inc.
Bundle-Activator = org.apache.camel.impl.osgi.Activator
Bundle-Name = camel-core
Bundle-DocURL = http://redhat.com
Bundle-Description = The Core Camel Java DSL based routerBundle-SymbolicName = paolo-s-hackBundle-Version = 2.12.0.redhat-610379
Bundle-License = http://www.apache.org/licenses/LICENSE-2.0.txt
Bundle-ManifestVersion = 2...Export-Package = org.apache.camel.fabric;uses:="org.apache.camel.util,org.apache.camel.model,org.apache.camel,org.apache.camel.processor,org.apache.camel.api.management,org.apache.camel.support,org.apache.camel.spi";version=1.0.1,...
在哪里可以看到Bundle-SymbolicName
并且导出的软件包的版本带有我设置的值。
同样,该功能在Karaf文档中进行了描述,您可能会发现包装协议参考非常有用。
在使用OSGi片段部署时间后覆盖OSGi标头
最后一个技巧很强大,但是如果您不想冒险让一个类加载器暴露一半的类,而让另一个类加载器暴露剩余的类(那些您可能已在重写的Export
添加的包),则可能需要您删除原始包。一。
实际上,有一种更好的方法来覆盖OSGi标头,它直接来自OSGi标准功能: OSGi Fragments 。
如果您不熟悉此概念,则直接取自OSGi Wiki的定义是:
捆绑包片段(或简称为片段)是一个捆绑包,其内容可用于另一个捆绑包(片段主机)。 重要的是,片段共享其父捆绑的类加载器。
该页面还提供了有关我将要描述的内容的进一步提示:
有时,片段用于“修补”现有的捆绑包。
我们可以使用此策略来:
- 在目标包的类路径中注入.jars
- 改变目标包的标题
我用第一种情况来修复配置错误的捆绑包,该捆绑包正在寻找一个不包含它的xml配置描述符,并且我提供了部署包含此内容的轻型Fragment Bundle。
但是我想在这里向您展示的用例是对在JBoss Fuse / Karaf上部署Byteman的方式的一种改进 。
如果你还记得我以前的帖子 ,因为Byteman类需要可从所有其他部署包,并可能需要提供访问每一个类,我们不得不Byteman包添加到org.osgi.framework.bootdelegation
属性,指示OSGi框架通过虚拟系统捆绑包(id = 0)公开列出的软件包 。
您可以使用headers 0
来验证当前正在使用的内容,因为它是jdk扩展和框架类的一长串,所以这里不包括输出。
如果添加我的包org.jboss.byteman.rule,org.jboss.byteman.rule.exception
,即使这些包也会在该命令的输出中列出。
该解决方案的问题在于,这是引导时间属性 。 如果要使用Byteman操作已经运行的实例的字节码,则必须在编辑此属性后重新启动它。
OSGi片段可以帮助您, 避免在引导时进行预配置。
我们可以构建一个没有实际内容的自定义空捆绑包,该捆绑包将附加到系统捆绑包并扩展其服务的包列表。
<Export-Package>org.jboss.byteman.rule,org.jboss.byteman.rule.exception
</Export-Package>
<Fragment-Host>system.bundle; extension:=framework
</Fragment-Host>
这是maven-bundle-plugin插件配置的节选,请参见此处以了解完整的Maven项目 ,尽管该项目实际上只是pom.xml
30行:
JBossFuse:karaf@root> install -s mvn:test/byteman-fragment/1.0-SNAPSHOT
一旦有了该配置,就可以使用Byteman,例如,在java.lang.String
默认构造函数中插入一行。
# find your Fuse process id
PROCESS_ID=$(ps aux | grep karaf | grep -v grep | cut -d ' ' -f2)# navigate to the folder where you have extracted Byteman
cd /data/software/redhat/utils/byteman/byteman-download-2.2.0.1/# export Byteman env variable:
export BYTEMAN_HOME=$(pwd)
cd bin/# attach Byteman to Fabric8 process, no output expected unless you enable those verbose flags
sh bminstall.sh -b -Dorg.jboss.byteman.transform.all $PROCESS_ID
# add these flags if you have any kind of problem and what to see what's going on: -Dorg.jboss.byteman.debug -Dorg.jboss.byteman.verbose# install our Byteman custom rule, we are passing it directly inline with some bash trick
sh bmsubmit.sh /dev/stdin <<OPTS# smoke test rule that uses also a custom output file
RULE DNS StringSmokeTest
CLASS java.lang.String
METHOD <init>()
AT ENTRY
IF TRUE
DO traceln(" works: " );
traceOpen("PAOLO", "/tmp/byteman.txt");
traceln("PAOLO", " works in files too " );
traceClose("PAOLO");
ENDRULEOPTS
现在,要验证Byteman是否正常运行,我们只需在Karaf shell中调用java.lang.String
构造函数即可:
JBossFuse:karaf@root> new java.lang.Stringworks:
按照我们的规则,您还将在/tmp/byteman.txt
看到内容
第三个技巧的灵感来自OSGi Wiki和Spring的这个有趣的页面 。
翻译自: https://www.javacodegeeks.com/2015/02/jboss-fuse-less-known-trick.html