瘋狂

          STANDING ON THE SHOULDERS OF GIANTS
          posts - 481, comments - 486, trackbacks - 0, articles - 1
            BlogJava :: 首頁 :: 新隨筆 :: 聯系 :: 聚合  :: 管理

          struts2 文件上傳和下載,以及部分源碼解析

          Posted on 2010-06-02 11:37 瘋狂 閱讀(8474) 評論(5)  編輯  收藏 所屬分類: java 、strutsweb

          struts2 文件上傳 和部分源碼解析,以及一般上傳原理

          (1)  單文件上傳 

          一.簡介

          Struts2并未提供自己的請求解析器,也就是就Struts2不會自己去處理multipart/form-data的請求,它需要調用其他請求解析器,將HTTP請求中的表單域解析出來。但Struts2在原有的上傳解析器基礎

          上做了進一步封裝,更進一步簡化了文件上傳。
          Struts2默認使用的是Jakarta的Common-FileUpload框架來上傳文件,因此,要在web應用中增加兩個Jar文件:commons-fileupload-1.2.jar和commons-io-1.3.1.jar。它在原上傳框架上做了進一步封裝

          ,簡化了文件上傳的代碼實現,取消了不同上傳框架上的編程差異。
          如果要改成其它的文件上傳框架,可以修改struts.multipart.parser常量的值為cos/pell,默認值是jakata。并在classpath中增加相應上傳組件的類庫

          例如配置成cos上傳

          struts.multipart.parser=cos

          struts.multipart.maxSize=1024  指定文件的最大字結數

          二.原理

          不管用common-fileUPload框架,還是用cos,都是通過將HTTP的數據保存到臨時文件夾,然后Struts使用fileUpload攔截器將文件綁定到Action的實例中。
          也就是配置文件的 <interceptor-ref name="fileUpload"/>
          我們可以通過源代碼struts2-code-XX.jar的struts-default.xml文件找到
          <interceptor name="fileUpload" class="org.apache.struts2.interceptor.FileUploadInterceptor"/>
          打開這個類的源代碼可以看見相關如下:
          /**
           * <!-- START SNIPPET: description -->
           * <p/>
           * Interceptor that is based off of {@link MultiPartRequestWrapper}, which is automatically applied for any request that
           * includes a file. It adds the following parameters, where [File Name] is the name given to the file uploaded by the
           * HTML form:
           * <p/>
           * <ul>
           * <p/>
           * <li>[File Name] : File - the actual File</li>
           * <p/>
           * <li>[File Name]ContentType : String - the content type of the file</li>
           * <p/>
           * <li>[File Name]FileName : String - the actual name of the file uploaded (not the HTML name)</li>
           * <p/>
           * </ul>
           * <p/>
          也就是說我們需要三個變量File(表單的name),其他兩個參數通過set個體方法有strtus調用

          接著下面是一些國際化提示的東西:
          * processed for all i18n requests. You can override the text of these messages by providing text for the following
           * keys:
           * <li>struts.messages.error.uploading - a general error that occurs when the file could not be uploaded</li>
           * <p/>
           * <li>struts.messages.error.file.too.large - occurs when the uploaded file is too large</li>
           * <p/>
           * <li>struts.messages.error.content.type.not.allowed - occurs when the uploaded file does not match the expected
           * content types specified</li>
           * <p/>
           * <li>struts.messages.error.file.extension.not.allowed - occurs when the uploaded file does not match the expected
           * file extensions specified</li>
           * <p/>
          例如struts.messages.error.content.type.not.allowed 表示文件類型錯誤:也就是說如果我們給攔截器配置了屬性allowedTypes 例如:
          <param name ="allowedTypes">image/bmp,image/png,image/gif,image/jpeg,image/jpg</param>    但是上傳的時候沒有上傳規定的類型
          struts2就會去我們的資源文件去找key為struts.messages.error.content.type.not.allowed的國際化資源給與提示這時候我們可以在我們的資源中配置這個key:
          例如:struts.messages.error.content.type.not.allowed=您上傳的文件類型只能為...!請重新選擇!
          (當然需要<constant name="struts.custom.i18n.resources" value="globalMessages"/>)globalMessages為資源前綴,然后通過:<s:fielderror/>來顯示提示
          <!-- START SNIPPET: parameters -->
           * <p/>
           * <ul>
           * <p/>
           * <li>maximumSize (optional) - the maximum size (in bytes) that the interceptor will allow a file reference to be set
           * on the action. Note, this is <b>not</b> related to the various properties found in struts.properties.
           * Default to approximately 2MB.</li>
           * <p/>
           * <li>allowedTypes (optional) - a comma separated list of content types (ie: text/html) that the interceptor will allow
           * a file reference to be set on the action. If none is specified allow all types to be uploaded.</li>
           * <p/>
           * <li>allowedExtensions (optional) - a comma separated list of file extensions (ie: .html) that the interceptor will allow
           * a file reference to be set on the action. If none is specified allow all extensions to be uploaded.</li>
           * </ul>
           * <p/>
          上面則是攔截器的相關參數,一目了然:maximumSize 上傳文件最大多少 默認:2MB。allowedTypes容許的上傳類型。allowedExtensions容許的擴展名
          接著是相關action的代碼說明:
          <!-- START SNIPPET: example-action -->
           *    package com.example;
           * <p/>
           *    import java.io.File;
           *    import com.opensymphony.xwork2.ActionSupport;
           * <p/>
           *    public UploadAction extends ActionSupport {
           *       private File file;
           *       private String contentType;
           *       private String filename;
           * <p/>
           *       public void setUpload(File file) {
           *          this.file = file;
           *       }
           * <p/>
           *       public void setUploadContentType(String contentType) {
           *          this.contentType = contentType;
           *       }
           * <p/>
           *       public void setUploadFileName(String filename) {
           *          this.filename = filename;
           *       }
           * <p/>
           *       public String execute() {
           *          //...
           *          return SUCCESS;
           *       }
           *  }
          其實最主要的是set方法的確定:我們跟蹤到大約238行:
           String contentTypeName = inputName + "ContentType";
           String fileNameName = inputName + "FileName";
          最終確定我們的private File file;屬性名稱可以隨便,
          但是filenam和contenttype的set方法要有規定 例如:
          如果private File myFile;

          則對應的其他的兩個屬性set方法如下:

          public void setMyFileContentType(String contentType) {
                   this.contentType = contentType;//當然contentType可以隨便起名 最終要的是set+MyFile+ContentType方法
              }

          public void setMyFileFileName(String filename) {
                     this.filename = filename;/當然filename可以隨便起名 最終要的是set+MyFile+FileName方法
                 }
          以下是實例:
          三.需要的jar包(默認使用commons-fileupload,如果使用cos,要將jar引進來)

          commons-logging-1.1.jar
          freemarker-2.3.8.jar
          ognl-2.6.11.jar
          struts2-core-2.0.6.jar
          xwork-2.0.1.jar
          commons-io-1.3.1.jar
          commons-fileupload-1.2.jar

          四.實例

          1.首先,創建上傳頁面

          Html代碼
          1.<%@page language="java" contentType = "text/html; charset=utf-8" pageEncoding = "utf-8"%>   
          2.<%@taglib prefix="s" uri ="/struts-tags"%>   
          3. 
          4.<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> 
          5.<html> 
          6.  <head> 
          19.  </head> 
          20.    
          21.  <body> 
          22.   <s:form action ="fileUpload" method ="POST" enctype ="multipart/form-data">   
          23.                <s:fielderror /> 
          24.        <s:file name ="upload"/>     
          25.        <s:submit />   
          26.    </s:form >   
          27. 
          28.  </body> 
          29.</html> 

          2.action


          1.package com;     
          2.    
          3.import java.io.BufferedInputStream;     
          4.import java.io.BufferedOutputStream;     
          5.import java.io.File;     
          6.import java.io.FileInputStream;     
          7.import java.io.FileOutputStream;     
          8.import java.io.InputStream;     
          9.import java.io.OutputStream;     
          10.import java.util.Date;     
          11.    
          12.import org.apache.struts2.ServletActionContext;     
          13.    
          14.import com.opensymphony.xwork2.ActionSupport;     
          15.    
          16.public class FileUploadAction extends ActionSupport {     
          17.         
          18.    private static final long serialVersionUID = 6452146812454l;     
          19.    
          20.    private File upload;     
          21.    
          22.    private String uploadContentType;     
          23.    
          24.    private String uploadFileName;     
          25.    
          26.    private String imageFileName;     
          27.    
          28.    public String getUploadContentType() {     
          29.        return uploadContentType;     
          30.    }     
          31.    
          32.    public void setUploadContentType(String uploadContentType) {     
          33.        this.uploadContentType = uploadContentType;     
          34.    }     
          35.    
          36.    public File getUpload() {     
          37.        return upload;     
          38.    }     
          39.    
          40.    public void setUpload(File upload) {     
          41.        this.upload = upload;     
          42.    }     
          43.    
          44.    public String getUploadFileName() {     
          45.        return uploadFileName;     
          46.    }     
          47.    
          48.    public void setUploadFileName(String uploadFileName) {     
          49.        this.uploadFileName = uploadFileName;     
          50.    }     
          51.    
          52.    public void setImageFileName(String imageFileName) {     
          53.        this.imageFileName = imageFileName;     
          54.    }     
          55.    
          56.    public String getImageFileName() {     
          57.        return imageFileName;     
          58.    }     
          59.    
          60.    private static void copy(File src, File dst) {     
          61.        try {     
          62.            InputStream in = null;     
          63.            OutputStream out = null;     
          64.            try {     
          65.                in = new BufferedInputStream(new FileInputStream(src));     
          66.                out = new BufferedOutputStream(new FileOutputStream(dst));     
          67.                byte[] buffer = new byte[1024*10];     
          68.                while (in.read(buffer) > 0) {     
          69.                    out.write(buffer);     
          70.                }     
          71.            } finally {     
          72.                if (null != in) {     
          73.                    in.close();     
          74.                }     
          75.                if (null != out) {     
          76.                    out.close();     
          77.                }     
          78.            }     
          79.        } catch (Exception e) {     
          80.            e.printStackTrace();     
          81.        }     
          82.    }     
          83.    
          84.    @Override    
          85.    public String execute() {     
          86.        System.out.println(uploadFileName);     
          87.             
          88.        imageFileName = System.currentTimeMillis() + uploadFileName.substring(uploadFileName.lastIndexOf("."));     
          89.        File imageFile = new File(ServletActionContext.getServletContext()     
          90.                .getRealPath("/uploadImages")     
          91.                + "/" + imageFileName);     //我們自己重新定義的文件名,也可以直接用 uploadFileName
          92.        copy(upload, imageFile);     
          93.        return SUCCESS;     
          94.    }     
          95.    
          96.}    
          97
          表單的enctype ="multipart/form-data,與一般的上傳一樣.

          <s:file name="upload">會將upload綁定到action的upload,其次他還會將上傳記文件的MIME類型綁定到uploadContentType,文件名綁定到uploadFileName中,他們是通過

          setUploadContentType和setUploadFileName進行綁定的,下面進行的多文件上傳也是同個道理,不過要用數組或者是list來進行綁定,然后多個文件的MIME類型也會綁定到以數組

          名字加ContentType和FileName的字符串數組中。   比如說上傳的文件的數組名為:File[] uploads,則它們的MIME類型綁定的對應的數組是uploadsFileName和uploadsContentType.

          3.struts.xml的配置

          Xml代碼

          Xml代碼
          1.<?xml version="1.0" encoding="UTF-8" ?>    
          2.<!DOCTYPE struts PUBLIC     
          3.    "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"     
          4.    "http://struts.apache.org/dtds/struts-2.0.dtd">    
          5.    
          6.<struts>    
          7.    <constant name="struts.multipart.saveDir" value="/tmp" />    
          8.    <package name="fileUploadDemo" extends="struts-default">    
          9.        <action name ="fileUpload" class ="com.FileUploadAction">      
          10.              
          11.            <interceptor-ref name ="fileUpload">      
          12.                <param name ="allowedTypes">image/bmp,image/png,image/gif,image/jpeg,image/jpg</param>      
          13.            </interceptor-ref>      
          14.            <interceptor-ref name ="defaultStack"/>      
          15.              
          16.            <result name ="input" >/fileUpload.jsp</result>    
          17.            <result name ="success">/showUpload.jsp </result>      
          18.        </action>      
          19.    </package>    
          20.</struts>    

          4.最后是web.xml的配置

          Xml代碼
          1.<?xml version="1.0" encoding="UTF-8"?>    
          2.<web-app version="2.4"      
          3.    xmlns="http://java.sun.com/xml/ns/j2ee"      
          4.    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"      
          5.    xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee      
          6.    http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">    
          7.         
          8.    <filter>      
          9.        <filter-name>struts-cleanup</filter-name>      
          10.        <filter-class>      
          11.            org.apache.struts2.dispatcher.ActionContextCleanUp     
          12.        </filter-class>      
          13.    </filter>      
          14.         
          15.    <filter>      
          16.        <filter-name>struts2</filter-name >      
          17.        <filter-class>      
          18.            org.apache.struts2.dispatcher.FilterDispatcher     
          19.        </filter-class >      
          20.    </filter>      
          21.         
          22.    <filter-mapping>      
          23.        <filter-name>struts-cleanup</filter-name >      
          24.        <url-pattern>/*</url-pattern>      
          25.    </filter-mapping>      
          26.    
          27.    <filter-mapping>      
          28.        <filter-name>struts2</filter-name >      
          29.        <url-pattern>/*</url-pattern >      
          30.    </filter-mapping>      
          31.         
          32.  <welcome-file-list>    
          33.    <welcome-file>index.jsp</welcome-file>    
          34.  </welcome-file-list>    
          35.</web-app>  

          (2) 多文件上傳

          多文件上傳
          與單文件上傳相似,實現多文件你可以將多個<s:file />綁定Action的數組或列表。如下例所示。

          < s:form action ="uploadList" method ="POST" enctype ="multipart/form-data" >
              < s:file label ="File (1)" name ="upload" />
              < s:file label ="File (2)" name ="upload" />
              < s:file label ="FIle (3)" name ="upload" />
              < s:submit />
          </ s:form > 清單14 多文件上傳JSP代碼片段
          如果你希望綁定到數組,Action的代碼應類似:

               private File[] uploads;
               private String[] uploadSFileName;
               private String[] uploadSContentType;

          多文件上傳數組綁定Action代碼片段
          如果你想綁定到列表,則應類似:

               private List < File > uploads ;
               private List < String > uploadSFileName ;
               private List < String > uploadSContentType ;
          多文件上傳列表綁定Action代碼片段

          另外是一般上傳文件的原理:當然具體可以看http協議的rfc文檔:
          關于multipart/form-data 相關資料可以看;http://www.ietf.org/rfc/rfc1867.txt 大約在[Page 1]的地方有介紹
           表單配置multipart/form-data 說明以二進制流的方式傳輸表單字段的數據:
          我們通過以下代碼看到request數據流中的內容:

           PrintWriter out = response.getWriter();
            InputStream is = request.getInputStream();
            
            BufferedReader br = new BufferedReader(
             new InputStreamReader(is));
            String buffer = null;
            while( (buffer = br.readLine()) != null)
            {
             //在頁面中顯示讀取到的請求參數
             out.println(buffer + "<br />");
            }
          out.flush();
          out.close();
          例如:我上傳一個文件D:\apache-tomcat-6018\bin\version.sh (tomcat版本文件)
          最終頁面顯示:
          -----------------------------7da1052ec05fe
          Content-Disposition: form-data; name="ff"; filename="D:\apache-tomcat-6018\bin\version.sh"
          Content-Type: text/plain

          #!/bin/sh

          # Licensed to the Apache Software Foundation (ASF) under one or more
          # contributor license agreements. See the NOTICE file distributed with
          # this work for additional information regarding copyright ownership.
          # The ASF licenses this file to You under the Apache License, Version 2.0
          # (the "License"); you may not use this file except in compliance with
          # the License. You may obtain a copy of the License at
          #
          # http://www.apache.org/licenses/LICENSE-2.0
          #
          # Unless required by applicable law or agreed to in writing, software
          # distributed under the License is distributed on an "AS IS" BASIS,
          # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
          # See the License for the specific language governing permissions and
          # limitations under the License.


          # resolve links - $0 may be a softlink
          PRG="$0"

          while [ -h "$PRG" ] ; do
          ls=`ls -ld "$PRG"`
          link=`expr "$ls" : '.*-> \(.*\)$'`
          if expr "$link" : '/.*' > /dev/null; then
          PRG="$link"
          else
          PRG=`dirname "$PRG"`/"$link"
          fi
          done

          PRGDIR=`dirname "$PRG"`
          EXECUTABLE=catalina.sh

          # Check that target executable exists
          if [ ! -x "$PRGDIR"/"$EXECUTABLE" ]; then
          echo "Cannot find $PRGDIR/$EXECUTABLE"
          echo "This file is needed to run this program"
          exit 1
          fi

          exec "$PRGDIR"/"$EXECUTABLE" version "$@"

          -----------------------------7da1052ec05fe--
          我們發現我們上傳的內容在
          -----------------------------7da1052ec05fe
          Content-Disposition: form-data; name="ff"; filename="D:\apache-tomcat-6018\bin\version.sh"
          Content-Type: text/plain和-----------------------------7da1052ec05fe--中間
          因此我們可以通過以下代碼來獲取上傳內容并保存:

          //取得HttpServletRequest的InputStream輸入流
          InputStream is = request.getInputStream();
          BufferedReader br = new BufferedReader(new InputStreamReader(is));
          String buffer = null;
          //循環讀取請求內容的每一行內容
          while( (buffer = br.readLine()) != null)
          {
           //如果讀到的內容以-----------------------------開始,
           //且以--結束,表明已到請求內容尾
           if(buffer.endsWith("--") && buffer
            .startsWith("-----------------------------"))//length為29
           {
            //跳出循環
            break;
           }
           //如果讀到的內容以-----------------------------開始,表明開始了一個表單域
           if(buffer.startsWith("-----------------------------"))
           {
            //如果下一行內容中有filename字符串,表明這是一個文件域
            if (br.readLine().indexOf("filename") > 1)
            {
             //跳過兩行,開始處理上傳的文件內容
             br.readLine();
             br.readLine();
             //以系統時間為文件名,創建一個新文件
             File file = new File(request.getRealPath("/")
              + System.currentTimeMillis());
                                   //當然我們可以讀取filenam來保存這里簡化
             //創建一個文件輸出流
             PrintStream ps = new PrintStream(new FileOutputStream(file));
             String content = null;
             //接著開始讀取文件內容
             while( (content = br.readLine()) != null)
             {
              //如果讀取的內容以-----------------------------開始,
              //表明開始了下一個表單域內容
              if(content.startsWith("-----------------------------"))length為29
              {
               //跳出處理
               break;
              }
              //將讀到的內容輸出到文件中
              ps.println(content);
             }
             //關閉輸出
             ps.flush();
             ps.close();
            }
           }
          }
          br.close();

          關于strtus2下載:
          下載最終是通過contentType和數據流將數據輸出到客戶端來實現,在struts中也是通過InputStream和相關的配置來實現:
          同樣最終到strtus的下載相關的源代碼:org.apache.struts2.dispatcher.StreamResult我們看到
           
               public static final String DEFAULT_PARAM = "inputName";

              protected String contentType = "text/plain";
              protected String contentLength;
              protected String contentDisposition = "inline";//在線
              protected String contentCharSet ;
              protected String inputName = "inputStream";
              protected InputStream inputStream;
              protected int bufferSize = 1024;
              protected boolean allowCaching = true;

          當然這些參數都可以在 <result  type="stream">中配置 例如;
          <param name="inputPath">\uploads\document.pdf</param>
          <!-- 指定下載文件的文件類型 -->
          <param name="contentType">Application/pdf</param>
          <!-- 指定由getTargetFile()方法返回被下載文件的InputStream -->
          <param name="inputName">targetFile</param>
          <param name="contentDisposition">attachment;filename="document.pdf"</param>
          <!-- 指定下載文件的緩沖大小 -->
          <param name="bufferSize">2048</param>
          其中:
              contentType:指定被下載文件的文件類型。 application/octet-stream 默認值,可以下載所有類型
                inputName:指定被下載文件的入口輸入流, 和DownloadAction中的getInputStream()對應,主要是獲得實際資源文件
                contentDisposition:指定下載的文件名和顯示方式,一般和文件名一致,但是要注意中文件名保存時亂碼問題,解決辦法就是進行編碼處理
          <!-- 指定由getTargetFile()方法返回被下載文件的InputStream -->
          <param name="inputName">targetFile</param>
          是下載的入口 我們不需要在我們的action里面配置targetFile變量 但需要getTargetFile方法,默認需要getInputStream()方法 也就是:inputName參數的值就是入口方法去掉get前綴、首字母小寫的

          字符串

          我們的action里面的代碼如下:


           private String inputPath;//通過strtus獲取文件地址 也可以直接寫例如:String inputPath = ServletActionContext.getRequest().getRealPath("\uploads\document.pdf");
           
           public void setInputPath(String value)
           {
            inputPath = value;
           }
          public InputStream getTargetFile() throws Exception
           { 
            return ServletActionContext.getServletContext().getResourceAsStream(inputPath);
           }
          如果報以下錯誤:
          Can not find a java.io.InputStream with the name [targetFile] in the invocation stack. Check the <param name="inputName"> tag specified for this action.

          實際問題是ServletActionContext.getServletContext().getResourceAsStream(inputPath);找不到資源,請檢查你的path是否正確。
          而關于下載實際struts做了什么呢?我們看一部分源代碼代碼就很明白了:

            HttpServletResponse oResponse = (HttpServletResponse) invocation.getInvocationContext().get(HTTP_RESPONSE);
           // Set the content type
          ...
           //Set the content length
          ...
           // Set the content-disposition
          ...
           // Set the cache control headers if neccessary
          ...
          // Get the outputstream
          //------------
                      oOutput = oResponse.getOutputStream();

                      if (LOG.isDebugEnabled()) {
                          LOG.debug("Streaming result [" + inputName + "] type=[" + contentType + "] length=[" + contentLength +
                              "] content-disposition=[" + contentDisposition + "] charset=[" + contentCharSet + "]");
                      }

                      // Copy input to output
                      LOG.debug("Streaming to output buffer +++ START +++");
                      byte[] oBuff = new byte[bufferSize];
                      int iSize;
                      while (-1 != (iSize = inputStream.read(oBuff))) {
                          oOutput.write(oBuff, 0, iSize);
                      }
                      LOG.debug("Streaming to output buffer +++ END +++");

                      // Flush
                      oOutput.flush();
                  }
                  finally {
                      if (inputStream != null) inputStream.close();
                      if (oOutput != null) oOutput.close();
                  }
          //-----------
          很簡單,就像以前在servlet中一樣通過getOutputStream 和配置content type ,content-disposition,cache control,content length這些參數的來實現。

          這樣就很簡單的實現了下載功能。
          以上是自己工作之余寫的以下總結,不對的地方希望大家指點,謝謝,轉載,請說明地址


          評論

          # re: struts2 文件上傳和下載,以及部分源碼解析  回復  更多評論   

          2011-12-27 22:55 by 熱太熱讓他熱天
          熱太熱兒童熱天熱天熱天

          # re: struts2 文件上傳和下載,以及部分源碼解析  回復  更多評論   

          2012-07-13 08:25 by 33
          frsfrefgr

          # re: struts2 文件上傳和下載,以及部分源碼解析  回復  更多評論   

          2012-09-04 13:41 by 33
          @33
          fdfdfd

          # re:  回復  更多評論   

          2012-09-04 13:42 by 33
          kjkj

          # re: struts2 文件上傳和下載,以及部分源碼解析  回復  更多評論   

          2013-07-13 11:16 by @張小梅
          看看,沒試,不知道好用不
          主站蜘蛛池模板: 邵阳县| 洪江市| 宁明县| 伊春市| 中方县| 阿勒泰市| 刚察县| 工布江达县| 张家界市| 台东市| 象州县| 孟村| 芜湖县| 鲁甸县| 海城市| 涡阳县| 宿州市| 滁州市| 车险| 正蓝旗| 开原市| 巴林左旗| 连南| 西吉县| 祁门县| 腾冲县| 眉山市| 沧州市| 额敏县| 陇西县| 乐平市| 遂宁市| 望江县| 平果县| 尉犁县| 文成县| 丘北县| 富宁县| 锡林浩特市| 菏泽市| 新田县|