架構探險筆記10-框架優化之文件上傳

確定文件上傳使用場景

通常情況下,我們可以通過一個form(表單)來上傳文件,就以下面的“創建客戶”為例來說明(對應的文件名是customer_create.jsp),需要提供一個form,并將其enctype屬性設為multipart/form-data,表示以form?data方式提交表單數據。

注意:enctype的默認值為application/x-www-form-urlencoded,表示以url?encoded方式提交表單數據。

下面我們使用jQuery與jQuery?Form插件快速編寫一個基于Ajax的文件上傳表單,代碼如下:

<%@ page pageEncoding="UTF-8" contentType="text/html;charset=UTF-8" language="java" %>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %><c:set var="BASE" value="${pageContext.request.contextPath}"/>
<html>
<head><title>客戶管理-創建客戶</title>
</head>
<body><h1>創建客戶界面</h1>
${msg}
<form id="customer_form" enctype="multipart/form-data"><table><tr><td>客戶名稱:</td><td><input type="text" name="name" value="${customer.name}"></td></tr><tr><td>聯系人:</td><td><input type="text" name="contact" value="${customer.contact}"></td></tr><tr><td>電話號碼:</td><td><input type="text" name="telephone" value="${customer.telephone}"></td></tr><tr><td>郵箱地址:</td><td><input type="text" name="email" value="${customer.email}"></td></tr><tr><td>照片:</td><td><input type="file" name="photo" value="${customer.photo}"></td></tr></table><button type="submit">保存</button>
</form><script src="${BASE}/asset/lib/jquery/jquery.min.js"></script>
<script src="${BASE}/asset/lib/jquery-form/jquery.form.min.js"></script>
<script>$(function () {$('#customer_form').ajaxForm({type:'post',url:'${BASE}/customer_create',success:function (data) {if(data){location.href = '${BASE}/customer';}}});});
</script>
</body>
</html>

當表單提交時,請求會轉發到CustomerController的createSubmit方法上。該方法帶有一個Param參數,我們打算通過該參數來獲取“表單字段的名值對映射”與“所上傳的文件參數對象”,應該如何編碼呢?下面是我們要實現的目標:

@Controller
public class CustomerController {/*** 處理 創建客戶請求 - 帶圖片*/@Action("post:/customer_create")public Data createSubmit(Param param){Map<String,Object> fieldMap = param.getFieldMap();FileParam fileParam = param.getFile("photo");boolean result = customerService.createCustomer(fieldMap,fileParam);return new Data(result);} 
}

調用Param的getFieldMap()方法來獲取表單字段的鍵值對映射(Map?fieldMap),指定一個具體的文件字段名稱photo,并調用getFile方法即可獲取對應的文件參數對象(FileParam?fileParam)。隨后,可調用customerService的createCustomer方法,將fieldMap與fileParam這兩個參數傳入。

Controller層的代碼就是這樣,具體業務邏輯都在Service層了,對于CustomerService而言,只需寫幾行代碼即可實現業務邏輯,將輸入參數存入數據庫,同時將文件上傳到服務器上。

@Service
public class CustomerService {/*** 創建客戶*/@Transactionpublic boolean createCustomer(Map<String,Object> fieldMap,FileParam fileParam){Boolean result = DBHelper.insertEntity(Customer.class,fieldMap);if (result){UploadHelper.uploadFile("/tmp/upload/",fileParam);}return result;}
}

可見,除了使用DatabaseHelper操作數據庫,還可以通過UploadHelper將文件上傳到指定的服務器目錄中。

注意:實際上,完全可以通過代碼來讀取配置文件中定義的文件上傳路徑,此處只是為了簡化,請注意。

我們把計劃要完成的事情總結一下:

(1)改造Param結構,可以通過它來獲取已上傳的文件參數(FileParam)

(2)使用UploadHelper助手類來上傳文件。

實現文件上傳功能?

我們不妨從FileParam開始,它實際上是一個用于封裝文件參數的JavaBean,代碼如下:

/*** @program: FileParam* @description: 封裝文件參數的Bean*/
public class FileParam {private String fieldName;  //文件表單的字段名private String fileName;   //文件名private long fileSize;  //文件大小private String contentType;    //上傳文件的Content-Type,可判斷文件類型private InputStream inputStream;   //上傳文件的字節輸入流public FileParam(String fieldName, String fileName, long fileSize, String contentType, InputStream inputStream) {this.fieldName = fieldName;this.fileName = fileName;this.fileSize = fileSize;this.contentType = contentType;this.inputStream = inputStream;}public String getFieldName() {return fieldName;}public String getFileName() {return fileName;}public long getFileSize() {return fileSize;}public String getContentType() {return contentType;}public InputStream getInputStream() {return inputStream;}
}

除了文件參數(FileParam),我們還需要一個表單參數(FormParam),代碼如下:?

/*** @program: FormParam* @description: 封裝表單參數*/
public class FormParam {private String fieldName;   //表單字段名private Object fieldValue;   //表單字段值public FormParam(String fieldName, Object fieldValue) {this.fieldName = fieldName;this.fieldValue = fieldValue;}public String getFieldName() {return fieldName;}public Object getFieldValue() {return fieldValue;}
}

在一個表單中,所有的參數可分為兩類:表單參數與文件參數。有必要將Param類做一個重構,讓它封裝這兩類參數,并提供一系列的get方法,用于從該對象中獲取指定的參數。

/*** @program: Param* @description: 請求參數對象*/
public class Param {private List<FormParam> formParamList;private List<FileParam> fileParamList;public Param(List<FormParam> formParamList) {this.formParamList = formParamList;}public Param(List<FormParam> formParamList, List<FileParam> fileParamList) {this.formParamList = formParamList;this.fileParamList = fileParamList;}/*** 獲取請求參數映射* @return*/public Map<String,Object> getFieldMap(){Map<String,Object> fieldMap = new HashMap<String,Object>();if (CollectionUtil.isNotEmpty(formParamList)){for (FormParam formParam:formParamList){String fieldName = formParam.getFieldName();   //表單參數名Object fieldValue = formParam.getFieldValue();   //表單參數值if (fieldMap.containsKey(fieldName)){   //如果已經有此參數名fieldValue = fieldMap.get(fieldName) + StringUtil.SEPARATOR + fieldValue;  // 舊的數據<-->新的數據作為value
                }fieldMap.put(fieldName,fieldValue);}}return fieldMap;}/*** 獲取上傳文件映射*/public Map<String,List<FileParam>> getFileMap(){Map<String,List<FileParam>> fileMap = new HashMap<String,List<FileParam>>();if (CollectionUtil.isNotEmpty(fileMap)){for (FileParam fileParam:fileParamList){    //遍歷文件參數String fieldName = fileParam.getFieldName();    //獲取表單文件字段名List<FileParam> fileParamList;if (fileMap.containsKey(fieldName)){    //如果Map已經存在fileParamList = fileMap.get(fieldName);   //獲取Map中的值}else{fileParamList = new ArrayList<FileParam>();  //否則,新建一個值
                }fileParamList.add(fileParam);   //fileMap.put(fieldName,fileParamList);   //放入到表單文件字段名,List<FileParam>的映射中
            }}return fileMap;}/*** 獲取所有上傳文件* @param fieldName 表單文件字段名* @return*/public List<FileParam> getFileList(String fieldName){return getFileMap().get(fieldName);}/*** 獲取唯一上傳文件* @param fieldName 表單文件字段名* @return*/public FileParam getFile(String fieldName){List<FileParam> fileParamList = getFileList(fieldName);if (CollectionUtil.isNotEmpty(fileParamList) && fileParamList.size() ==1){return fileParamList.get(0);}return null;}/*** 驗證參數是否為空* @return*/public boolean isEmpty(){return CollectionUtil.isEmpty(formParamList) && CollectionUtil.isEmpty(fileParamList);}/*** 根據參數名獲取String型參數值* @param name* @return*/public String getString(String name){return CastUtil.castString(getFieldMap().get(name));}/*** 根據參數名獲取Double型參數值* @param name* @return*/public Double getDouble(String name){return CastUtil.castDouble(getFieldMap().get(name));}/*** 根據參數名獲取Long型參數值* @param name* @return*/public long getLong(String name){return CastUtil.castLong(getFieldMap().get(name));}/*** 根據參數名獲取int型參數值* @param name* @return*/public int getInt(String name){return CastUtil.castInt(getFieldMap().get(name));}/*** 根據參數名獲取boolean型參數值* @param name* @return*/public boolean getBoolean(String name){return CastUtil.castBoolean(getFieldMap().get(name));}}

可見Param包含了兩個成員變量:List<formParamList>與List<fileParamList>;它們分別封裝了表單參數與文件參數,隨后提供了兩個構造器,用于初始化Param對象,還提供了兩個get方法,分別用于獲取所有的表單參數與文件參數。返回值均為Map類型,其中Map表示請求參數映射,Map表示上傳文件映射。對于同名的請求參數,通過一個特殊的分隔符進行了處理,該分隔符定義在StringUtil類中,代碼如下:

    /*** 分隔符*/public static final String SEPARATOR = String .valueOf((char)29);

對于同名的上傳文件,通過一個List進行了封裝,可輕松實現多文件上傳的需求。可通過List?getFileList(String fieldName)?方法獲取所有上傳文件,若只上傳了一個文件,則可直接使用FileParam?getFile(String fieldName)方法獲取唯一上傳文件。還提供了一個boolean?isEmpty()方法,用于驗證參數是否為空。最后,提供了一組根據參數名獲取指定類型的方法,例如,String?getString(String name)、double getDouble(String name)等。

可借助Apache?Commons提供的FileUpload類庫實現文件上傳特性,首先需要在pom.xml中添加如下依賴:

        <!--文件上傳--><dependency><groupId>commons-fileupload</groupId><artifactId>commons-fileupload</artifactId><version>1.3.1</version></dependency>

接下來我們需要編寫一個UploadHelper類來封裝Apache?Commons?FileUpload的相關代碼:

import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.io.FileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.smart4j.framework.bean.FileParam;
import org.smart4j.framework.bean.FormParam;
import org.smart4j.framework.bean.Param;
import org.smart4j.framework.util.CollectionUtil;
import org.smart4j.framework.util.FileUtil;
import org.smart4j.framework.util.StreamUtil;
import org.smart4j.framework.util.StringUtil;import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import java.io.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;/*** @program: UploadHelper* @description: 文件上傳助手類* @author: Created by Autumn* @create: 2018-12-14 16:21*/
public final class UploadHelper {private static final Logger LOGGER = LoggerFactory.getLogger(UploadHelper.class);/*** Apache Commons FileUpload提供的Servlet文件上傳對象*/private static ServletFileUpload servletFileUpload;/*** 初始化*/public static void init(ServletContext servletContext){/*獲取tomcat的work目錄*/File repository = (File) servletContext.getAttribute("javax.servlet.context.tempdir");/*** DiskFileItemFactory構造的兩個參數*  第一個參數:sizeThreadHold - 設置緩存(內存)保存多少字節數據,默認為10240字節,即10K*    如果一個文件沒有大于10K,則直接使用內存直接保存成文件就可以了。*    如果一個文件大于10K,就需要將文件先保存到臨時目錄中去。*  第二個參數 File 是指臨時目錄位置 - 可以不用tomcat的work目錄可以用任意一個目錄*/DiskFileItemFactory fileItemFactory = new DiskFileItemFactory(DiskFileItemFactory.DEFAULT_SIZE_THRESHOLD, repository);servletFileUpload = new ServletFileUpload(fileItemFactory);int uploadLimit = ConfigHelper.getAppUploadLimit();  //獲取文件上傳限制默認為10(M)if (uploadLimit != 0){servletFileUpload.setFileSizeMax(uploadLimit*1024*1024);   //設置單文件最大大小為10M
        }}/*** 判斷請求是否為multipart類型*/public static boolean isMultipart(HttpServletRequest request){return ServletFileUpload.isMultipartContent(request);}/*** 創建請求對象* 將request轉換為Param參數* @return*/public static Param createParam(HttpServletRequest request) throws IOException {List<FormParam> formParamList = new ArrayList<FormParam>();List<FileParam> fileParamList = new ArrayList<FileParam>();try{/*解析request*/Map<String,List<FileItem>> fileItemListMap = servletFileUpload.parseParameterMap(request);   //將request轉換為Mapif (CollectionUtil.isNotEmpty(fileItemListMap)){//遍歷Map集合,一個表單名可能有多個文件for (Map.Entry<String,List<FileItem>> fileItemListEntry : fileItemListMap.entrySet()){String fieldName = fileItemListEntry.getKey();    //獲取表單字段名List<FileItem> fileItemList = fileItemListEntry.getValue();   //文件集合if (CollectionUtil.isNotEmpty(fileItemListMap)){for (FileItem fileItem:fileItemList){   //遍歷文件集合if (fileItem.isFormField()){   //如果是表單字段String fieldValue = fileItem.getString("UTF-8");formParamList.add(new FormParam(fieldName,fieldValue));}else{   //如果是文件String fileName = FileUtil.getRealFileName(new String(fileItem.getName().getBytes(),"UTF-8"));   //獲取文件名if (StringUtil.isNotEmpty(fileName)){  //如果文件名不為空long fileSize = fileItem.getSize();  //獲取文件大小String contentType = fileItem.getContentType();   //獲取文件類型InputStream inputStream = fileItem.getInputStream();   //獲取文件輸入流fileParamList.add(new FileParam(fieldName,fileName,fileSize,contentType,inputStream));}}}}}}} catch (FileUploadException e) {LOGGER.error("create param failure",e);throw new RuntimeException(e);}return new Param(formParamList,fileParamList);}/*** 上傳文件* @param basePath* @param fileParam*/public static void uploadFile(String basePath,FileParam fileParam){try{if (fileParam != null){String filePath = basePath + fileParam.getFileName();   //路徑+文件名FileUtil.createFile(filePath);  //創建文件InputStream inputStream = new BufferedInputStream(fileParam.getInputStream());  //獲取文件的輸入流OutputStream outputStream = new BufferedOutputStream(new FileOutputStream(filePath));   //獲取輸出流StreamUtil.copyStream(inputStream,outputStream);   //輸入流拷貝到輸出流中
            }} catch (FileNotFoundException e) {LOGGER.error("upload file failure",e);throw new RuntimeException(e);}}/*** 批量上傳文件* @param basePath* @param fileParamList*/public static void uploadFile(String basePath,List<FileParam> fileParamList){try {if (CollectionUtil.isNotEmpty(fileParamList)){for (FileParam fileParam : fileParamList){uploadFile(basePath,fileParam);}}}catch (Exception e){LOGGER.error("upload file failure",e);throw new RuntimeException(e);}}
}

需要提供一個init方法,在該方法中初始化ServletFileUpload對象。一般情況下,只需設置一個上傳文件的臨時目錄與上傳文件的最大限制;上傳文件的臨時目錄可設置為應用服務器的臨時目錄,上傳文件的最大限制可讓用戶自行配置。所以我們使用了ConfigHelper.getAppUploadLimit()來獲取,可以在smart.properties文件中進行配置。

首先,在ConfigConstant中添加一個配置常量APP_UPLOAD_LIMIT;

    String APP_UPLOAD_LIMIT = "smart.framework.app.upload_limit";

這也就意味著,我們可以在smart.properties文件中使用smart.framwork.app.upload_limit配置項來設定上傳文件的最大限制。

然后,在ConfigHelper中添加一個int getAppUploadLimit()方法,用于獲取該配置的值,此時可設置該配置的初始值(10),也就是說,若不在smart.properties文件中提供該配置,則上傳文件的最大限制是10MB。

public class ConfigHelper {/*** 獲取應用文件上傳限制* @return*/public static int getAppUploadLimit(){return PropsUtil.getInt(CONFIG_PROPS,ConfigConstant.APP_UPLOAD_LIMIT,10);}
}

在UploadHelper中提供一個boolean isMultipart(HttpServletRequest request)方法,用于判斷當前請求對象是否為multipart類型。只有在上傳文件時對應的請求類型才是multipart類型,也就是說,可通過isMultipart方法來判斷當前請求時否為文件上傳請求。

接下來提供一個非常重要的方法,可從當前請求中創建Param對象,它就是Param?createParam(HttpServletRequest?request)方法:其中我們使用了ServletFileUpload對象來解析請求參數,并通過遍歷所有請求參數來初始化List?formParamList與List?fileParamList變量的值。在遍歷請求參數時,需要對當前的org.apache.commons.fileupload.FileItem對象進行判斷,若為普通表單字段(調用fileItem.isFormField()返回true),則創建FormParam對象,并添加到formParamList對象中。否則即為文件上傳字段,通過FileUtil提供的getRealFileName來獲取上傳文件后的真實文件名,并從FileItem對象中構造FileParam對象,添加到fileParamList對象中,最后,通過formParamList與fileParamList來構造Param對象并返回。

FileUtil代碼如下

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;/*** @program: FileUtil* @description: 文件操作工具類* @author: Created by Autumn* @create: 2018-12-19 13:03*/
public class FileUtil {private static final Logger LOGGER = LoggerFactory.getLogger(FileUtil.class);/*** 獲取真實文件名(自動去掉文件路徑)** @param fileName* @return*/public static String getRealFileName(String fileName) {return FilenameUtils.getName(fileName);}/*** 創建文件** @param filePath* @return*/public static File createFile(String filePath) {File file;file = new File(filePath);   //根據路徑創建文件try {File parentDir = file.getParentFile();   //獲取文件父目錄if (!parentDir.exists()) {  //判斷上層目錄是否存在FileUtils.forceMkdir(parentDir);   //創建父級目錄
            }} catch (IOException e) {LOGGER.error("create file failure",e);throw new RuntimeException(e);//e.printStackTrace();
        }return file;}
}

最后提供兩個用于上傳文件的方法,一個用于上傳單個文件,另一個用于批量上傳。此時用到了StreamUtil工具類的copyStream方法,代碼如下:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.*;/*** @program: StreamUtil* @description: 流操作常用工具類* @author: Created by Autumn* @create: 2018-10-24 15:41*/
public class StreamUtil {private static final Logger LOGGER = LoggerFactory.getLogger(StreamUtil.class);/*** 從輸入流中獲取字符串* @param is* @return*/public static String getString(InputStream is){StringBuilder sb = new StringBuilder();try {BufferedReader reader = new BufferedReader(new InputStreamReader(is));String line;while((line=reader.readLine())!=null){sb.append(line);}} catch (IOException e) {LOGGER.error("get string failure",e);throw new RuntimeException(e);}return sb.toString();}/*** 將輸入流復制到輸出流* @param inputStream 輸入流* @param outputStream 輸出流*/public static void copyStream(InputStream inputStream, OutputStream outputStream){try {int length;byte[] buffer = new byte[4*1024];while((length = inputStream.read(buffer,0,buffer.length)) != -1){outputStream.write(buffer,0,length);}outputStream.flush();} catch (IOException e) {LOGGER.error("copy stream failure",e);throw new RuntimeException(e);} finally {try {inputStream.close();outputStream.close();} catch (IOException e) {LOGGER.error("close stream failure",e);}}}}

現在UploadHelper已編寫完畢,接下來需要找一個地方來調用init方法。整個web框架的入口也就是DispatcherServlet的init方法了,所有我們需要在該方法中調用UploadHelper的init方法。

除了在DispatcherServlet的init方法中添加一行代碼,還需要對service代碼進行一些重構。首先需要跳過/favicon.ico請求,只處理普通的請求。然后需要判斷請求對象是否為上傳文件,針對兩種不同的情況來創建Param對象,其中通過UploadHelper來創建的方式已在前面描述了。相應的,我們也對以前的代碼進行封裝,提供一個名為RequestHelper類,并通過它的createParam方法來初始化Param對象。

import org.smart4j.framework.bean.FormParam;
import org.smart4j.framework.bean.Param;
import org.smart4j.framework.util.ArrayUtil;
import org.smart4j.framework.util.CodecUtil;
import org.smart4j.framework.util.StreamUtil;
import org.smart4j.framework.util.StringUtil;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;/*** @program: RequestHelper* @description: 請求助手類* @author: Created by Autumn* @create: 2018-12-25 13:22*/
public class RequestHelper {public static Param createParam(HttpServletRequest request) throws IOException {List<FormParam> formParamList = new ArrayList<>();formParamList.addAll(parseParameterNames(request));formParamList.addAll(parseInputStream(request));return new Param(formParamList);}/*** 獲取Form表單普通參數并放入List<FormParam>中* 適用于application/x-www-form-urlencoded* @param request* @return List<FormParam>*/private static List<FormParam> parseParameterNames(HttpServletRequest request){List<FormParam> formParamList = new ArrayList<FormParam>();Enumeration<String> paramNames = request.getParameterNames();   //獲取request中的所有參數名稱枚舉while (paramNames.hasMoreElements()){   //遍歷參數名枚舉String fieldName = paramNames.nextElement();   //獲取參數名稱//!!!!!!!!獲取參數值(例如CheckBox的值有多個) request.getParameter(String name)是獲得相應名的數據,如果有重復的名,則返回第一個的值.String[] fieldValues = request.getParameterValues(fieldName);if (ArrayUtil.isNotEmpty(fieldValues)){   //判斷是否為空Object fieldValue;   //參數最終值if (fieldValues.length == 1){  //如果只有一個值fieldValue = fieldValues[0];   //直接賦值} else {  //如果有多個值(CheckBox多選)StringBuilder sb = new StringBuilder("");for (int i = 0; i< fieldValues.length; i++){  //遍歷
                        sb.append(fieldValues[i]);if (i != fieldValues.length-1){  //如果不是最后一個sb.append(StringUtil.SEPARATOR);  //加上通用分割符
                        }}fieldValue = sb.toString();}formParamList.add(new FormParam(fieldName,fieldValue));   //將參數鍵值對加入List參數列表中去
            }}return formParamList;}/*** 獲取參數流并放入List<FormParam>中* 適用于application/json,text/xml,multipart/form-data文本流或者大文件形式提交的請求或者xml等形式的報文* @param request* @return* @throws IOException*/private static List<FormParam> parseInputStream(HttpServletRequest request) throws IOException {List<FormParam> formParamList = new ArrayList<FormParam>();String body = CodecUtil.decodeURL(StreamUtil.getString(request.getInputStream()));if (StringUtil.isNotEmpty(body)){String[] kvs = StringUtil.splitString(body,"&");if (ArrayUtil.isNotEmpty(kvs)){for (String kv:kvs) {String[] array = StringUtil.splitString(kv, "=");if (ArrayUtil.isNotEmpty(array) && array.length == 2){String fieldName = array[0];String fieldValue = array[1];formParamList.add(new FormParam(fieldName,fieldValue));}}}}return formParamList;}
}

可見以上代碼邏輯并未變化,只是將以前放在DispatcherServlet中的相關代碼搬到了RequestHelper中了。最后獲取的View同樣也分兩種情況進行了處理,只是此時并未提供其他類來封裝這些代碼,而是直接在當前類中添加了兩個私有方法handleViewResult與handleDataResult。

重構后的Dispatcher代碼

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.smart4j.framework.bean.Data;
import org.smart4j.framework.bean.Handler;
import org.smart4j.framework.bean.Param;
import org.smart4j.framework.bean.View;
import org.smart4j.framework.helper.*;
import org.smart4j.framework.util.*;
import javax.servlet.*;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.Method;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;/*** @program: DispatcherServlet* @description: 請求轉發器* @author: Created by Autumn* @create: 2018-10-24 11:34*/@WebServlet(urlPatterns = "/*",loadOnStartup = 0)
public class DispatcherServlet extends HttpServlet {private static final Logger LOGGER = LoggerFactory.getLogger(DispatcherServlet.class);@Overridepublic void init(ServletConfig servletConfig) throws ServletException {//初始化相關Helper類
        HelperLoader.init();//獲取ServletContext對象(用于注冊Servlet)ServletContext servletContext = servletConfig.getServletContext();//注冊處理JSP的ServletServletRegistration jspServlet = servletContext.getServletRegistration("jsp");jspServlet.addMapping(ConfigHelper.getAppJspPath()+"*");//注冊處理靜態資源的默認ServletServletRegistration defaultServlet = servletContext.getServletRegistration("default");defaultServlet.addMapping(ConfigHelper.getAppAssetPath()+"*");//初始化上傳文件大小,以及超過最大大小存放的目錄
        UploadHelper.init(servletContext);}@Overrideprotected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {//獲取請求方法與請求路徑String requestMethod = req.getMethod().toLowerCase();String requestPath = req.getPathInfo();if (requestPath.equals("\favicon.ico")){return ;}//獲取Action處理器Handler handler= ControllerHelper.getHandler(requestMethod,requestPath);if(handler!=null){//獲取Controller類機器Bean實例Class<?> controllerClass = handler.getControllerClass();Object controllerBean = BeanHelper.getBean(controllerClass);Param param;if (UploadHelper.isMultipart(req)){   //如果是multipart/form-data streamparam = UploadHelper.createParam(req);   //multipart方式}else{   //如果是非multipart方式提交(即application/x-www-form-urlencoded,application/json,text/xml)param = RequestHelper.createParam(req);   //非multipart表單方式
            }/*將一下代碼放入RequestHelper中去//創建請求參數對象Map<String,Object> paramMap = new HashMap<String, Object>();Enumeration<String> paramNames = req.getParameterNames();while(paramNames.hasMoreElements()){String paramName = paramNames.nextElement();String paramValue = req.getParameter(paramName);paramMap.put(paramName,paramValue);}//獲取請求body中的參數String body = CodecUtil.decodeURL(StreamUtil.getString(req.getInputStream()));if (StringUtil.isNotEmpty(body)){String[] params = StringUtil.splitString(body,"&");if (ArrayUtil.isNotEmpty(params)){for (String param:params){String[] array = StringUtil.splitString(param,"=");if (ArrayUtil.isNotEmpty(array)&&array.length==2){String paramName = array[0];String paramValue = array[1];paramMap.put(paramName,paramValue);}}}}Param param = new Param(paramMap);*/Object result = null;//調用Action方法Method actionMethod = handler.getActionMethod();/*優化沒有參數的話不需要寫參數*/if (param.isEmpty()){  //如果沒有參數result = ReflectionUtil.invokeMethod(controllerBean,actionMethod);  //就不傳參數}else{  //有參數result = ReflectionUtil.invokeMethod(controllerBean,actionMethod,param);  //傳參數
            }//處理Action方法返回值if (result instanceof View){//返回JSP頁面
                handleViewResult((View) result, req, resp);}else if (result instanceof Data){//返回Json數據
                handleDataResult((Data) result, resp);}}else{LOGGER.error("Request-Handler Mapping get null by Request("+requestMethod+","+requestPath+")");throw new RuntimeException("Request-Handler Mapping get null by Request("+requestMethod+","+requestPath+")");}}/*** 處理Json格式的數據* @param result Data對象* @param resp* @throws IOException*/private void handleDataResult(Data result, HttpServletResponse resp) throws IOException {Data data = result;Object model = data.getModel();if (model!=null){resp.setContentType("application/json");resp.setCharacterEncoding("UTF-8");PrintWriter writer = resp.getWriter();String json = JsonUtil.toJson(model);writer.write(json);writer.flush();writer.close();}}/*** 處理視圖結果* @param result View對象(jsp路徑+數據)* @param req* @param resp* @throws IOException* @throws ServletException*/private void handleViewResult(View result, HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException {View view = result;String path = view.getPath();if (StringUtil.isNotEmpty(path)){if (path.startsWith("/")){   //如果View的Path以/開頭則以項目根目錄為根路徑resp.sendRedirect(req.getContextPath()+path);} else {    //如果View的Path沒有以/開頭,則以配置的APPJSP(/WEB-INF/view/)為根目錄Map<String,Object> model = view.getModel();for (Map.Entry<String,Object> entry:model.entrySet()){req.setAttribute(entry.getKey(),entry.getValue());}req.getRequestDispatcher(ConfigHelper.getAppJspPath()+path).forward(req,resp);}}}
}

此時,一個簡單的文件上傳特性已基本具備,可以在框架中正常使用了。

可能出現的問題

獲取注冊處理JSP的Servlet報錯

問題代碼

這是因為tomcat用的是maven插件,并不是真實的tomcat。所以導致獲取jsp的servlet失敗。

jQuery未引入導致用原生form提交

原生form提交的幾個要素

action:url 地址,服務器接收表單數據的地址

method:提交服務器的http方法,一般為post和get

enctype: 表單數據提交時使用的編碼類型,默認使用"pplication/x-www-form-urlencoded"。如果是使用POST請求,則請求頭中的content-type指定值就是該值。如果表單中有上傳文件,編碼類型需要使用"multipart/form-data",類型,才能完成傳遞文件數據。

寫了method、enctype和action后,最后form表單的提交按鈕要用

<input type="submit">保存</input>

缺一個都會用默認的get方式提交。

<form id="customer_form" action="${BASE}/customer_create" method="post" enctype="multipart/form-data"><input type="submit">保存</input>
</form>

后臺獲取文件沒有內容(此bug由個人失誤導致,可過濾)

?調試框架源碼發現一個方法判斷有誤,寫成了局部變量fileMap了。寫時候一個不小心,調試要調試半天吶

最終文件上傳完畢,結果如下。

框架源碼

項目源碼(使用開發框架)

?

轉載于:https://www.cnblogs.com/aeolian/p/10118806.html

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/388682.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/388682.shtml
英文地址,請注明出處:http://en.pswp.cn/news/388682.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

matlab飛行數據仿真,基于MATLAB的飛行仿真

收稿日期: 2005 - 05 - 15   第 23卷  第 06期 計  算  機  仿  真 2006年 06月    文章編號: 1006 - 9348 (2006) 06 - 0057 - 05 基于 MATLAB的飛行仿真 張鐳 ,姜洪洲 ,齊潘國 ,李洪人 (哈爾濱工業大學電液伺服仿真及試驗系統研究所 ,黑龍江 哈爾濱 150001) 摘要:該…

Windows Server 2003 DNS服務安裝篇

導讀-- DNS(Domain Name System&#xff0c;域名系統)是一種組織成層次結構的分布式數據庫&#xff0c;里面包含有從DNS域名到各種數據類型(如IP地址)的映射“貴有恒&#xff0c;何必三更起五更勤;最無益&#xff0c;只怕一日曝十日寒。”前一段時間巴哥因為一些生活瑣事而中止…

正則表達式matlab,正則表達式中一個word的匹配?@MATLAB - 優秀的Free?OS(Linux)版 - 北大未名BBS...

我目前想做的就是判斷一個str是否可以被認為是有效的MATLAB index。最好的方法是直接運行&#xff0c;然后看運行結果或報錯類型&#xff0c;但是我不打算在不知道是什么類型的東西之前運行它&#xff0c;所以可以預先parse一下&#xff0c;簡單判斷是否“長得跟有效的MATLAB i…

arima模型怎么擬合_7個統計測試,用于驗證和幫助擬合ARIMA模型

arima模型怎么擬合什么是ARIMA&#xff1f; (What is ARIMA?) ARIMA models are one of the most classic and most widely used statistical forecasting techniques when dealing with univariate time series. It basically uses the lag values and lagged forecast error…

jQuery禁止Ajax請求緩存

一 現象 get請求在有些瀏覽器中會緩存。瀏覽器不會發送請求&#xff0c;而是使用上次請求獲取到的結果。 post請求不會緩存。每次都會發送請求。 二 解決 jQuery提供了禁止Ajax請求緩存的方法&#xff1a; $.ajax({type: "get",url: "http://www.baidu.com?_&…

python 實例

參考 http://developer.51cto.com/art/201804/570408.htm 轉載于:https://www.cnblogs.com/artesian0526/p/9552510.html

[WPF]ListView點擊列頭排序功能實現

[WPF]ListView點擊列頭排序功能實現 這是一個非常常見的功能&#xff0c;要求也很簡單&#xff0c;在Column Header上顯示一個小三角表示表示現在是在哪個Header上的正序還是倒序就可以了。微軟的MSDN也已經提供了實現方式。微軟的方法中&#xff0c;是通過ColumnHeader Templ…

天池幸福感的數據處理_了解幸福感與數據(第1部分)

天池幸福感的數據處理In these exceptional times, the lockdown left many of us with a lot of time to think. Think about the past and the future. Think about our way of life and our achievements. But most importantly, think about what has been and would be ou…

標線markLine的用法

series: [{markLine: {itemStyle: {normal: { lineStyle: { type: solid, color:#000 },label: { show: true, position:left } }},data: [{name: 平均線,// 支持 average, min, maxtype: average},{name: Y 軸值為 100 的水平線,yAxis: 100},[{// 起點和終點的項會共用一個 na…

php pfm 改端口,羅馬2ESF和PFM 修改建筑 軍團 派系 兵種等等等很多東西的教程

本帖最后由 clueber 于 2013-10-5 12:30 編輯本人是個羅馬死忠加修改黨&#xff0c;恩&#xff0c;所以分享一下自己的修改心得修改工具為ESF1.0.7和PFM3.0.3首先是ESF修改。ESF可以用來改開局設定和存檔&#xff0c;修改開局設定是startpos.esf文件&#xff0c;在存檔在我這里…

紅草綠葉

從小到大喜歡陰天&#xff0c;喜歡下雨&#xff0c;喜歡那種潮濕的感覺。卻又絲毫容不得腳上有一絲的水汽&#xff0c;也極其討厭穿涼鞋。小時候特別喜歡去山上玩&#xff0c;偷桃子柿子&#xff0c;一切一切都成了美好的回憶&#xff0c;長大了&#xff0c;那些事情就都不復存…

wpf listview 使用

單列&#xff1a; <ListView Grid.Column"1" Height"284" HorizontalAlignment"Left" Margin"64,73,0,0" Name"listView1" VerticalAlignment"Top" Width"310" > <ListView.Items…

php 獲取當天到23 59,js 獲取當天23點59分59秒 時間戳 (最簡單的方法)

原生Ajax 和Jq Ajax前言:這次介紹的是利用ajax與后臺進行數據交換的小例子,所以demo必須通過服務器來打開.服務器環境非常好搭建,從網上下載wamp或xampp,一步步安裝就ok,然后再把寫好的頁面放在服務器中指定的 ...『TCP&sol;IP詳解——卷一&#xff1a;協議』讀書筆記——1…

詹森不等式_注意詹森差距

詹森不等式背景 (Background) In Kaggle’s M5 Forecasting — Accuracy competition, the square root transformation ruined many of my team’s forecasts and led to a selective patching effort in the eleventh hour. Although it turned out well, we were reminded t…

【轉載】儒林外史人物——荀玫

寫在前面&#xff1a;本博客內容為轉載&#xff0c;原文URL&#xff1a;http://blog.sina.com.cn/s/blog_9132ac5b0101iukw.html 說完周進&#xff0c;本應順著說范進&#xff0c;但我覺得荀玫他們村的事情過于喜感&#xff0c;想先說荀玫。 荀玫簡直是儒林中的某類標桿人物&am…

WebM VP8 SDK Usage/關于WebM VP8 SDK的用法

WebM是Google提出的新的網絡視頻格式&#xff0c;本質上是個MKV的殼&#xff0c;封裝VPX中的VP8視頻流與Vorbis OGG音頻流。目前Firefox、Opera、Chrome都能直接打開WebM視頻文件而無需其他任何亂七八糟的插件。我個人倒是很喜歡WebM的OGG音頻&#xff0c;雖然在低比特率下不如…

數據分析師 需求分析師_是什么讓分析師出色?

數據分析師 需求分析師重點 (Top highlight)Before we dissect the nature of analytical excellence, let’s start with a quick summary of three common misconceptions about analytics from Part 1:在剖析卓越分析的本質之前&#xff0c;讓我們從第1部分中對分析的三種常…

JQuery發起ajax請求,并在頁面動態的添加元素

頁面html代碼&#xff1a; <li><div class"coll-tit"><span class"coll-icon"><iclass"sysfont coll-default"></i>全域旅游目的地</span></div><div class"coll-panel"><div c…

arcgis鏡像圖形工具,ArcGis圖形編輯

一、編輯工具條介紹二、草圖工具介紹Sketch Tool&#xff1a;使用草圖工具來創建點要素或是線或面要素的節點。雙擊或是F2鍵結束草圖狀態&#xff0c;轉化為要素。Intersection Tool&#xff1a;使用相交工具在兩個線要素相交(或延長相交)的地方創建一個節點。如圖&#xff1a;…