您的位置:首页 > 编程语言 > Java开发

java最基本实现文件上传的过程

2016-11-22 19:19 381 查看
文件上传:
将commons-fileupload-1.2.2.jar和commons-io-2.1.jar两个包导入到项目中去。
首先我们建立一个test.jsp文件:
<!--为啥这里的action中的url要加request.getContextPath()呢?因为我没有通过servlet访问这个test.jsp,而是直接访问这个文件,

如果不加的话浏览器就会在test.jsp所在的文件夹里找file.do文件,当然是找不到的  -->

<!--注意!表单的enctype属性一定要由原来的文本编码格式改成下面的二进制文本编码格式 -->

<form method="post" action="<%=request.getContextPath()%>/file.do" enctype="multipart/form-data">

Username:
<input type="text" name="username"/><br/>

File:<input type="file" name="file"/><br/>

<input type="submit" value="提交"/>

</form>

然后我们创建一个类继承于HttpServlet:
public class fileServlet extends HttpServlet {

@Override

protected void doGet(HttpServletRequestreq, HttpServletResponse
resp) throws ServletException, IOException {

}

@Override

protected void doPost(HttpServletRequestreq, HttpServletResponse
resp) throws ServletException, IOException {

try {

file01(req,
resp);

} catch (FileUploadExceptione) {

e.printStackTrace();

}

}

public void file01(HttpServletRequestreq, HttpServletResponse
resp) throws FileNotFoundException, FileUploadException, IOException{

/*

 * 由于enctype改成了multipart/form-data将表单数据(键值对)全部转化成二进制字节流,requsername的属性名都找不到,所以下面两句全部输出null

 * System.out.println(req.getParameter("username"));

System.out.println(req.getParameter("file"));*/

boolean isMutipart = ServletFileUpload.isMultipartContent(req);//这个req中的数据是不是以multipart/form-data来编码的

if(isMutipart){

InputStream
is=null;

FileOutputStream
fos=null;

try {

ServletFileUpload
upload = new ServletFileUpload();

FileItemIterator
iterator = upload.getItemIterator(req);

while(iterator.hasNext()){

FileItemStream
fis = iterator.next();

//创建一个输入流来获取fis中的数据,使用了Multipart之后就表示通过字节数据进行传递,所以需要将其转化为输入流进行处理

is =
fis.openStream();

if(fis.isFormField()){//isFormField()方法用来判断是否是普通的表单域

//是表单域就输出表单域的名称,这里运行就输出:username

System.out.println(fis.getFieldName());

//通过Streams中的asString方法可以把流中的数据转换成String

System.out.println(Streams.asString(is));//这里就能输出username的值

//System.out.println(fis.getName());//输出:null

}else{

//System.out.println(fis.isFormField());//输出:false

System.out.println(fis.getFieldName());//输出:file

System.out.println(fis.getName());//如果不是表单域就说明是我上传的文件,此时输出的就是我上传的文件的名称

String path =
req.getSession().getServletContext().getRealPath("/fileSave");

path=path+"/"+fis.getName();

fos=new FileOutputStream(path);

byte[]
buf = new byte[1024];

int length=0;

while((length=is.read(buf))>0){

fos.write(buf, 0,length);//这样就实现了将上传的文件保存到制定文件路径下

}

}

}

}finally{

if(is!=null)is.close();

if(fos!=null)fos.close();

}

}

}}

以上就是文件上传的基本写法,上面就达到了用户上传一个文件,系统接受这个文件并保存到制定目录的目的。但是上面的方式是肯定不能直接引用到我们的shop01项目中的,因为不适合项目中的copypropertieUtil的copyProperties方法。为此,我们利用装饰器的设计理念做出了如下改进。首先创建了一个MultipartRequestWrapper,继承于HttpServletRequestWrapper:
public class MultipartRequestWrapperextends HttpServletRequestWrapper
{

//HttpServletResquest中封装的数据经过转化以后都放到这个Map中

private Map<String, String[]>params=new HashMap<String, String[]>();

private static final Stringpath =
"D:/WorkSpace/file/WebContent/fileSave";

private static final String[]type={"png","jpg","gif","bmp"};//允许上传的文件格式

public MultipartRequestWrapper(HttpServletRequestrequest) {

super(request);

setParams(request);

}

public void setParams(HttpServletRequestreq){

boolean isMultipart = ServletFileUpload.isMultipartContent(req);

if(isMultipart){

InputStream
is=null;

try {

ServletFileUpload
upload = new ServletFileUpload();

FileItemIterator
iterator = upload.getItemIterator(req);

while(iterator.hasNext()){

FileItemStream
fis = iterator.next();

is =
fis.openStream();

if(fis.isFormField()){

String[] values=null;

if(params.containsKey(fis.getFieldName())){

values =
params.get(fis.getFieldName());

//让数组的长度加1

values = Arrays.copyOf(values,values.length+1);

values[values.length-1] = Streams.asString(is);

}else{

values =
new String[]{Streams.asString(is)};

}

params.put(fis.getFieldName(),values);

}else{

if(is.available()>0){//要输入流中有数据才将上传的文件自动保存,否则什么都不做

String exName = FilenameUtils.getExtension(fis.getName());//获取文件后缀名

if(checkType(exName)){

//对于低版本的IE而言,浏览器会获取上传的文件的绝对路径,此时我们通过fis.getName()得到的就是文件在用户电脑上的绝对路径,下面的Stresms.copy程序就坑定要出错,所以这里我们得转换一下得到真实的文件名 StringfileName = FilenameUtils.getName(fis.getName());

//将上传的文件自动保存至指定路径,并且自动关闭流(第三个参数设置为true的意思就是自动关闭流)

Streams.copy(is,
new FileOutputStream(path+"/"+fileName),true);

params.put(fis.getFieldName(),new String[]{path+"/"+fileName});

}

}

}

}

} catch (FileUploadException | IOExceptione) {

e.printStackTrace();

}

}else{

params =
req.getParameterMap();

}

}

public boolean checkType(StringexName){

for(String
str: type){

if(str.equals(exName))return true;

}

return false;

}

@Override

public String getParameter(Stringname) {

String[] values =
params.get(name);

if(values!=null){

return params.get(name)[0];

}

return null;

}

@Override

public Map<String, String[]> getParameterMap() {

return params;

}

@Override

public String[]
getParameterValues(Stringname) {

return params.get(name);

}

}

接着我们在fileServlet中创建一个对HttpServleRequest进行装饰方法就能达到完美运用的目的:
public void file02(HttpServletRequestreq, HttpServletResponse
resp){

if(ServletFileUpload.isMultipartContent(req)){

//因为HttpServletRequestWrapper实现了HttpServletRequest接口,所以这里我们能直接让req指向MultipartRequestWrapper装饰完的HttpServletRequest

 req =
new MultipartRequestWrapper(req);

}

}

2016年5月20日前后几天我在文件上传这一块遇到了很多问题,下面就我所遇到的问题做一些总结:
(1)HttpServletRequestWrapper继承于ServletRequestWrapper,并且实现了HttpServletRequest接口。而且ServletRequestWrapper和HttpServletRequest都实现(继承)了ServletRequest接口,这就是为什么传入参数是HttpServletRequest的方法能够处理HttpServletRequestWrapper的子类的原因。
(2)jsp页面中${param.***}是通过getParameter来取值的(这个是jstl的EL知识,详细的介绍在帮助文档里有,可以去看看。),即使传入页面的requset是装饰后的request也是一样的。不过如果传入页面的是被装饰后的request,那么我们通过getParameter只能获取装饰时封装好的键值对Map中的值,如果此时我们通过jsp:param标签来试图封装新值到request中,结果肯定是失败的,至少我们没有办法获取封装的新值。但是值得注意的是即使request被装饰了,set(get)Attribute还是能用的,因为我们并没有覆盖这两个方法。
(3)在做文件上传的时候一定要注意输入流有没有被关闭,被关闭了就取不到数据了。我在做的时候就遇到这样的问题,Streams的一些方法是会默认关闭流的,比如asString、copy,所以用了这些方法之后就不能再用例如fis.openStream之类的方法了,这样是会报错的,你流都已经关闭了还怎么打开?还有我在试图直接将输入流InputStream通过setAttribute存起来后,在其他地方再getAttribute获得输入流,可此时的输入流已经不能用了,因为已经关闭了,为什么会关闭,我想是因为InputStream继承了Closeable,Closeable又继承了AutoCloseable,因此能够自动关闭流,具体的介绍可以看网页:http://blog.csdn.net/hengyunabc/article/details/18459463
https://yq.aliyun.com/articles/18170
(4)我们可以通过request.getInputStream()来获取一个取request中数据的输入流,不过第一次读取的时候可以读取到数据,但是接下来的读取操作都读取不到数据,为什么呢?关键在我们读取InputStream的时候用的是read方法,该方法程序中用到了一个pos指针,他指示每次读取之后下一次要读取的起始位置,在每次读取InputStream的时候会更新pos的值,当你下次再来读取的时候是从pos的位置开始的,而不是从头开始。这也就导致了两次调用request.getInputStream,第二次的时候肯定获取不了值,因为第一次读取完成之后pos指针在末尾,下次再读取肯定读取不到,因为request.getInputStream两次调用返回的对象是同一个对象,读取的是同一个Stream。而我遇到的问题中ServletFileUpload的getItemIterator方法本质上就是使用了request的getInputStream方法,每当我们调用getItemIterator获取FileItemIterator接着遍历整个request之后,就相当于用read方法读了一遍整个request的InpuStream(因为Streams的asString和copy方法本质上就是read方法),所以我们不能用我们创建的RequestWrapper两次封装一个request。详细介绍可以参看网页:
http://ayaoxinchao.iteye.com/blog/2110902
http://www.tuicool.com/articles/rEreEb
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  java 文件上传