SSH框架開發之控制層(Struts2)

前言:###

很多Java開發人員覺得SSH框架開發沒有SSM框架開發那么得心應手,從入門程度來講的確是這樣,那么試問你是愿意用很多人都會的,還是愿意用不太好掌握的、用的人相對較少的呢?
我們必須清楚,這個社會就是這樣,“可替代性越普遍你被別人替代的可能就越高”。這,是不爭的事實。
如果你想了解SSH框架,不妨先看看我這篇關于Struts2框架文章;如果你還是一名在校軟件工程系的大學生,那么想不想面試的時候加分呢?那么,請慢慢看下去:

配圖.jpg

步驟:

一、Struts和Struts2的背景

二、Struts2的工作原理

三、案例:利用Struts2接收頁面參數

四、Struts2攔截器


一、Struts和Struts2的背景

PS:看到這里的小伙伴們,請務必抱著好奇心繼續看下去哦!

什么是Struts呢?
我們這樣認為:Struts是流行和成熟的基于MVC設計模式的Web應用程序框架,能夠幫助我們減少用MVC設計模式來開發Web應用的時間。

可能有些小伙伴會問了,什么是MVC呢?如下:

MVC模型.png

那什么又是Struts2呢?
我這樣理解的:Struts2是結合Struts1和webwork的一個升級版,在穩定性以及性能等各個方面都比Struts1和webwork好,可謂集兩者之所長。

二、Struts2的工作原理

struts2的工作原理.png

上面這幅圖是Struts2的工作原理圖,Struts2是在我們的web.xml中進行配置的一個過濾器,當我們web項目啟動的時候,過濾器就會生效。

首先,用戶通過HttpServletRequest用戶請求,經過一系列的Struts2核心的過濾器向下執行。

①.ActionContextCleanUp是其中的一個可選的過濾器,非必須的哦;
②.Other filters(SiteMesh,etc)過濾器主要是用于與其他的框架進行集成;③.FilterDispatcher也是Struts2的一個核心過濾器,我們需要知道的是在Struts2.1.2之前是FilterDispatcher,而在Struts2.1.3版本之上被改為StrutsPrepareAndExecuteFilter。

可能有人要問了,為什么FilterDispatcher會被StrutsPrepareAndExecuteFilter替代呢?

舉個例子:假如我們現在想寫一個過濾器,我們往往會放在Struts2核心的過濾器的頂端,也就是在ActionContextCleanUp執行之前,寫我們自己的Filter;假如說我們需要在Struts2攔截之后再寫過濾器,也就是說我在執行Action之前,編寫過濾器。通過FilterDisoatcher是做不到的, 而升級版的StrutsPrepareAndExecuteFilter就可以做到在執行Action之前,添加我們自己的過濾器。


然后,如果后綴名為.action的就會進入ActionMapper,請求并在ActionMapper查找我們這個請求有沒有指定的一個Action,假如說有的話,就返回上一個過濾器并向左邊走。當走到ActionProxy的時候,ActionProxy就可以通過ConfigurationManager讀取到struts.xml,并找到具體的Action類,又通過ActionProxy的代理,創建我們action的一個反向的實例。
再然后,經過一系列的攔截器之前,執行到我們的Action,返回到Result(也是一個字符串對象),這個字符串對應的就是我們的視圖,也就是圖上的Template,包括jsp,FreeMarker等等。再經過一系列的攔截器之后,通過HttpServletResponse返回到HttpServletRequest中,也就是返回到用戶的實例進行顯示。

PS:關于Struts2攔截器我會在后面介紹,請大家務必理解并牢記Struts2的工作原理,不論是在工作還是面試都會有所涉及哦!假如你面試的公司正在使用SSH框架,那恭喜你,在面試官面前認可度+1了哦。

三、案例:利用Struts2接收頁面參數

首先,我們分別討論使用三種方式接受參數:
1.使用Action的屬性接收參數
2.使用DomainModel接收參數
3.使用ModelDriven接收參數

準備工作:

success.jsp:

<%@ page language="java" import="java.util.*" contentType="text/html; charset=utf-8"%>
<%
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
%>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
  <head>
    <base href="<%=basePath%>">
    <title>My JSP 'success.jsp' starting page</title>
  </head>

  <body>
    This is success.jsp. <br>
  </body>
</html>

index.jsp:

<%@ page language="java" import="java.util.*" contentType="text/html; charset=utf-8"%>
<%
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
%>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
  <head>
    <base href="<%=basePath%>">
    <title>My JSP 'index.jsp' starting page</title>
  </head>
  
  <body>
    <form action="Loginaction.action" method="post">
        用戶名:<input type="text" name="username" /><br>
        密碼:<input type="password" name="password" /><br>
        <input type="submit" value="提交" />
    </form>
  </body>
</html>

配置struts.xml:

    <action name="Loginaction" method="login" class="com.action.IndexAction">
            <result>/success.jsp</result>
    </action>

1.使用Action的屬性接收參數
首先我們新建一個IndexAction.java并繼承ActionSupport:

public class IndexAction  extends ActionSupport{
    private String username;//用戶名
    private String password;//密碼

        public String getUsername() {
        return username;
    }
    public void setUsername(String username) {
        this.username = username;
    }
    public String getPassword() {
        return password;
    }
    public void setPassword(String password) {
        this.password = password;
    }

    public String login(){
        System.out.println(username);
        return SUCCESS;
    }
}

然后啟動tomact,運行程序,我們可以看到后臺有輸出,并且頁面成功跳轉。這說明了我們已經能夠通過這種方式獲取參數了,但是假如我們有些頁面非常的大,有幾十個甚至上百個,那么這個時候我們是不是要建上百個屬性呢?所以這種方法對于我們開發是非常復雜的,當然也不利于維護。

曾經我們說過,Java是一種面向對象的語言,那么我們能不能把這些屬性放在一個對象里面,來實現各方面的開發呢?答案是肯定可以的。這就牽扯到我們的第二種方式:

2.使用DomainModel接收參數
修改后的IndexAction.java:

public class IndexAction  extends ActionSupport{
    private User user;

        public User getUser() {
        return user;
    }
    public void setUser(User user) {
        this.user = user;
    }

    public String login(){
        System.out.println(user.getUsername());
        return SUCCESS;
    }
}

獨立出來的用戶(User)類:

/**
 * 用戶實體類
 */
public class User {
    private String username;//用戶名
    private String password;//密碼
    public String getUsername() {
        return username;
    }
    public void setUsername(String username) {
        this.username = username;
    }
    public String getPassword() {
        return password;
    }
    public void setPassword(String password) {
        this.password = password;
    }
}

現在我們停下來想一下,假如說我們現在什么都不改,那么我們通過jsp中的提交方式能不能自動往private User user;里面傳遞參數呢?答案是肯定不行的,因為假如說我們有多個對象,每個對象都有這樣的參數的話,它傳遞到private User user;里面的參數都賦值的話,就肯定會亂。那么我們怎么指定呢?我們可以修改index.jsp里面的屬性名稱:

<form action="Loginaction.action" method="post">
用戶名:<input type="text" name="user.username" /><br>
密碼:<input type="password" name="user.password" /><br>
<input type="submit" value="提交" />
</form>

這樣就代表我們的username和password是出入到private User user;這個參數對象里面的,而不是其他的參數對象里面。

然后啟動tomact,運行程序,我們可以看到后臺有輸出,并且頁面成功跳轉。說明這種方式也是可行的。

3.使用ModelDriven接收參數
修改后的IndexAction.java:

public class IndexAction  extends ActionSupport implements ModelDriven<User>{
    private User user=new User();//必須實例化,并且去掉getters和setters方法

    public String login(){
        System.out.println(user.getUsername());
        return SUCCESS;
    }
    @Override
    public User getModel() {
        return user;
    }
}

然后啟動tomact,運行程序,我們可以看到后臺有輸出,并且頁面成功跳轉。

如果說我們傳入的參數是一個集合怎么辦呢?我們可以這樣:
向User.java里面添加一個成員變量,并實現getters和setters方法:

    private List<String> booklist;
    public List<String> getBooklist() {
        return booklist;
    }
    public void setBooklist(List<String> booklist) {
        this.booklist = booklist;
    }

修改后的index.jsp:

        <form action="Loginaction.action" method="post">
        用戶名:<input type="text" name="username" /><br>
        密碼:<input type="password" name="password" /><br>
        書籍1:<input type="text" name="booklist[0]" /><br>
        書籍2:<input type="text" name="booklist[1]" /><br>
        <input type="submit" value="提交" />
        </form>

修改后的IndexAction.java:

public class IndexAction  extends ActionSupport implements ModelDriven<User>{
    private User user=new User();

    public String login(){
        System.out.println(user.getUsername());
                System.out.println(user.getBookList().get(0));
                System.out.println(user.getBookList().get(1));
        return SUCCESS;
    }
    @Override
    public User getModel() {
        return user;
    }
}

再次啟動tomcat并運行程序,控制臺成功打印輸出。那么如果private List<String> booklist→private List<User> booklist呢?也就是說傳入的是一個對象呢?怎么修改index.jsp和IndexAction.java來測試是否成功呢?這個問題就留給讀者們思考啦。

經過三種傳參的對比,在實際應用中我們一般使用ModelDriven接收參數,為什么呢?因為低耦合高內聚啊。
PS:別問我什么是低耦合高內聚是什么,我會滿臉黑線的...

四、Struts2攔截器

什么是攔截器?
攔截器是Struts2里面非常重要的一個概念,是Struts2的核心功能實現。攔截器方法在Action執行之前或者之后執行,這里我們舉個例子,比如說在數據轉移、類型轉換、數據校驗之前或者之后做一些處理操作。

試想一下,如果多個攔截器組合在一起呢?這就是我們常說的攔截器棧
①.從結構上看,攔截器棧相當于多個攔截器的組合。
②.從功能上看,攔截器棧也是攔截器,在用法上基本一致。

攔截器的工作原理:
其實,也就一句話,攔截器的執行過程是一個遞歸的過程。怎么理解呢?學過數學的應該都知道什么是遞歸思想吧,也就是說:從最外層的攔截器開始依次向內部(Action)深入,得到結果集(Result)并進行視圖(jsp)的匹配,再依次經過從內向外的攔截器。

案例<使用攔截器實現權限的控制>:

準備工作:
在/WEB-INF/page/目錄下創建manager.jsp:

<html>
  <head>
    <base href="<%=basePath%>">
    <title>My JSP 'manager.jsp' starting page</title>
  </head>
  
  <body>
    這是一個后臺管理頁面,只有已登錄的用戶才能訪問!
  </body>
</html>

login.jsp:

<html>
  <head>
    <base href="<%=basePath%>">
    <title>My JSP 'login.jsp' starting page</title>
  </head>
  
  <body>
    <h2>用戶登錄</h2>
    <form action="login.action" method="post">
    用戶名:<input type="text" name="username" /><br/>
    密碼:<input type="password" name="password" /><br/>
    <input type="submit" value="登錄" />
    </form>
  </body>
</html>

LoginAction.java:

public class LoginAction extends ActionSupport implements SessionAware {
    private String username;//用戶名
    private String password;//密碼
    private Map<String, Object> session;//通過實現SessionAware接口,定義session獲取jsp頁面的信息

    @Override
    public void setSession(Map<String, Object> session) {
        this.session=session;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
    
    //處理登陸請求
    public String login() {
        if ("admin".equals(username) && "admin".equals(password)) {
            session.put("loginInfo", username);
            return SUCCESS;
        }else {
            session.put("loginError", "用戶名或者密碼不正確!");
            return ERROR;
        }
    }
}

通過這個Action,我們可以配置Struts.xml了:

<!-- 通過此action訪問后臺管理頁面,需要判斷用戶是否已經登錄,如果沒有登錄跳轉到登陸界面 -->
        <action name="auth">
            <result>/WEB-INF/page/manager.jsp</result>
        </action>

        <action name="login" class="com.action.LoginAction" method="login">
            <result name="success">/WEB-INF/page/manager.jsp</result>
            <result name="error">/login.jsp</result>
        </action>

配置好Struts.xml以后,我們需要在用戶登錄界面配置用戶登錄失敗的信息:
修改后的login.jsp:

<html>
  <head>
    <base href="<%=basePath%>">
    <title>My JSP 'login.jsp' starting page</title>
  </head>
  
  <body>
    <h2>用戶登錄</h2>
    ${loginError }
    <form action="login.action" method="post">
    用戶名:<input type="text" name="username" /><br/>
    密碼:<input type="password" name="password" /><br/>
    <input type="submit" value="登錄" />
    </form>
  </body>
</html>

接下來開始編寫我們的攔截器:
AuthInterceptor.java:

public class AuthInterceptor extends AbstractInterceptor {

    @Override
    public String intercept(ActionInvocation invocation) throws Exception {
        /*
         * 首先我們要判斷用戶是否已經登錄,那么就需要得到前面定義的session,
         * 怎么得到session呢?可以通過ActionContext的getContext()方法獲取上下文。
         */
        ActionContext context = ActionContext.getContext();
        Map<String, Object> session = context.getSession();
        if (session.get("loginInfo") != null) {//說明用戶已登錄
            String result = invocation.invoke();
            return result;
        } else {//說明用戶未登錄
            return "login";
        }
    }
}

然后還得修改Struts.xml:

<!-- 注冊攔截器 -->
    <interceptors>
        <interceptor name="auth" class="com.interceptor.AuthInterceptor"></interceptor>
        <!-- 自定義攔截器棧mystack,組合了默認攔截器defaultStack和auth -->
        <interceptor-stack name="mystack">
            <interceptor-ref name="defaultStack"></interceptor-ref>
            <interceptor-ref name="auth"></interceptor-ref>
        </interceptor-stack>
    </interceptors>
    
    <!-- 通過此action訪問后臺管理頁面,需要判斷用戶是否已經登錄,如果沒有登錄跳轉到登陸界面 -->
        <action name="auth">
            <result>/WEB-INF/page/manager.jsp</result>
            <!-- 如果AuthInterceptor里面返回的是login字符串,返回登陸界面 -->
            <result name="login">/login.jsp</result>
            <!-- 只需要引用自定義攔截器棧就可以了 -->
            <interceptor-ref name="mystack"></interceptor-ref>
        </action>
        
        <action name="login" class="com.action.LoginAction" method="login">
            <result name="success">/WEB-INF/page/manager.jsp</result>
            <result name="error">/login.jsp</result>
        </action>

這里我們需要明白的是,當我們手動添加了一個攔截器,默認的攔截器將不會生效,所以我們可以自定義一個攔截器棧將這兩個攔截器放入里面。就實際項目而言,會有十幾個或者更多的攔截器,如果不使用攔截器棧,那么每一個攔截器你都需要在action標簽里面配置,的確是一件很麻煩的事。

這里,我們這個權限校驗也就完成了。已經能夠完成登錄的用戶可以查看后臺信息,未登錄的用戶是不可以查看后臺信息的。

如果大家覺得我寫的對你有幫助,請順手點個贊支持一下唄;如果大家覺得我有寫的不對的地方,歡迎大家多多發言。謝謝!

轉載請注明作者及文章出處噢!

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容