UDN-企业互联网技术人气社区

板块导航

浏览  : 1238
回复  : 0

[讨论交流] Java 中使用动态代码

[复制链接]
芭芭拉的头像 楼主
发表于 2016-10-16 10:34:31 | 显示全部楼层 |阅读模式
  O2O互联网的运营开发最大的特点就是每次运营活动规则千奇百怪,需要有许多个性化的配置,如何例A活动需要针对新用户做发红包的活动,B活动针对全部用户做发红包活动,而在B活动中针对新用户发x面额的红包,而针对老用户发y面值的红包。两个活动规则差别较大,如果每次都个性化开发,会非常浪费时间,因此如何支持规则的动态配置是个很大的挑战。

  Java不是解决动态层问题的理想语言,这些动态层问题包括原型设计、脚本处理等。

  公司的项目主要基于Java平台,在实践中发现主要有两种方式可以实现:

  统一表达式语言

  动态语言,如Groovy

  JUEL(Java 统一表达式语言)

  Java*统一表达式语言(英语:Unified Expression Language,简称JUEL*)是一种特殊用途的编程语言,主要在Java Web应用程序用于将表达式嵌入到web页面。Java规范制定者和Java Web领域技术专家小组制定了统一的表达式语言。JUEL最初包含在JSP 2.1规范JSR-245中,后来成为Java EE 7的一部分,改在JSR-341中定义。

  主要的开源实现有:OGNL ,MVEL ,SpEL ,JUEL ,Java Expression Language (JEXL) ,JEval ,Jakarta JXPath 等。这里主要介绍在实践中使用较多的MVEL、OGNL和SpEL。

  OGNL(Object Graph Navigation Library)

  在Struts 2 的标签库中都是使用OGNL表达式访问ApplicationContext中的对象数据,OGNL主要有三个重要因素:

  Expression

  Expression是整个OGNL的核心内容,所有的OGNL操作都是针对表达式解析后进行的。通过Expression来告知OGNL操作到底要干些什么。因此,Expression其实是一个带有语法含义的字符串,整个字符串将规定操作的类型和内容。OGNL表达式支持大量Expression,如“链式访问对象”、表达式计算、甚至还支持Lambda表达式。

  Root对象:

  OGNL的Root对象可以理解为OGNL的操作对象。当我们指定了一个表达式的时候,我们需要指定这个表达式针对的是哪个具体的对象。而这个具体的对象就是Root对象,这就意味着,如果有一个OGNL表达式,那么我们需要针对Root对象来进行OGNL表达式的计算并且返回结果。

  ApplicationContext

  有个Root对象和Expression,我们就可以使用OGNL进行简单的操作了,如对Root对象的赋值与取值操作。但是,实际上在OGNL的内部,所有的操作都会在一个特定的数据环境中运行。这个数据环境就是ApplicationContext(上下文环境)。OGNL的上下文环境是一个Map结构,称之为OgnlContext。Root对象也会被添加到上下文环境当中去。
  1. Foo foo = new Foo();
  2. foo.setName("test");
  3. Map<String, Object> context = new HashMap<String, Object>();
  4. context.put("foo",foo);
  5. String expression = "foo.name == 'test'";
  6. try {
  7.     Boolean result = (Boolean) Ognl.getValue(expression,context);
  8.     System.out.println(result);
  9. } catch (OgnlException e) {
  10.     e.printStackTrace();
  11. }
复制代码

  这段代码就是判断对象foo的name属性是否为test。

  OGNL的具体语法参见OGNL language guide 。

  MVEL

  MVEL最初作为Mike Brock创建的 Valhalla项目的表达式计算器(expression evaluator)。Valhalla本身是一个早期的类似 Seam 的“开箱即用”的Web 应用框架,而 Valhalla 项目现在处于休眠状态, MVEL则成为一个继续积极发展的项目。相比最初的OGNL、JEXL和JUEL等项目,而它具有远超它们的性能、功能和易用性 – 特别是集成方面。它不会尝试另一种JVM语言,而是着重解决嵌入式脚本的问题。

  MVEL特别适用于受限环境 – 诸如由于内存或沙箱(sand-boxing)问题不能使用字节码生成。它不是试图重新发明Java,而是旨在提供一种Java程序员熟悉的语法,同时还加入了简短的表达式语法。

  MVEL主要使用在Drools,是Drools规则引擎不可分割的一部分。

  MVEL语法较为丰富,不仅包含了基本的属性表达式,布尔表达式,变量复制和方法调用,还支持函数定义,详情参见MVEL Language Guide 。

  MVEL在执行语言时主要有解释模式(Interpreted Mode)和编译模式(Compiled Mode )两种:

  解释模式(Interpreted Mode)是一个无状态的,动态解释执行,不需要负载表达式就可以执行相应的脚本。

  编译模式(Compiled Mode)需要在缓存中产生一个完全规范化表达式之后再执行。

  解释模式
  1. //解释模式
  2. Foo foo = new Foo();
  3. foo.setName("test");
  4. Map context = new HashMap();
  5. String expression = "foo.name == 'test'";
  6. VariableResolverFactory functionFactory = new MapVariableResolverFactory(context);
  7. context.put("foo",foo);
  8. Boolean result = (Boolean) MVEL.eval(expression,functionFactory);
  9. System.out.println(result);
复制代码

  编译模式
  1. //编译模式
  2. Foo foo = new Foo();foo.setName("test");Map context = new HashMap();String expression = "foo.name == 'test'";VariableResolverFactory functionFactory = new MapVariableResolverFactory(context);context.put("foo",foo);
  3. Serializable compileExpression = MVEL.compileExpression(expression);
  4. Boolean result = (Boolean) MVEL.executeExpression(compileExpression, context, functionFactory);
复制代码

  SpEL

  SpEl(Spring表达式语言)是一个支持查询和操作运行时对象导航图功能的强大的表达式语言。 它的语法类似于传统EL,但提供额外的功能,最出色的就是函数调用和简单字符串的模板函数。SpEL类似于Struts2x中使用的OGNL表达式语言,能在运行时构建复杂表达式、存取对象图属性、对象方法调用等等,并且能与Spring功能完美整合,如能用来配置Bean定义。

  SpEL主要提供基本表达式、类相关表达式及集合相关表达式等,详细参见Spring 表达式语言 (SpEL) 。

  类似与OGNL,SpEL具有expression(表达式),Parser(解析器),EvaluationContext(上下文)等基本概念;类似与MVEL,SpEl也提供了解释模式和编译模式两种运行模式。
  1. //解释器模式
  2. Foo foo = new Foo();
  3. foo.setName("test");
  4. // Turn on:
  5. // - auto null reference initialization
  6. // - auto collection growing
  7. SpelParserConfiguration config = new SpelParserConfiguration(true,true);
  8. ExpressionParser parser = new SpelExpressionParser(config);
  9. String expressionStr = "#foo.name == 'test'";
  10. StandardEvaluationContext context = new StandardEvaluationContext();
  11. context.setVariable("foo",foo);
  12. Expression expression = parser.parseExpression(expressionStr);
  13. Boolean result = expression.getValue(context,Boolean.class);

  14. //编译模式
  15. config = new SpelParserConfiguration(SpelCompilerMode.IMMEDIATE, RunSpel.class.getClassLoader());
  16. parser = new SpelExpressionParser(config);
  17. context = new StandardEvaluationContext();
  18. context.setVariable("foo",foo);
  19. expression = parser.parseExpression(expressionStr);
  20. result = expression.getValue(context,Boolean.class);
复制代码

  Groovy

  Groovy除了Gradle 上的广泛应用之外,另一个大范围的使用应该就是结合Java使用动态代码了。Groovy的语法与Java非常相似,以至于多数的Java代码也是正确的Groovy代码。Groovy代码动态的被编译器转换成Java字节码。由于其运行在JVM上的特性,Groovy可以使用其他Java语言编写的库。

  Groovy可以看作给Java静态世界补充动态能力的语言,同时Groovy已经实现了java不具备的语言特性:

  函数字面值;

  对集合的一等支持;

  对正则表达式的一等支持;

  对xml的一等支持;

  Groovy作为基于JVM的语言,与表达式语言存在语言级的不同,因此在语法上比表达还是语言更灵活。Java在调用Groovy时,都需要将Groovy代码编译成Class文件。

  Groovy 可以采用GroovyClassLoader、GroovyShell、GroovyScriptEngine和JSR223 等方式与Java语言集成。

  GroovyClassLoader

  GroovyClassLoader是一个定制的类装载器,负责解释加载Java类中用到的Groovy类,也可以编译,Java代码可通过其动态加载Groovy脚本并执行。
  1. class FooCompare{
  2.     boolean compare(String toCompare){
  3.         Foo foo = new Foo();
  4.         foo.name = "test";
  5.         return foo.name == toCompare;
  6.     }
  7. }
复制代码
  1. GroovyClassLoader loader = new GroovyClassLoader();
  2. Class groovyClass = null;
  3. try {
  4.     String path = "FooCompare.groovy";
  5.     groovyClass = loader.parseClass(new File(path));
  6. } catch (IOException e) {
  7.     e.printStackTrace();
  8. }
  9. GroovyObject groovyObject = null;
  10. try {
  11.     groovyObject = (GroovyObject) groovyClass.newInstance();
  12. } catch (InstantiationException e) {
  13.     e.printStackTrace();
  14. } catch (IllegalAccessException e) {
  15.     e.printStackTrace();
  16. }
  17. result = groovyObject.invokeMethod("compare", "test");
  18. assert result.equals(Boolean.TRUE);
  19. System.out.print(result);
复制代码

  GroovyShell

  GroovyShell允许在Java类中(甚至Groovy类)求任意Groovy表达式的值。可以使用Binding对象输入参数给表达式,并最终通过GroovyShell返回Groovy表达式的计算结果。
  1. Foo foo = new Foo();
  2. foo.setName("test");
  3. Binding binding = new Binding();
  4. binding.setVariable("foo",foo);
  5. GroovyShell shell = new GroovyShell(binding);
  6. String expression = "foo.name=='test'";
  7. Object result = shell.evaluate(expression);
  8. assert result.equals(Boolean.TRUE);
复制代码

  GroovyScriptEngine

  GroovyShell多用于推求对立的脚本或表达式,如果换成相互关联的多个脚本,使用GroovyScriptEngine会更好些。GroovyScriptEngine从您指定的位置(文件系统,URL,数据库,等等)加载Groovy脚本,并且随着脚本变化而重新加载它们。如同GroovyShell一样,GroovyScriptEngine也允许您传入参数值,并能返回脚本的值。

  FooScript.groovy
  1. package blog.brucefeng.info.groovy

  2. foo.name=="test";
复制代码

  1. Foo foo = new Foo();
  2. foo.setName("test");
  3. Binding binding = new Binding();
  4. binding.setVariable("foo",foo);
  5. String[] paths = {"/demopath/"}
  6. GroovyScriptEngine gse = new GroovyScriptEngine(paths);
  7. try {
  8.     result = gse.run("FooScript.groovy", binding);
  9. } catch (ResourceException e) {
  10.     e.printStackTrace();
  11. } catch (ScriptException e) {
  12.     e.printStackTrace();
  13. }
  14. assert result.equals(Boolean.TRUE);
复制代码

  JSR223

  JSR223 是Java 6提供的一种从Java内部执行脚本编写语言的方便、标准的方式,并提供从脚本内部访问Java 资源和类的功能,可以使用其运行多种脚本语言如JavaScript和Groovy等。
  1. Foo foo = new Foo();
  2. foo.setName("test");
  3. ScriptEngineManager factory = new ScriptEngineManager();
  4. ScriptEngine engine1 = factory.getEngineByName("groovy");
  5. engine1.put("foo",foo);
  6. try {
  7.    result =  engine1.eval(expression);
  8. } catch (javax.script.ScriptException e) {
  9.     e.printStackTrace();
  10. }
  11. assert result.equals(Boolean.TRUE);
复制代码

  使用中经常出现的问题

  因此Java每次调用Groovy代码都会将Groovy编译成Class文件,因此在调用过程中会出现JVM级别的问题。如使用GroovyShell的parse方法导致perm区爆满的问题,使用GroovyClassLoader加载机制导致频繁gc问题和CodeCache用满,导致JIT禁用问题等,相关问题可以参考Groovy与Java集成常见的坑 。

  性能对比

  在这里简单对上面介绍到的OGNL、MVEL、SpEL和Groovy2.4 的性能进行大致的性能测试(简单测试):

实现方式
耗时(ms)
Java
13
OGNL
2958
MVEL
225
SpEL
1023
Groovy
99

  通过这个简单测试发现,Groovy 2.4的性能已经足够的好,而MVEL的性能依然保持强劲,不过已经远远落后与Groovy,在对性能有一定要求的场景下已经不建议使用OGNL和SpEL。
不过动态代码的执行效率还是远低于Java,因此在高性能的场景下慎用。

  以下是测试代码:
  1. package blog.brucefeng.info.performance

  2. class GroovyCal{
  3.     Integer cal(int x,int y,int z){
  4.         return x + y*2 - z;
  5.     }
  6. }
复制代码

  1. package blog.brucefeng.info.performance;
  2. public class RunPerform {

  3.     public static void main(String[] args) {
  4.         try {
  5.             int xmax = 100,ymax = 100,zmax= 10;
  6.             runJava(xmax, ymax, zmax);
  7.             runOgnl(xmax, ymax, zmax);
  8.             runMvel(xmax, ymax, zmax);
  9.             runSpel(xmax, ymax, zmax);
  10.             runGroovyClass(xmax, ymax, zmax);
  11.         } catch (Exception e) {
  12.             e.printStackTrace();
  13.         }

  14.     }

  15.     public static void runJava(int xmax, int ymax, int zmax) {
  16.         Date start = new Date();
  17.         Integer result = 0;
  18.         for (int xval = 0; xval < xmax; xval++) {
  19.             for (int yval = 0; yval < ymax; yval++) {
  20.                 for (int zval = 0; zval <= zmax; zval++) {
  21.                     result += xval + yval * 2 - zval;
  22.                 }
  23.             }
  24.         }
  25.         Date end = new Date();
  26.         System.out.println("time is : " + (end.getTime() - start.getTime()) + ",result is " + result);

  27.     }

  28.     public static void runOgnl(int xmax, int ymax, int zmax) throws OgnlException {
  29.         String expression = "x + y*2 - z";
  30.         Map<String, Object> context = new HashMap<String, Object>();
  31.         Integer result = 0;
  32.         Date start = new Date();
  33.         for (int xval = 0; xval < xmax; xval++) {
  34.             for (int yval = 0; yval < ymax; yval++) {
  35.                 for (int zval = 0; zval <= zmax; zval++) {
  36.                     context.put("x", xval);
  37.                     context.put("y", yval);
  38.                     context.put("z", zval);
  39.                     Integer cal = (Integer) Ognl.getValue(expression, context);
  40.                     result += cal;
  41.                 }
  42.             }
  43.         }
  44.         Date end = new Date();
  45.         System.out.println("Ognl:time is : " + (end.getTime() - start.getTime()) + ",result is " + result);
  46.     }

  47.     public static void runMvel(int xmax, int ymax, int zmax) {
  48.         Map context = new HashMap();
  49.         String expression = "x + y*2 - z";
  50.         Serializable compileExpression = MVEL.compileExpression(expression);
  51.         Integer result = 0;
  52.         Date start = new Date();
  53.         for (int xval = 0; xval < xmax; xval++) {
  54.             for (int yval = 0; yval < ymax; yval++) {
  55.                 for (int zval = 0; zval <= zmax; zval++) {
  56.                     context.put("x", xval);
  57.                     context.put("y", yval);
  58.                     context.put("z", zval);
  59.                     VariableResolverFactory functionFactory = new MapVariableResolverFactory(context);
  60.                     Integer cal = (Integer) MVEL.executeExpression(compileExpression, context, functionFactory);
  61.                     result += cal;
  62.                 }
  63.             }
  64.         }
  65.         Date end = new Date();
  66.         System.out.println("MVEL:time is : " + (end.getTime() - start.getTime()) + ",result is " + result);

  67.     }

  68.     public static void runSpel(int xmax, int ymax, int zmax) {
  69.         SpelParserConfiguration config;
  70.         ExpressionParser parser;
  71.         config = new SpelParserConfiguration(SpelCompilerMode.IMMEDIATE, RunSpel.class.getClassLoader());
  72.         parser = new SpelExpressionParser(config);
  73.         StandardEvaluationContext context = new StandardEvaluationContext();
  74.         Integer result = 0;
  75.         String expressionStr = "#x + #y*2 - #z";
  76.         Date start = new Date();
  77.         for (Integer xval = 0; xval < xmax; xval++) {
  78.             for (Integer yval = 0; yval < ymax; yval++) {
  79.                 for (Integer zval = 0; zval <= zmax; zval++) {
  80.                     context.setVariable("x", xval);
  81.                     context.setVariable("y", yval);
  82.                     context.setVariable("z", zval);
  83.                     Expression expression = parser.parseExpression(expressionStr);
  84.                     Integer cal = expression.getValue(context, Integer.class);
  85.                     result += cal;
  86.                 }
  87.             }
  88.         }
  89.         Date end = new Date();
  90.         System.out.println("SpEL:time is : " + (end.getTime() - start.getTime()) + ",result is " + result);

  91.     }



  92.     public static void runGroovyClass(int xmax, int ymax, int zmax) {
  93.         GroovyClassLoader loader = new GroovyClassLoader();
  94.         Class groovyClass = null;
  95.         try {
  96.             groovyClass = loader.parseClass(new File(
  97.                     "GroovyCal.groovy"));
  98.         } catch (IOException e) {
  99.             e.printStackTrace();
  100.         }
  101.         GroovyObject groovyObject = null;
  102.         try {
  103.             groovyObject = (GroovyObject) groovyClass.newInstance();
  104.         } catch (InstantiationException e) {
  105.             e.printStackTrace();
  106.         } catch (IllegalAccessException e) {
  107.             e.printStackTrace();
  108.         }
  109.         Integer result = 0;
  110.         Date start = new Date();
  111.         for (int xval = 0; xval < xmax; xval++) {
  112.             for (int yval = 0; yval < ymax; yval++) {
  113.                 for (int zval = 0; zval <= zmax; zval++) {
  114.                     Object[] args = {xval,yval,zval};
  115.                     Integer cal = (Integer) groovyObject.invokeMethod("cal", args);
  116.                     result += cal;
  117.                 }
  118.             }
  119.         }
  120.         Date end = new Date();
  121.         System.out.println("Groovy Class:time is : " + (end.getTime() - start.getTime()) + ",result is " + result);

  122.     }
  123. }
复制代码

相关帖子

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

关于我们
联系我们
  • 电话:010-86393388
  • 邮件:udn@yonyou.com
  • 地址:北京市海淀区北清路68号
移动客户端下载
关注我们
  • 微信公众号:yonyouudn
  • 扫描右侧二维码关注我们
  • 专注企业互联网的技术社区
版权所有:用友网络科技股份有限公司82041 京ICP备05007539号-11 京公网网备安1101080209224 Powered by Discuz!
快速回复 返回列表 返回顶部