當前位置:才華齋>IT認證>JAVA認證>

Java中的動態程式碼程式設計

JAVA認證 閱讀(1.75W)

Java不是解決動態層問題的理想語言,這些動態層問題包括原型設計、指令碼處理等。

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物件也會被新增到上下文環境當中去。

Foo foo = new Foo();ame("test");Map context = new HashMap();("foo",foo);String expression = " == 'test'";try {

Boolean result = (Boolean) alue(expression,context);

tln(result);} catch (OgnlException e) {

tStackTrace();}

這段程式碼就是判斷物件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)需要在快取中產生一個完全規範化表示式之後再執行。

  解釋模式

//解釋模式Foo foo = new Foo();ame("test");Map context = new HashMap();String expression = " == 'test'";VariableResolverFactory functionFactory = new MapVariableResolverFactory(context);("foo",foo);Boolean result = (Boolean) (expression,functionFactory);tln(result);

  編譯模式

//編譯模式Foo foo = new Foo();ame("test");Map context = new HashMap();String expression = " == 'test'";VariableResolverFactory functionFactory = new MapVariableResolverFactory(context);("foo",foo);Serializable compileExpression = ileExpression(expression);Boolean result = (Boolean) uteExpression(compileExpression, context, functionFactory);

SpEL

SpEl(Spring表示式語言)是一個支援查詢和操作執行時物件導航圖功能的強大的表示式語言。 它的語法類似於傳統EL,但提供額外的功能,最出色的就是函式呼叫和簡單字串的模板函式。SpEL類似於Struts2x中使用的OGNL表示式語言, 能在執行時構建複雜表示式、存取物件圖屬性、物件方法呼叫等等,並且能與Spring功能完美整合,如能用來配置Bean定義。

SpEL主要提供基本表示式、類相關表示式及集合相關表示式等,詳細參見Spring 表示式語言 (SpEL) 。

類似與OGNL,SpEL具有expression(表示式),Parser(解析器),EvaluationContext(上下文)等基本概念;類似與MVEL,SpEl也提供瞭解釋模式和編譯模式兩種執行模式。

//直譯器模式Foo foo = new Foo();ame("test");// Turn on:// - auto null reference initialization// - auto collection growingSpelParserConfiguration config = new SpelParserConfiguration(true,true);ExpressionParser parser = new SpelExpressionParser(config);String expressionStr = "# == 'test'";StandardEvaluationContext context = new StandardEvaluationContext();ariable("foo",foo);Expression expression = eExpression(expressionStr);Boolean result = alue(context,s);//編譯模式config = new SpelParserConfiguration(DIATE, lassLoader());parser = new SpelExpressionParser(config);context = new StandardEvaluationContext();ariable("foo",foo);expression = eExpression(expressionStr);result = alue(context,s);

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指令碼並執行。

class FooCompare{

boolean compare(String toCompare){

Foo foo = new Foo();

= "test";

return == toCompare;

}}

GroovyClassLoader loader = new GroovyClassLoader();Class groovyClass = null;try {

String path = "vy";

groovyClass = eClass(new File(path));} catch (IOException e) {

tStackTrace();}GroovyObject groovyObject = null;try {

groovyObject = (GroovyObject) nstance();} catch (InstantiationException e) {

tStackTrace();} catch (IllegalAccessException e) {

tStackTrace();}result = keMethod("compare", "test");assert ls();t(result);

GroovyShell

GroovyShell允許在Java類中(甚至Groovy類)求任意Groovy表示式的值。可以使用Binding物件輸入引數給表示式,並最終通過GroovyShell返回Groovy表示式的計算結果。

Foo foo = new Foo();ame("test");Binding binding = new Binding();ariable("foo",foo);GroovyShell shell = new GroovyShell(binding);String expression = "=='test'";Object result = uate(expression);assert ls();

GroovyScriptEngine

GroovyShell多用於推求對立的指令碼或表示式,如果換成相互關聯的多個指令碼,使用GroovyScriptEngine會更好些。 GroovyScriptEngine從您指定的位置(檔案系統,URL,資料庫,等等)載入Groovy指令碼,並且隨著指令碼變化而重新載入它們。如同 GroovyShell一樣,GroovyScriptEngine也允許您傳入引數值,並能返回指令碼的值。

vy

package vy

=="test";

Foo foo = new Foo();ame("test");Binding binding = new Binding();ariable("foo",foo);String[] paths = {"/demopath/"}GroovyScriptEngine gse = new GroovyScriptEngine(paths);try {

result = ("vy", binding);} catch (ResourceException e) {

tStackTrace();} catch (ScriptException e) {

tStackTrace();}assert ls();

JSR223

JSR223 是Java 6提供的一種從Java內部執行指令碼編寫語言的方便、標準的方式,並提供從指令碼內部訪問Java 資源和類的功能,可以使用其執行多種指令碼語言如JavaScript和Groovy等。

Foo foo = new Foo();ame("test");ScriptEngineManager factory = new ScriptEngineManager();ScriptEngine engine1 = ngineByName("groovy");("foo",foo);try {

result = (expression);} catch (ptException e) {

tStackTrace();}assert ls();

使用中經常出現的問題

因此Java每次呼叫Groovy程式碼都會將Groovy編譯成Class檔案,因此在呼叫過程中會出現JVM級別的問題。如使用 GroovyShell的parse方法導致perm區爆滿的問題,使用GroovyClassLoader載入機制導致頻繁gc問題和 CodeCache用滿,導致JIT禁用問題等,相關問題可以參考Groovy與Java整合常見的坑 。

效能對比

在這裡簡單對上面介紹到的OGNL、MVEL、SpEL和Groovy2.4 的效能進行大致的效能測試(簡單測試):

實現方式耗時(MS)
Java13
OGNL2958
MVEL225
SpEL1023
Groovy99

通過這個簡單測試發現,Groovy 2.4的效能已經足夠的好,而MVEL的效能依然保持強勁,不過已經遠遠落後與Groovy,在對效能有一定要求的場景下已經不建議使用OGNL和SpEL。

不過動態程式碼的執行效率還是遠低於Java,因此在高效能的場景下慎用。

以下是測試程式碼:

package ormanceclass GroovyCal{

Integer cal(int x,int y,int z){

return x + y*2 - z;

}}

package ormance;public class RunPerform {

public static void main(String[] args) {

try {

int xmax = 100,ymax = 100,zmax= 10;

runJava(xmax, ymax, zmax);

runOgnl(xmax, ymax, zmax);

runMvel(xmax, ymax, zmax);

runSpel(xmax, ymax, zmax);

runGroovyClass(xmax, ymax, zmax);

} catch (Exception e) {

tStackTrace();

}

}

public static void runJava(int xmax, int ymax, int zmax) {

Date start = new Date();

Integer result = 0;

for (int xval = 0; xval < xmax; xval++) {

for (int yval = 0; yval < ymax; yval++) {

for (int zval = 0; zval <= zmax; zval++) {

result += xval + yval * 2 - zval;

}

}

}

Date end = new Date();

tln("time is : " + (ime() - ime()) + ",result is " + result);

}

public static void runOgnl(int xmax, int ymax, int zmax) throws OgnlException {

String expression = "x + y*2 - z";

Map context = new HashMap();

Integer result = 0;

Date start = new Date();

for (int xval = 0; xval < xmax; xval++) {

for (int yval = 0; yval < ymax; yval++) {

for (int zval = 0; zval <= zmax; zval++) {

("x", xval);

("y", yval);

("z", zval);

Integer cal = (Integer) alue(expression, context);

result += cal;

}

}

}

Date end = new Date();

tln("Ognl:time is : " + (ime() - ime()) + ",result is " + result);

}

public static void runMvel(int xmax, int ymax, int zmax) {

Map context = new HashMap();

String expression = "x + y*2 - z";

Serializable compileExpression = ileExpression(expression);

Integer result = 0;

Date start = new Date();

for (int xval = 0; xval < xmax; xval++) {

for (int yval = 0; yval < ymax; yval++) {

for (int zval = 0; zval <= zmax; zval++) {

("x", xval);

("y", yval);

("z", zval);

VariableResolverFactory functionFactory = new MapVariableResolverFactory(context);

Integer cal = (Integer) uteExpression(compileExpression, context, functionFactory);

result += cal;

}

}

}

Date end = new Date();

tln("MVEL:time is : " + (ime() - ime()) + ",result is " + result);

}

public static void runSpel(int xmax, int ymax, int zmax) {

SpelParserConfiguration config;

ExpressionParser parser;

config = new SpelParserConfiguration(DIATE, lassLoader());

parser = new SpelExpressionParser(config);

StandardEvaluationContext context = new StandardEvaluationContext();

Integer result = 0;

String expressionStr = "#x + #y*2 - #z";

Date start = new Date();

for (Integer xval = 0; xval < xmax; xval++) {

for (Integer yval = 0; yval < ymax; yval++) {

for (Integer zval = 0; zval <= zmax; zval++) {

ariable("x", xval);

ariable("y", yval);

ariable("z", zval);

Expression expression = eExpression(expressionStr);

Integer cal = alue(context, s);

result += cal;

}

}

}

Date end = new Date();

tln("SpEL:time is : " + (ime() - ime()) + ",result is " + result);

}

public static void runGroovyClass(int xmax, int ymax, int zmax) {

GroovyClassLoader loader = new GroovyClassLoader();

Class groovyClass = null;

try {

groovyClass = eClass(new File(

"vy"));

} catch (IOException e) {

tStackTrace();

}

GroovyObject groovyObject = null;

try {

groovyObject = (GroovyObject) nstance();

} catch (InstantiationException e) {

tStackTrace();

} catch (IllegalAccessException e) {

tStackTrace();

}

Integer result = 0;

Date start = new Date();

for (int xval = 0; xval < xmax; xval++) {

for (int yval = 0; yval < ymax; yval++) {

for (int zval = 0; zval <= zmax; zval++) {

Object[] args = {xval,yval,zval};

Integer cal = (Integer) keMethod("cal", args);

result += cal;

}

}

}

Date end = new Date();

tln("Groovy Class:time is : " + (ime() - ime()) + ",result is " + result);

}}