1. 程式人生 > >深入分析JavaWeb Item47 -- Struts2攔截器與文件上傳下載

深入分析JavaWeb Item47 -- Struts2攔截器與文件上傳下載

com custom public 面向切面 lang down pri 多功能 art

一、struts2中的攔截器(框架功能核心)

1、過濾器VS攔截器

過濾器VS攔截器功能是一回事。

過濾器是Servlet規範中的技術,能夠對請求和響應進行過濾。

攔截器是Struts2框架中的技術。實現AOP(面向切面)的編程思想。是可插拔的, 能夠對訪問某個 Action 方法之前或之後實施攔截。

攔截器棧(Interceptor Stack): 將攔截器按一定的順序聯結成一條鏈. 在訪問被攔截的方法時, Struts2攔截器鏈中的攔截器就會按其之前定義的順序被依次調用

Struts2運行原理 - 底層分析

技術分享

2、自己定義攔截器

struts2定義了一個攔截器接口Interceptor接口。
Interceptor接口裏面有三個抽象方法
技術分享

  • init: 該方法將在攔截器被創建後馬上被調用, 它在攔截器的生命周期內僅僅被調用一次. 能夠在該方法中對相關資源進行必要的初始化
  • interecept: 每攔截一個動作請求, 該方法就會被調用一次.
  • destroy: 該方法將在攔截器被銷毀之前被調用, 它在攔截器的生命周期內也僅僅被調用一次.
    Struts 會依次調用程序猿為某個 Action 而註冊的每個攔截器的 interecept 方法.每次調用 interecept 方法時, Struts 會傳遞一個 ActionInvocation 接口的實例.

ActionInvocation: 代表一個給定動作的運行狀態, 攔截器能夠從該類的對象裏獲得與該動作相關聯的 Action 對象和 Result 對象. 在完畢攔截器自己的任務之後, 攔截器將調用 ActionInvocation 對象的 invoke 方法前進到 Action 處理流程的下一個環節.

還能夠調用 ActionInvocation 對象的 addPreResultListener 方法給 ActionInvocation 對象 “掛” 上一個或多個 PreResultListener 監聽器. 該監聽器對象能夠在動作運行完畢之後, 開始運行動作結果之前做些事情

自己定義攔截器步驟:

a、編寫一個類。實現com.opensymphony.xwork2.interceptor.Interceptor接口,或者繼承
com.opensymphony.xwork2.interceptor.AbstractInterceptor類。(適配器模式),一般都選擇繼承AbstractInterceptor

(攔截器會駐留內存)。

由於AbstractInterceptor 類實現了 Interceptor 接口. 並為 init, destroy 提供了一個空白的實現

編寫兩個攔截器InterceptorDemo1 ,和InterceptorDemo2

package com.itheima.interceptor;

import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.interceptor.AbstractInterceptor;

public class InterceptorDemo1 extends AbstractInterceptor {
    //動作的每次訪問都會調用該方法
    public String intercept(ActionInvocation invocation) throws Exception {
        System.out.println("攔截前Demo1");
        String rtvalue = invocation.invoke();//放行,這裏為什麽返回string?
由於終於的結果返回的Action的Result。而action的結果是string類型
        System.out.println("攔截後Demo1");
        return rtvalue;
    }

}
package com.itheima.interceptor;

import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.interceptor.AbstractInterceptor;
import com.opensymphony.xwork2.interceptor.PreResultListener;

public class InterceptorDemo2 extends AbstractInterceptor {
    //動作的每次訪問都會調用該方法
    public String intercept(ActionInvocation invocation) throws Exception {

//      invocation.addPreResultListener(new PreResultListener() {
//          
//          public void beforeResult(ActionInvocation invocation, String resultCode) {
//              System.out.println("結果顯示前");
//          }
//      });

        System.out.println("攔截前Demo2");
        String rtvalue = invocation.invoke();//放行
        System.out.println("攔截後Demo2");
        return rtvalue;
    }

}

b、須要在struts.xml中進行定義,定義攔截器,先定義在使用。

<package name="p1" extends="struts-default">
    <!-- 定義攔截器:僅僅對當前包有效 -->
    <interceptors>
        <interceptor name="interceprotDemo1" class="com.itheima.interceptor.InterceptorDemo1"></interceptor>
        <interceptor name="interceprotDemo2" class="com.itheima.interceptor.InterceptorDemo2"></interceptor>
    </interceptors>

</package>

c、在動作配置中就能夠使用了

<action name="action1" class="com.itheima.action.Demo1Action" method="execute">
        <!-- 使用定義的攔截器。如過沒有指定不論什麽的攔截器,默認使用default-stack棧中的全部攔截器。
            一旦指定了不論什麽一個攔截器,默認的就無效了
         -->
         <interceptor-ref name="interceprotDemo1"></interceptor-ref>
         <interceptor-ref name="interceprotDemo2"></interceptor-ref>
         <result>/success.jsp</result>
</action>

實現動作類Demo1Action

package com.itheima.action;

import com.opensymphony.xwork2.ActionSupport;

public class Demo1Action extends ActionSupport {

    @Override
    public String execute() throws Exception {
        System.out.println("execute運行了");
        return SUCCESS;
    }

}

運行結果

技術分享

由於struts2中如文件上傳。數據驗證,封裝請求參數到action等功能都是由系統默認的defaultStack中的攔截器實現的,所以我們定義的攔截器須要引用系統默認的defaultStack,這樣應用才幹夠使用struts2框架提供的眾多功能。

如過沒有指定不論什麽的攔截器。默認使用default-stack棧中的全部攔截器;一旦指定了不論什麽一個攔截器,默認的就無效了除了要使用自己定義的攔截器之外,還要使用defaultStack,能夠這麽辦

方法一:(自己使用),僅僅需在action中配置自己定義的和defaultStack默認的就能夠了。

技術分享

方法二:(大家都用的時候),假設希望包下的全部action都使用自己定義的攔截器, 要使用攔截器棧 interceptor-stack,定義一個interceptor-stack。然後在action中能夠通過<default-interceptor-ref name=“mydefaultStack”/>把攔截器定義為默認攔截器,mydefaultStack名字能夠自己取。

<interceptors>
            <interceptor name="interceprotDemo1" class="com.itheima.interceptor.InterceptorDemo1"></interceptor>
            <interceptor name="interceprotDemo2" class="com.itheima.interceptor.InterceptorDemo2"></interceptor>
            <interceptor-stack name="mydefaultStack">
                <interceptor-ref name="defaultStack"></interceptor-ref>
                <interceptor-ref name="interceprotDemo1"></interceptor-ref>
                <interceptor-ref name="interceprotDemo2"></interceptor-ref>
            </interceptor-stack>
</interceptors>
<action name="action3" class="com.itheima.action.LoginAction" method="login">
            <interceptor-ref name="mydefaultStack"></interceptor-ref>
            <result>/success.jsp</result>
</action>

3、Struts2 自帶的攔截器

技術分享

技術分享

技術分享

案例1:檢查用戶是否登錄

1、 編寫頁面login.jsp

  <body>
    <form action="${pageContext.request.contextPath}/login.action" method="post">
        <input type="text" name="username"/><br/>
        <input type="text" name="password"/><br/>
        <input type="submit" value="登錄"/>
    </form>
  </body>

2、編寫登錄校驗的攔截器LoginCheckInterceptor 類

package com.itheima.interceptor;

import javax.servlet.http.HttpSession;

import org.apache.struts2.ServletActionContext;

import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.interceptor.AbstractInterceptor;

public class LoginCheckInterceptor extends AbstractInterceptor {

    public String intercept(ActionInvocation invocation) throws Exception {
        HttpSession session = ServletActionContext.getRequest().getSession();//通過ServletActionContext對象獲得session對象
        Object user = session.getAttribute("user");
        if(user==null){
            //沒有登錄
            return "login";//返回到某個邏輯視圖
        }
        return invocation.invoke();//放行
    }

}

3、編寫配置文件struts.xml

<package name="p2" extends="struts-default">
        <interceptors>
            <interceptor name="loginCheckInterceptor" class="com.itheima.interceptor.LoginCheckInterceptor"></interceptor>
            <interceptor-stack name="mydefaultStack">
                <interceptor-ref name="defaultStack"></interceptor-ref>
                <interceptor-ref name="loginCheckInterceptor"></interceptor-ref>
            </interceptor-stack>
        </interceptors>
        <action name="login" class="com.itheima.action.CustomerAction" method="login">
            <result>/login.jsp</result>
        </action>
    </package>

4、編寫動作類CustomerAction

package com.itheima.action;

import org.apache.struts2.ServletActionContext;

import com.opensymphony.xwork2.ActionSupport;

public class CustomerAction extends ActionSupport {
    public String login(){
        System.out.println("登錄");
        ServletActionContext.getRequest().getSession().setAttribute("user", "ppp");
        return SUCCESS;
    }
}

案例2:監測動作方法的運行效率

編寫時間監測過濾器TimerInterceptor

package com.itheima.interceptor;

import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.interceptor.AbstractInterceptor;

public class TimerInterceptor extends AbstractInterceptor {

    public String intercept(ActionInvocation invocation) throws Exception {
        long time = System.nanoTime();
        String rtvalue = invocation.invoke();
        System.out.println(rtvalue+"運行耗時:"+(System.nanoTime()-time)+"納秒");
        return rtvalue;
    }

}

編寫配置文件

<package name="p2" extends="struts-default">
        <interceptors>
            <interceptor name="loginCheckInterceptor" class="com.itheima.interceptor.LoginCheckInterceptor"></interceptor>
            <interceptor name="timerInterceptor" class="com.itheima.interceptor.TimerInterceptor"></interceptor>
            <interceptor-stack name="mydefaultStack">
                <interceptor-ref name="defaultStack"></interceptor-ref>
                <interceptor-ref name="loginCheckInterceptor"></interceptor-ref>
                <interceptor-ref name="timerInterceptor"></interceptor-ref>
            </interceptor-stack>
        </interceptors>
            <result name="login">/login.jsp</result>
        </action>
    </package>

從上面能夠看出。在一個action 中能夠配置多個過濾器。

4、自己定義攔截器:能夠指定攔截的方法或不攔截的方法

能夠指定攔截的方法或不攔截的方法。編寫過濾器時。能夠實現類MethodFilterInterceptor。裏面有兩個字段,通過註入參數就能夠指定那些不攔截。兩個參數僅僅要用一個就可以,當攔截較少是,能夠用includeMethods ,當攔截較多是。能夠用排除的方法excludeMethods 。

excludeMethods = Collections.emptySet();//排除那些
includeMethods = Collections.emptySet();//包含那些

案例:再續登錄校驗的樣例。

1、編寫過濾器LoginCheckInterceptor

package com.itheima.interceptor;

import javax.servlet.http.HttpSession;

import org.apache.struts2.ServletActionContext;

import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.interceptor.AbstractInterceptor;
import com.opensymphony.xwork2.interceptor.MethodFilterInterceptor;

public class LoginCheckInterceptor extends MethodFilterInterceptor {
    protected String doIntercept(ActionInvocation invocation) throws Exception {
        HttpSession session = ServletActionContext.getRequest().getSession();
        Object user = session.getAttribute("user");
        if(user==null){
            //沒有登錄
            return "login";//返回到某個邏輯視圖
        }
        return invocation.invoke();//放行
    }

}

2、編寫配置文件

技術分享

3、編寫動作類CustomerAction

package com.itheima.action;

import org.apache.struts2.ServletActionContext;

import com.opensymphony.xwork2.ActionSupport;

public class CustomerAction extends ActionSupport {
    public String add(){
        System.out.println("調用add的service方法");
        return SUCCESS;
    }
    public String edit(){
        System.out.println("調用edit的service方法");
        return SUCCESS;
    }
    public String login(){
        System.out.println("登錄");
        ServletActionContext.getRequest().getSession().setAttribute("user", "ppp");
        return SUCCESS;
    }
}

4、編寫頁面
addCustomer.jsp

 <body>
    加入客戶
  </body>

editCustomer.jsp

  <body>
   改動客戶
  </body>

login.jsp

  <body>
    <form action="${pageContext.request.contextPath}/login.action" method="post">
        <input type="text" name="username"/><br/>
        <input type="text" name="password"/><br/>
        <input type="submit" value="登錄"/>
    </form>
  </body>

success.jsp

  <body>
    oyeah
  </body>

二、文件上傳與下載

Struts2開發的三板斧。頁面jsp—配置文件struts2.xml—-還有動作類Action

文件上傳前提:
form表單的method必須是post
form表單的enctype必須是multipart/form-data
提供type=”file”的上傳輸入域

Struts 對文件上傳的支持的一些規則

技術分享

1、單文件上傳

開發步驟:

1、在WEB-INF/lib下加入commons-fileupload-1.2.1.jar、commons-io-1.3.2.jar。這兩個文件能夠從http://commons.apache.org/下載

2、第二步:編寫upfile.jsp ,把form表的enctype設置為:“multipart/form-data“,例如以下:

<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%@ taglib uri="/struts-tags" prefix="s"%>
<body>
    <s:actionerror/>
    <hr/>
    <s:fielderror></s:fielderror>
    <form action="${pageContext.request.contextPath}/upload1.action" method="post" enctype="multipart/form-data"><!-- 以MIME的方式傳遞
-->
        用戶名:<input type="text" name="username"/><br/>
        靚照:<input type="file" name="photo"/><br/>
        <input type="submit" value="上傳"/>
    </form>
  </body>

編寫錯誤頁面error.jsp

 <body>
    server忙,一會再試。

</body>

success.jsp

  <body>
    上傳成功
  </body>

3、編寫UploadAction1 類:在Action類中加入屬性,屬性相應於表單中文件字段的名稱:

package com.itheima.actions;

import java.io.File;
import java.io.IOException;

import org.apache.commons.io.FileUtils;
import org.apache.struts2.ServletActionContext;

import com.opensymphony.xwork2.ActionSupport;
//文件上傳:fileUpload攔截器完畢的
public class UploadAction1 extends ActionSupport {

    private String username;
    private File photo;//和表單的上傳字段名保持一致。

類型是File類型的 private String photoFileName;//上傳的文件名稱 private String photoContentType;//上傳文件的MIME類型 //省略getter和setter方法 public String upload(){ System.out.println(photoFileName+":"+photoContentType); //普通字段: System.out.println(username); //上傳字段:上傳到某個文件夾。存到應用的images文件夾下 String realPath = ServletActionContext.getServletContext().getRealPath("/images"); File directory = new File(realPath); if(!directory.exists()){ directory.mkdirs(); } try { FileUtils.copyFile(photo, new File(directory, photoFileName)); return SUCCESS; } catch (IOException e) { e.printStackTrace(); return ERROR; } } }

在struts.xml文件裏添加例如以下配置

<action name="upload1" class="com.itheima.actions.UploadAction1" method="upload">
    <interceptor-ref name="defaultStack">
        <param name="fileUpload.allowedTypes">image/jpeg,image/png</param>
        <param name="fileUpload.allowedExtensionsSet">jpg,jpeg,png</param>
    </interceptor-ref>
    <result>/success.jsp</result>
    <result name="error">/error.jsp</result>
    <result name="input">/index.jsp</result>
</action>

原理分析:

a 、FileUpload 攔截器負責處理文件的上傳操作, 它是默認的 defaultStack 攔截器棧的一員. 攔截器有 3 個屬性能夠設置.

  • maximumSize: 上傳文件的最大長度(以字節為單位), 默認值為 2 MB
  • allowedTypes: 同意上傳文件的類型, 各類型之間以逗號分隔
  • allowedExtensions: 同意上傳文件擴展名, 各擴展名之間以逗號分隔
    能夠在 struts.xml 文件裏覆蓋這 3 個屬性

技術分享

b、超出大小或非法文件的上傳,會報錯(轉向一個input的視圖)

通過:
<s:actionError/> <s:feildError/>顯示錯誤消息的提示

c、錯誤消息提示改為中文版:借助國際化的消息資源文件

假設是通過配置全局默認參數引起的錯誤。最好用全局的消息資源文件。
struts2默認的提示資源文件:struts2-core-**.jar 的org.apache.struts2的struts-message.properties文件裏。

比著key值覆蓋相應的value就可以。

配置例如以下:

struts.messages.error.uploading=Error uploading: {0}
struts.messages.error.file.too.large=File too large: {0} "{1}" "{2}" {3}
struts.messages.error.content.type.not.allowed=Content-Type not allowed: {0} "{1}" "{2}" {3}
struts.messages.error.file.extension.not.allowed=File extension not allowed: {0} "{1}" "{2}" {3}

{0}:<input type=“file” name=“uploadImage”>中name屬性的值
{1}:上傳文件的真實名稱
{2}:上傳文件保存到暫時文件夾的名稱
{3}:上傳文件的類型(對struts.messages.error.file.too.large是上傳文件的大小)

源代碼:
技術分享

改動顯示錯誤的資源文件的信息

第一步:創建新的資源文件 比如fileuploadmessage.properties,放置在src下
           在該資源文件裏添加例如以下信息
struts.messages.error.uploading=上傳錯誤: {0}
struts.messages.error.file.too.large=上傳文件太大: {0} "{1}" "{2}" {3}
struts.messages.error.content.type.not.allowed=上傳文件的類型不同意: {0} "{1}" "{2}" {3}
struts.messages.error.file.extension.not.allowed=上傳文件的後綴名不同意: {0} "{1}" "{2}" {3}

 第二步:在struts.xml文件載入該資源文件

       <!-- 配置上傳文件的出錯信息的資源文件 -->
       <constant name="struts.custom.i18n.resources" value=“cn….xxx.fileuploadmessage“/>

2、多文件上傳

上傳多個文件, 能夠使用數組或 List,其它和單文件上傳相似。

package com.itheima.actions;

import java.io.File;
import java.io.IOException;

import org.apache.commons.io.FileUtils;
import org.apache.struts2.ServletActionContext;

import com.opensymphony.xwork2.ActionSupport;
//文件上傳:fileUpload攔截器完畢的
public class UploadAction2 extends ActionSupport {

    private String username;
    private File[] photo;//和表單的上傳字段名保持一致。類型是File類型的 .數組或List
    private String[] photoFileName;//上傳的文件名稱
    private String[] photoContentType;//上傳文件的MIME類型

    public String upload(){
        //上傳字段:上傳到某個文件夾。

存到應用的images文件夾下 String realPath = ServletActionContext.getServletContext().getRealPath("/images"); File directory = new File(realPath); if(!directory.exists()){ directory.mkdirs(); } try { for(int i=0;i<photo.length;i++){ FileUtils.copyFile(photo[i], new File(directory, photoFileName[i])); } return SUCCESS; } catch (IOException e) { e.printStackTrace(); return ERROR; } } }

3、文件下載

原理:struts2提供了stream結果類型,該結果類型就是專門用於支持文件下載功能的
指定stream結果類型 須要指定一個 inputName參數。該參數指定一個輸入流。提供被下載文件的入口

編碼步驟:
1、動作類DownloadAction :

package com.itheima.actions;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.net.URLEncoder;

import org.apache.commons.io.FilenameUtils;
import org.apache.struts2.ServletActionContext;

import com.opensymphony.xwork2.ActionSupport;

public class DownloadAction extends ActionSupport {
    private InputStream image;//用in有問題的
    private String filename;//文件名稱
    private long filesize;
    public InputStream getImage() {
        return image;
    }

    public void setImage(InputStream image) {
        this.image = image;
    }

    public String getFilename() {
        return filename;
    }

    public long getFilesize() {
        return filesize;
    }

    public String download() throws Exception{
        //給image字節流賦值
        String fileRealPath = ServletActionContext.getServletContext().getRealPath("/WEB-INF/classes/黴女.jpg");
        filename = FilenameUtils.getName(fileRealPath);
        //方式一:中文文件要進行URL編碼
//      filename = URLEncoder.encode(filename, "UTF-8");
        filesize = new File(fileRealPath).length();
        System.out.println(filename);
        image = new FileInputStream(fileRealPath);
        return SUCCESS;
    }
}

struts.xml配置文件:主要是對stream類型的結果進行配置

<struts>
    <constant name="struts.devMode" value="true" />
    <constant name="struts.ognl.allowStaticMethodAccess" value="true" />
        <action name="download" class="com.itheima.actions.DownloadAction" method="download">
            <result type="stream">

                <param name="inputName">image</param><!--動作類中InputStream的字段名,須要在Action中提供getTargetFile方法,返回inputStream-->
                <param name="contentType">application/octet-stream</param><!--告訴瀏覽器響應頭。文件的MIME格式,調用Action中的getContentType方法-->
                <!-- 在struts.xml中使用OGNL表達式獲取動作類中屬性的值。

調用動作類中的 getFilename()--> <!-- 中文文件名稱編碼:方式二.使用OGNL表達式,調用URLEncode的靜態方法 --> <!-- 默認OGNL調用靜態方法是不行的,須要開啟一個常量開關.struts.ognl.allowStaticMethodAccess=true --> <param name="contentDisposition">attachment;[email protected]@encode(filename,‘UTF-8‘)}</param><!-- 告訴瀏覽器的下載方式--> <param name="contentLength">${filesize}</param> </result> </action> </package> </struts>

攔截器和文件上傳就寫到這裏了。好累!

深入分析JavaWeb Item47 -- Struts2攔截器與文件上傳下載