??本文涉及重要概念:反射、內(nèi)省、BeanUtils工具、JSP標(biāo)簽。
??這幾個(gè)概念看起來相關(guān)性不大,其實(shí)都是用于控制JavaBean的方法。其中,前面三種都是在java(例如在Servlet)文件中訪問和修改JavaBean的方法。而JSP標(biāo)簽則是在jsp文本中訪問JavaBean的方法。
??在這之前,如果我們想要操作屬性,一般情況下我們先是先將類實(shí)例化來得到一個(gè)對(duì)象。然后通過setter和getter方法對(duì)這個(gè)對(duì)象的屬性進(jìn)行操作,當(dāng)然,這是訪問屬性,如果直接用對(duì)象來調(diào)用方法也沒什么不妥。
??雖然JSP標(biāo)簽方法看起來繁雜(主要是指標(biāo)簽使用方面),但是它的原理確實(shí)最簡(jiǎn)單的,和上面類似,它對(duì)屬性的操作本質(zhì)上還是直接通過對(duì)象的getter和setter方法來進(jìn)行實(shí)現(xiàn)(具體的標(biāo)簽的內(nèi)容將在后面描述)。
??而其他三個(gè)都是采用不同方式來進(jìn)行屬性的訪問。按照我的理解,反射功能最為強(qiáng)大,它允許通過一個(gè)實(shí)例化對(duì)象找到類的完整信息并得到Class類。而Class類除了能操作屬性還能操作方法。內(nèi)省則是JDK中專門設(shè)計(jì)用于操作Java對(duì)象屬性的一套API。BeanUtils是Apache軟件基金會(huì)提供的一套能動(dòng)態(tài)訪問Java對(duì)象屬性的API,在這三者中最為簡(jiǎn)潔。這三個(gè)類都需要依賴于其他的類來進(jìn)行對(duì)象屬性的設(shè)置,并且不管是哪一種方法,原有的對(duì)象都從屬性的調(diào)用者變成了參數(shù)。而真正的方法的調(diào)用者是我們所依賴的這些新的對(duì)象。反射中所依賴的是Class類調(diào)用getDeclaredField(String<屬性名>)方法得到的Field類、內(nèi)省中所依賴的主要是PropertyDescriptor類,而BanUtils工具中所依賴的主要是BeanUtils類。但是BeanUtils類不需要實(shí)例化就可以調(diào)用方法,而反射和內(nèi)省的這些類都需要實(shí)例化。
反射:
??任何對(duì)象通過getclass()方法它就可以得到一個(gè)class類的實(shí)例,并且通過這個(gè)class類的方法就可以獲取類的完整信息。
??假如我有一個(gè)蘋果對(duì)象、一個(gè)貓對(duì)象和一個(gè)手機(jī)對(duì)象,它們各自使用getclass()都可以返回一個(gè)class對(duì)象。盡管這三個(gè)對(duì)象本身有不同的屬性和方法。但是它們返回的class對(duì)象卻有相同的方法和屬性(不過這些值不同)。通過對(duì)象調(diào)用getClass()獲得一個(gè)Class類的實(shí)例,需要先創(chuàng)建出一個(gè)對(duì)象(先要有一個(gè)蘋果/貓/手機(jī))。
??除了使用對(duì)象.getClass()方法來獲得class實(shí)例以外,還可以通過Class類的靜態(tài)方法forName(),用類的全路徑名獲取一個(gè)Class實(shí)例。這種方法不需要提前建立一個(gè)(蘋果/貓/狗)對(duì)象。
Class clazz = Class.forName("cn.itcast.chapter08.reflection.Person");
??這里是直接由于Class類來創(chuàng)建Class實(shí)例,并在參數(shù)中引入具體的類(例如這里的Person類)的路徑。
Class類調(diào)用forName(<Person類的路徑>)方法--->創(chuàng)建關(guān)于 Person類的clazz實(shí)例-----> clazz實(shí)例可以創(chuàng)建Person類的實(shí)例。
Person p= (Person)clazz.newInstance();
這段話看起來有一點(diǎn)繞。
??使用clazz創(chuàng)建對(duì)象時(shí),如果有多個(gè)構(gòu)造方法,那么情況更復(fù)雜,需要使用getConstructor()方法得到一個(gè)構(gòu)造方法對(duì)象(實(shí)際上是Constructor對(duì)象)的數(shù)組,然后由數(shù)組中對(duì)應(yīng)的某個(gè)Constructor對(duì)象(通過下標(biāo)選取)來創(chuàng)建對(duì)象。(在我看來,這里數(shù)組中的每個(gè)cons元素都可以理解為選定了特定構(gòu)造方法的clazz)。
Constructor cons[] = clazz.getConstructors();
Person p = (Person)cons[0].newInstance("李芳",30);
??和通過對(duì)象直接調(diào)用setter或者getter來修改屬性不同,現(xiàn)在用了反射。要修改對(duì)象的屬性,對(duì)象無需調(diào)用任何的方法,只需要老老實(shí)實(shí)作為參數(shù)進(jìn)行傳遞就行了。
具體的屬性會(huì)用一個(gè)Field類型的對(duì)象來表示,而具體的方法會(huì)用一個(gè)Method對(duì)象來表示。這些對(duì)象都是通過class類來生成。
Field ageField = clazz.geetDeclaredField("age");
ageField.setAccessible(true);
ageField.set(p,20);
反射獲取方法部分示例代碼如下所示:
Method md = clazz.getMethod("sayHello",String.class,int.class);
String result =(String)md.invoke(clazz.newInstance())
??也即是說,不管是通過反射調(diào)用屬性還是方法,原本是通過該類的實(shí)例化對(duì)象來調(diào)用,而現(xiàn)在全部可以通過class類的實(shí)例對(duì)象(例如clazz)來完成。并且屬性、方法全部都變成了對(duì)象(age屬性生成一個(gè)Field對(duì)象,sayHello方法生成一個(gè)Method對(duì)象),并且這些對(duì)象全部都可以通過clazz一步得到,而原來的實(shí)例對(duì)象從方法和屬性的執(zhí)行者,現(xiàn)在變成了參與者。
內(nèi)省
??內(nèi)省是專門用于操作java對(duì)象的屬性,它比反射技術(shù)更加便捷。
??反射可以實(shí)現(xiàn)操作方法、屬性的功能,而這里內(nèi)省專門操作java對(duì)象的屬性,從功能上看略差一籌。但是在屬性的操作方面,它有它的便捷之處。在內(nèi)省中也運(yùn)用到了反射中的class類。
Person p = new Person();
PropertyDescriptor pd = new PropertyDescriptor("name",p.getclass());
Method methodName = pd.getWriterMethod();
methodName.invoke(p,"小明");
??同樣是訪問屬性,這里的思路跟用反射訪問屬性的思路就不同了。上面說過反射是利用class類的一個(gè)實(shí)例clazz來調(diào)用getDeclaredField(String name)方法來得到一個(gè)關(guān)于某個(gè)屬性Field對(duì)象,這個(gè)Filed對(duì)象就可以直接更改對(duì)應(yīng)的屬性值。
??而這里p.getClass()同樣生成一個(gè)class類的實(shí)例,但是呢不直接利用這個(gè)實(shí)例,而是繼續(xù)把這個(gè)class實(shí)例和我們的屬性值作為參數(shù)創(chuàng)建出一個(gè)PropertyDescriptor對(duì)象。
??這兩者的區(qū)別,如果是第一種方法,那么當(dāng)我要訪問多個(gè)屬性,那我還是只需要一個(gè)clazz對(duì)象。但如果是通過內(nèi)省的方法,每次訪問一個(gè)屬性,都需要新創(chuàng)建一個(gè)Class類實(shí)例對(duì)象,并作為參數(shù)用于生成新的PropertyDescriptor對(duì)象。
??這還沒完,如果想要對(duì)屬性進(jìn)行更改。還需要調(diào)用getWriteMethod()得到一個(gè)method對(duì)象,這個(gè)method對(duì)象才能夠?qū)傩赃M(jìn)行更改,如果只是進(jìn)行讀取,那么就調(diào)用getReadMethod()。說句實(shí)在話,我看不出來哪里便捷了。
Beanutils工具
??beanutils提供了比反射和內(nèi)省更為簡(jiǎn)單的操作,Beanutils工具包還需要一個(gè)logging包來配合使用。logging包中包裝了各種日志API的實(shí)現(xiàn)。BeanUtils工具中最核心的是org.apache.commons.beanutils包下的BeanUtils類。BeanUtils直接調(diào)用setProperty方法就可以搞定屬性的修改。
Person p = new Person();
BeanUtils.setProperty(p,"name","Jack");
BeanUtils.getProperty(p,"name");
并且能夠通過populate方法同時(shí)修改多個(gè)屬性。
Map<String,Object>map = new HashMap<String,Object>();
map.put("name","張三");
map.put("age",10)
BeanUtils.populate(p,map)
但是我們同樣也要意識(shí)到,這里只是訪問屬性方便。方法就不見得能訪問了。
JSP標(biāo)簽訪問JavaBean
??jsp文件訪問JavaBean和更改其屬性可以通過標(biāo)簽來進(jìn)行實(shí)現(xiàn)。但是其對(duì)屬性的操作本質(zhì)上還是利用了這個(gè)對(duì)象直接調(diào)用getter和setter的方法來進(jìn)行實(shí)現(xiàn)。因此,接下來的內(nèi)容是對(duì)標(biāo)簽用法的一些介紹。
??<jsp:useBean>表示可以在某個(gè)指定的域的范圍中查找一個(gè)指定名稱的JavaBean對(duì)象。如果已經(jīng)存在對(duì)象則返回該對(duì)象,否則實(shí)例化一個(gè)JavaBean對(duì)象并儲(chǔ)存在指定的域中。
??<jsp:setProperty>標(biāo)簽有name、property、param和value四個(gè)屬性。其中name和property好理解,表明要修改的是名為name的JavaBean實(shí)例對(duì)象的property屬性。這時(shí)候,如果我們?cè)L問這個(gè).jsp文件,URL鏈接后面的參數(shù)如果和property的名字相對(duì)應(yīng)的,會(huì)自動(dòng)賦值到這個(gè)屬性。如果參數(shù)名字和property名字不同但是仍然想賦值,那么可以用param進(jìn)行映射。如果就算參數(shù)相同也不希望進(jìn)行賦值的,可以使用value屬性為其賦值,這樣就不受URL請(qǐng)求中的參數(shù)的影響。
JSP開發(fā)模型的Model1與Model2的區(qū)別的理解
JSP Model1:JSP+JavaBean
JSP Model2:Servlet + JSP + JavaBean
??可以看到,兩種模型都使用了JavaBean技術(shù)。不同之處在于JspModel1模型沒有引入專門的Servlet。因此控制邏輯都在.jsp中進(jìn)行實(shí)現(xiàn),不可避免地.jsp頁面中含有大量的代碼片段。
??而JspModel2則是將部分代碼片段抽離出來,封裝到Servlet中,只留下一些與顯示相關(guān)的代碼段。實(shí)現(xiàn)了 頁面顯示、流程控制和業(yè)務(wù)邏輯 的分離。
??說的通俗些,.jsp曾經(jīng)是一個(gè)無所不能的存在,但是我們不忍心看它忙的焦頭爛額,所以我們用JavaBean來幫它控制數(shù)據(jù)庫。再使用Servlet幫他處理一些業(yè)務(wù)邏輯,這樣也有利于代碼的維護(hù)。