前言
本文主要给大家介绍的是关于jsp简易文件上传组件的实现方法,分享出来供大家参考学习,下面话不多说,来一起看看详细的介绍吧。
文件上传,包括但不限于图片上传,是 web 开发中司空见惯的场景,相信各位或多或少都曾写过这方面相关的代码。java 界若说文件上传,则言必称 apache commons fileupload,论必及 smartupload。更甚者,servlet 3.0 将文件上传列入 jsr 标准,使得通过几个注解就可以在 servlet 中配置上传,无须依赖任何组件。使用第三方组件或 servlet 自带组件固然强大,但只靠 jsp 亦能完成任务,且短小而精悍,岂不美哉?本文实现的方法纯然基于 jsp 代码,没有弄成 servlet 和专门的 class(.java),实现方法纯粹是基于 jsp,没有太高的技术难度。实际使用过程中直接部署即可。
操作组件的代码行数不超过 10 行,只需几个步骤:
- 生成组件实例
- 设置实例属性
- 调用上传/下载方法
- 处理调用结果
首先是上传页面,本例是一张静态的 html。
上传成功如下图所示。
使用 post 的表单,设置 contenttype 为 multipart/form-data 多段数据,还要记得 input 的 name 属性。
<html> <body> <form action="action.jsp" enctype="multipart/form-data" method="post"> selectimage: <input type="file" name="myfile" /><br> <input type="submit" value="upload" /> </form> </body> </html>
action 中接受客户端请求的服务端代码在 action.jsp 中。action.jsp 通过 <%@include file="upload.jsp"%>包含了核心 java 代码,而 upload.jsp 里面又包含了另外一个 uploadrequest.jsp 文件。总之,我们这个小小的 java 程序,一共包含了 uploadrequest 请求信息类、uploadexception 自定义异常类和最重要的 upload 类这三个类。
<%@page pageencoding="utf-8"%>
<%@include file="upload.jsp"%>
<%
uploadrequest ur = new uploadrequest();// 创建请求信息,所有参数都在这儿设置
ur.setrequest(request); //一定要传入 request
ur.setfileoverwrite(true);// 相同文件名是否覆盖?true=允许覆盖
upload upload = new upload();// 上传器
try {
upload.upload(ur);
} catch (uploadexception e) {
response.getwriter().println(e.tostring());
}
if (ur.isok()) // 上传成功
response.getwriter().println("上传成功:" + ur.getuploaded_save_filename());
else
response.getwriter().println("上传失败!");
%>
这里创建了 uploadrequest 实例。文件上传操作通常会附加一些限制,如:文件类型、上传文件总大小、每个文件的最大大小等。除此以外,作为一个通用组件还需要考虑更多的问题, 如:支持自定义文件保存目录、支持相对路径和绝对路径、支持自定义保存的文件的文件名称等。这些配置通通在 uploadrequest 里设置。
至于 jsp 里面的类,我愿意多说说。 jsp 里面允许我们定义 java 的类,类本是可以是 static,但不能有 static 成员。实际上 jsp 类都是内部类,定义 static 与否关系不大。如果不能定义 static 方法,就把 static 方法移出类体外,书写成,
<%!
/**
* 获取开头数据头占用的长度
*
* @param datebytes
* 文件二进制数据
* @return
*/
private static int getstartpos(byte[] datebytes) {
....
}
>
<%! ... %> 和 <% ... %> 不同,前者是定义类成员的。
好~我们在看看 uploadrequest.jsp,就知道具体配置些什么。
<%@page pageencoding="utf-8"%>
<%!/**
* 上传请求的 bean,包含所有有关请求的信息
* @author frank
*
*/
public static class uploadrequest {
/**
* 上传最大文件大小,默认 1 mb
*/
private int maxfilesize = 1024 * 1000;
/**
* 保存文件的目录
*/
private string upload_save_folder = "e:\temp\";
/**
* 上传是否成功
*/
private boolean isok;
/**
* 是否更名
*/
private boolean isnewname;
/**
* 成功上传之后的文件名。如果 isnewname = false,则是原上传的名字
*/
private string uploaded_save_filename;
/**
* 相同文件名是否覆盖?true=允许覆盖
*/
private boolean isfileoverwrite = true;
private httpservletrequest request;
/**
* @return the maxfilesize
*/
public int getmaxfilesize() {
return maxfilesize;
}
/**
* @param maxfilesize the maxfilesize to set
*/
public void setmaxfilesize(int maxfilesize) {
maxfilesize = maxfilesize;
}
/**
* @return the upload_save_folder
*/
public string getupload_save_folder() {
return upload_save_folder;
}
/**
* @param upload_save_folder the upload_save_folder to set
*/
public void setupload_save_folder(string upload_save_folder) {
this.upload_save_folder = upload_save_folder;
}
/**
* @return the isok
*/
public boolean isok() {
return isok;
}
/**
* @param isok the isok to set
*/
public void setok(boolean isok) {
this.isok = isok;
}
/**
* @return the isnewname
*/
public boolean isnewname() {
return isnewname;
}
/**
* @param isnewname the isnewname to set
*/
public void setnewname(boolean isnewname) {
this.isnewname = isnewname;
}
/**
* @return the uploaded_save_filename
*/
public string getuploaded_save_filename() {
return uploaded_save_filename;
}
/**
* @param uploaded_save_filename the uploaded_save_filename to set
*/
public void setuploaded_save_filename(string uploaded_save_filename) {
this.uploaded_save_filename = uploaded_save_filename;
}
/**
* @return the isfileoverwrite
*/
public boolean isfileoverwrite() {
return isfileoverwrite;
}
/**
* 相同文件名是否覆盖?true=允许覆盖
* @param isfileoverwrite the isfileoverwrite to set
*/
public void setfileoverwrite(boolean isfileoverwrite) {
this.isfileoverwrite = isfileoverwrite;
}
/**
* @return the request
*/
public httpservletrequest getrequest() {
return request;
}
/**
* @param request the request to set
*/
public void setrequest(httpservletrequest request) {
this.request = request;
}
}
%>
这是一个普通的 java bean。完成上传逻辑的是 upload 类。
其原理是:
1、由客户端把要上传的文件生成 request 数据流,与服务器端建立连接;
2、在服务器端接收 request 流,将流缓存到内存中;
3、由服务器端的内存把文件输出到指定的目录。
upload.jsp 完整代码如下所示。
<%@page pageencoding="utf-8" import="java.io.*"%>
<%@include file="uploadrequest.jsp"%>
<%!
public static class uploadexception extends exception {
private static final long serialversionuid = 579958777177500819l;
public uploadexception(string msg) {
super(msg);
}
}
public static class upload {
/**
* 接受上传
*
* @param urequest
* 上传 pojo
* @return
* @throws uploadexception
*/
public uploadrequest upload(uploadrequest urequest) throws uploadexception {
httpservletrequest req = urequest.getrequest();
// 取得客户端上传的数据类型
string contenttype = req.getcontenttype();
if(!req.getmethod().equals("post")){
throw new uploadexception("必须 post 请求");
}
if (contenttype.indexof("multipart/form-data") == -1) {
throw new uploadexception("未设置表单 multipart/form-data");
}
int formdatalength = req.getcontentlength();
if (formdatalength > urequest.getmaxfilesize()) { // 是否超大
throw new uploadexception("文件大小超过系统限制!");
}
// 保存上传的文件数据
byte datebytes[] = new byte[formdatalength];
int byteread = 0, totalread = 0;
try(datainputstream in = new datainputstream(req.getinputstream());){
while (totalread < formdatalength) {
byteread = in.read(datebytes, totalread, formdatalength);
totalread += byteread;
}
} catch (ioexception e) {
e.printstacktrace();
throw new uploadexception(e.tostring());
}
// 取得数据分割字符串
int lastindex = contenttype.lastindexof("="); // 数据分割线开始位置boundary=---------------------------
string boundary = contenttype.substring(lastindex + 1, contenttype.length());// ---------------------------257261863525035
// 计算开头数据头占用的长度
int startpos = getstartpos(datebytes);
// 边界位置
int endpos = byteindexof(datebytes, boundary.getbytes(), (datebytes.length - startpos)) - 4;
// 创建文件
string filename = urequest.getupload_save_folder() + getfilename(datebytes, urequest.isnewname());
urequest.setuploaded_save_filename(filename);
file checkedfile = initfile(urequest);
// 写入文件
try(fileoutputstream fileout = new fileoutputstream(checkedfile);){
fileout.write(datebytes, startpos, endpos - startpos);
fileout.flush();
urequest.setok(true);
} catch (filenotfoundexception e) {
e.printstacktrace();
throw new uploadexception(e.tostring());
} catch (ioexception e) {
e.printstacktrace();
throw new uploadexception(e.tostring());
}
return urequest;
}
}
/**
* 获取开头数据头占用的长度
*
* @param datebytes
* 文件二进制数据
* @return
*/
private static int getstartpos(byte[] datebytes) {
int startpos;
startpos = byteindexof(datebytes, "filename="".getbytes(), 0);
startpos = byteindexof(datebytes, "n".getbytes(), startpos) + 1; // 遍历掉3个换行符到数据块
startpos = byteindexof(datebytes, "n".getbytes(), startpos) + 1;
startpos = byteindexof(datebytes, "n".getbytes(), startpos) + 1;
return startpos;
}
/**
* 在字节数组里查找某个字节数组,找到返回>=0,未找到返回-1
* @param data
* @param search
* @param start
* @return
*/
private static int byteindexof(byte[] data, byte[] search, int start) {
int index = -1;
int len = search.length;
for (int i = start, j = 0; i < data.length; i++) {
int temp = i;
j = 0;
while (data[temp] == search[j]) {
// system.out.println((j+1)+",值:"+data[temp]+","+search[j]);
// 计数
j++;
temp++;
if (j == len) {
index = i;
return index;
}
}
}
return index;
}
/**
* 如果没有指定目录则创建;检测是否可以覆盖文件
*
* @param urequest
* 上传 pojo
* @return
* @throws uploadexception
*/
private static file initfile(uploadrequest urequest) throws uploadexception {
file dir = new file(urequest.getupload_save_folder());
if (!dir.exists())
dir.mkdirs();
file checkfile = new file(urequest.getuploaded_save_filename());
if (!urequest.isfileoverwrite() && checkfile.exists()) {
throw new uploadexception("文件已经存在,禁止覆盖!");
}
return checkfile;
}
/**
* 获取 post body 中的文件名
*
* @param datebytes
* 文件二进制数据
* @param isautoname
* 是否自定命名,true = 时间戳文件名
* @return
*/
private static string getfilename(byte[] datebytes, boolean isautoname) {
string savefile = null;
if(isautoname){
savefile = "2016" + system.currenttimemillis();
} else {
string data = null;
try {
data = new string(datebytes, "utf-8");
} catch (unsupportedencodingexception e) {
e.printstacktrace();
data = "errfilename";
}
// 取得上传的文件名
savefile = data.substring(data.indexof("filename="") + 10);
savefile = savefile.substring(0, savefile.indexof("n"));
savefile = savefile.substring(savefile.lastindexof("\") + 1, savefile.indexof("""));
}
return savefile;
}
%>
通过 datainputstream 读取流数据到 databytes 中然后写入 fileoutputstream。另外还有些围绕配置的逻辑。
值得一提的是,tomcat 7 下 jsp 默认的 java 语法仍旧是 1.6 的。在 jsp 里面嵌入 java 1.7 特性的代码会抛出“resource specification not allowed here for source level below 1.7”的异常。于是需要修改 tomcat/conf/web.xml 里面的配置文件,找到 <servlet> 节点,加入下面粗体部分才可以。注意是 jsp 节点,不是 default 节点(很相似)。
<servlet> <servlet-name>jsp</servlet-name> <servlet-class>org.apache.jasper.servlet.jspservlet</servlet-class> <init-param> <param-name>fork</param-name> <param-value>false</param-value> </init-param> <init-param> <param-name>xpoweredby</param-name> <param-value>false</param-value> </init-param> lt;strong> <init-param> <param-name>compilersourcevm</param-name> <param-value>1.7</param-value> </init-param> <init-param> <param-name>compilertargetvm</param-name> <param-value>1.7</param-value> </init-param></strong> <load-on-startup>3</load-on-startup> </servlet>
至此,一个简单的文件上传器就完成了。但是本组件的缺点还是很明显的,试列举两项:一、上传流占用内存而非磁盘,所以上传大文件时内存会吃紧;二、尚不支持多段文件上传,也就是一次只能上传一个文件。
源码下载:http://xiazai.jb51.net/201707/yuanma/simpleupload(jb51.net).rar
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作能带来一定的帮助,如果有疑问大家可以留言交流,谢谢大家对硕编程的支持。
【说明】:本文章由站长整理发布,文章内容不代表本站观点,如文中有侵权行为,请与本站客服联系(QQ:254677821)!