一般狀況下,咱們能夠經過一個form(表單)來上傳文件,就如下面的「建立客戶」爲例來講明(對應的文件名是customer_create.jsp),須要提供一個form,並將其enctype屬性設爲multipart/form-data,表示以form data方式提交表單數據。html
注意:enctype的默認值爲application/x-www-form-urlencoded,表示以url encoded方式提交表單數據。java
下面咱們使用jQuery與jQuery Form插件快速編寫一個基於Ajax的文件上傳表單,代碼以下:jquery
<%@ 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參數,咱們打算經過該參數來獲取「表單字段的名值對映射」與「所上傳的文件參數對象」,應該如何編碼呢?下面是咱們要實現的目標:git
@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這兩個參數傳入。github
Controller層的代碼就是這樣,具體業務邏輯都在Service層了,對於CustomerService而言,只需寫幾行代碼便可實現業務邏輯,將輸入參數存入數據庫,同時將文件上傳到服務器上。web
@Service public class CustomerService { /** * 建立客戶 */ @Transaction public 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將文件上傳到指定的服務器目錄中。ajax
注意:實際上,徹底能夠經過代碼來讀取配置文件中定義的文件上傳路徑,此處只是爲了簡化,請注意。數據庫
咱們把計劃要完成的事情總結一下:apache
(1)改造Param結構,能夠經過它來獲取已上傳的文件參數(FileParam)json
(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轉換爲Map if (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); @Override public void init(ServletConfig servletConfig) throws ServletException { //初始化相關Helper類 HelperLoader.init(); //獲取ServletContext對象(用於註冊Servlet) ServletContext servletContext = servletConfig.getServletContext(); //註冊處理JSP的Servlet ServletRegistration jspServlet = servletContext.getServletRegistration("jsp"); jspServlet.addMapping(ConfigHelper.getAppJspPath()+"*"); //註冊處理靜態資源的默認Servlet ServletRegistration defaultServlet = servletContext.getServletRegistration("default"); defaultServlet.addMapping(ConfigHelper.getAppAssetPath()+"*"); //初始化上傳文件大小,以及超過最大大小存放的目錄 UploadHelper.init(servletContext); } @Override protected 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 stream param = 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); } } } }
此時,一個簡單的文件上傳特性已基本具有,能夠在框架中正常使用了。
問題代碼
這是由於tomcat用的是maven插件,並非真實的tomcat。因此致使獲取jsp的servlet失敗。
原生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了。寫時候一個不當心,調試要調試半天吶
最終文件上傳完畢,結果以下。