1.官方介紹的地址: http://struts.apache.org/2.1.6/docs/convention-plugin.html 2.struts.xml文件配置 只挑選幾個重要的常量說明: (1) <constant name="struts.locale" value="zh_CN"/> <constant name="struts.i18n.encoding" value="UTF-8"/> struts2.1.6 現(xiàn)在只需要一個struts.xml文件就可以了。以前配struts.locale=zh_CN,struts.i18n.encoding=UTF-8,應用起動時會報一個警告,說沒有配置locale,必須要在struts.properties里面配置才不會報錯,現(xiàn)在這個問題已經(jīng)解決了,所有配置都可以在xml文件中指定了。 (2) <constant name="struts.action.extension" value="action,do,,"/> 擴展名可以指定為空。這樣地址欄比較好看。但也會有個問題,就是一些其他servlet映射,如cxf,我們會映射地址為/services/*,現(xiàn)在這個地址也變成struts2控制范圍的地址了,如果按默認的配置會報找不到action的錯誤。解決辦法是修改mapper類。這在以后文章中會提到。 (3) <constant name="struts.enable.DynamicMethodInvocation" value="true"/> <constant name="struts.enable.SlashesInActionNames" value="true"/> 開啟動態(tài)方法。要實現(xiàn)零配置,就是需要動態(tài)方法調(diào)用。開啟action名稱可以有 “/”,一個請求地址有多個“/”,struts2就不會再使用類路徑掃描的命名空間,只會使用配置的名稱。所以既想action名稱里使用“/”,又想用struts2默認搜索的命名空間,只能自己修改一下convention插件的實現(xiàn)類了。 (4) <constant name="struts.ui.theme" value="simple"/> 不用dojo的及struts2復雜標簽樣式的就把主題設置為simple,這樣可以不加載多余的模板。 (5) <constant name="struts.devMode" value="true"/> <constant name="struts.i18n.reload" value="true"/> <constant name="struts.configuration.xml.reload" value="true"/> <constant name="struts.convention.classes.reload" value="true" /> 開啟開發(fā)者模式,在平時開發(fā)時修改action的annotation配置可以不重啟,但是修改struts.xml文件還是要重啟。修改類的具體內(nèi)容,debug模式下可以不重啟,或是使用javarebel,這個不在討論范圍。 (6) <constant name="struts.convention.result.path" value="/WEB-INF/pages/"/> 指定結(jié)果頁面路徑。 convention插件會自動在此路徑中尋找文件。放到WEB-INF的目的的保護文件資源,只能通過程序內(nèi)部跳轉(zhuǎn)才能訪問,我們的權(quán)限攔截器或其他權(quán)限處理只要加到action上就可以了。 (7) <constant name="struts.convention.action.suffix" value="Action"/> <constant name="struts.convention.action.name.lowercase" value="true"/> <constant name="struts.convention.action.name.separator" value="_"/> 一個action名字的獲取。比如為HelloWorldAction。按照配置,actionName為hello_world。 (8)<constant name="struts.convention.action.disableScanning" value="false"/> 是否不掃描類。一定要設為false,否則convention插件不起作用,零配置也沒有意義。 (9)<constant name="struts.convention.default.parent.package" value="default"/> 設置默認的父包,一般我們都設置一個default包繼承自struts-default。大部分類再繼承default。如果有特殊的類需要特殊的包,只能在action中再指定父包了。 (10) <constant name="struts.convention.package.locators" value="action"/> <constant name="struts.convention.package.locators.disable" value="false"/> <constant name="struts.convention.package.locators.basePackage" value=""/> 確定搜索包的路徑。只要是結(jié)尾為action的包都要搜索。basePackage按照默認不用配置,如果配置,只會找以此配置開頭的包。locators及l(fā)ocators.basePackage都是一組以逗號分割的字符串。 (11) <constant name="struts.convention.exclude.packages" value="org.apache.struts.*,org.apache.struts2.*,org.springframework.web.struts.*,org.springframework.web.struts2.*,org.hibernate."/> 排除哪些包不搜索。按默認配置即可。逗號分割字符串。 (12) <constant name="struts.convention.action.includeJars" value="" /> 包括哪些jar包中的action。逗號分割字符串。 (13)<constant name="struts.convention.relative.result.types" value="dispatcher,freemarker,velocity"/> 默認返回的結(jié)果類型搜索。按順序先找相關(guān)的dispatcher的jsp文件是否存在。然后再找freemarker,再找velocity。 (14)<constant name="struts.convention.result.flatLayout" value="true"/> 如果此值設為true,如果一個action的命名空間為/login,名稱為HelloWorldAction。result返回值是success,默認會找到/WEB-INF/pages/login/hello_world.jsp(如果有hello_world_success.jsp就找這個文件,連接符“_”是在<constant name="struts.convention.action.name.separator" value="_"/>中配置的)。如果有一個action的result返回值是“error”,就會找/WEB-INF/pages /login/hello_world_error.jsp。 如果此值設為false,如果一個action的命名空間為/login,名稱為HelloWorldAction。result返回值是success,默認會找到/WEB- INF/pages/login/hello_world/index.jsp(如果有success.jsp就找這個文件)。如果有一個action的result返回值是“error”,就會找/WEB-INF/pages /login/hello_world/error.jsp。 (15) <constant name="struts.convention.action.mapAllMatches" value="false"/> <constant name="struts.convention.action.checkImplementsAction" value="false"/> <constant name="struts.mapper.alwaysSelectFullNamespace" value="false"/> <constant name="struts.convention.redirect.to.slash" value="true"/> 這幾個配置沒有太多的實際意義,本著最小檢查的原則就可以。 (16)默認攔截器配置,已經(jīng)簡化了許多,一般不需要chain和 fileupload。modelDriven也沒什么用,如果我們要使用restfull插件會有用。其實最簡單只要一個params就可以了。我加入 exception是為了開發(fā)時的異常。servletConfig是為了包裝一下request,reponse等對象,staticParams是為了可以配置${}形式參數(shù)。actionMappingParams是struts2.1新增的,我初步認為是可以在action配置中傳參數(shù),這個還有些疑問。 1.action配置 按照第一篇中的配置,設置locator為action,則默認一個類的命名空間為: 類路徑 命名空間 com.example.action.MainAction -> / com.example.action.products.Display -> /products 完整url為: 類路徑 完整url com.example.action.MainAction -> /main com.example.action.products.Display -> /products/display com.example.action.company.ShowCompanyDetailsAction -> /company/details/show_company_details 3.action類寫法 如果要實現(xiàn)基本零配置,命名空間和action名稱按照默認的類路徑搜索。默認執(zhí)行方法是execute。方法返回值是success,就會找。 Java代碼
返回結(jié)果對應: 類路徑 實際url(WEB-INF/pages) /hello_world /hello_world.jsp或/hello_world_success.jsp /hello_world!edit /hello_world_xyz.jsp /hello_world!delete /hello_world_depp.jsp 基本的零配置就是這樣,如果要有redirect、chain等跳轉(zhuǎn)方式,interceptor的配置,就要加入annotation。 2.action類中的annotation convention插件的annotation都在annotation這個包中,基本和xml配置相對應。 annotation 意義 作用域 ParentPackage 指定繼承的包(struts2)名 包,類 Namespaces 給一個action指定多個命名空間(一般不用) 包,類 Namespace 指定一個命名空間(一般不用,按默認的類路徑即可) 包,類,Namespaces注解中 actions 給一個action指定多個名稱 方法 action 指定一個action名稱 類,方法,actions注解中 InterceptorRefs 一組攔截器集合(只能是名稱,必須先在xml文件中配置好) 類,action注解中 InterceptorRef 一個攔截器 類,InterceptorRefs注解中 ResultPath 結(jié)果映射的路徑(已經(jīng)在struts.xml文件中指定) 包,類 Results 多個結(jié)果集合 類,action注解中 Result 一個結(jié)果定義 類,Results注解中 需要注意: 1)包(java)級別是在package-info.java這個文件中配置,可以配置整個包的定義,但是命名空間就不能按默認的類路徑掃描了,必須也要顯式的配置。所以這個文件最好不要使用。 2)不在包(java)級別配置就要在類(java)級別配置,可能每個類都要寫一句ParentPackage,指定繼承的包(struts2),以獲得父包中的攔截器配置及其他公用配置。如果沒有特殊的攔截器配置則不用配置ParentPackage。 3)最好把annotation配到類級別,因為這樣action名稱及命名空間都可用默認的類路徑掃描,不用我們顯式的配置。只要配置特殊的result和攔截器即可。類中的其他方法使用動態(tài)方法調(diào)用的方式使用同一個配置。我們知道struts2所有配置都是啟動加載到map中的,減少配置,會提高一定性能。而動態(tài)方法這種模式是零配置必不可少的,但我很不喜歡嘆號出現(xiàn)在地址欄中,所以在后面的文章中我會結(jié)合restful插件及自己修改源代碼實現(xiàn)意義上的動態(tài)方法調(diào)用,但形式上是別的方式,地址欄會比較美觀。 Java代碼
4)如果有極其特殊的情況需要為action的每個方法單獨配置,這時就不能用默認的類掃描了,必須是先配置action,指定名稱,再在action中配置result和interceptor。這就會使配置增多,違背的零配置的初衷。有時我們?yōu)榱诵阅茉蛳氚褦r截器細化到具體的action上,我想如果真是這樣,我們還不如再寫一個action類。還有struts2.1.6原有的配置是如果action名稱中有“/”,會認為這是一個命名空間,不會再按照類掃描的默認值。例如: Java代碼
調(diào)用方法execute的url為 : /different/url (命名空間已經(jīng)更換) 1.Convention插件的主要實現(xiàn)淺析 1.1 PackageBasedActionConfigBuilder 這個類最重要,是整個程序的入口。 1.1.1 buildActionConfigs方法進行初始化配置,其中findActions掃描類路徑,我沒有深入研究這個方法具體是怎么找到所有類的。只是找到全部類后,和我們的配置文件中限定的范圍匹配、過濾,存入一個set中。然后buildConfiguration(set)循環(huán)分析這些類。 1.1.2 buildConfiguration方法,首先創(chuàng)建一個map類型的packageConfigs。鍵為包(struts2)名,值為PackageConfig.Builder對象,這個對象可以創(chuàng)建PackageConfig對象。 然后循環(huán)找到的類,分析包名(java),determineActionNamespace方法分析命名空間,得到一個list對象。 再循環(huán)所有命名空間,determineActionName方法分析類名稱、類的默認方法(這個是寫死在程序中的,就是execute方法)。 getPackageConfig方法分析得到PackageConfig.Builder對象。 getActionAnnotations方法分析得到action類方法的annotation配置。 循環(huán)每個方法的配置,調(diào)用createActionConfig方法分析,把results,interceptors,exceptionMappings等配置放入ActionConfig.Builder對象,再把ActionConfig對象(由ActionConfig.Builder生成)放入PackageConfig.Builder中。 buildIndexActions創(chuàng)建默認索引action。這個好像用處不大。 最后把PackageConfig對象放入Configuration對象中,這是最頂級的配置。我們在任何時間和地點都可以得到Configuration對象,并對其進行分析。 1.1.3 determineActionNamespace方法是確定一個action類在web應用中的命名空間,先找這個類的Namespace注解,找到后放入一個存儲命名空間的list。再找Namespaces注解,一個action可以有多個命名空間。如果有注解則按照注解來確定一個action的命名空間,如果沒有,則分析這個action所在包(java)的路徑,按照struts2.xml中配置的規(guī)則來確定。這個規(guī)則就是截取到定義的locator,在這個locator之后的包(java)全部作為命名空間,類名作為action名稱。 1.1.4 determineActionName方法是確定一個action類在web應用中的名稱。由ActionNameBuilder(接口)的方法來實現(xiàn),這個接口的具體實現(xiàn)類,插件默認為SEOActionNameBuilder。被稱為搜索引擎友好的名稱。會把action類的name按單詞分解,然后用連接符連起來。默認連接符是"-",我們可以設置為"_"。 1.1.5 getPackageConfig方法是確定一個action類在web應用中的繼承的包(struts2)。先找這個類的ParentPackage注解,如果有注解則按照注解來確定一個action的父包(struts2),如果沒有,按照struts.xml中配置的規(guī)則來確定。這個規(guī)則就是defaultParentPackage。得到父包(struts2)后要拼成: actionPackage + "#" + parentPkg.getName() + "#" + actionNamespace 的形式,這是xwork里的規(guī)定。 1.1.6 getActionAnnotations方法是確定一個action類的方法上的annotation配置。先找方法的Actions注解,一個方法可以有多個action映射。再找Action注解,放入一個map中,鍵是方法名,值是存儲一組acton映射的list對象。 1.1.7 createActionConfig方法構(gòu)造ActionConfig.Builder對象,逐一判斷interceptors,results,exceptionMappings,都是從類一級開始判斷是否有此注解,再從方法的action注解中尋找。InterceptorMapBuilder,ResultMapBuilder是兩個接口,提供通過注解構(gòu)造Interceptor和Result的方法,插件分別提供了默認的實現(xiàn)DefaultInterceptorMapBuilder和DefaultResultMapBuilder。而buildExceptionMappings只是本類中的一個方法。 1.2 DefaultInterceptorMapBuilder 先找action類是否存在InterceptorRefs注解,再看是否存在InterceptorRef注解,再看action注解中是否定義了InterceptorRefs。 還用到了StringTools的createParameterMap方法把注解中的params(形式為{key1,value1,key2,value2,......})轉(zhuǎn)化成一個map。 buildInterceptorList方法利用了xwork中的InterceptorBuilder的一個靜態(tài)方法constructInterceptorReference把攔截器注入到配置中。 而一個action所繼承的父包中的攔截器,或是默認攔截器,并不在這個類中構(gòu)造。而是由xwork根據(jù)包(struts2)的繼承關(guān)系加載(actionPackage + "#" + parentPkg.getName() + "#" + actionNamespace 這是xwork里規(guī)定的形式,已經(jīng)由PackageBasedActionConfigBuilder 配置)。 1.3 DefaultResultMapBuilder 1.3.1 build方法,確定defaultResultPath,構(gòu)造包含ResultConfig的map對象,再通過擴展名獲得一個包含ResultTypeConfig的map對象。createFromResources方法獲得默認返回結(jié)果頁面,然后查找action注解中的results配置,再找類級別的Results注解,再找類級別的Result注解,相同的肯定會覆蓋。createFromAnnotations。 1.3.2 createFromResources方法中使用servletContext.getResourcePaths方法尋找頁面。如果struts.xml中配置flatLayout為true則直接找到以命名空間為名稱的文件夾,在此文件夾中尋找頁面,如果flatLayout為false,則會找到以命名空間為名稱的文件夾,再找到此文件夾中的以action名稱命名的子文件夾,在這個文件夾中尋找頁面。 1.3.3 makeResults方法找默認的返回頁面,如果沒有路徑?jīng)]有包含resultcode(定義的字符串)的頁面,則按默認順序?qū)ふ襰uccess,input,error。比如hello_world.jsp文件(flatLayout為true,連接符為"_"),如果沒有hello_world_success.jsp,hello_world_input.jsp,hello_world_error.jsp文件,同時"success","input","error",又沒有顯式的配置,只是作為結(jié)果字符串返回,則程序默認會用hello_world.jsp來匹配三種結(jié)果。如果結(jié)果字符串resultcode是"edit",同時又沒有顯式的配置,則必會找hello_world_edit.jsp。 1.3.4 createFromAnnotations這個方法就是把注解轉(zhuǎn)換成ResultConfig配置。 1.4 ConventionsServiceImpl 是result配置的輔助類。determineResultPath方法先判斷struts.xml文件中的配置,再判斷action類的注解中是否有ResultPath,如果有將覆蓋struts.xml中配置。 getResultTypesByExtension方法提供一個map對象,默認的result結(jié)果返回。 其實通過看這些方法,我們也基本了解了struts2的整個配置過程,非常繁瑣,很多的判斷確實很耗費資源,使用xml配置也一樣。我們也知道所有配置信息都是應用啟動時加載,存入map中常駐內(nèi)存。所以我們應該盡可能減少配置,多使用動態(tài)方法調(diào)用。 1.5 ConventionUnknownHandler 是UnknownHandler接口的一個實現(xiàn),用來處理找不到相應配置的情況。在struts2.1的dtd中新增了一個<unknown-handler-stack>元素,可以配置一組handler。 handleUnknownAction方法處理找不到action的情況。這個我感覺用處不大。 handleUnknownResult方法處理找不到result的情況。這個方法可以有很多擴展。比如我想定義一種返回值形式:redirect->xxx.do?ad=12或chain->xxx.do。用這種形式比寫注解要方便的多。 handleUnknownActionMethod方法處理找不到action中方法的情況。這個默認沒有實現(xiàn)。 |