struts簡介
Struts是Apache軟件基金會(ASF)贊助的一個開源項目。它最初是jakarta項目中的一個子項目,并在2004年3月成為ASF的頂級項目。它通過采用JavaServlet/JSP技術(shù),實現(xiàn)了基于JavaEEWeb應(yīng)用的MVC設(shè)計模式的應(yīng)用框架,是MVC經(jīng)典設(shè)計模式中的一個經(jīng)典產(chǎn)品。
Struts發(fā)展歷史
Struts是作為ApacheJakarta項目的組成部分,項目的創(chuàng)立者希望通過對該項目的研究,改進(jìn)和提高JavaServerPages、servlet、標(biāo)簽庫以及面向?qū)ο蟮募夹g(shù)水準(zhǔn)。
Struts這個名字的來源于在建筑和舊式飛機(jī)中使用的支持金屬架。之所以這個框架叫做“struts”,是為了提醒我們記住那些支撐我們房屋,建筑,橋梁,甚至我們踩高蹺的基礎(chǔ)支撐。這也是解釋struts在開發(fā)web應(yīng)用程序中所扮演的角色的精彩描述。
Struts的含義是”支柱,枝干”,它的目的是為了減少程序開發(fā)的時間,項目的創(chuàng)建者認(rèn)為JSP,servlet的存在雖然可以幫助用戶解決大部分問題,但是由于它們的編碼對項目的開發(fā)帶來了許多的不方便,可重用性也差,所以struts應(yīng)運而生,幫助用戶在最短的時間內(nèi)解決這些問題。Struts框架提供如下服務(wù):
(1)作為控制器的Servlet。
(2)提供大量的標(biāo)簽庫。
(3)提供了用于國際化的框架,利用不同的配置文件,可以幫助用戶選擇合適自己的語言。
(4)提供了JDBC的實現(xiàn),來定義數(shù)據(jù)源和數(shù)據(jù)庫連接池。
(5)XML語法分析工具。
(6)文件下載機(jī)制。
Struts原理
Struts是對JSPModel2設(shè)計標(biāo)準(zhǔn)的一種實現(xiàn),下面分別從模型(Model)、視圖(view)和控制器3個部分介紹Struts的體系結(jié)構(gòu)和工作原理。調(diào)用流程如下所示。
(1)視圖(view)
在Struts中,視圖層包含兩個部分,JSP頁面和ActionForm。
ActionForm封裝了用戶提交的表單信息,其實ActonForm本質(zhì)上就是JavaBean,這些JavaBean中沒有業(yè)務(wù)邏輯,只提供了所有屬性的getter和setter方法,這些屬性和用戶表單中的輸入項是一一對應(yīng)的。在Struts中就是通過ActionForm把用戶表單信息提交給控制器。
JSP頁面是經(jīng)典MVC中主要的視圖組件,主要是信息顯示和控制器處理結(jié)果顯示的功能。
除了以上,struts還提供了一個強(qiáng)大的struts標(biāo)簽庫,來幫助用戶解決顯示邏輯,并且利用ActonForm組件將信息傳遞到控制層。
(2)控制器(Controller)
在控制層,struts提供了一個控制器組件ActionServlet,它繼承自HttpServlet,并重載了HttpServlet的doGet(),doPost()方法,可以接受HTTP的響應(yīng),并進(jìn)行轉(zhuǎn)發(fā),同時還提供了使用XML進(jìn)行轉(zhuǎn)發(fā)Mapping(映射)的功能。
(3)模型(Model)
模型表示狀態(tài)和業(yè)務(wù)邏輯的處理,在一般的web應(yīng)用程序中,用JavaBean或者EJB來實現(xiàn)系統(tǒng)的業(yè)務(wù)邏輯。在Struts中,struts提供Action對象,來管理業(yè)務(wù)邏輯的調(diào)用,幫助用戶分離業(yè)務(wù)邏輯,也就是說struts本身不實現(xiàn)業(yè)務(wù)邏輯,但可以調(diào)用已完成的業(yè)務(wù)邏輯。
Struts工作流程
Struts工作流程如下所示。
ActionServlet是struts中核心的控制器,所有的用戶請求都必須通過ActionServlet的處理,而struts-config.xml是struts中核心的配置文件,在這個文件中配置了用戶請求URL和控制器Action的映射關(guān)系,ActionServlet通過這個配置文件把用戶的請求發(fā)送到對應(yīng)的控制器中。
在struts web應(yīng)用程序中,當(dāng)web應(yīng)用程序啟動的時候,就會初始化ActionServlet在初始化ActionServlet的時候會加載struts-config.xml配置文件,在加載成功后會把這些URL和控制器映射關(guān)系存放在ActionMapping對象或者其他對象中。當(dāng)ActionServlet接收到用戶請求的時候,就會按照下面的流程對用戶請求進(jìn)行處理。
(1)ActionServlet接收到用戶的請求后,會根據(jù)請求URL尋找匹配的ActionMapping對象,如果匹配失敗,說明用戶請求的URL路徑信息有誤,所以返回請求路徑無效的信息,當(dāng)找到匹配的ActionMapping的時候,進(jìn)入到下一步。
(2)當(dāng)ActionServlet找到匹配的ActionMapping對象的時候,會根據(jù)ActionMapping中的映射信息判斷對應(yīng)的ActionForm對象是否存在,如果不存在對應(yīng)的ActionForm對象就創(chuàng)建一個新的ActionForm對應(yīng),并把用戶提交的表單信息保存到這個ActionForm對象中。
(3)在struts-config.xml中這個配置文件,可以配置表單是否需要驗證,如果需要驗證,就調(diào)用ActionForm中的validate()方法對用戶輸入的表單進(jìn)行驗證。
(4)如果ActionForm的validate()方法返回了ActionErrors對象,則表明驗證失敗,ActionServlet把這個頁面返回到用戶輸入的界面,提示用戶重新輸入。如果方法的返回值為null,就表明驗證已經(jīng)通過,可以進(jìn)入下一步處理。
(5)ActionServlet可以根據(jù)ActionMapping對象查找用戶請求轉(zhuǎn)發(fā)給哪個控制器Action,如果對應(yīng)的Action對象不存在,就創(chuàng)建這個對象,并調(diào)用這個Action的excute()方法。
(6)業(yè)務(wù)邏輯控制器Action的execute()方法就會返回一個ActionForward對象,ActionServlet把控制器處理的結(jié)果轉(zhuǎn)發(fā)到ActionForward對象指定的JSP頁面。
(7)ActionForward對象指定的JSP頁面根據(jù)返回的處理結(jié)果,用合適形式把服務(wù)器處理的結(jié)果展示給用戶,到這里為止,一個客戶請求的整個過程完畢。
以上初步struts框架進(jìn)行了介紹,和對原理進(jìn)行了簡單的分析。至于struts是如何實現(xiàn)MVC的,ActionServlet屬于Controller部分,Action和ActionForm屬于Model層,還是Action屬于Controller層,不同的人對struts有不同的理解。接下來真正的運用到實踐中,在實踐中深刻去體會,原理固然重要,重要的是運用,是能駕馭和使用這個框架。就像學(xué)習(xí)開車一樣,不是一蹴而就的。
MVC向struts MVC框架演變過程
版本一 基本的MVC
首先是創(chuàng)建一個jsp索引頁面index.jsp,index.jsp創(chuàng)建一個表單。
如果我們完成添加用戶的模塊,我們提交表單的時候要把這個表單提交給一個servlet,于是我們在src自己的包中建立自己的servlet繼承HttpServlet,TestServlet同時覆蓋父類的doGet和doPost方法。
提交的的表單信息,我們可以在TestServlet中通過request.getParameter(“username”);取出來。之后把從表單中的數(shù)據(jù)提交到數(shù)據(jù)庫,我們的servlet作為控制器沒有添加到數(shù)據(jù)庫的職責(zé),于是我們把訪問數(shù)據(jù)庫的業(yè)務(wù)邏輯放到UserManager.java類中。在這個類中實現(xiàn)添加用戶的任務(wù)。
1
2
3
4
5
6
7
8
9
|
package com.bjpowernode.servlet; public class UserManager { public void add(String username) { System.out.println( "UserManager.add()-->>" + username); } } |
在TestServlet中調(diào)用UserManager中的方法。同時讓頁面轉(zhuǎn)向到添加成功的頁面add_success.jsp。
TestServlet代碼以及在web.xml中配置TestServlet.
1
2
3
4
5
6
7
8
9
10
11
|
<servlet> <servlet-name>TestServlet</servlet-name> <servlet- class >com.bjpowernode.servlet.TestServlet</servlet- class > </servlet> <servlet-mapping> <servlet-name>TestServlet</servlet-name> <url-pattern>/servlet/TestServlet</url-pattern> </servlet-mapping> |
Testservlet代碼如下所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
package com.bjpowernode.servlet; import java.io.IOException; import java.util.List; import javax.servlet.ServletException; importjavax.servlet.http.HttpServlet; importjavax.servlet.http.HttpServletRequest; importjavax.servlet.http.HttpServletResponse; public class TestServlet extendsHttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String username = request.getParameter(“username”); UserManager userManager = new UserManager(); userManager.add(username); request.getRequestDispatcher(“/add_success.jsp”).forward(request,response); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request,response); } } |
索引頁面index.jsp代碼,通過servlet/TestServlet來轉(zhuǎn)到TestServlet。
1
2
3
4
|
<form action= "servlet/queryUser.action" method= "post" > 姓名:<input type= "text" name= "username" ><br> <input type= "submit" value= "提交" ><br> </form> |
這樣采用MVC完成了用戶的添加任務(wù)。
版本二 通過判斷標(biāo)識變量
當(dāng)我們不僅要完成用戶的添加而是要完成用戶的增刪改查的時候我們可以在servlet中進(jìn)行判斷,是添加還是刪除等。并且在傳遞字符串的時候要加上一個標(biāo)識表示相應(yīng)的操作。這樣在servlet中會有很多的if和else語句。
版本三 servlet模式匹配 截取字符串判斷
我們可以在配置servlet的時候配置成為匹配模式<url-pattern>*.action</url-pattern>任何的*.action的都會轉(zhuǎn)到到TestServlet,這樣我們可以截取URL來判斷轉(zhuǎn)到哪個頁面。但在TestServlet中仍然有很多的ifelse語句進(jìn)行判斷。這樣我們的TestServlet代碼如下所示。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
|
import java.io.IOException; import java.util.List; importjavax.servlet.ServletException; importjavax.servlet.http.HttpServlet; importjavax.servlet.http.HttpServletRequest; importjavax.servlet.http.HttpServletResponse; public class TestServlet extendsHttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //截取url. String requestURI = request.getRequestURI(); //System.out.println("requestURI="+ requestURI); //截取http://localhost:8080/test_servlet/servlet/addUser.action test_servlet后面的東西。 String path = requestURI.substring(requestURI.indexOf( "/" , 1 ),requestURI.indexOf( "." )); //截取得到虛目錄。/servlet/addUser System.out.println( "path=" + path); String username = request.getParameter( "username" ); UserManager userManager = new UserManager(); String forward = "" ; //判斷截取的path和哪個要加載的頁面相等. if ( "/servlet/delUser" .equals(path)) { userManager.del(username); forward = "del_success.jsp" ; //request.getRequestDispatcher("/del_success.jsp").forward(request,response); } else if ( "/servlet/addUser" .equals(path)) { userManager.add(username); forward= "add_success.jsp" ; //request.getRequestDispatcher("/add_success.jsp").forward(request,response); } else if ( "/servlet/modifyUser" .equals(path)) { userManager.modify(username); forward= "modify_success.jsp" ; //request.getRequestDispatcher("/modify_success.jsp").forward(request,response); } else if ( "/servlet/queryUser" .equals(path)) { List userList = userManager.query(username); request.setAttribute( "userList" ,userList); forward= "query_success.jsp" ; //request.getRequestDispatcher("/query_success.jsp").forward(request,response); } else { throw new RuntimeException( "請求失敗!" ); } request.getRequestDispatcher(forward).forward(request,response); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throwsServletException, IOException { doGet(request,response); } } |
UserManager代碼如下所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
package com.bjpowernode.servlet; import java.util.ArrayList; import java.util.List; public class UserManager { public void add(String username) { System.out.println( "UserManager.add()-->>" + username); } public void del(String username) { System.out.println( "UserManager.del()-->>" + username); } public void modify(String username) { System.out.println( "UserManager.modify()-->>" + username); } public List query(String username) { System.out.println( "UserManager.query()-->>" + username); List userList = new ArrayList(); userList.add( "a" ); userList.add( "b" ); userList.add( "c" ); return userList; } } |
同時建立查詢成功,刪除成功,修改成功jsp頁面。
這樣在index.jsp頁面中通過表單的action屬性來轉(zhuǎn)到相應(yīng)的頁面。
1
2
3
4
5
6
7
|
<body> <form action= "servlet/queryUser.action" method= "post" > 姓名:<input type= "text" name= "username" ><br> <input type= "submit" value= "提交" ><br> </form> </body> |
版本三的缺點是if太多,不穩(wěn)定,當(dāng)我們添加或者刪除一個if的時候還需要重新編譯源程序。這樣無法適應(yīng)變化的需求。所以我們在此基礎(chǔ)上進(jìn)行改進(jìn)就需要去掉if語句,可以把改變的部分分離出來,變成可配置的,這樣就變得靈活的,需要改動jsp的文件名,在配置文件中配置一下就可以了。
版本四 if else抽象出接口和類+配置文件
首先我們把轉(zhuǎn)到的地址字符串放到一個字符串變量中,這樣TestServlet中的doGet方法,代碼如下。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
String username = request.getParameter( "username" ); UserManager userManager = new UserManager(); String forward = "" ; //判斷截取的path和哪個要加載的頁面相等. if ( "/servlet/delUser" .equals(path)) { userManager.del(username); forward= "del_success.jsp" ; } else if ( "/servlet/addUser" .equals(path)) { userManager.add(username); forward= "add_success.jsp" ; }elseif( "/servlet/modifyUser" .equals(path)) { userManager.modify(username); forward= "modify_success.jsp" ; } else if ( "/servlet/queryUser" .equals(path)) { List userList = userManager.query(username); request.setAttribute( "userList" ,userList); forward= "query_success.jsp" ; } else { thrownew RuntimeException( "請求失敗!" ); } request.getRequestDispatcher(forward).forward(request,response); |
統(tǒng)一完成了轉(zhuǎn)向。當(dāng)if語句是滿足某種條件,條件不同的時候轉(zhuǎn)到不同的頁面,添加有添加的邏輯,修改有修改的邏輯,這樣我們可以抽象出接口。對添加的action操作添加,對刪除的action做出刪除任務(wù),對修改的action做出修改。把每一個if語句中的變成為類去實現(xiàn),抽象出一個Action接口,接口中方法execute()方法,返回轉(zhuǎn)向路徑字符串。類圖如下所示。
共同的Action接口,不同的實現(xiàn)。
Action接口代碼。
1
2
3
4
5
6
7
8
9
10
|
package com.bjpowernode.servlet; importjavax.servlet.http.HttpServletRequest; importjavax.servlet.http.HttpServletResponse; public interface Action { public String execute(HttpServletRequest request, HttpServletResponse response) throws Exception; } |
AddUserAction代碼。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
package com.bjpowernode.servlet; importjavax.servlet.http.HttpServletRequest; importjavax.servlet.http.HttpServletResponse; public class AddUserActionimplements Action { @Override public String execute(HttpServletRequest request,HttpServletResponse response) throws Exception { String username = request.getParameter( "username" ); //Stringsex = request.getParameter("sex"); //........... //調(diào)用業(yè)務(wù)邏輯. UserManager userManager = new UserManager(); userManager.add(username); return "/add_success.jsp" ; } } |
DelUserAction代碼。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
package com.bjpowernode.servlet; importjavax.servlet.http.HttpServletRequest; importjavax.servlet.http.HttpServletResponse; public class DelUserActionimplements Action { @Override public String execute(HttpServletRequest request,HttpServletResponse response) throws Exception { String username = request.getParameter( "username" ); //String sex = request.getParameter("sex"); //........... //調(diào)用業(yè)務(wù)邏輯. UserManager userManager = new UserManager(); try { userManager.del(username); } catch (Exceptione) { return "del_error.jsp" ; } return "/del_success.jsp" ; } } |
ModifyUserAction代碼。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
package com.bjpowernode.servlet; importjavax.servlet.http.HttpServletRequest; importjavax.servlet.http.HttpServletResponse; public class ModifyUserActionimplements Action { @Override public String execute(HttpServletRequest request,HttpServletResponse response) throwsException { String username = request.getParameter( "username" ); //String sex = request.getParameter("userId"); //...........等其他... //調(diào)用業(yè)務(wù)邏輯. UserManager userManager = new UserManager(); userManager.modify(username); return "/modify_success.jsp" ; } } |
QueryUserAction代碼。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
package com.bjpowernode.servlet; import java.util.List; importjavax.servlet.http.HttpServletRequest; importjavax.servlet.http.HttpServletResponse; public class QueryUserActionimplements Action { @Override public String execute(HttpServletRequest request,HttpServletResponse response) throwsException { String username = request.getParameter( "username" ); //Stringsex = request.getParameter("userId"); //其他查詢條件. //...........等其他... //調(diào)用業(yè)務(wù)邏輯. UserManager userManager = new UserManager(); List userList = userManager.query(username); request.setAttribute( "userList" ,userList); return "/query_success.jsp" ; //轉(zhuǎn)向路徑都可以通過配置文件讀取。 } } |
這樣使用多態(tài)方式調(diào)用不同的Action,轉(zhuǎn)向代碼如下所示。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
//用多態(tài)的方式. Action action = null ; if ( "/servlet/delUser" .equals(path)) { action= new DelUserAction(); } else if ( "/servlet/addUser" .equals(path)) { action= new AddUserAction(); } else if ( "/servlet/modifyUser" .equals(path)) { action= new ModifyUserAction(); } else if ( "/servlet/queryUser" .equals(path)) { action= new QueryUserAction(); } else { throw new RuntimeException( "請求失敗!" ); } //取得action后傳遞過去。動態(tài)調(diào)用ACtion中的execute方法。 String forward = null ; try { forward= action.execute(request,response); } catch (Exception e) { e.printStackTrace(); } //根據(jù)路徑完成轉(zhuǎn)向。 request.getRequestDispatcher(forward).forward(request, response); |
上述調(diào)用不同的action,我們可以把不同的請求和對應(yīng)的action配置到自己的xml文件中。配置哪個請求對應(yīng)哪個Action。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
<action-config> <action path= "/servlet/delUser" type= "com.bjpowernode.servlet.DelUserAction" > <forward name= "success" >/del_success.jsp</forward> <forward name= "error" >/del_error.jsp</forward> </action> <action path= "/servlet/addUser" type= "com.bjpowernode.servlet.AddUserAction" > <forward name= "success" >/add_success.jsp</forward> <forward name= "error" >/add_error.jsp</forward> </action> <action path= "/servlet/modifyUser" type= "com.bjpowernode.servlet.ModifyUserAction" > <forward name= "success" >/modify_success.jsp</forward> <forward name= "error" >/modify_error.jsp</forward> </action> <action path= "/servlet/queryUser" type= "com.bjpowernode.servlet.QueryUserAction" > <forward name= "success" >/query_success.jsp</forward> <forward name= "error" >/query_error.jsp</forward> </action> </action-config> |
這樣我們就可以讀取配置文件來進(jìn)行相應(yīng)的操作。每個action對應(yīng)著一些信息,它的class,執(zhí)行成功以及執(zhí)行失敗的配置。我們在讀取配置文件的時候可以把這些放到Map對象中。代碼如下所示。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
|
ActionMapping { private String path; private String type; Map forwardMap; } forwardMap { key= "success" ; value= "/del_success.jsp" key= "error" ; value= "/del_error.jsp" ; } Map map = new HashMap(); map.put( "/servlet/delUser" ,); map.put( "/servlet/addUser" ,); map.put( "/servlet/modifyUser" ,); map.put( "/servlet/queryUser" ,); //如果是刪除的ActionMapping存儲如下: actionMapping { path= "/servlet/delUser" ; type= "com.bjpowernode.servlet.DelUserAction" ; forwardMap { key= "success" ,value= "/del_success.jsp" ; key= "error" ,value= "/del_error.jsp" ; } } String path = "/servlet/delUser" ; //根據(jù)截取的URL請求,到Map中取得本次請求對應(yīng)的Action。 ActionMapping actionMapping =(ActionMappint)map.get(path); // 取得本次請求對應(yīng)的Action類的完成路徑。 String type =actionMappint.getType(); //com.bjpowernode.servlet.DelUserAction //通過反射動態(tài)實例化Action Aciton action =(Action) class .forName(type).newInstance(); //取得action后傳遞過去。動態(tài)調(diào)用ACtion中的execute方法。 String forward =action.execute(request,response); //根據(jù)路徑完成轉(zhuǎn)向。 request.getRequestDispatcher(forward).forward(request,response); |
我們用時序圖來描述上述調(diào)用過程(如圖)。
Struts就是采用上述思路。Struts框架是把上圖中前端控制器servlet進(jìn)行了封裝,我們只要繼承struts的Action去調(diào)用業(yè)務(wù)邏輯和轉(zhuǎn)向。讀取配置文件的工作也是封裝到了struts框架中。前篇講解了struts框架的應(yīng)用實例,我們可以進(jìn)行對比。