Struts 2中实现文件下载修正中文问题.docx
《Struts 2中实现文件下载修正中文问题.docx》由会员分享,可在线阅读,更多相关《Struts 2中实现文件下载修正中文问题.docx(16页珍藏版)》请在冰豆网上搜索。
Struts2中实现文件下载修正中文问题
Struts2中实现文件下载(修正中文问题)
在BlogJava上已经有一位作者阐述了文件上传的问题,地址是在Struts2中实现文件上传,因此我就不再讨论那个话题了。
我今天简单介绍一下Struts2的文件下载问题。
我们的项目名为struts2hello,所使用的开发环境是MyEclipse6,当然其实用哪个IDE都是一样的,只要把类库放进去就行了,文件下载不需要再加入任何额外的包。
读者可以参考文档:
http:
//beansoft.java-cn.org/myeclipse_doc_cn/struts2_demo.pdf,来了解怎么下载和配置基本的Struts2开发环境。
为了便于大家对比,我把完整的struts.xml的配置信息列出来:
Xml代码
1
xmlversion="1.0"encoding="UTF-8"?
>
2
DOCTYPEstrutsPUBLIC
3"-//ApacheSoftwareFoundation//DTDStrutsConfiguration2.0//EN"
4"http:
//struts.apache.org/dtds/struts-2.0.dtd">
5
6
7
8
--在这里添加Action定义-->
9
10
--简单文件下载-->
11
12
13text/plain
14inputStream
15attachment;filename="struts2中文.txt"
164096
17
18
19
20
--文件下载,支持中文附件名-->
21
22
--初始文件名-->
23Struts中文附件.txt
24
25text/plain
26inputStream
27
--使用经过转码的文件名作为下载文件名,downloadFileName属性
28对应action类中的方法getDownloadFileName()-->
29attachment;filename="${downloadFileName}"
304096
31
32
33
34
--下载现有文件-->
35
36/download/系统说明.doc
37
--初始文件名-->
38系统说明.doc
39
40application/octet-stream;charset=ISO8859-1
41inputStream
42
--使用经过转码的文件名作为下载文件名,downloadFileName属性
43对应action类中的方法getDownloadFileName()-->
44attachment;filename="${downloadFileName}"
454096
46
47
48
49
50
51
Struts2中对文件下载做了直接的支持,相比起自己辛辛苦苦的设置种种HTTP头来说,现在实现文件下载无疑要简便的多。
说起文件下载,最直接的方式恐怕是直接写一个超链接,让地址等于被下载的文件,例如:
下载file1.zip,之后用户在浏览器里面点击这个链接,就可以进行下载了。
但是它有一些缺陷,例如如果地址是一个图片,那么浏览器会直接打开它,而不是显示保存文件的对话框。
再比如如果文件名是中文的,它会显示一堆URL编码过的文件名例如%3457...。
而假设你企图这样下载文件:
http:
//localhost:
8080/struts2hello/download/系统说明.doc,Tomcat会告诉你一个文件找不到的404错误:
HTTPStatus404-/struts2hello/download/ϵͳ˵Ã÷.doc。
虽然目前还没发现直接配置Struts2来正确的下载中文名字的附件,不过好在作者对JSP中的文件下载比较了解,因此我们另有办法解决这个问题。
另外一个最大的用途,就是动态的生成并下载文件了,例如动态的下载生成的EXCEL,PDF,验证码图片等等。
本节内容就依次讨论简单的下载文件代码,下载中文附件,最后介绍如何下载已经存在的文件。
先说文件下载,编写一个普通的Action就可以了,只需要提供一个返回InputStream流的方法,该输入流代表了被下载文件的入口,这个方法用来给被下载的数据提供输入流,意思是从这个流读出来,再写到浏览器那边供下载。
这个方法需要由开发人员自己来编写,只需要返回值为InputStream即可。
在我们的例子中方法的签名是:
publicInputStreamgetInputStream()throwsException,当然它也可以是别的名字,例如getDownloadFile()。
好了,现在我们所写的这个进行文件下载的Action类example.FileDownloadAction的源代码清单如下:
Java代码
52packageexample;
53
54importjava.io.ByteArrayInputStream;
55
56importjava.io.InputStream;
57
58importcom.opensymphony.xwork2.Action;
59
60publicclassFileDownloadActionimplementsAction{
61
62publicInputStreamgetInputStream()throwsException{
63
64returnnewByteArrayInputStream("Struts2下载示例".getBytes());
65
66}
67
68publicStringexecute()throwsException{
69
70returnSUCCESS;
71
72}
73
74}
注意这里唯一特殊的方法就是getInputStream(),在这个方法里面我们使用了一个数组输入流来从字符串转换成的数组作为数据的来源进行读取。
也许方法体中使用这样的实现代码:
Java代码
75returnnewjava.io.FileInputStream(“c:
\\test.txt”);//从系统磁盘文件读取数据
这样会更直观一些。
文件下载的第二步,乃是在struts.xml中对action进行配置,其代码清单如下所示:
Xml代码
76
--简单文件下载-->
77
78
79
80
81
82text/plain
83
84inputStream
85
86attachment;filename="struts2.txt"
87
884096
89
90
91
92
这个action特殊的地方在于result的类型是一个流(stream),配置stream类型的结果时,因为无需指定实际的显示的物理资源,所以无需指定location属性,只需要指定inputName属性,该属性指向被下载文件的来源,对应着Action类中的某个属性,类型为InputStream。
下面则列出了和下载有关的一些参数列表:
参数
说明
contentType
内容类型,和互联网MIME标准中的规定类型一致,例如text/plain代表纯文本,text/xml表示XML,image/gif代表GIF图片,image/jpeg代表JPG图片
inputName
下载文件的来源流,对应着action类中某个类型为Inputstream的属性名,例如取值为inputStream的属性需要编写getInputStream()方法
contentDisposition
文件下载的处理方式,包括内联(inline)和附件(attachment)两种方式,而附件方式会弹出文件保存对话框,否则浏览器会尝试直接显示文件。
取值为:
attachment;filename="struts2.txt",表示文件下载的时候保存的名字应为struts2.txt。
如果直接写filename="struts2.txt",那么默认情况是代表inline,浏览器会尝试自动打开它,等价于这样的写法:
inline;filename="struts2.txt"
bufferSize
下载缓冲区的大小
在这里面,contentType属性和contentDisposition分别对应着HTTP响应中的头Content-Type和Content-disposition头。
好,我们先来看看这个例子,发布运行项目后键入测试地址:
http:
//localhost:
8080/struts2hello/download.action,将会看到浏览器弹出一个文件保存对话框,如图12.12所示。
图12.12文件下载对话框(IE7和Firefox3)
如果此时使用某些工具来探测浏览器返回的HTTP头,将会看到下列内容:
HTTP/1.1200OK
Server:
Apache-Coyote/1.1
Content-disposition:
attachment;filename="struts2.txt"
Content-Type:
text/plain
Transfer-Encoding:
chunked
Date:
Sun,02Mar200802:
58:
25GMT
。
所以用来下载的action配置中,只有两个是和浏览器有关的:
contentType和contentDisposition。
关于contentType的取值,如果是未知的文件类型,或者说出现了浏览器不能打开的文件,例如.bean文件,或者说这个action是用来做动态文件下载的,事先并不知道未来的文件类型是什么,那么我们可以把它的值设置成为:
application/octet-stream;charset=ISO8859-1,注意一定要加入charset,否则某些时候会导致下载的文件出错;有人说这时也可以设置成为application/x-download,根据笔者的实践,这个头也能正常工作,然而个别时候会出现浏览器无法识别的问题。
而contentDisposition,如果其取值是filename="struts2.txt",或者是inline;filename="struts2.txt",运行后你可以看到浏览器直接显示了文件的内容:
Struts2下载示例,而不再弹出对话框提示用户保存文件到硬盘上。
所以读者如果想确保文件是被下载而不是被打开,务必使用格式attachment;filename="struts2.txt",不要丢了attachment;这个类型信息。
至此,关于文件下载的技术内容,已经告一段落。
然而做中文系统,不可避免的要解决中文附件的下载问题。
关于这个内容,也无权威的资料可查,我们只能用实践中得到的解决方案来处理。
也许有读者以为将filename属性设置为filename=”struts2中文.txt”就能解决问题了,好,就来试试,把contentDisposition修改成:
attachment;filename="struts2中文.txt"
。
再次键入地址进行测试,看看显示的结果,如图12.13所示。
唉,真是完全不给面子!
IE压根就不能显示出来文件名,草草敷衍了download_action了事。
Firefox稍好点,还出来了一个对话框,但是很显然,那个显示的struts2--txt绝对不是我们日思夜想的struts2中文.txt。
怎么办?
解决方法是有,那就是用ISO8859-1编码来显示这个中文字符,可以阅读12.8参考资料一节中的JSP文件下载的相对完整代码(解决中文问题和Weblogic报错)这篇文章,可以这样认为,所有的文件下载代码都是基于同样的纯Servlet的方式来进行的。
如果是Java代码,我们可以这样做:
图12.13IE和Firefox下的中文文件下载对话框
StringdownFileName=newString(“struts2中文.txt”.getBytes(),"ISO8859-1");
然后把生成的结果字符串放到XML文件中就行了,然而它的输出类似于struts2?
?
.txt,是无法直接写道我们的XML配置文件中的。
所以,我们想到的的办法,就是在Action类中写一个方法来做转码,使它成为某个属性,所以要以get开头。
然后,再用12.3.8给Action注入参数(param)值一节的内容,将文件名以正常的方式设置为action类的某个属性,最后呢,再利用一个小小的param参数取值中的伎俩:
${属性名},它可以直接从action类中动态获取某个属性值。
好了,现在让我们来看看第二个文件下载类FileDownloadAction2的代码:
Java代码
93packageexample;
94
95importjava.io.ByteArrayInputStream;
96
97importjava.io.InputStream;
98
99importjava.io.UnsupportedEncodingException;
100
101importcom.opensymphony.xwork2.Action;
102
103publicclassFileDownloadAction2implementsAction{
104
105privateStringfileName;//初始的通过param指定的文件名属性
106
107publicInputStreamgetInputStream()throwsException{
108
109returnnewByteArrayInputStream("Struts2下载示例".getBytes());
110
111}
112
113publicStringexecute()throwsException{
114
115returnSUCCESS;
116
117}
118
119publicvoidsetFileName(StringfileName){
120
121this.fileName=fileName;
122
123}
124
125/**提供转换编码后的供下载用的文件名*/
126
127publicStringgetDownloadFileName(){
128
129StringdownFileName=fileName;
130
131try{
132
133downFileName=newString(downFileName.getBytes(),"ISO8859-1");
134
135}catch(UnsupportedEncodingExceptione){
136
137e.printStackTrace();
138
139}
140
141returndownFileName;
142
143}
144
145}
这个类有两个属性,第一个是fileName,它是需要被指定的下载文件名;第二个则是动态的仅仅由getDownloadFileName()这个方法定义的属性downloadFileName,它的值随着fileName而动态变动,仅仅是把它转换成了ISO8859方式的西欧字符集。
接下来就是如何配置这个action了,这是关键的地方所在,现在配置一个新的action,名为download2,其源代码如下:
Xml代码
146
--文件下载,支持中文附件名-->
147
148
149
150
--初始文件名-->
151
152Struts中文附件.txt
153
154
155
156text/plain
157
158inputStream
159
160
--使用经过转码的文件名作为下载文件名,downloadFileName属性
161
162对应action类中的方法getDownloadFileName()-->
163
164attachment;filename="${downloadFileName}"
165
1664096
167
168
169
170
其中特殊的代码就是${downloadFileName},它的效果相当于运行的时候将action对象的属性的取值动态的填充在${}中间的部分,我们可以认为它等价于+action.getDownloadFileName()。
好了,现在让我们重新发布然后运行这个项目,键入地址:
http:
//localhost:
8080/struts2hello/download2.action进行访问,可以看到运行结果完全正确,如图12.14所示。
图12.14正确显示了文件下载名的对话框(IE和Firefox)
在本节的最后部分,我们来讨论一下如何下载已经存在于当前Web应用目录下的已经存在的文件。
一般的网站可能会把要下载的文件放在某个固定的目录下,例如WebRoot/download,在这个子目录下,我们放了一个名为系统说明.doc的文件,希望最后我们的action能够正确的下载这个文件。
要检验下载是否成功非常简单,文件内容仅仅是粗体的系统说明书这五个字,而word文件坏一个字节的话都是打不开的,所以下载后再用word打开即可检验是否成功。
现在我们创建第三个文件下载的Action类,名为example.FileDownloadAction3,其源代码清单如下所示:
Java代码
171packageexample;
172
173importjava.io.InputStream;
174
175importjava.io.UnsupportedEncodingException;
176
177importorg.apache.struts2.ServletActionContext;
178
179importcom.opensymphony.xwork2.Action;
180
181publicclassFileDownloadAction3implementsAction{
182
183privateStringfileName;//初