進(jìn)入正文,本文是嘗試分析下Fel的實(shí)現(xiàn)原理,以及優(yōu)缺點(diǎn)和aviator——我自己開源的EL之間的簡(jiǎn)單比較。
Fel的實(shí)現(xiàn)原理跟Simple EL是類似,都是使用template生成中間代碼——也就是普通的java代碼,然后利用javac編譯成class,最后運(yùn)行,當(dāng)然,這個(gè)過(guò)程都是動(dòng) 態(tài)的。JDK6已經(jīng)引入了編譯API,在此之前的版本可以調(diào)用sun的類來(lái)編譯,因?yàn)閖avac其實(shí)就是用java實(shí)現(xiàn)的?;氐紽el里 面,F(xiàn)elCompiler15就是用 com.sun.tools.javac.Main來(lái)編譯,而FelCompiler16用標(biāo)準(zhǔn)的javax.tools.JavaCompiler來(lái)編譯的。
文法和語(yǔ)法解釋這塊是使用antlr這個(gè)parse generator生成的,這塊不多說(shuō),有興趣可以看下antlr,整體一個(gè)運(yùn)行的過(guò)程是這樣:
這個(gè)思路我在實(shí)現(xiàn)aviator之前就想過(guò),但是后來(lái)考慮到API需要用的sun獨(dú)有的類,而且要求classpath必須有tools.jar這個(gè)依賴包,就放棄了這個(gè)思路,還是采用ASM生成字節(jié)碼的方式。題外,velocity的優(yōu)化可以采用這個(gè)思路,我們有這么一個(gè)項(xiàng)目是這么做的,也準(zhǔn)備開源了。
看看Fel生成的中間代碼,例如a+b這樣的一個(gè)簡(jiǎn)單的表達(dá)式,假設(shè)我一開始不知道a和b的類型,編譯是這樣:
Expression exp = fel.compile("a+b", null);
我稍微改了下FEL的源碼,讓它打印中間生成的java代碼,a+b生成的中間結(jié)果為:
import com.greenpineyu.fel.common.NumberUtil;
import com.greenpineyu.fel.Expression;
import com.greenpineyu.fel.context.FelContext;
import org.apache.commons.lang.ObjectUtils;
import org.apache.commons.lang.StringUtils;
public class Fel_0 implements Expression{
public Object eval(FelContext context) {
java.lang.Object var_1 = (java.lang.Object)context.get("b"); //b
java.lang.Object var_0 = (java.lang.Object)context.get("a"); //a
return (ObjectUtils.toString(var_0))+(ObjectUtils.toString(var_1));
}
}
可見,F(xiàn)EL對(duì)表達(dá)式解析和解釋后,利用template生成這么一個(gè)普通的java類,而a和b都從context中獲取并轉(zhuǎn)化為Object類型,這里沒有做任何判斷就直接認(rèn)為a和b是要做字符串相加,然后拼接字符串并返回。
問(wèn)題出來(lái)了,因?yàn)闆]有在編譯的時(shí)候傳入context(我們這里是null),F(xiàn)EL會(huì)將a和b的類型默認(rèn)都為java.lang.Object,a+b解釋為字符串拼接。但是運(yùn)行的時(shí)候,我完全可以傳入a和b都為數(shù)字,那么結(jié)果就非常詭異了:
Expression exp = fel.compile("a+b", null);
Map<String, Object> env=new HashMap<String, Object>();
env.put("a", 1);
env.put("b", 3.14);
System.out.println(exp.eval(new MapContext(env)));
輸出:
1+3.14的結(jié)果,作為字符串拼接就是13.14,而不是我們想要的4.14。如果將表達(dá)式換成a*b,就完全運(yùn)行不了
import com.greenpineyu.fel.common.NumberUtil;
import com.greenpineyu.fel.Expression;
import com.greenpineyu.fel.context.FelContext;
import org.apache.commons.lang.ObjectUtils;
import org.apache.commons.lang.StringUtils;
public class Fel_0 implements Expression{
public Object eval(FelContext context) {
java.lang.Object var_1 = (java.lang.Object)context.get("b"); //b
java.lang.Object var_0 = (java.lang.Object)context.get("a"); //a
return (var_0)*(var_1);
}
}
[Fel_0.java:14: 運(yùn)算符 * 不能應(yīng)用于 java.lang.Object,java.lang.Object]
at com.greenpineyu.fel.compile.FelCompiler16.compileToClass(FelCompiler16.java:113)
at com.greenpineyu.fel.compile.FelCompiler16.compile(FelCompiler16.java:87)
at com.greenpineyu.fel.compile.CompileService.compile(CompileService.java:66)
at com.greenpineyu.fel.FelEngineImpl.compile(FelEngineImpl.java:62)
at TEst.main(TEst.java:14)
Exception in thread "main" java.lang.NullPointerException
at TEst.main(TEst.java:18)
這個(gè)問(wèn)題對(duì)于Simple EL同樣存在,如果沒有在編譯的時(shí)候能確定變量類型,這無(wú)法生成正確的中間代碼,導(dǎo)致運(yùn)行時(shí)出錯(cuò),并且有可能造成非常詭異的bug。
這個(gè)問(wèn)題的本質(zhì)是因?yàn)镕el和Simple EL沒有自己的類型系統(tǒng),他們都是直接使用java的類型的系統(tǒng),并且必須在編譯的時(shí)候確定變量類型,才能生成高效和正確的代碼,我們可以將它們稱為“強(qiáng)類型的EL“。
現(xiàn)在讓我們?cè)诰幾g的時(shí)候給a和b加上類型,看看生成的中間代碼:
fel.getContext().set("a", 1);
fel.getContext().set("b", 3.14);
Expression exp = fel.compile("a+b", null);
Map<String, Object> env = new HashMap<String, Object>();
env.put("a", 1);
env.put("b", 3.14);
System.out.println(exp.eval(new MapContext(env)));
查看中間代碼:
import com.greenpineyu.fel.common.NumberUtil;
import com.greenpineyu.fel.Expression;
import com.greenpineyu.fel.context.FelContext;
import org.apache.commons.lang.ObjectUtils;
import org.apache.commons.lang.StringUtils;
public class Fel_0 implements Expression{
public Object eval(FelContext context) {
double var_1 = ((java.lang.Number)context.get("b")).doubleValue(); //b
double var_0 = ((java.lang.Number)context.get("a")).doubleValue(); //a
return (var_0)+(var_1);
}
}
可以看到這次將a和b都強(qiáng)制轉(zhuǎn)為double類型了,做數(shù)值相加,結(jié)果也正確了:
Simple EL我沒看過(guò)代碼,這里猜測(cè)它的實(shí)現(xiàn)也應(yīng)該是類似的,也應(yīng)該有同樣的問(wèn)題。
相比來(lái)說(shuō),aviator這是一個(gè)弱類型的EL,在編譯的時(shí)候不對(duì)變量類型做任何假設(shè),而是在運(yùn)行時(shí)做類型判斷和自動(dòng)轉(zhuǎn)化。過(guò)去提過(guò),我給aviator的定位是一個(gè)介于EL和script之間的東西,它有自己的類型系統(tǒng)。 例如,3這個(gè)數(shù)字,在java里可能是long,int,short,byte,而aviator統(tǒng)一為AviatorLong這個(gè)類型。為了在這兩個(gè)類 型之間做適配,就需要做很多的判斷和box,unbox操作。這些判斷和轉(zhuǎn)化都是運(yùn)行時(shí)進(jìn)行的,因此aviator沒有辦法做到Fel這樣的高效,但是已 經(jīng)做到至少跟groovy這樣的弱類型腳本語(yǔ)言一個(gè)級(jí)別,也超過(guò)了JXEL這樣的純解釋EL,具體可以看這個(gè)性能測(cè)試。
強(qiáng)類型還是弱類型,這是一個(gè)選擇問(wèn)題,如果你能在運(yùn)行前就確定變量的類型,那么使用Fel應(yīng)該可以達(dá)到或者接近于原生java執(zhí)行的效率,但是失去了靈活性;如果你無(wú)法確定變量類型,則只能采用弱類型的EL。
EL涌現(xiàn)的越來(lái)越多,這個(gè)現(xiàn)象有點(diǎn)類似消息中間件領(lǐng)域,越來(lái)越多面向特定領(lǐng)域的輕量級(jí)MQ的出現(xiàn),而不是原來(lái)那種大而笨重的通用MQ大行其道,一方面是互 聯(lián)網(wǎng)應(yīng)用的發(fā)展,需求不是通用系統(tǒng)能夠滿足的,另一方面我認(rèn)為也是開發(fā)者素質(zhì)的提高,大家都能造適合自己的輪子。從EL這方面來(lái)說(shuō),我也認(rèn)為會(huì)有越來(lái)越多 特定于領(lǐng)域的,優(yōu)點(diǎn)和缺點(diǎn)一樣鮮明的EL出現(xiàn),它們包含設(shè)計(jì)者自己的目標(biāo)和口味,選擇很多,就看取舍。
posted @ 2011-09-17 12:52 dennis 閱讀(9953) | 評(píng)論 (5) | 編輯 收藏