学习JavaWeb(六)——EL表达式、JSTL标签库、文件操作

前言:

继续学习jsp,学习EL表达式和JSTL标签库来简化jsp文件的脚本编写,学习文件上传和文件下载操作。

 

 

 

 


EL表达式

概念与作用

EL表达式的全程是:Expression Language,是表达式语言。

EL表达式的作用:EL表达式主要是替代jsp页面中的表达式脚本在jsp页面中的输出(因为EL表达式要比jsp的表达式脚本要简洁很多)。

上篇文章我们做的那两个例子你应该感受到了,纯jsp脚本写逻辑利用代码脚本和表达式脚本,那个逻辑写出来简直不是人看的,感谢EL表达式的出现。

 

开始学习

我们来创建一个新工程作为说明:

老规矩,还是一个Java Enterprise的项目,一个Web Application 。

<body>
    <%
        request.setAttribute("key","value");
    %>
    表达式脚本输出key的值是:<%=request.getAttribute("key")%>
    <br />
    EL表达式输出key的值是:${key}
</body>

如此就输出了

表达式脚本输出key的值是:value
EL表达式输出key的值是:value

注意,如果我们做个变化,将取值取一个不存在的值

<body>
    <%
        request.setAttribute("key","value");
    %>
    表达式脚本输出key的值是:<%=request.getAttribute("key1")%>
    <br />
    EL表达式输出key的值是:${key1}
</body>

最后输出结果

表达式脚本输出key的值是:null
EL表达式输出key的值是:

EL表达式在输出null值的时候,输出的是空串。jsp表达式脚本输出null值的时候,输出的是null

可见,对于EL表达式,去取一个不存在的值最后什么也不会输出,这对与用户来说是非常友好的,因为一般用户是不会知道null是什么意思的(如果表达式脚本要实现这个效果,还需要判断一下,这就更麻烦了,可见EL表达式真是简洁)。

 

EL表达式搜索四大域的顺序

EL表达式主要是在jsp页面中输出数据。

主要是输出域对象中的数据。

还记得jsp的四大域对象吗?

  • pageContext(PageContextImpl类):当前jsp页面范围内有效
  • request(HttpServletRequest类):一次请求内有效
  • session(HttpSession类):一个会话范围内有效(打来浏览器访问服务器,直到关闭浏览器)
  • application(ServletContext类):整个web工程范围内都有效(只要web工程不停止,数据都在)

输出很简单:

<body>
    <%
        pageContext.setAttribute("key","pageContext");
    %>
    ${ key }
</body>

即可,如果页面中四个域对象都有相同的键 key,即:

<body>
    <%
        pageContext.setAttribute("key","pageContext");
        request.setAttribute("key","request");
        session.setAttribute("key","session");
        application.setAttribute("key","application");
    %>
    ${ key }
</body>

会输出 pageContext ,这不是因为代码顺序,是因为这四大域对象的优先级问题:

pageContext=》request=》session=》Application

当四个域中都有相同的key的数据的时候,EL表达式会按照四个域从小到大的顺序去进行搜索,找到就输出。

 

EL表达式输出Bean的普通属性、数组属性、List集合属性、map集合属性

什么是JavaBean

简单笼统的说就是一个类,一个可复用的类。
javaBean在MVC设计模型中是model,又称模型层,在一般的程序中,我们称它为数据层,就是用来设置数据的属性和一些行为,然后我会提供获取属性和设置属性的get/set方法。JavaBean是一种JAVA语言写成的可重用组件。为写成JavaBean,类必须是具体的和公共的,并且具有无参数的构造器。JavaBean通过提供符合一致性设计模式的公共方法将内部域暴露成员属性。众所周知,属性名称符合这种模式,其他Java类可以通过自身机制发现和操作这些JavaBean 属性。
用户可以使用JavaBean将功能、处理、值、数据库访问和其他任何可以用java代码创造的对象进行打包,并且其他的开发者可以通过内部的JSP页面、Servlet、其他JavaBean、applet程序或者应用来使用这些对象。用户可以认为JavaBean提供了一种随时随地的复制和粘贴的功能,而不用关心任何改变。
JavaBean可分为两种:一种是有用户界面(UI,UserInterface)的JavaBean;还有一种是没有用户界面,主要负责处理事务(如数据运算,操纵数据库)的JavaBean。JSP通常访问的是后一种JavaBean。

 

EL表达式输出

创建一个包——java.luoluo.pojo

POJO(Plain Ordinary Java Object)简单的Java对象,实际就是普通JavaBeans

定义一个Person.java作为bean类

public class Person {
    private String name;
    private String[] phones;
    private List<String> cities;
    private Map<String,Object> map;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Person(String name, String[] phones,
         List<String> cities, Map<String, Object> map) {
        this.name = name;
        this.phones = phones;
        this.cities = cities;
        this.map = map;
    }

    public Person() {
    }

    @Override
    public String toString() {
        return "Person{" +
                "name=" + name +
                ", phones=" + Arrays.toString(phones) +
                ", cities=" + cities +
                ", map=" + map +
                '}';
    }

    public String[] getPhones() {
        return phones;
    }

    public void setPhones(String[] phones) {
        this.phones = phones;
    }

    public List<String> getCities() {
        return cities;
    }

    public void setCities(List<String> cities) {
        this.cities = cities;
    }

    public Map<String, Object> getMap() {
        return map;
    }

    public void setMap(Map<String, Object> map) {
        this.map = map;
    }
}

然后来写一个jsp界面test.jsp

<head>
    <title>这是一个测试</title>
</head>
<body>
    <%
        Person person=new Person();
        person.setName("国哥好帅!");
        person.setPhones(new String[]{"18788788567",
                "1234234232","18323424323"});
        List<String> cities=new ArrayList<String>();
        cities.add("北京");
        cities.add("上海");
        cities.add("深圳");
        person.setCities(cities);

        Map<String,Object> map=new HashMap<>();
        map.put("key1","value1");
        map.put("key2","value2");
        map.put("key3","value3");
        person.setMap(map);

        pageContext.setAttribute("person",person);
    %>
    输出Person:${person}<br /><br />
    输出name属性:${person.name} <br /><br />
    输出数组phones属性地址:${person.phones} <br /><br />
    输出数组phones属性元素:${person.phones[0]} <br /><br />
    输出集合cities中的元素:${person.cities} <br /><br />
    输出集合cities中指定元素:${person.cities[0]} <br /><br />
    输出集合Map的值:${person.map} <br /><br />
    输出集合Map某个key的值:${person.map.key1} <br /><br />
</body>

注意,本质上EL表达式去输出实则是去寻找对应属性的get方法

 

EL表达式常用操作符

操作符 描述
. 访问一个Bean属性或者一个映射条目
[] 访问一个数组或者链表的元素
( ) 组织一个子表达式以改变优先级
+
减或负
*
/ or div
% or mod 取模
== or eq 测试是否相等
!= or ne 测试是否不等
< or lt 测试是否小于
> or gt 测试是否大于
<= or le 测试是否小于等于
>= or ge 测试是否大于等于
&& or and 测试逻辑与
|| or or 测试逻辑或
! or not 测试取反
empty 测试是否空值

empty

其中,empty运算的作用是测试一个数据是否为空,如果为空,则输出true,不为空输出false

以下几种情况为空:

  1. 值为null的时候
  2. 值为空串的时候(””)
  3. 值是Object类型数组,长度为零的时候
  4. list集合,元素个数为零
  5. map集合,元素个数为零

测试代码:

    <%
        request.setAttribute("emptyNull",null);
    %>
    ${empty emptyNull}

结果会是true,表明是一个空值,如果是request.setAttribute(“emptyNull”,new Object());,则结果是false。

三元运算

EL表达式中还支持一些三元运算(A?B:C)

${12==2 ? "真的" : "假的"}

“.” 和 [] 中括号 运算符

在上面我们学习EL表达式输出bean对象那里就已经使用了这两个符号,总结一下就是:

点运算:可以输出Bean对象中某个属性的值。

[]中括号运算,可以输出有序集合中某个元素的值,并且中括号运算,还可以输出map集合中key里还有特殊字符的key值。

<%
    Map<String,Object> map=new HashMap<String,Object>();
    map.put("a.a.a","aaaValue");
    request.setAttribute("map",map);
%>
${ map['a.a.a']}

EL表达式中的11个隐含对象

11个隐含对象

EL表达式中11个隐含对象,是EL表达式中自己定义的,可以直接使用。

隐式对象
类型
作用
pageContext
PageContextImpl
可以获取jsp的九大内置对象
pageScope
Map
代表pageContext域中用于保存属性的Map对象
requestScope
Map
代表request域中用于保存属性的Map对象
sessionScope
Map
代表session域中用于保存属性的Map对象
applicationScope
Map
代表application域中用于保存属性的Map对象
param
Map
可以获取请求参数的值
paramValues
Map
可以获取请求参数的值(多个值)
header
Map
可以获取http请求头字段的Map对象
headerValues
Map
可以获取http请求头字段的Map对象(多个值)
cookie
Map
可以获取当前请求的Cookie信息
initParam
Map
可以获取在web.xml中配置的<context-param>上下文参数

 

四大Scope

上面的11个隐含对象中,四大scope是使用的比较多的,四大Scope分别对应jsp中的四大域对象,下面我们来做实验。

scope:范围

<%
    pageContext.setAttribute("key1","pageContext");
%>
pageScope.key1: ${ pageScope.key1 }

界面就会输出pageScope.key1:pageContext

前几天的示例我们知道,直接${ key1 } 这样就可以输出这个上下文对象的内容。但是这样在不同域对象相同键的时候会根据顺序输出,例如PageContext存在一个key1,Request 也有一个 key1,就会取出PageContext的key1对应的那个值,而直接使用EL隐式对象,就不必考虑这种情况了。

pageContext

pageContext可以得到jsp的九大内置对象

使用示例:

<body>
    <!--注意,jsp的表达式脚本中是调用方法,
        EL表达式则是通过.操作符得到属性-->
    协议(表达式脚本):<%=request.getScheme()%> <br />
    协议:${ pageContext.request.scheme }  <br />
    服务器ip:${ pageContext.request.serverName }  <br />
    服务器端口:${ pageContext.request.serverPort }  <br />
    工程路径:${ pageContext.request.contextPath }  <br />
    请求方法:${ pageContext.request.method }  <br />
    客户端的ip地址:${ pageContext.request.remoteHost }  <br />
    会话的id编号:${ pageContext.session.id }  <br />
</body>

 

其余的隐式对象

<body>
    <%--获取get参数--%>
    参数:${ param }<br/>
    参数中user的值:${ param.user }<br />
    参数数组中的值:${ paramValues.user[0] }<br/>
    <hr />
    <%--获取请求头的信息--%>
    请求头的UA头:${ header['User-Agent'] }<br />
    请求头数组中的UA头:${ headerValues['User-Agent'][0] }<br />
    连接情况Connection:${ header.Connection }<br />
    <hr />
    <%--得到cookie--%>
    cookie的键:${ cookie }<br />
    cookie的键对应的值:${ cookie.JSESSIONID } <br />
    cookie的名称:${ cookie.JSESSIONID.name } <br />
    cookie的值:${ cookie.JSESSIONID.value } <br />
    <hr />
    <%--initParam--%>
    <%--这里修改了web.xml配置文件,增加了内容
        <context-param>
            <param-name>username</param-name>
            <param-value>root</param-value>
        </context-param>
        <context-param>
            <param-name>url</param-name>
            <param-value>jdbc:mysql</param-value>
        </context-param>
    --%>
    利用initparam输出&lt;Context-param&gt;的值:${ initParam.username }
</body>

http://127.0.0.1:8080/EL/test.jsp?user=luoluo&pass=cool 访问结果:


JSTL标签库

JSTL标签库

JSP标准标签库(JSTL)是一个JSP标签集合,它封装了JSP应用的通用核心功能。

JSTL支持通用的、结构化的任务,比如迭代,条件判断,XML文档操作,国际化标签,SQL标签。 除了这些,它还提供了一个框架来使用集成JSTL的自定义标签。

JSTL标签库,全称是 JSP Standard Tag Libary JSP标准标签库。是一个不断完善的开放源代码的JSP标签库。EL表达式主要是为了替换jsp中的表达式脚本,而标签库则是为了替换代码脚本。这样使整个jsp页面变得更加整洁。

JSTL由五个不同功能的标签库组成:

其中,数据库标签库和XML标签库已经几乎不用了,因为根据JavaEE的三层结构,对数据库的操作、XML(早期客户端向服务端传数据的方式)是DAO持久层的任务。

使用方法:通过taglib命令引入标签库:

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

使用类似这样的命令即可,引入前缀和地址

 

JSTL标签库的使用

  1. 在本项目的WEB-INF下建立一个lib目录,然后导入JSTL标签库的jar包
  2. 使用JSTL表达式

重点在于了解怎么去导入JSTL包。

我们先通过官方下载地址下载一个压缩包,然后解压。

然后将解压内容中的jstl.jar和standard.jar导入你的javaweb项目,然后添加到库(在WEB-INF/lib下面)

然后,你在该项目下输入 <c: 如果会出现 foreach 等提示,这说明导入成功!

使用jstl语句,IDEA会自动为你添加taglib的对应的命令。

 

core核心库使用

<c:set />

作用:set标签可以往域里保存数据

<%--
<c:set />
作用:set标签可以往域中保存数据
scope:属性设置保存到哪个域(page表示PageContext域,诸如此类)
var:设置key的值
value:设置值
--%>
保存之前:${ pageScope.abc }<br/>
<c:set scope="page" var="abc" value="test"/>
保存之后:${ pageScope.abc }<br/>

保存之前:
保存之后:test

<c:if >

作用:用来做if判断

<%--
    <c:if />
    作用:if标签用来做if判断
    test属性表示判断的条件(使用EL表达式)
--%>
<c:if test="${12==12}">
    <h1>12等于12</h1>
</c:if>

提示:如果页面报错:According to TLD or attribute directive in tag file, attribute test does not accept any expres,这是因为版本不兼容原因,参考这篇文章,修改声明为

<%@ taglib uri="http://java.sun.com/jstl/core_rt" prefix="c"%>

<c:choose>、<c:when>、<c:otherwise>

作用:多路判断。跟switch……case……default 非常接近

<%
    request.setAttribute("height",188);
%>
<c:choose>
    <c:when test="${ requestScope.height>190 }">
        <h2>小巨人</h2>
    </c:when>
    <c:when test="${ requestScope.height>180 }">
        <h2>很高</h2>
    </c:when>
    <c:when test="${ requestScope.height>170 }">
        <h2>还可以</h2>
    </c:when>
    <c:otherwise>
        <h2>有点矮</h2>
    </c:otherwise>
</c:choose>

很高

需要注意的点:

  • 标签中不能使用html注释(使用jsp注释替换)
  • choose标签不同于编程语言中的switch,程序会从上向下执行查看when标签,只要符合条件就会结束choose标签,若没有when标签符合条件,则会进入otherwise标签。
  • when标签的父标签一定是choose标签(注意choose标签嵌套的情况)

 

<c:forEach />

foreach标签就是实现循环的功能,遍历输出使用,非常简单,我们通过几个小例子你就懂了:

  • 遍历1到10的输出
<%--
var属性表示循环的变量(也是当前正在遍历到的数据)
--%>
<c:forEach begin="1" end="10" var="i">
    <h6>${i}</h6>
</c:forEach>
  • 遍历Object类型的数组
<%--
    items表示遍历的数据源
--%>
<%
    request.setAttribute("arr",new String[]{
            "朝辞白帝彩云间",
            "千里江陵一日还",
            "两岸猿声啼不住",
            "轻舟已过万重山"});
%>
<c:forEach items="${requestScope.arr}" var="item">
    <h4 style="color: darkviolet">${item}</h4>
</c:forEach>

  • 遍历Map集合
<%
    Map<String,Object> map=new HashMap<String,Object>();
    map.put("key1","value1");
    map.put("key2","value2");
    map.put("key3","value3");
    request.setAttribute("map",map);
%>
<c:forEach items="${requestScope.map}" var="entry">
    <h1>${ entry.key }-${ entry.value }:${ entry }</h1>
</c:forEach>

  • 遍历List集合

示例:list中存放Student类,有属性:编号、用户名、密码、年龄、电话信息

我们先写一个JavaBean类,Student,具有id、username、password、age、phone五个私有属性,通过get/set方法访问。

然后在jsp页面中

<%
    List<Student> studentList=new ArrayList<Student>();
    for (int i=1;i<=10;i++){
        studentList.add(new Student(i,""+i,""+i,18+i,""+i));
    }
    request.setAttribute("stus",studentList);
%>
<table  style="border: 2px solid lawngreen">
    <tr>
        <th style="border: 2px solid ">序号</th>
        <th style="border: 2px solid ">姓名</th>
        <th style="border: 2px solid ">密码</th>
        <th style="border: 2px solid ">年龄</th>
        <th style="border: 2px solid ">电话</th>
    </tr>
    <c:forEach items="${requestScope.stus}" var="stu">
        <tr>
            <td style="border: 2px solid ">${stu.id}</td>
            <td style="border: 2px solid ">${stu.username}</td>
            <td style="border: 2px solid ">${stu.password}</td>
            <td style="border: 2px solid ">${stu.age}</td>
            <td style="border: 2px solid ">${stu.phone}</td>
        </tr>
    </c:forEach>
</table>

补充一下forEach当中的varStatus属性,意为当前的变量的状态,源自一个接口:

 


文件上传

文件上传是一个非常常见的功能,例如邮箱中附件的上传、社交软件头像的上传。

文件上传操作

  1. 需要一个form标签(表单),并且method=post
  2. form标签的enctype值必须为 multipart/form-data
  3. 在form标签当中使用input type=file 添加上传的文件
  4. 编写服务器代码接收,处理上传的数据

 

代码示例

<form action="" method="post" enctype="multipart/form-data">
    用户名:<input type="text" name="username" /> <br/>
    头像:<input type="file" name="photo" /><br>
    <input type="submit" value="上传">
</form>

刷新页面就可以上传了:

接下来我们来写一个Servlet文件(UploadServlet)来接收客户端的上传

新建Servlet并且配置一下,然后记得在jsp文件的form标签中指定好action

我这里是action=”http://localhost:8080/EL/upload”

<servlet>
    <servlet-name>UploadServlet</servlet-name>
    <servlet-class>com.luoluo.servlet.UploadServlet</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>UploadServlet</servlet-name>
    <url-pattern>/upload</url-pattern>
</servlet-mapping>

接下来只要在UploadServlet.java文件写对应post请求的响应即可。

protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    System.out.println("有文件上传过来了!");
}

上传的Http协议内容介绍

上面的例子提交了之后,那么在服务器端收到了哪些内容呢?

“上传的文件的数据”为了整洁,Chrome浏览器没有显示

数据是以二进制流的形式去请求给服务器的

所以我们在服务器端可以这样接收:

protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    System.out.println("有文件上传过来了!");
    //得到字节输入流
    ServletInputStream inputStream=req.getInputStream();
    //一个缓冲区,1024字节
    byte[] buffer=new byte[1024];
    //将输入流的内容读到缓冲区中并且返回长度
    int read=inputStream.read(buffer);
    System.out.println(new String(buffer,0,read));
}

提交一个姓名和一张照片后,你会看到输出这样的信息:

------WebKitFormBoundaryK5Bcx1t3xwdi8dkp
Content-Disposition: form-data; name="username"

asdsadsd
------WebKitFormBoundaryK5Bcx1t3xwdi8dkp
Content-Disposition: form-data; name="photo"; filename="netModel.png"
Content-Type: image/png

塒NG

犙h 蓁}o黕}锄?9搒?*?*宏?	~8枋揔Ue澨笸?'?  
……………………

后面没有显示完

这一帮子乱字就是图片转成文字的形式,在谷歌浏览器中的请求体中并没有显示出来这些东西,因为太乱了。

解析数据

数据成功上传之后下一步,我们就是要解析和得到上传的内容

上面的操作可知,我们服务端收到的是描述头+主体,接下来我们就要获取主体。

很多第三方jar包已经封装好了API供你使用,这里我们学习的jar包是——commons-fileupload.jar。

commons-fileupload  jar包  官网下载

另外,我们还需要下载一个commons-io 的jar包,否则最后运行的时候,tomcat会报错

commons-io   jar包  官网下载

导入资源包后添加到库。

FileUpload包中有一个ServletUpload类,主要用于解析上传的数据。

  • ServletUpload类的常用方法
方法名称 方法描述
public void setSizeMax(long sizeMax) 设置请求信息实体内容的最大允许的字节数
public List <FileItem>  parseReuqest(HttpServletRequest req) 解析form表单中的每个字符的数据,返回一个FileItem对象的集合
public static final boolean isMultiparyContent(HttpServletRequest req) 判断请求信息中的内容是否是“multiparty/form-data”类型(多段的格式)
public void setHeaderEncoding(String encoding) 设置转换时所使用的字符集编码
  • FileItem类的常用方法
方法名称 方法描述
public boolean isFormFieId() 判断FileItem对象封装的数据类型.普通表单字段返回true,文件表单字段返回false.
public String getName() 获得文件上传字段中的文件名,普通表单字段返回Null.
public String getFieIdName() 返回表单字段元素的name属性值
public void write(File file) 将FileItem对象中保存的主题内容保存到指定的文件中.
public String getString() 将FileItem对象中保存的主题内容以一个字符串返回.其重载方法public String getString(Stirng encoding) 中的参数用指定的字符集编码方式
public long getSize() 返回上传文件的字节数
  • DiskFileItemFactory类的常用方法
方法名称 方法描述
public void setSizeThreshold(int sizeThreshold) 设置内存缓冲区的大小
public void setRepository(Stirng path) 设置临时文件存放的目录

我们接着上面的例子继续往下写:

@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    //先判断上传的数据是否是多段数据(只有是多段数据才是文件上传)
    if(ServletFileUpload.isMultipartContent(req)){
        //创建FileItemFactory工厂实现类
        FileItemFactory fileItemFactory=new DiskFileItemFactory();
        //创建用于解析上传数据的工具类ServletFileUpload类
        ServletFileUpload servletFileUpload=new ServletFileUpload(fileItemFactory);
        //解析上传的数据,得到每一个标签项
        try {
            List<FileItem> list=servletFileUpload.parseRequest(req);
            for (FileItem f: list) {
                if(f.isFormField()){
                    //普通表单项
                    System.out.println("表单项的name:"
                            +f.getFieldName());
                    System.out.println("表单项的value:"
                            +f.getString("UTF-8"));
                }else{
                    //上传的文件
                    System.out.println("表单项的name:"
                            +f.getFieldName());
                    System.out.println("上传的文件名:"
                            +f.getName());
                    //保存文件
                    f.write(new File("e:\\"+f.getName()));
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

测试可知,客户端上传的图片文件成功保存在了本地的E盘下。

 


文件下载

基本代码

文件下载相对来说就比文件上传简单很多了

一般的文件下载我们可以采用http中的a标签来进行下载,我们这里介绍通过Servlet的方式进行下载:

protected void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
    //获取要下载的文件名
    String downloadFileName="maogai.docx";
    //读取要下载的文件内容(通过ServletContext对象)
    ServletContext servletContext=getServletContext();
    //获取要下载的文件类型
    String mimeType=servletContext.getMimeType(
            "/file/"+downloadFileName);
    System.out.println(mimeType);
    //在回传前,通过响应头告诉客户端返回的数据类型
    resp.setContentType(mimeType);
    //还要告诉客户端收到的数据是用于下载使用(还是用响应头)
    //这个操作是为了避免用户下载图片等文件而图片只是显示了一下而已
    /*Content-Disposition:表示收到的数据怎么处理
        attachment:附件,即下载使用
        filename:表示指定下载的文件名
     */
    resp.setHeader(
            "Content-Disposition",
            "attachment;filename="+downloadFileName);
    /*
     *  返回映射到指定路径的应用程序资源的 InputStream(如果存在);
     *  否则返回 null。
     *   “/” 即到了web目录
     **/
    InputStream resourceAsAtream=servletContext.getResourceAsStream(
            "/file/"+downloadFileName);
    //利用commons-io.jar的工具类做流的操作(文件输入流流给响应输出流)
    OutputStream outputStream=resp.getOutputStream();
    //把下载的文件内容回传给客户端
    IOUtils.copy(resourceAsAtream,outputStream);
}

代码仔细阅读即可理解。

升级实现

使用URLEncoding解决常用浏览器的编码问题

审视上面代码,客户端下载文件时的那个文件名是在setHeader中指定的,那个filename你可以任意起名字,不一定是downloadFileName,但是注意:如果是中文的话是存在一些编码问题的,解决方法是把中文设置成URL编码

resp.setHeader(
        "Content-Disposition",
        "attachment;filename="
                + URLEncoder.encode("有意思.docx","UTF-8"));

通过审查元素我们可以发现,HTTP响应头中filename=%E6%9C%89%E6%84%8F%E6%80%9D.docx

上面这个URL解决方法是针对IE或Chrome浏览器的,而Firefox浏览器使用的是BASE64编码。

BASE64编解码解决火狐浏览器的附件中文名问题

思路其实很简单:把响应头修改:

attachment;filename=中文名
attachment;filename==?charset?B?xxxxx?=

解释一下:

  1. =?:表示编码内容的开始
  2. charset:表示字符集
  3. B:表示BASE64编码
  4. xxxxx:表示文件名BASE64编码后的内容
  5. ?=:表示编码内容的结束

于是我们修改响应头:

resp.setHeader(
        "Content-Disposition",
        "attachment;filename==?UTF-8?B?"
                +new BASE64Encoder().encode("有意思.docx".getBytes("UTF-8"))
                +"?=");

可以看到中文正确的显示了过来。

火狐浏览器的问题搞定,不过IE又不合适了,因为IE默认不会BASE64编码。

Chrome浏览器是都没问题的,谷歌终究是牛逼

 

 

分析User-Agent见风使舵

接下来我们来根据不同浏览器来写不同的响应头,从而让这些主流浏览器都支持。

if(req.getHeader("User-Agent").contains("Firefox")){
    //火狐浏览器,使用BASE64编码
    resp.setHeader(
            "Content-Disposition",
            "attachment;filename==?UTF-8?B?"
                    +new BASE64Encoder().encode("有意思.docx".getBytes("UTF-8"))
                    +"?=");
}else{
    //其他浏览器,使用URL编码
    resp.setHeader( "Content-Disposition",
            "attachment;filename="
                    + URLEncoder.encode("有意思.docx","UTF-8"));
}

OK,这样就实现了根据不同浏览器动态切换效果。

 

 


 

 

 

 

商业转载 请联系作者获得授权,非商业转载 请标明出处,谢谢

发表评论