Apache Camel源码研究之Language
Apache Camel通过
Language
将Expression
和Predicate
的构造操作合并在一起,减少了概念,也降低了扩展难度,是的整体架构更加清晰。
1. 概述
Apache Camel为了更好地实现EIP,在架构设计层面抽象出了Language
的概念来充当Expression
和Predicate
的工厂。而对于后两者,Camel内置了多种实现,并且留出了足够的扩展以满足使用者的自定义需求,本文我们将较为深入地研究Apache Camel在这方面的努力,以便未来更加自如地面对可能的业务需求。
2. 源码解读
本次的用例如下:
@Test
public void transform() throws Exception {CamelTestUtil.defaultPrepareTest2(new RouteBuilder() {@Overridepublic void configure() throws Exception {from("stream:in?promptMessage=Enter something:")// .transform().constant("fulizhe") // .to("stream:err");//}});
}
为了尽量减少外部变量以专注本次的主题,我们选择了最为简单的ConstantLanguage
,最终效果是不论我们输入任何内容,最终打印到控制台上的都是"fulizhe"。
以上用例启动时候得到如下堆栈:
综合前几篇博客的分析,从以上堆栈图中我们可以得出如下结论:
- 路由配置时候的
transform()
会导致TransformDefinition
实例的构建,而该实例类型间接继承自ProcessorDefinition<T>
,于是就有了上面截图中的createProcessor(RouteContext routeContext)
方法的回调。 createProcessor(RouteContext routeContext)
方法的调用最终将构建出TransformProcessor
实例介入到Camel Route的执行链条中,关于这一点之前的博客也已经论述过了。- 观察上一步中提到的
TransformProcessor
实例在Camel Route逻辑链条中的执行,首当其冲的就是对Expression.evaluate(Exchange exchange, Class<T> type)
回调,Apache Camel会将得到的值根据当前Exchange的MEP(Message Exchange Pattern)设置到对应Message的Body中。
在得出以上结论之后,现在的问题就成了Camel是如何找到相应Expression的实现类的?追寻这个问题的答案,我们找到了ExpressionDefinition.createExpression()
方法:
首先我们来看看这个
ExpressionDefinition
类。我们意外地发现该类直接实现了接口Expression
,Predicate
。相较于我们之前研究的ProcessorDefinition<T>
只用于初始化操作,ExpressionDefinition
类同时还承担了运行时Expression
,Predicate
对内的统一门面,也就是说,Apache Camel提供的Expression
,Predicate
能力,是通过继承自该类来完成的,这一点从以下继承链可见一斑。
从以上继承链中,我们不仅可以看到耳熟能详的constant(对应ConstantExpression
),simple(对应SimpleExpression
),method(对应MethodCallExpression
/BeanExpression
)之外,还有其他多达十余种实现。正所谓的"总有一款适合你!"遵循本文最开始的用例,我们将目光集中到
ConstantExpression
类上,再结合下面贴出的ExpressionDefinition.createExpression()
方法实现,应该可以对于问题获得一个较为清晰的答案:// ExpressionDefinition.createExpression() public Expression createExpression(CamelContext camelContext) {if (getExpressionValue() == null) {if (getExpressionType() != null) {setExpressionValue(getExpressionType().createExpression(camelContext));} else if (getExpression() != null) {// 初次进入, 都会走这里ObjectHelper.notNull("language", getLanguage());// 核心, 其中的检索逻辑正是Language强大扩展性的来源, 也正式将Language和Expression关联上了; // 而这其中的检索逻辑正是依赖于CamelContext.resolveLanguage(String language)的实现, 具体分析参见下方// 而这里的getLanguage()方法正是检索的关键, 子类就是通过覆写该方法来达到自定义实现类的目的的. 例如ConstantExpression覆盖返回的就是 constant 。Language language = camelContext.resolveLanguage(getLanguage());if (language == null) {throw new NoSuchLanguageException(getLanguage());}String exp = getExpression();// should be true by defaultboolean isTrim = getTrim() == null || getTrim();// trim if configured to trimif (exp != null && isTrim) {exp = exp.trim();}// 这一步说明了, 我们可以使用外置的配置文件来存放我们的expression, 格式如下:// 1. resource:file:nameOfFile// 2. resource:classpath:nameOfFile// 3. resource:http:uri// resolve the expression as it may be an external script from the classpath/file etcexp = ResourceHelper.resolveOptionalExternalScript(camelContext, exp);// 在本例中就是回调ConstantLanguage实现自接口Language的createExpression(String expression)方法setExpressionValue(language.createExpression(exp));// 此处提供了 AfterPropertiesConfigured 接口实现的回调, Expression的实现类(正如上面所论述的, 一般都是ExpressionDefinition)可以通过实现该接口来完成更多的配置configureExpression(camelContext, getExpressionValue());}}return getExpressionValue(); }
通过以上分析,现在最后的疑问就是Language实现类的检索了,追寻堆栈,我们最终找到了DefaultCamelContext
类。
// DefaultCamelContext.resolveLanguage()public Language resolveLanguage(String language) {Language answer;synchronized (languages) {answer = languages.get(language);// check if the language is singleton, if so return the shared instanceif (answer instanceof IsSingleton) {boolean singleton = ((IsSingleton) answer).isSingleton();if (singleton) {return answer;}}// 核心, 这里正是决定从何处检索相应的Language实现的关键// 默认的实现者是DefaultLanguageResolver// 其会在 META-INF/services/org/apache/camel/language/ 下查找相应文件中注册的Language实现类// 另外一个关于 META-INF/services/org/apache/camel/language/resolver/ 目录就交给读者自己去探索了// language not known or not singleton, then use resolveranswer = getLanguageResolver().resolveLanguage(language, this);// inject CamelContext if awareif (answer != null) {if (answer instanceof CamelContextAware) {((CamelContextAware) answer).setCamelContext(this);}if (answer instanceof Service) {try {startService((Service) answer);} catch (Exception e) {throw ObjectHelper.wrapRuntimeCamelException(e);}}languages.put(language, answer);}}return answer;}
通过以上源码,我们可以得出结论:
- Apache Camel在检索相应
Expressison
实现类的时候,会从META-INF/services/org/apache/camel/language/
下查找相应文件。 - 而这个文件名正是由继承自
ExpressionDefinition
类的子类,通过覆写基类的getLanguage()
方法来提供的。例如ConstantExpression.getLanguage()
返回的constant
字符串。 - 遵循以上分析,我们可以在camel-core-xxx.jar找到如下内容:
- 可以看出,camel-core提供了有限个数的expression实现,其他诸如Groovy,JavaScript,SpEL等等都只是提供了相应的契约,而并没有将实现也硬编码在camel-core内部,毕竟这将引入过多的依赖,得不偿失。而我们在使用相关的expression扩展的时候,就需要引入对应的依赖了。例如使用xquery(
XQueryExpression
)时所对应的camel-saxon。
3. 自定义扩展
看到了Apache Camel提供如此多的扩展,肯定有不少小伙伴跃跃欲试想要自己实现一个来玩玩,这对于增加对Apache Camel了解都是大有裨益的。而对于这一块,Apache Camel作为一个极其活跃的社区,当然也是最大化地降低了这方面的门槛 —— 这就是LanguageExpression
。
通过LanguageExpression
,只需要简单地几步就能实现自己的Language并无缝并入到Camel的执行链条中。
// 定义ExpressionDefinition子类
public class LqExpression extends ExpressionDefinition {public LqExpression() {}public LqExpression(String expression) {super(expression);}public String getLanguage() {return "lq";}
}// 定义Language实现类
public class LqLanguage extends LanguageSupport implements Language {@Overridepublic Expression createExpression(String expression) {// 这里直接借用了Camel内置的 simple expressionreturn SimpleBuilder.simple(expression);}@Overridepublic Predicate createPredicate(String expression) {return ExpressionToPredicateAdapter.toPredicate(createExpression(expression));}
}// 注册
1. 创建 META-INF/services/org/apache/camel/language/lq 文件
2. 以上文件中填入: class=x.y.z.language.LqLanguage// 测试用例
CamelTestUtil.defaultPrepareTest2(new RouteBuilder() {@Overridepublic void configure() throws Exception {from("stream:in?promptMessage=Enter something:")//.routeId("customLanuage")// .transform().language("lq", "${body.length}") ////.transform().language("simple", "${body.length}") ////.filter("lq", "${body.length} > 3")////.setBody(language("lq", "${body.length}").to("stream:err");//}
});
4. 总结
以本文研究的constant为例:
ConstantExpression
。ConstantExpression
(继承自ExpressionDefinition
, 间接实现Expression
,Predicate
) ,其提供给DefaultCamelContext
检索相应Language
实现类的线索,也就是覆写自基类的getLanguage()
方法所返回的"constant",其指向的是META-INF/services/org/apache/camel/language/constant
文件。ConstantLanguage
。而在上述META-INF/services/org/apache/camel/language/constant
文件中所记录的正是ConstantLanguage
类,该类直接实现Language
接口, 进而提供真正的Expression
,Predicate
实现类。
5. Links
- 《Camel In Action》 P461
- 《Apache Camel Developer’s Cookbook》P109
- Languages - Office Site
- Expression - Office Site
Apache Camel源码研究之Language相关推荐
- Apache Camel源码研究之Rest
本文以Camel2.24.3 + SpringBoot2.x 为基础简单解读Camel中的Rest组件的源码级实现逻辑. 0. 目录 1. 前言 2. 源码解读 2.1 启动时 2.1.1 `Rest ...
- Apache Camel源码研究之Intercept
Intercept作为一个极其强大的扩展机制,其理念几乎存在于所有知名框架中,诸如Spring,Mybatis,Tomcat等等都无一例外地提供了相应的支持,在保持自身框架本身整洁的同时,实现对各类业 ...
- Apache Jackrabbit源码研究(五)
上文最后提到jackrabbit的检索默认实现类QueryImpl,先熟悉一下该类的继承层次 QueryImpl继承自抽象类AbstractQueryImpl,而抽象类实现了Query接口(JCR的接 ...
- Apache Tika源码研究(七)
tika怎样加载Parser实现类的,怎样根据文档的mime类型调用相应的Parser实现类,本文接着分析 先熟悉一下tika的解析类的相关接口和类的UML模型: Parser接口的源码如下: /** ...
- WebRTC源码研究(4)web服务器工作原理和常用协议基础
文章目录 WebRTC源码研究(4)web服务器工作原理和常用协议基础 前言 做WebRTC 开发为啥要懂服务器开发知识 1. Web 服务器简介 2. Web 服务器的类型 3. Web 服务器的工 ...
- WebRTC源码研究(4)web服务器工作原理和常用协议基础(转载)
前言 前面3篇博客分别对WebRTC框架的介绍,WebRTC源码目录,WebRTC的运行机制进行了介绍,接下来讲解一点关于服务器原理的知识.后面博客会写关于WebRTC服务器相关的开发,目前git上面 ...
- 一起谈.NET技术,.NET Framework源码研究系列之---万法归宗Object
经过前面三篇关于.NET Framework源码研究系列的随笔,相信大家都发现其实.NET Framework的实现其实并不复杂,也许跟我们自己做的项目开发差不多.本人也是这样的看法.不过,经过仔细深 ...
- Nginx源码研究之nginx限流模块详解
这篇文章主要介绍了Nginx源码研究之nginx限流模块详解,小编觉得挺不错的,现在分享给大家,也给大家做个参考.一起跟随小编过来看看吧 高并发系统有三把利器:缓存.降级和限流: 限流的目的是通过对并 ...
- 转载一篇《Redis源码研究—哈希表》重点是如何重新哈希
<Redis源码研究-哈希表>来自:董的博客 网址:http://dongxicheng.org/nosql/redis-code-hashtable/ 转载于:https://www.c ...
最新文章
- 组策略之账户安全设置
- spring mvc 接入cas登录
- 数学建模大赛赛题解析:Mathorcup高校数学建模挑战赛-基于收得率预测模型的转炉炼钢的成本优化
- 使用layui框架时,在input文本框中显示当前页面时间的方法
- Nulgrind:最小的Valgrind工具
- 注册表操作命令reg
- windows7安装cuda10.2
- 推荐一个开源好用的ER图、流程图作图软件-draw.io
- 汇编语言与微机接口——交通灯设计
- FPGA的Zynq 7000学习--基于黑金AX7010开发板的Hello World 实验
- 读书寄语:安忍的智慧
- 仿写“跳一跳”微信小游戏
- Web报表系统葡萄城报表:报表设计
- Python使用Scrapy爬虫框架全站爬取图片并保存本地(@妹子图@)
- 如何开发微信小程序呢
- 【Unity游戏开发笔记】手游-涂鸦弹跳开发分析
- YOLO系列文章汇总
- FormData的用途
- matlab 蒙特卡洛法书籍,[转载]matlab的蒙特卡洛算法
- JAVAWEB开发之工作流详解(一)——Activiti的环境搭建、插件安装、核心API
热门文章
- 在CSS中实现height:100%-200px; width:100%-200px,既长度或宽度百分百减去200px
- 基于机智云平台的泵站智能巡检系统
- 微软开源的浏览器自动化工具-Playwright
- html wmf 不显示,在Word、Excel、PPT中不能显示WMF图片
- 盒子模型(CSS重点)
- 小米平板android版本,想要翻身还需努力 小米平板2安卓版评测
- openssl version mismatch. built against 30000010, you have 30100000
- vps虚拟服务器主机,vps虚拟服务器主机
- Wireshark lua 插件提取PCAP报文中文件,图片,视频
- 上大学之前,一定要明白这10大潜规则,你会少走很多人生弯路