1、什么是框架?
框架就是一組程序的集合,本質上是一組jar包的集合,jar包中存有class文件或一些資源文件。框架的誕生是為了體能幫助程序員快速進行項目的開發,提供一些輔助性的、便捷性的開發API,該API中已經對現有的編程語言sdk及一些功能進行了封裝,程序員只需要遵循框架的一些約定,即可調用該API快速開發出符合業務需求的功能及程序。
2、Java的三層結構與框架
Java開發中的分層結構為:
表現層,也叫action層/包,負責處理與界面交互的相關操作,主要框架有Struts2,Spring MVC;
業務層,也叫service層/包,負責復雜的業務邏輯計算和判斷,主要框架有Spring(繼承了事務處理的功能);
持久層,也叫dao層/包,負責將業務邏輯數據進行持久化存儲,主要框架有Hibernate,MyBatis等;
我們一般將用于表現層的框架叫做MVC框架。
3、MVC模型
MVC是一種程序設計的思想模型。將整個程序的功能縱向拆分為數據模型Model,視圖View,控制器Controller。控制器是連接模型和視圖的橋梁,通過控制器來更新模型對應的數據,并將模型的數據展示在視圖中。
4、Struts2的發展歷史
由于早期的程序開發是基于Servlet+JSP+JavaBean最原始的開發模式,基本上是按部就班地開發,效率比較低下。
后來Apache組織推出了基于MVC模式的輕量級Web應用框架Apache Struts1,該結構將整個程序開發的代碼結構進行了合理的劃分,提供了一些比較實用的功能,如驗證,國際化等。一經推出,迅速流行。
但是經過開發者的實踐,發現Struts1存在一些問題,比如線程不安全,靈活性低,ServletAPI耦合性高,頁面間傳值復雜等。
后來又有一些新的框架,如SpringMVC和OpenSymphony的WebWork, 他們能夠避免Struts1中的一些缺點,從而比Struts1更能讓開發者接受。
當然作為MVC框架的發起者Apache組織也不甘落后,在WebWork框架的基礎之上借鑒了Struts1的優點重新開發了一個新的Struts框架,即Struts2框架,該框架是一個輕量級的Web應用框架,該框架不兼容Struts1框架。
5、Struts2框架的核心功能介紹
6、Struts2框架的使用步驟
1)拷貝struts2下載包中/apps/struts2-blank/WEB-INF/lib目錄中的jar包到項目中的WEB-INF/lib目錄中,將這些jar包右鍵->add as Library即可。
struts運行必備jar包介紹:
struts2-core-2.3.4.jar, 核心類庫;
xwork-core-2.3.4.jar, Command模式框架,用于是一個容器,用于創建和銷毀bean對象;
ognl-3.0.3.jar, 對象圖導航語言,用于讀寫對象的屬性;
freemarker-2.3.18.jar, 用于編寫UI標簽的模板;
commons-logging-1.1.x.jar, 用于支持Log4j 和JDK的日志記錄;
commons-fileupload-1.2.2.jar, 文件上傳組件;
commons-io-2.0.1.jar, io讀寫操作(如文件傳輸)依賴的jar包;
commons-lang-2.5.jar, 對java.lang包的增強;
2)在web.xml中配置Struts2的前端控制器StrutsPreparedAndExecuteFilter:
struts2
struts2
/*
3)拷貝struts.xml文件到項目的資源文件目錄中;
4)定義一個類HelloAction,提供一個public String execute(){}的方法(方法名可以修改),在其中返回一個字符串,用于對應邏輯視圖的名稱,如login。注意定義Action實現類既可以使用POJO即普通的Java對象外,還可以實現
5)在struts.xml文件中對HelloAction類進行配置,以表示將其對象的創建和銷毀的管理交給Struts2的xwork容器去管理。
/pages/user/login.jsp
6) 在web根目錄/pages/user/下創建一個login.jsp文件。里面可以寫一句話:請登錄。
7) 部署web項目到tomcat服務器上,通過瀏覽器訪問HelloAction:
http://localhost:8080/webproject/pss/hello.action
7、Struts2的執行流程
1)web.xml文件中配置了Struts2的核心過濾器StrutsPrepareAndExecuteFilter,用于攔截所有的url請求。
2)在web項目啟動的時候,會執行StrutsPrepareAndExecuteFilter對象中的init(FilterConfig)方法,在該方法中將傳入的FilterConfig對象包裝為FilterHostConfig對象,創建并調用InitOperations對象中的initDispatcher(HostConfig)方法,在該方法中將HostConfig對象中的ServletContext對象和初始化參數Map對象包裝成一個Dispatcher對象,保存在StrutsPrepareAndExecuteFilter對象中,并根據Dispatcher對象分別創建PrepareOperations和ExecuteOperations對象。前者用來做一些初始化和清理操作,如createActionContext, cleanupRequest, cleanupDispatcher等方法;后者有executeAction方法,該方法用于調用dispatcher對象的serviceAction方法,傳入HttpServletRequest, HttpServletResponse, 以及ActionMapping對象。
3)瀏覽器通過url發起對web項目資源的訪問。如http://localhost:8080/webproject/pss/hello
4)當Tomcat收到請求后,經過一些包裝和處理后,會執行StrutsPrepareAndExecuteFilter對象中的doFilter方法,在該方法中先判斷請求的url是否是已排除的url,如果是,則直接調用FilterChain對象的doFilter(req, resp)方法進行放行;如果不是,則先調用PrepareOperations對象中的setEncodingAndLocale方法,該方法最終目的是使用指定編碼格式(默認utf-8)對request進行編碼(調用request.setCharacterEncoding(encoding)方法), 以及調用response.setLocale(Locale)方法。
然后調用PrepareOperations對象的createActionContext方法,在該方法中先通過ValueStackFactory創建ValueStack對象,調用該對象的getContext()方法拿到map成員變量,然后調用dispatcher對象的createContextMap方法,將返回的Map對象中的key-value值存入ValueStack對象中。
然后通過ValueStack對象中的map創建ActionContext對象,并將該對象保存到ActionContext中的靜態變量中。
調用PrepareOperations對象的wrapRequest(HttpServletRequest)方法將HttpServletRequest封裝為StrutsRequestWrapper對象,即子類對象。該對象中有一個boolean變量disableRequestAttributeValueStackLookup,用于在getAttribute(String)方法被調用時,決定是否不從ValueStack中取值。默認是false,即允許從ValueStack中取值。
調用PrepareOperations對象的findActionMapping(request,response,forceLookup)方法,從request對象中根據struts.actionMapping這個key拿到ActionMapping對象并返回。如果request中沒有該對象或者其為null,則通過dispatcher.getContainer().getInstance(ActionMapper.class).getMapping(request, dispatcher.getConfigurationManager())來創建出ActionMapping對象。
如果當前request對應的ActionMapping為null,經過ExecuteOperations對象execute.executeStaticResourceRequest(request, response)確認請求的是靜態資源對象(即非Action對象), 則直接調用FilterChain對象的doFilter(request,response) 對當前請求進行放行處理;
否則,調用ExecuteOperations對象的executeAction方法,在該方法中會先根據ActionMapping對象來決定是否要調用struts.xml中注冊的某個Action對象,如果是,則創建對應的ActionProxy(代理對象),并將請求的處理交給ActionProxy對象,ActionProxy則創建一個ActionInvocation的實例,由ActionInvocation實例來實現對Action實例中指定的業務方法進行調用。但在調用Action對象的前后會涉及到攔截器棧或攔截器對象的調用。
當Action被調用完畢,ActionInvocation會根據struts.xml中的配置找到對應的返回結果result,可以是JSP或html或其他Action鏈等。
注意:在整個struts框架中,所有的對象都是由xwork容器負責創建和管理的。
8、Struts2的配置相關的文件及其加載順序(由先到后):
2)struts-default.xml 在struts2-core-?.jar包中,主要包含了框架依賴對象的配置和結果類型,以及攔截器等配置;
3)struts-plugin.xml 文件存在于struts-?-plugin-?.jar包中,主要用于配置struts的框架,由框架提供;
4)struts.xml 文件,就是我們在struts2項目的source folder目錄中定義的文件。可以用來配置常量,package和Action,攔截器等;
5)struts.properties 文件,該文件可以由開發者創建并跟struts.xml文件放在同一個目錄中,可以用來覆蓋default.properties中的常量的定義;
6)web.xml文件,該文件是web項目中WEB-INF目錄下的重要配置文件。
注意:由于加載有先后順序,所以后面文件配置中的常量會覆蓋前面加載的文件中同名的常量。
9. struts2中常見常量介紹:
用于指定默認編碼集,用在request.setCharacterEncoding(String)方法中,以及freemarker、velocity等。
其中freemarker是模板引擎,是一種基于模板允許其中key的值變動,將其生成輸出的文本如html格式的文本的工具)。
其中velocity是一個基于java的模板引擎, 允許僅使用簡單的模板語言(template language)來引用由java代碼定義的對象。
指定請求資源的后綴名,如.action, 以及沒有后綴名等。
設置瀏覽器是否緩存靜態內容,默認為true。一般在生產環境下使用,而在開發階段為了測試,最好關閉。
當struts的配置文件(xml格式)修改后,系統自動重新加載,而不用重啟tomcat。默認為false。
struts.devMode = true
設置開發者模式,該模式下修改struts.xml文件后不需要重啟服務器即可生效。
設置默認的視圖主題樣式,simple指定為簡化的主題樣式。
struts.enable.DynamicMethodInvocation = false
設置是否支持動態方法調用。為true時,就可以在struts.xml配置“*”的通配符,來調用action里的方法。
例如:
/pages/success.jsp
/pages/error.jsp
調用時就可以通過url來傳遞方法名,即動態方法調用。如:
http://localhost:8080/webproject/pss/login_mymethod
表示系統會調用com.abc.action.LoginAction類的對象中的mymethod方法。該方法返回“success”時,跳轉到web根目錄下/pages/success.jsp文件;如果返回“error”時,跳轉到web根目錄/pages/error.jsp文件。
10、struts.xml文件的編寫規則
常量的定義:
如果要在里面定義常量,格式為:
包的定義:
如果要定義包,使用
節點來定義。
其中name屬性為包名,不同的包,name值應該不同。
extends屬性表示當前的包繼承自哪個包,一般都繼承自struts-default包。繼承了struts-default包,則表示當前包擁有了struts-default包所定義的資源,如返回結果類型,攔截器等。struts-default包是在struts-core-?.jar/struts-default.xml文件中定義。
namespace屬性表示定義了一個命名空間,一般以”/“開頭,如”/pss“。命令空間和節點的name屬性組合成了所在包下面的action對象的訪問路徑。如
則action資源的請求路徑為:http://服務器ip:tomcat端口號/項目的上下文路徑/包的namespace值/action的name值.action
abstract屬性用來將包定義為抽象的包,即只能用來被繼承,而里面沒有定義任何的節點,也就是沒有任何的action對象。
Action實現類的定義:
class屬性對應Action實現類的全限定名,默認是ActionSupport類;
method屬性指定要調用Action的業務方法;
節點定義在節點中或者中,用于配置業務方法執行結果對應跳轉或重定向到的視圖路徑。
格式為:
name屬性用來定義邏輯視圖名,對應于action中業務方法執行后返回的字符串,缺省為"success";
type屬性用來指定跳轉的類型,缺省是dispatcher表示請求轉發到jsp文件(頁面),redirect表示重定向到jsp文件,chain表示從一個action請求轉發到另一個action,redirectAction表示從一個action重定向到另一個action,stream表示返回流對象,用于文件下載;
如:/pages/ok.jsp
注意:如果要訪問不同命名空間下的action,例如要訪問”/pss“命名空間中的name值為hello的action,則在節點中,添加如下兩個節點:
/pss
hello
action的擴展:比如要執行name值為hello的action對象中的abc()業務方法,有三種做法:
1)直接在節點中配置method屬性的值為abc;
2)http://服務器ip:端口號/web項目上下文路徑/package namespace/hello!abc
此種方式需要配置常量:
3)使用通配符
則請求的url為:http://服務器ip:端口號/web項目上下文路徑/package namespace/hello_abc
11、如何在Action中獲取request,response,session,cookie等?
首先我們要明白在Servlet中的業務方法service方法中,已經通過參數將HttpServletRequest和HttpServletResponse兩個對象傳入進來了,我們可以直接用,通過request.getSession()就可以拿到Session對象,通過request.getCookies()就可以拿到所有的Cookie對象。
那么我們現在來看下,在Struts的Action中如何獲取上述四個對象呢?有兩種方式:
方式一:(有耦合,不推薦)通過實現Struts提供的感知接口來自動獲取,如ServletRequestAware,ServletResponseAware,SessionAware等接口。
之所以能通過感知接口拿到相關的Servlet對象,是因為在struts-core-?.jar包中的struts-default.xml文件中定義了servletConfig對應的類。
我們來看看ServletConfigInterceptor類的代碼:
方式二:直接通過ServletActionContext工具類中的靜態方法來獲取相關對象。
方式三:(強烈推薦)通過ActionContext對象中的方法來獲取。ActionContext對象本身代表著當前Action對象的上下文環境信息,也就是對應著一次請求的相關信息。
ActionContext.getContext() 獲取到ActionContext的對象,通過該對象調用getParameters()獲取到所有參數的Map集合。
ActionContext是線程安全的,里面定義了ThreadLocal actionContext對象,實現了將ActionContext對象與當前線程綁定在一起。
12、JSP頁面與Action組件間相互注入數據的幾種方式:
首先我們要明白,凡是請求中的參數,可以在Action中直接使用request.getParameter(key)或者request.getParamterValues(key)方式來獲取到參數的值或數組值。但是這種做法很麻煩,而且需要在Servlet中做類型轉換。有沒有更好的做法呢?當然是有,請看下文。
方式一:將Action看做一個Model對象,在Action中直接定義private的屬性名,提供對應的setter方法即可。
方式二:(推薦的方式)創建一個含有屬性的Model類,在Action中通過對該Model類提供屬性及getter和setter方法,頁面會通過ognl表達式對數據進行注入。
方式三:創建一個含有屬性的Model類,讓Action實現ModelDriven接口,該接口的的泛型類型為該Model類。在Action中創建一個Model類的對象作為屬性,在復寫的getModel()方法中,返回當前Action中的Model類的對象。
顯然方式一如果傳遞的數據個數比較多時,代碼比較臃腫;方式三需要實現ModelDriven接口,有耦合性;方式二顯然是最佳的使用方式。但在實際的開發中,經常是方式二和方式一的組合使用。
13、攔截器Interceptor
所謂的攔截器是指能夠對指定Action對象的調用進行動態攔截的組件,可以在調用之前和之后實施攔截并執行某段代碼,也可以完全阻止某個Action對象方法的調用。
攔截器主要的功能是將Action中的一些通用的功能提取出來,定義在攔截器中。當某個Action需要該功能時,只需要給該Action指定對應的攔截器即可。從而提高了代碼的可重用性,也實現類裝配式和可插拔式的體系結構,使得整個系統結構更加靈活。
攔截器的特點:1、簡化Action的實現,將重復的單一功能從Action中剝離出來;2、每個攔截器只實現某一種特定的功能;3、攔截器的出現實現了代碼的模塊化編程(可插拔式編程);4、攔截器整合了Actoin中重復的代碼(功能),提高了代碼的復用性。
攔截器棧Interceptor Stack,是將多個攔截器按照一定的順序連接形成的一條鏈。當給某個Action對象指定了攔截器棧,則在該Action對象方法被調用時,會按照攔截器棧中的先后順序來指定攔截的攔截方法。
struts內置的攔截器都定義在struts-core-?.jar包中的struts-default.xml文件中:
params攔截器,用于把請求參數設置到相應的Action對象的屬性中,并自動進行類型轉換;
modelDriven攔截器,用于將getModel()方法返回的模型對象存入到OgnlValueStack中。一般只需要讓Action實現ModelDriven接口,即可使得Action配置了該攔截器;
exception攔截器,用在拋異常的時候,用于捕獲異常;
validation攔截器,用于讀取項目中的*-validation.xml文件,并使得這些文件中聲明的校驗生效;
token攔截器,用于核對當前Action請求是否有效,防止重復提交Action請求;
fileUpload攔截器,用于處理文件上傳;
workflow攔截器,用于校驗,調用Action的validate方法,如果有錯誤,則重新定位到名為“input”的結果視圖;
servletConfig攔截器,用于通過感知接口,獲取感應的對象,如HttpServletRequest, HttpServletResponse,HttpSession等;
14、自定義一個攔截器
開發步驟:
2)定義一個Action,如HelloAction,實現了ActionSupport接口。
在struts.xml文件中配置如下,注意由于攔截器返回了login,所以也要配置。當然我們可以直接把login這個result配置到中,但是這樣的話,每個用到該攔截器的action都要配置login。顯然,我們應該將login配置為一個全局的result。
3)接下來就是對攔截器的配置,攔截器的配置其實也是在struts.xml文件中配置的:
首先要通過節點來定義攔截器,即指定對應的類和名字:
在上圖中,我們可以在節點中定義一個攔截器棧,在該棧中將默認的攔截器棧與我們所定義的攔截器組合成一個新的攔截器棧。注意順序,先是defaultStack,然后才是我們定義的攔截器。如上圖。
然后通過節點來指定該包下面的action都同一應用的攔截器。如:
注意:如果要對某一個action不使用我們自定義的攔截器棧,則需要在節點中聲明使用默認的攔截器棧。即:
15、國際化i18n
所謂的國際化,指的是讓程序能夠支持在不同的語言環境下顯示不同語言的內容。如在中文環境下顯示中文內容,在英文語言環境下顯示英文內容。
struts2支持國際化,是由于其內置有i18n的攔截器:
那么如何才能在項目中配置國際化的語言內容呢?
首先分別定義不同語言環境對應的properties文件,如文件名為constant,則中文環境下的properties文件全名應該為constant_zh_CN.properties,英文環境下的properties文件的全名為constant_en_US.properties。
然后在struts中配置國際化文件:
最后在jsp文件下要用的話,必須使用taglib命令引入struts標簽庫:
在需要從properties文件中拿常量的地方,使用即可拿到key對應的值。
注意:對于properties文件中key的值,如果要在action中拿到,需要action對象實現了ActionSupport接口,通過getText(key)即可拿到key在不同語言環境中的value值。
16、OGNL是什么?
Object Graphic Navigation Language對象圖導航語言,是一種表達式語言,可以理解為OGNL是EL表達式語言的一種升級版,通過簡單一致的表達式語法,可以存取對象的任意屬性,調用對象的方法,遍歷整個對象的結構圖,實現字段類型轉化等功能。使用相同的表達式去存取對象的屬性,取得對象中的數據。
該表達式是將對象的引用值用點串聯起來,從左到右,每一次表達式計算返回的結果成為當前對象,后面部分接著在當前對象上進行計算,一直到全部表達式計算完成,返回最后得到的對象。OGNL則針對這條基本原則進行不斷的擴充,從而使之支持對象樹、數組、容器的訪問,甚至是類似SQL中的投影選擇等操作。
例如,假設當前環境(對象)中的根對象是student,而student對象中有個scores數組,該數組中第一個值為總分,則可以通過ognl表達式:studet.scores[0]拿到總分的值。
OGNL支持+-*/運算符,是struts2默認的表達式語言;支持類的靜態方法的調用和值的訪問;支持賦值操作和表達式串聯;可以操作集合對象;可以直接new一個對象;
OGNL通常要結合Struts2的一些標志一起使用,如#,$,%;
其中#主要用來訪問ONGL上下文和action上下文,#代表ActionContext.getContext()。
如#parameters.id[0]相當于request.getParameterValues("id").get(0);
#request.userName相當于request.getAttribute("userName");
#session.userName相當于session.getAttribute("userName");
#application.userName相當于application.getAttribute("userName");
attr 用于按request > session > application順序訪問其屬性(attribute) #attr.userName相當于按順序在以上三個范圍(scope)內讀取userName屬性,直到找到為止;
構造Map,如#{'foo1':'bar1', 'foo2':'bar2'};
17、ValueStack值棧
本質上是Struts2的接口com.opensymphony.xwork2.util.ValueStack,Struts容器使用com.opensymphony.xwork2.ognl.OgnlValueStack來封裝每一次請求的數據,也就是說每一次請求都會創建一個新的ValueStack對象,在該對象中封裝了本次請求的相關數據信息。也意味著Struts2中的ValueStack對象可以使用ognl表達式來存取其中的數據。那么ValueStack對象可以通過request.getAttribute("struts.valueStack")的方式拿到,也可以通過調用ActionContext對象的getValueStack()方法來拿到。也就是意味著在request對象中有一個名為struts.valueStack的key,其對應的值為ValueStack對象。
我們先看看源碼:
其實OgnlValueStack對象的棧結構的實現,是由其里面的root變量來實現的。
我們可以在root中拿到當前Action的對象,在context中拿到作用域對象(request,session,application)的Map格式的對象及參數對象parameters等。
如果要獲取Action對象中的屬性的值,由于Action對象本身在root的棧頂位置,所以在jsp中可以通過來直接獲取;
注意:
1) 有一種情況會導致Action不在棧頂位置,例如當前Action中new了一個對象作為其屬性的值。此時,可以在context的key為params中拿到值。
2) 如果需要從root棧的棧頂拿值,則可以通過如下方式在jsp中拿到:
如果要獲取的值對應的key在ValueStack的context中,則在jsp中可以通過的方式獲取。
把數據放入ValueStack的root中:valueStack.getRoot().push(Object) ,valueStack.getRoot().add(0,Object),valueStack.set(key, Object),以及在Action中帶有getter方法的屬性。
把數據放入ValueStack的context中,valueStack.getContext().put(key, value)或者ActionContext.getContext().put(key, value)。
注意:在jsp中如果需要查看valueStack中的所有數據,則可以添加如下代碼:
18、輸入校驗
輸入校驗,一般用于對jsp頁面在提交(發出請求)時,在被請求的Action中執行validate()方法,開發者只需要在該方法中編寫驗證代碼即可。如果驗證不通過,只需要調用super.addFileError("字段名稱","錯誤信息")即可在Action中業務方法被調用之前終止當前的請求。同時,需要在struts.xml文件中配置名為input的result,用來指定當輸入校驗失敗時,需要跳轉到的頁面。
如果某些業務方法不需要被校驗,則可以通過在該方法上添加注解@SkipValidation即可。
如果只對Action中的業務方法abc()進行調用前的校驗,則validate()方法的方法名改為validateAbc()即可。
如果需要在某個方法被校驗失敗時跳轉到的不是input視圖,而是其它視圖,比如abc視圖。則可以在被校驗的方法上通過注解來更改校驗失敗的視圖名。如@InputConfig(resultName="abc")。
注意:
validation攔截器已經對input方法作了特殊處理,不會對其進行校驗處理。
validation攔截器只是在調用input方法時執行了validate或validateXxx方法,當校驗失敗時,由該validate方法提供調用addFiledError()方法。在validation攔截器之后有個workflow攔截器,該攔截器會檢查FiledError中是否有錯誤數據,如果有,就會跳轉到input結果碼對應的頁面。
19、文件上傳
使用struts2提供的文件上傳組件相對來說比較容易些。
首先,在jsp頁面中,引入struts2的標簽庫struts-tag,然后在需要上傳文件的地方使用標簽來讓用戶選擇文件:
在提交到的Action中,定義三個屬性,并提供對應的setter方法,然后在業務方法如execute中進行文件的copy操作即可。然后在struts.xml文件中注冊此Action。
注意:文件上傳要求jsp中form的請求時post,且enctype="multipart/form-data"值。struts2的form標簽的method屬性的默認值就是post。
我們還可以通過參數注入的方式來設置FileUploadInterceptor攔截器的允許上傳文件最大值和允許上傳文件類型這兩個參數。
具體設置如下:
1024*1024*5
png,jpg
20、文件下載