三、使用Janino来优化Java代码
在大程序中,都希望尽可能的提高程序的灵活性,但往往事与愿违。这意味着程序的很多功能都退化成了单个的可配置的功能点。例如,假设一个应用程序负责数据报表,且有众多的因素会制约报表的最后格式。包括用户现场环境、服务端环境、目前的数据集、用户喜好、用户权限等因素。这意味着程序的行为将被委派多次。而对大数据集更复杂的操作,这种转变无疑会降低程序的性能。而动态的代码编译则可以有效的解决此问题。
这种方法有效的原因在于以下两个问题的答案:
1. 程序的配置运行时的程度
2. 性能问题在可配置代码中的局部化程度
如果以上两个问题都显得很重要,则动态编译对程序可能有益;否则,努力将有可能白费。自然而然,所需的成本也将相对的高些,于是程序也需相应的运行更有效率些。所以,专业类库所提供的“不透明”的服务(例如搜索、模式匹配或是数学运算等)将是不错的选择。
为了评价作者程序所采用的技术,作者写了一个叫BasicEvalFactory的工厂类。对于能完成基本的数学运算法则(加减乘除)语言,此类可以预解析它。程序的执行有意地比较基础化,其重点在于保证评估程序的高效性以及能产生一个优化的好性能结果。请记住,本例想比较Janino编译生成的代码与现成的已优化过的代码之间的性能差异。
BasicEvalFactory类的代码量有400行之多,而且包含大量的内部类;由于长度关系,这里不一一列举,整个评价程序的代码可以免费的下载(见资源部分)。所有的代码实现了如下的接口:
计算参数表态式的评价方法类实现了如下的接口public interface Evaluator ......{
![]()
public double evaluate(Variables vars);
}
public interface Variables ...{
![]()
double getVariable(String name);
![]()
}
接着作者使用Janino写了第二个实现。与第一个实现不同,第二个实现的代码量比较少,可以在这里列举出来。这是因为作者可以简单的把待评价的表达式转为Java表达式。此技术的优势在于Janino负责对表达式的解析工作,而由JVM负责完成评价工作。结果是,所需写的代码量变少了,因此可以在这里全部列出。它直接展现了如何使用Janino:
public class JaninoEvalFactory ...{
![]()
private static Pattern PATTERN =
Pattern.compile("([a-zA-Z]+)");
![]()
private static SimpleCompiler compiler =
new SimpleCompiler();
![]()
public static Evaluator fromString(String string) ...{
![]()
StringBuffer varCode = new StringBuffer();
Matcher matcher = PATTERN.matcher(string);
Set names = new HashSet();
while (matcher.find()) ...{
String name = matcher.group(0);
if (names.contains(name))
continue;
varCode.append("double " + name
+ " = vars.getVariable(\"" + name
+ "\");");
names.add(name);
}
![]()
String source = "package janinotest.eval;\n"
+"public class JaninoEvaluator implements Evaluator {\n"
+"\tpublic double evaluate(Variables vars) {\n" + "\t\t"
+varCode + "\n" + "\t\treturn "+ string +";\n" + "\t}\n"
+"}\n";
![]()
try ...{
compiler.cook(new StringReader(source));
Class clss = compiler.getClassLoader().loadClass(
"janinotest.eval.JaninoEvaluator");
Evaluator eval = (Evaluator) clss.newInstance();
return eval;
} catch (Exception e) ...{
throw new IllegalArgumentException(e.getMessage());
}
}
![]()
}
此类中的静态方法负责完成如下工作:
1. 表达式的参数定义为PATTERN类型,从包含Java代码的字符串利用上面定义过的Variables接口给其赋值。
2. 为了实现评价功能,这里联合使用了外部提供的表达式和一个基类的定义,以生成Java源。
3. 通过使用SimpleCompiler 对象将Java源编译成源代码。
4. 接着通过compiler类加载器来加载JaninoEvaluator类,并采用反射机制进行实例化。
5. 最后返回完成后的Evaluator对象。
例如,当提供的表达式为“(x + y)/(x - y) * 100/(x*y)”时,返回的源如下public class JaninoEvaluator implements Evaluator ...{
public double evaluate(Variables vars) ...{
double x = vars.getVariable("x");
double y = vars.getVariable("y");
return (x + y)/(x - y) * 100/(x*y);
}
}
当然,使用Janino时也需要注意到它的局限性,尽管很少。其中关键的一点是Janino缺少对Java1.5版本新特色的支持,如对泛化及for-loop语法的支持。
