下面梳理一下Filter的两个应用案例,同时将之前的知识点串联起来。
案例1:使用Filter实现统一全站编码
form.jsp + CharacterServlet + CharacterFilter
之前遇到的乱码问题:
在Servlet文件中的字符使用字符输出流打印出来显示结果是乱码。
//Servlet中doGet()方法的代码
String data = "中国";
PrintWriter out = response.getWriter();
out.print(data);
原因在于response采用的是ISO8859-1
的字符码表进行编码,而浏览器则是使用默认的GB2312
进行解码。在编码时已经出现错误,因为ISO8859-1不兼容中文字符。
这里需要注意的是编码的并非浏览器,而是response
对象,所以我们通过设置response对象的编码方式为UTF-8,就可以让字符编码成UTF-8格式。
response.setCharacterEncoding("utf-8");
但还需要让浏览器使用UTF-8编码进行解码。所以还需要设置浏览器的编码方式为UTF-8。
response.setHeader("Content-Type","text/html;charset=utf-8");
可以使用下面的一句代码实现上面提到的两个功能:
response.setContentType("text/html/charset=utf-8");
这就解决了乱码。但是我们需要在每一个Servlet文件中,都设置response对象的编码方式,这样会书写很多重复代码。而本案例就是来解决这个问题的。如果顺着上面的思路,感觉只需要将上面的代码添加到CharacterFilter中就大功告成了。但实际上并非如此。
还有很多需要考虑的因素。在上面的乱码案例中,字符只不过是Servlet程序中定义的一个变量的值。而在实际的应用中,我们要面对的字符的来源还有通过POST方法的表单提交的参数,以及通过GET方法中URL地址中的参数。
上面的案例没有涉及到request,因为已经说过,这个字符是存在于Servlet的一个自定义的字符,不是来源于request。但现在不同。无论是通过GET还是POST方法获取参数,这个参数或者说字符都是从request中通过getParameter()方法获取的。由于post方式的参数放在请求消息体中(还记得POST方式保密性更好,不会在URL中体现出来吗),所以当使用POST方式时,我们设置消息体的编码方式就可以解决乱码问题:
request.setCharacterEncoding("utf-8");
这时候使用getParameter()方法能够得到正常的字符串。
而对于GET方法,使用GET方法就容易出现乱码的情况。
这里的解决方法是:用CharacterRequest继承默认的包装类HttpServletRequestWrapper并对其getParameter方法进行重写,这样重新调用getParameter就可以得到正确编码的字符串了。改写后的getParameter方法同样还适用于POST方法,不过使用POST方法就不会执行下面try中的代码,而是直接返回super.getParameter(name)中得到的值value。(重写的部分主要也就是指try部分的代码内容)
public String getParameter(String name){
String value = super.getParameter(name);
if (value == null)
return null;
//判断请求方式,如果是使用get方法执行下面代码
...
try{
value = new String(value.getBytes("iso-8859-1"),"utf-8");
}catch(...){...}
...
}
执行完下面代码,就是使用characterRequest对象对reques对象进行了包装。新包装的characterRequest对象具有重载的getParameter()方法。
CharacterRequtest characterRequest = new CharacterRequest(request);
chain.doFilter(characterRequest,response);
为什么不直接继承HttpServletRequest,却要继承包装类呢?还要这里还进行了装饰类与继承的区别的探讨,网上也有很多这方面的讨论。
如果只是继承,当然可以实现getParameter方法的重写,但是需要新建对象来调用类的方法。这时就需要将原本request对象中的信息传递给整个新的request对象,就显得很繁琐了。而这里直接就可以实现request对象的传递,使得新的对象立马包含了原有对象的信息。
简而言之,如果需要重载,那么就使用继承。如果要求使用原有对象,那么是使用装饰类。如果要求重载的同时还需要使用原有对象,那么就需要继承一个装饰类,正如本案例所做的那样。
案例2:Filter实现页面的静态化
Book + BookDao + BookServlet + index_book.jsp +show.jsp + StaticResponse + StaticFilter
不考虑Filter的话,其他文件我们已经比较熟悉了。BookDao提供了实现查询所有图书
和 按分类查询图书
的两个功能。查询结果使用BeanListHandler接口
进行处理,封装成Book对象保存在List中(下面以按分类查询图书的代码为例)。
private QueryRunner qr = new QueryRunner(JDBCUtils.getDataSource());
//查询所有
public ListfindCategory(){
try{
String sql = "select * from t_book where category=?";
return qr.query(sql,new BeanListHandler(Book.class),category);
}catch(SQLException e){
throw new RuntimeException(e);
}
}
在index_book.jsp
中,存在如下代码:
JavaSE分类
通过点击这个超连接,就可以跳转到BookServlet
,并且在请求中带上category=1
这个参数。而显示结果(遍历List)的功能在show.jsp
中进行完成。到此为止,已经完成了基本的功能。现在引入StaticResponse
和StaticFIlter
有什么作用呢。
StaticFilter的作用是判断是否存在现成的静态页面,如果存在则重定向到静态页面,如果不存在就新建静态页面。创建静态页面的方法如下:
private FilterConfig config;
...
public void doFilter(...){
//以下步骤都是为静态页面创建一个绝对路径
ServletContext sc = config.getServletContext();
String category = request.getParameter("category");
String key = "book_" + category;
String staticPath = key + ".html";
String realPath = sc.getRealPath("/html/"+staticPath);
//根据绝对路径创建一个新的静态页面,然后保存到staticMap中,根据key来查询静态页面的地址staticPath
MapstaticMap = (Map)sc.getAttribute("static_map");
staticMap.put(key,staticPath);
//将response的数据整合到这个静态页面中,使用StaticResponse类来实现,这个类另外进行说明。
StaticResponse sr = new StaticResponse(res,realPath);
//重定向到静态页面
res.sendRedirect(req.getContextPath()+"/html/"+staticPath);
}
以上只是StaticFilter用于创建新的静态页面的一部分代码。在创建新的静态页面前,都需要进行判断和查询(如果已经存在就不需要创建)。
具体来说:首先需要判断sc对象的static_map属性中的staticMap是否为空,如果为空则需要为sc对象的static_map属性新建一个staticMap对象。然后根据category参数查询是否存在该静态页面,如果存在则跳转到对应的静态页面(这部分代码不列出,只表明思路)。
接下来看看staticResponse
的实现,这个类也是继承于包装类HttpServletResponseWrapper
,上面已经提及,这么做的是因为要进行方法的重写,同时还要利用原有对象的信息而不是创建新的对象。其关键代码如下:
pw= new PrintWriter(staticPath,"utf-8");
该行代码位于StaticResponse类
的构造方法中。通过下面的代码来创建StaticResponse对象。
StaticResponse sr = new StaticResponse(res,realPath);
一旦新建StaticResponse对象,初始化完成后就会生成一个pw对象,并且对getWriter方法进行重写,用于返回这个pw。也就是说使用getWriter()来进行输出时,就会获取pw这个新的输出流,而不是使用普通的输出流。pw在进行输出时,也会向指定的文件输出,这样我们的静态文件就有了内容。
但是找遍所有的文件也没有看见有getWriter
的字样。这是因为jsp标签在搞鬼,要知道在show.jsp页面中最终显示出来的内容都是JspServlet(jsp页面默认的Servlet程序)调用getWriter()逐个字符打印出来的,只不过我们在jsp页面中用了<% %>
来实现这个功能,将这些重复繁琐的工作交给JspServlet去完成。也就是说我们<%% >里面的内容就是转化为Servlet并且通过getWriter()方法打印出来的,现在对getWriter方法进行了重写,JspServlet调用getWriter()的时候就会将内容输出到静态文件中去了。