Apache Camel通过LanguageExpressionPredicate的构造操作合并在一起,减少了概念,也降低了扩展难度,是的整体架构更加清晰。

1. 概述

Apache Camel为了更好地实现EIP,在架构设计层面抽象出了Language的概念来充当ExpressionPredicate的工厂。而对于后两者,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"。

以上用例启动时候得到如下堆栈:

综合前几篇博客的分析,从以上堆栈图中我们可以得出如下结论:

  1. 路由配置时候的transform()会导致TransformDefinition实例的构建,而该实例类型间接继承自ProcessorDefinition<T>,于是就有了上面截图中的createProcessor(RouteContext routeContext)方法的回调。
  2. createProcessor(RouteContext routeContext)方法的调用最终将构建出TransformProcessor实例介入到Camel Route的执行链条中,关于这一点之前的博客也已经论述过了。
  3. 观察上一步中提到的TransformProcessor实例在Camel Route逻辑链条中的执行,首当其冲的就是对Expression.evaluate(Exchange exchange, Class<T> type)回调,Apache Camel会将得到的值根据当前Exchange的MEP(Message Exchange Pattern)设置到对应Message的Body中。

在得出以上结论之后,现在的问题就成了Camel是如何找到相应Expression的实现类的?追寻这个问题的答案,我们找到了ExpressionDefinition.createExpression()方法:

  1. 首先我们来看看这个ExpressionDefinition类。我们意外地发现该类直接实现了接口 Expression, Predicate。相较于我们之前研究的ProcessorDefinition<T>只用于初始化操作,ExpressionDefinition类同时还承担了运行时Expression, Predicate对内的统一门面,也就是说,Apache Camel提供的Expression, Predicate能力,是通过继承自该类来完成的,这一点从以下继承链可见一斑。

    从以上继承链中,我们不仅可以看到耳熟能详的constant(对应ConstantExpression),simple(对应SimpleExpression),method(对应MethodCallExpression/BeanExpression)之外,还有其他多达十余种实现。正所谓的"总有一款适合你!"

  2. 遵循本文最开始的用例,我们将目光集中到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;}

通过以上源码,我们可以得出结论:

  1. Apache Camel在检索相应Expressison实现类的时候,会从META-INF/services/org/apache/camel/language/下查找相应文件。
  2. 而这个文件名正是由继承自ExpressionDefinition类的子类,通过覆写基类的getLanguage()方法来提供的。例如ConstantExpression.getLanguage()返回的constant字符串。
  3. 遵循以上分析,我们可以在camel-core-xxx.jar找到如下内容:
  4. 可以看出,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为例:

  1. ConstantExpressionConstantExpression(继承自ExpressionDefinition, 间接实现Expression, Predicate) ,其提供给DefaultCamelContext检索相应Language实现类的线索,也就是覆写自基类的getLanguage()方法所返回的"constant",其指向的是 META-INF/services/org/apache/camel/language/constant文件。
  2. ConstantLanguage。而在上述 META-INF/services/org/apache/camel/language/constant文件中所记录的正是 ConstantLanguage类,该类直接实现Language接口, 进而提供真正的Expression, Predicate实现类。

5. Links

  1. 《Camel In Action》 P461
  2. 《Apache Camel Developer’s Cookbook》P109
  3. Languages - Office Site
  4. Expression - Office Site

Apache Camel源码研究之Language相关推荐

  1. Apache Camel源码研究之Rest

    本文以Camel2.24.3 + SpringBoot2.x 为基础简单解读Camel中的Rest组件的源码级实现逻辑. 0. 目录 1. 前言 2. 源码解读 2.1 启动时 2.1.1 `Rest ...

  2. Apache Camel源码研究之Intercept

    Intercept作为一个极其强大的扩展机制,其理念几乎存在于所有知名框架中,诸如Spring,Mybatis,Tomcat等等都无一例外地提供了相应的支持,在保持自身框架本身整洁的同时,实现对各类业 ...

  3. Apache Jackrabbit源码研究(五)

    上文最后提到jackrabbit的检索默认实现类QueryImpl,先熟悉一下该类的继承层次 QueryImpl继承自抽象类AbstractQueryImpl,而抽象类实现了Query接口(JCR的接 ...

  4. Apache Tika源码研究(七)

    tika怎样加载Parser实现类的,怎样根据文档的mime类型调用相应的Parser实现类,本文接着分析 先熟悉一下tika的解析类的相关接口和类的UML模型: Parser接口的源码如下: /** ...

  5. WebRTC源码研究(4)web服务器工作原理和常用协议基础

    文章目录 WebRTC源码研究(4)web服务器工作原理和常用协议基础 前言 做WebRTC 开发为啥要懂服务器开发知识 1. Web 服务器简介 2. Web 服务器的类型 3. Web 服务器的工 ...

  6. WebRTC源码研究(4)web服务器工作原理和常用协议基础(转载)

    前言 前面3篇博客分别对WebRTC框架的介绍,WebRTC源码目录,WebRTC的运行机制进行了介绍,接下来讲解一点关于服务器原理的知识.后面博客会写关于WebRTC服务器相关的开发,目前git上面 ...

  7. 一起谈.NET技术,.NET Framework源码研究系列之---万法归宗Object

    经过前面三篇关于.NET Framework源码研究系列的随笔,相信大家都发现其实.NET Framework的实现其实并不复杂,也许跟我们自己做的项目开发差不多.本人也是这样的看法.不过,经过仔细深 ...

  8. Nginx源码研究之nginx限流模块详解

    这篇文章主要介绍了Nginx源码研究之nginx限流模块详解,小编觉得挺不错的,现在分享给大家,也给大家做个参考.一起跟随小编过来看看吧 高并发系统有三把利器:缓存.降级和限流: 限流的目的是通过对并 ...

  9. 转载一篇《Redis源码研究—哈希表》重点是如何重新哈希

    <Redis源码研究-哈希表>来自:董的博客 网址:http://dongxicheng.org/nosql/redis-code-hashtable/ 转载于:https://www.c ...

最新文章

  1. 组策略之账户安全设置
  2. spring mvc 接入cas登录
  3. 数学建模大赛赛题解析:Mathorcup高校数学建模挑战赛-基于收得率预测模型的转炉炼钢的成本优化
  4. 使用layui框架时,在input文本框中显示当前页面时间的方法
  5. Nulgrind:最小的Valgrind工具
  6. 注册表操作命令reg
  7. windows7安装cuda10.2
  8. 推荐一个开源好用的ER图、流程图作图软件-draw.io
  9. 汇编语言与微机接口——交通灯设计
  10. FPGA的Zynq 7000学习--基于黑金AX7010开发板的Hello World 实验
  11. 读书寄语:安忍的智慧
  12. 仿写“跳一跳”微信小游戏
  13. Web报表系统葡萄城报表:报表设计
  14. Python使用Scrapy爬虫框架全站爬取图片并保存本地(@妹子图@)
  15. 如何开发微信小程序呢
  16. 【Unity游戏开发笔记】手游-涂鸦弹跳开发分析
  17. YOLO系列文章汇总
  18. FormData的用途
  19. matlab 蒙特卡洛法书籍,[转载]matlab的蒙特卡洛算法
  20. JAVAWEB开发之工作流详解(一)——Activiti的环境搭建、插件安装、核心API

热门文章

  1. 在CSS中实现height:100%-200px; width:100%-200px,既长度或宽度百分百减去200px
  2. 基于机智云平台的泵站智能巡检系统
  3. 微软开源的浏览器自动化工具-Playwright
  4. html wmf 不显示,在Word、Excel、PPT中不能显示WMF图片
  5. 盒子模型(CSS重点)
  6. 小米平板android版本,想要翻身还需努力 小米平板2安卓版评测
  7. openssl version mismatch. built against 30000010, you have 30100000
  8. vps虚拟服务器主机,vps虚拟服务器主机
  9. Wireshark lua 插件提取PCAP报文中文件,图片,视频
  10. 上大学之前,一定要明白这10大潜规则,你会少走很多人生弯路