Java Web开发中的过滤器(Filter)
1、Filter介绍
Filter技术是servlet 2.3新增加的功能。servlet2.3是sun公司于2000年10月发布的,它的开发者包括许多个人和公司团体,充分体现了sun公司所倡导的代码开放性原则。在众多参与者的共同努力下,servlet2.3比以往功能都强大了许多,而且性能也有了大幅提高。
通过Filter功能,用户可以改变一个request和修改一个response。Filter不是一个servlet,它不能产生一个response,但是,它能够在一个request到达servlet之前预处理request,也可以在response离开servlet时处理response。
这样,Filter可以对所有的发往Java服务器的请求(包括对html,js,图片等静态资源的请求)做拦截,从而实现一些特殊的功能,如URL级别的权限访问控制、过滤敏感词汇、压缩响应信息等。
2、Filter执行机制
Servlet API中提供了一个Filter接口,开发web应用时,如果编写的Java类实现了这个接口,则把这个java类称之为过滤器Filter。通过Filter类,可以实现用户在访问某个目标资源之前,对访问的请求和响应进行拦截。
Filter接口中有方法void doFilter(ServletRequest req,ServletResponse res,FilterChain chain)
用来执行filter的工作。每一个filter从doFilter()
方法中得到当前的request及response对象。在这个方法里,可以进行任何的针对request及response的操作,包括收集数据,包装数据等。filter调用chain.doFilter()
方法将请求从该filter中放行。之后,有可能请求到了要访问的资源对象,也有可能把控制权交给了下一个filter。
当我们编写好Filter,并配置对哪个web资源进行拦截后,WEB服务器每次在调用web资源的service方法之前,都会先调用一下filter的doFilter方法,因此,在该方法内编写代码可达到如下目的:
- 调用目标资源之前,让一段代码执行。
- 是否调用目标资源(即是否让用户访问web资源)。
- 调用目标资源之后,让一段代码执行。
web服务器在调用doFilter方法时,会传递一个filterChain对象进来,filterChain对象是filter接口中最重要的一个对象,它也提供了一个doFilter方法,开发人员可以根据需求决定是否调用此方法,调用该方法,则web服务器就会调用web资源的service方法,即web资源就会被访问,否则web资源不会被访问。
3、Filter入门示意
这里我们编写一个简单的Filter示意代码。
首先,新建一个Filter类,实现javax.servlet.Filter
接口。
com.lfqy.web.filter.FilterTrial01
:
package com.lfqy.web.filter;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
/**
* Created by chengxia on 2019/11/4.
* filter的三种典型应用:
1、可以在filter中根据条件决定是否调用chain.doFilter(request, response)方法,即是否让目标资源执行
2、在让目标资源执行之前,可以对request\response作预处理,再让目标资源执行
3、在目标资源执行之后,可以捕获目标资源的执行结果,从而实现一些特殊的功能
*/
public class FilterTrial01 implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("The filter is initializing...");
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
//设置request和response的编码格式
servletRequest.setCharacterEncoding("UTF-8");
servletResponse.setCharacterEncoding("UTF-8");
//设置请求的contenttype
servletResponse.setContentType("text/html;charset=UTF-8");
System.out.println("Before the request is handled...");
System.out.println("url: " + ((HttpServletRequest)servletRequest).getRequestURL());
//让目标资源执行,放行
filterChain.doFilter(servletRequest,servletResponse);
System.out.println("After the request is handled...");
}
@Override
public void destroy() {
System.out.println("The filter is destroying...");
}
}
在这个filter的实现类中,在请求的执行前后,分别输出提示信息和请求的url到控制台。
然后,在配置文件中,添加这个filter的定义。
WEB-INF/web.xml
:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<filter>
<filter-name>FilterTrial01</filter-name>
<filter-class>com.lfqy.web.filter.FilterTrial01</filter-class>
</filter>
<filter-mapping>
<filter-name>FilterTrial01</filter-name>
<!-- "/*"表示拦截所有的请求 -->
<url-pattern>*.jsp</url-pattern>
<url-pattern>*.html</url-pattern>
</filter-mapping>
</web-app>
从这个配置文件的内容,可以看出,filter的配置分为两部分:一部分是定义filter的名称和filter实现类的关联关系,另外一部分是定义filter所拦截请求的过滤规则。这里我们配置了两个请求url的拦截规则,也就是拦截所有对jsp和html页面的请求(前面已经说明filter可以拦截对静态资源的请求)。前一部分我们称为注册Filter,后一部分我们称为映射Filter。
这里需要注意urlpattern的配置规则:
- 以
/
开头和以/*
结尾的是用来做路径映射的。 - 以前缀
*.
开头的是用来做扩展映射的。 -
/
是用来定义default servlet映射的。 - 剩下的都是用来定义详细映射的。比如:
/aa/bb/cc.jsp
所以,如果urlpattern配置成/*.jsp
会报错,因为它既属于路径映射,又属于后缀映射,应用服务器会困惑。
最后,编写两个测试页面。
index.jsp
:
<%--
Created by IntelliJ IDEA.
User: chengxia
Date: 2019/11/4
Time: 7:47 AM
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Trial Page for Filter</title>
</head>
<body>
<h3>This is a filter page.</h3>
</body>
</html>
test.html
:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Gogogo</title>
</head>
<body>
<h1>gogogo</h1>
</body>
</html>
这样,启动服务器之后,分别访问http://localhost:8080/test.html
和http://localhost:8080/index.jsp
,可以看到控制台输出如下:
Before the request is handled...
url: http://localhost:8080/test.html
After the request is handled...
Before the request is handled...
url: http://localhost:8080/index.jsp
After the request is handled...
4、Filter其它说明
4.1 Filter链
在一个web应用中,可以开发编写多个Filter,这些Filter组合起来称之为一个Filter链。
web服务器根据Filter在web.xml
文件中的注册顺序,决定先调用哪个Filter,当第一个Filter的doFilter方法被调用时,web服务器会创建一个代表Filter链的FilterChain对象传递给该方法。在doFilter方法中,开发人员如果调用了FilterChain对象的doFilter方法,则web服务器会检查FilterChain对象中是否还有filter,如果有,则调用第2个filter,如果没有,则调用目标资源。
4.2 Filter生命周期
4.2.1 创建
Filter的创建和销毁由应用服务器负责。 web应用启动时,应用服务器将创建Filter的实例对象,并调用其init方法,完成对象的初始化功能,从而为后续的用户请求作好拦截的准备工作,filter对象只会创建一次,init方法也只会执行一次。通过init方法的参数,可获得代表当前filter配置信息的FilterConfig对象。
4.2.2 销毁
Web容器调用destroy方法销毁Filter。destroy方法在Filter的生命周期中仅执行一次。在destroy方法中,可以释放过滤器使用的资源。
4.2.3 FilterConfig接口
用户在配置filter时,可以使用<init-param>
为filter配置一些初始化参数,当web容器实例化Filter对象,调用其init方法时,会把封装了filter初始化参数的filterConfig对象传递进来。因此开发人员在编写filter时,通过filterConfig对象的方法,就可获得:
-
String getFilterName()
:得到filter的名称。 -
String getInitParameter(String name)
: 返回在部署描述中指定名称的初始化参数的值。如果不存在返回null. -
Enumeration getInitParameterNames()
:返回过滤器的所有初始化参数的名字的枚举集合。 -
public ServletContext getServletContext()
:返回Servlet上下文对象的引用。
如下是一个带参数的filter的例子。
com.lfqy.web.filter.FilterInitParamTrial
:
package com.lfqy.web.filter;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
/**
* Created by chengxia on 2019/11/4.
* filter的三种典型应用:
1、可以在filter中根据条件决定是否调用chain.doFilter(request, response)方法,即是否让目标资源执行
2、在让目标资源执行之前,可以对request\response作预处理,再让目标资源执行
3、在目标资源执行之后,可以捕获目标资源的执行结果,从而实现一些特殊的功能
*/
public class FilterInitParamTrial implements Filter {
private String filterName;
private String filterFunc;
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("The filter is initializing...");
filterName = filterConfig.getInitParameter("FilterName");
filterFunc = filterConfig.getInitParameter("Function");
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
//设置request和response的编码格式
servletRequest.setCharacterEncoding("UTF-8");
servletResponse.setCharacterEncoding("UTF-8");
//设置请求的contenttype
servletResponse.setContentType("text/html;charset=UTF-8");
System.out.println(filterName + " for function " + filterFunc + "executing. Before the request is handled...");
System.out.println("url: " + ((HttpServletRequest)servletRequest).getRequestURL());
//让目标资源执行,放行
filterChain.doFilter(servletRequest,servletResponse);
System.out.println(filterName + " for function " + filterFunc + "executing. After the request is handled...");
}
@Override
public void destroy() {
System.out.println("The filter is destroying...");
}
}
WEB-INF/web.xml
:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<filter>
<filter-name>FilterTrial01</filter-name>
<filter-class>com.lfqy.web.filter.FilterTrial01</filter-class>
</filter>
<filter>
<filter-name>FilterInitParamTrial</filter-name>
<filter-class>com.lfqy.web.filter.FilterInitParamTrial</filter-class>
<init-param>
<description>测试Filter配置参数01</description>
<param-name>FilterName</param-name>
<param-value>PaopaoFilter</param-value>
</init-param>
<init-param>
<description>测试Filter配置参数02</description>
<param-name>Function</param-name>
<param-value>Test filter init param...</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>FilterTrial01</filter-name>
<!-- "/*"表示拦截所有的请求 -->
<url-pattern>*.jsp</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>FilterInitParamTrial</filter-name>
<!-- "/*"表示拦截所有的请求 -->
<url-pattern>*.html</url-pattern>
</filter-mapping>
</web-app>
重启tomcat服务器,配置生效之后,访问http://localhost:8080/test.html
,控制台输出如下:
PaopaoFilter for function Test filter init param...executing. Before the request is handled...
url: http://localhost:8080/test.html
PaopaoFilter for function Test filter init param...executing. After the request is handled...
可见,过滤器中配置的初始参数已经被拿到。
4.3 映射Filter时的dispatcher属性
<dispatcher>指定过滤器所拦截的资源被 Servlet 容器调用的方式,可以是REQUEST,INCLUDE,FORWARD和ERROR之一,默认REQUEST。用户可以设置多个<dispatcher> 子元素用来指定 Filter 对资源的多种调用方式进行拦截。如下:
<filter-name>filterName</filter-name>
<url-pattern>/*</url-pattern>
<dispatcher>REQUEST</dispatcher>
<dispatcher>FORWARD</dispatcher>
</filter-mapping>
<dispatcher>
子元素可以设置的值及其意义:
- REQUEST:当用户直接访问页面时,Web容器将会调用过滤器。如果目标资源是通过RequestDispatcher的
include()
或forward()
方法访问时,那么该过滤器就不会被调用。 - INCLUDE:如果目标资源是通过RequestDispatcher的
include()
方法访问时,那么该过滤器将被调用。除此之外,该过滤器不会被调用。 - FORWARD:如果目标资源是通过RequestDispatcher的
forward()
方法访问时,那么该过滤器将被调用,除此之外,该过滤器不会被调用。 - ERROR:如果目标资源是通过声明式异常处理机制调用时,那么该过滤器将被调用。除此之外,过滤器不会被调用。
5、Filter使用场景示例
5.1 装饰器模式
5.1.1 定义
装饰器模式,又名包装(Wrapper)模式。装饰模式以对客户端透明的方式扩展对象的功能,是继承关系的一个替代方案。装饰模式是在不必改变原类文件和使用继承的情况下,动态的扩展一个对象的功能。在装饰器模式中,会创建一个包装对象,用来装饰包裹真实的对象。
5.1.2 使用场景说明
当某个对象的方法不适应业务需求时,通常有2种方式可以对方法进行增强:
- 编写子类,覆盖需增强的方法。
- 使用装饰器设计模式对方法进行增强。
在实际应用中遇到需增强对象的方法时,到底选用哪种方式比较好呢?这个没有具体的定式,只能是根据具体的需求来采用具体的方式,不过有一种情况下,必须使用Decorator设计模式:即被增强的对象,开发人员只能得到它的对象,无法得到它的class文件。比如request、response对象,开发人员之所以在servlet中能通过sun公司定义的HttpServletRequest\response接口去操作这些对象,是因为Tomcat服务器厂商编写了request、response接口的实现类。web服务器在调用servlet时,会用这些接口的实现类创建出对象,然后传递给servlet程序。此种情况下,由于开发人员根本不知道服务器厂商编写的request、response接口的实现类是哪个?在程序中只能拿到服务器厂商提供的对象,因此就只能采用Decorator设计模式对这些对象进行增强。
5.1.3 装饰器模式的实现
在实现装饰器模式时,首先看需要被增强对象继承了什么接口或父类,编写一个类也去继承这些接口或父类。然后,在类中定义一个变量,变量类型即需增强对象的类型,同时,在类中定义一个构造函数,构造函数的参数是需增强的对象。最后,覆盖需增强的方法,编写增强的代码。
这里需要注意,装饰器模式中,必须重新编写接口或者父类的所有方法。对于其中,根本不需要增强的方法,只需要简单调用被增强对象(装饰器类中的成员变量)的同名方法即可。
5.2 Servlet API中对于请求对象和响应对象的默认装饰器实现
Servlet API中提供了一个request对象的Decorator设计模式的默认实现类HttpServletRequestWrapper,HttpServletRequestWrapper类实现了request接口中的所有方法,但这些方法的内部实现都是仅仅调用了一下所包装的的 request对象的对应方法,以避免用户在对request对象进行增强时需要实现request接口中的所有方法。
Servlet API中也提供了response对象的Decorator设计模式的默认实现类HttpServletResponseWrapper,HttpServletResponseWrapper类实现了response接口中的所有方法,但这些方法的内部实现都是仅仅调用了一下所包装的的 response对象的对应方法,以避免用户在对response对象进行增强时需要实现response接口中的所有方法。
这两个现成的装饰器类中,都定义了各自的包装对象,实现了request对象和reponse对象对应接口的方法。只不过在方法的实现中,都是简单调用对应包装对象的方法。这样的好处是,当我们自己实现装饰器类的时候,只需要继承这两个线程的装饰器类,然后,重写我们想扩展功能的方法即可。
5.2.1 使用Decorator模式包装request对象解决get请求中的中文参数乱码问题
5.2.1.1 问题原因
Tomcat 7之前的版本,对于URL中字符的编码都是使用iso8859-1
。对于Get请求来说,参数是放在URL中的,所以,对于GET请求,在获得请求参数的时候,如果没有进行合适的转码操作,取出的参数值就可能是乱码。Tomcat 8以后默认的URL编码格式是utf-8。
在Tomcat配置文件conf/server.xml
中设置URIEncoding的值为UTF-8:
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443"
URIEncoding="utf-8"/>
但是不建议这么做。因为这样相当于应用层的代码依赖于Tomcat的设置,有损可移植性。
这里用的是Tomcat 8,为了说明这个例子,这里将Tomcat默认的URL编码方式改成了``
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443"
URIEncoding="iso-8859-1"/>
5.2.1.2 代码示例
首先,写一个解决编码问题的filter实现类,它的作用就是拦截请求,然后,将请求做一个转码包装,然后放行。
com.lfqy.web.filter.CharacterEncodingFilter
:
package com.lfqy.web.filter;
import com.lfqy.web.wrapper.CharacterEncodingRequest;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* Created by chengxia on 2019/11/13.
*/
public class CharacterEncodingFilter implements Filter {
private FilterConfig filterConfig = null;
//设置默认的字符编码
private String defaultCharset = "UTF-8";
public void init(FilterConfig filterConfig){
//在初始化时,得到过滤器的配置信息
this.filterConfig = filterConfig;
}
public void doFilter(ServletRequest req, ServletResponse resp,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) resp;
//得到在web.xml中配置的字符编码
String charset = filterConfig.getInitParameter("charset");
if(charset==null){
charset = defaultCharset;
}
request.setCharacterEncoding(charset);
response.setCharacterEncoding(charset);
response.setContentType("text/html;charset="+charset);
CharacterEncodingRequest requestWrapper = new CharacterEncodingRequest(request);
chain.doFilter(requestWrapper, response);
}
public void destroy(){
//do nothing in destory.
}
}
写一个请求对象的包装类,在包装类中,重写获取请求参数的getParameter方法,如果是get请求,就转码之后,再返回值。
com.lfqy.web.wrapper.CharacterEncodingRequest
:
package com.lfqy.web.wrapper;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
/**
* Created by chengxia on 2019/11/11.
*/
public class CharacterEncodingRequest extends HttpServletRequestWrapper {
//定义一个成员变量,该对象就是被增强对象
private HttpServletRequest req;
//定义一个构造函数,其参数是被增强对象
public CharacterEncodingRequest(HttpServletRequest req){
super(req);
this.req = req;
}
@Override
public String getParameter(String name){
try{
//获取参数的值
String value= this.req.getParameter(name);
if(value==null){
return null;
}
//如果不是以get方式提交数据的,就直接返回获取到的值
if(!this.req.getMethod().equalsIgnoreCase("get")) {
return value;
}else{
//如果是以get方式提交数据的,就对获取到的值进行转码处理
value = new String(value.getBytes("ISO8859-1"),this.req.getCharacterEncoding());
return value;
}
}catch (Exception e) {
throw new RuntimeException(e);
}
}
}
接下来写一个jsp页面,用来分别发送GET请求和POST请求:
EncodingFilterTest.jsp
:
<%--
Created by IntelliJ IDEA.
User: chengxia
Date: 2019/11/13
Time: 7:53 AM
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%--引入jstl标签库 --%>
<%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<html>
<head>
<title>Encoding Filter Test</title>
</head>
<body>
<%--使用c:url标签构建url,构建好的url存储在reqGetUrl变量中--%>
<c:url value="/EncodingTestServlet" scope="page" var="reqGetUrl">
<%--构建的url的附带的中文参数 ,参数名是:petname,值是:于泡泡--%>
<c:param name="petname" value="于泡泡"></c:param>
</c:url>
<%--使用get的方式访问 --%>
<a href="${reqGetUrl}">超链接(get方式请求)</a>
<hr/>
<%--使用post方式提交表单 --%>
<form action="${pageContext.request.contextPath}/EncodingTestServlet" method="post">
用户名:<input type="text" name="petname" value="于泡泡" />
<input type="submit" value="post方式提交">
</form>
</body>
</html>
为了接收前面的POST请求,需要再写一个Servlet。
com.lfqy.web.servlet.EncodingTestServlet
:
package com.lfqy.web.servlet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
/**
* Created by chengxia on 2019/11/13.
*/
public class EncodingTestServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//接收参数
String petname = request.getParameter("petname");
//获取请求方式
String method = request.getMethod();
//获取输出流
PrintWriter out = response.getWriter();
out.write("请求的方式:"+method);
out.write("<br/>");
out.write("接收到的参数:"+petname);
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
最后看下配置文件WEB-INF/web.xml
:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<filter>
<filter-name>FilterTrial01</filter-name>
<filter-class>com.lfqy.web.filter.FilterTrial01</filter-class>
</filter>
<filter>
<filter-name>FilterInitParamTrial</filter-name>
<filter-class>com.lfqy.web.filter.FilterInitParamTrial</filter-class>
<init-param>
<description>测试Filter配置参数01</description>
<param-name>FilterName</param-name>
<param-value>PaopaoFilter</param-value>
</init-param>
<init-param>
<description>测试Filter配置参数02</description>
<param-name>Function</param-name>
<param-value>Test filter init param...</param-value>
</init-param>
</filter>
<filter>
<filter-name>CharacterEncodingFilter</filter-name>
<filter-class>com.lfqy.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<description>设置编码信息</description>
<param-name>charset</param-name>
<param-value>utf-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>FilterTrial01</filter-name>
<!-- "/*"表示拦截所有的请求 -->
<url-pattern>*.jsp</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>FilterInitParamTrial</filter-name>
<!-- "/*"表示拦截所有的请求 -->
<url-pattern>*.html</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>CharacterEncodingFilter</filter-name>
<!-- "/*"表示拦截所有的请求 -->
<url-pattern>/*</url-pattern>
</filter-mapping>
<servlet>
<servlet-name>EncodingTestServlet</servlet-name>
<servlet-class>com.lfqy.web.servlet.EncodingTestServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>EncodingTestServlet</servlet-name>
<url-pattern>/EncodingTestServlet</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>PostUrlParamTestServlet</servlet-name>
<servlet-class>com.lfqy.web.servlet.PostUrlParamTestServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>PostUrlParamTestServlet</servlet-name>
<url-pattern>/PostUrlParamTestServlet</url-pattern>
</servlet-mapping>
</web-app>
启动项目在Tomcat服务器上运行,然后访问http://localhost:8080/EncodingFilterTest.jsp
,效果如下:
这样,无论点击链接还是点击按钮,都会能够得到正确的请求参数并回显。
GET请求
POST请求
5.2.2 使用Decorator模式包装request对象实现HTML字符的转义
用户在浏览器页面输入的内容,如果其中含有HTML的中需要转义的字符,需要进行转义之后才能够正常显示。这里就介绍如何通过装饰器实现该功能。
首先,新建一个装饰器类,用于实现请求内容中参数值的转义。
com.lfqy.web.wrapper.HTMLCharacterEscapeRequest
:
package com.lfqy.web.wrapper;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
/**
* Created by chengxia on 2019/11/11.
*/
public class HTMLCharacterEscapeRequest extends HttpServletRequestWrapper {
//定义一个成员变量,该对象就是被增强对象
private HttpServletRequest req;
//定义一个构造函数,其参数是被增强对象
public HTMLCharacterEscapeRequest(HttpServletRequest req){
super(req);
this.req = req;
}
@Override
public String getParameter(String name){
//获取参数的值
String value= this.req.getParameter(name);
if(value==null || value.length() ==0 ){
return value;
}
return filter(value);
}
private String filter(String msg){
if (msg == null){
return null;
}
char content[] = new char[msg.length()];
msg.getChars(0, msg.length(), content, 0);
StringBuffer result = new StringBuffer(content.length + 50);
for (int i = 0; i < content.length; i++) {
switch (content[i]) {
case '<':
result.append("<");
break;
case '>':
result.append(">");
break;
case '&':
result.append("&");
break;
case '"':
result.append(""");
break;
default:
result.append(content[I]);
}
}
return result.toString();
}
}
新建一个filter实现类。
com.lfqy.web.filter.HTMLCharacterEscapeFilter
:
package com.lfqy.web.filter;
import com.lfqy.web.wrapper.CharacterEncodingRequest;
import com.lfqy.web.wrapper.HTMLCharacterEscapeRequest;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* Created by chengxia on 2019/11/13.
*/
public class HTMLCharacterEscapeFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
//do nothing
}
public void doFilter(ServletRequest req, ServletResponse resp,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) resp;
HTMLCharacterEscapeRequest requestWrapper = new HTMLCharacterEscapeRequest(request);
chain.doFilter(requestWrapper, response);
}
public void destroy(){
//do nothing in destory.
}
}
写一个jsp页面,用来测试。
HTMLCharacterEscapeTest.jsp
:
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<!DOCTYPE HTML>
<html>
<head>
<title>Html Character Escape Test</title>
</head>
<body>
<form action="${pageContext.request.contextPath}/HTMLCharacterEscapeTestServlet" method="post">
留言:
<textarea rows="8" cols="70" name="message">
<script type="text/javascript">
alert("This is my house.")
</script>
<a href="http://www.poorage.com">An Unknown Website</a>
</textarea>
<input type="submit" value="发表">
</form>
</body>
</html>
写一个Servlet,用于接收测试页面发送的请求。
com.lfqy.web.servlet.HTMLCharacterEscapeTestServlet
:
package com.lfqy.web.servlet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
/**
* Created by chengxia on 2019/11/13.
*/
public class HTMLCharacterEscapeTestServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//接收参数
String msg = request.getParameter("message");
//获取输出流
PrintWriter out = response.getWriter();
out.write("message got:"+"<br/>"+msg);
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
最后,在配置文件中,配置Servlet和过滤器。
WEB-INF/web.xml
:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<filter>
<filter-name>FilterTrial01</filter-name>
<filter-class>com.lfqy.web.filter.FilterTrial01</filter-class>
</filter>
<filter>
<filter-name>FilterInitParamTrial</filter-name>
<filter-class>com.lfqy.web.filter.FilterInitParamTrial</filter-class>
<init-param>
<description>测试Filter配置参数01</description>
<param-name>FilterName</param-name>
<param-value>PaopaoFilter</param-value>
</init-param>
<init-param>
<description>测试Filter配置参数02</description>
<param-name>Function</param-name>
<param-value>Test filter init param...</param-value>
</init-param>
</filter>
<filter>
<filter-name>CharacterEncodingFilter</filter-name>
<filter-class>com.lfqy.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<description>设置编码信息</description>
<param-name>charset</param-name>
<param-value>utf-8</param-value>
</init-param>
</filter>
<filter>
<filter-name>HTMLCharacterEscapeFilter</filter-name>
<filter-class>com.lfqy.web.filter.HTMLCharacterEscapeFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>FilterTrial01</filter-name>
<!-- "/*"表示拦截所有的请求 -->
<url-pattern>*.jsp</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>FilterInitParamTrial</filter-name>
<!-- "/*"表示拦截所有的请求 -->
<url-pattern>*.html</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>CharacterEncodingFilter</filter-name>
<!-- "/*"表示拦截所有的请求 -->
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>HTMLCharacterEscapeFilter</filter-name>
<!-- "/*"表示拦截所有的请求 -->
<url-pattern>/*</url-pattern>
</filter-mapping>
<servlet>
<servlet-name>EncodingTestServlet</servlet-name>
<servlet-class>com.lfqy.web.servlet.EncodingTestServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>EncodingTestServlet</servlet-name>
<url-pattern>/EncodingTestServlet</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>PostUrlParamTestServlet</servlet-name>
<servlet-class>com.lfqy.web.servlet.PostUrlParamTestServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>PostUrlParamTestServlet</servlet-name>
<url-pattern>/PostUrlParamTestServlet</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>HTMLCharacterEscapeTestServlet</servlet-name>
<servlet-class>com.lfqy.web.servlet.HTMLCharacterEscapeTestServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>HTMLCharacterEscapeTestServlet</servlet-name>
<url-pattern>/HTMLCharacterEscapeTestServlet</url-pattern>
</servlet-mapping>
</web-app>
启动服务器之后,访问http://localhost:8080/HTMLCharacterEscapeTest.jsp
,效果如下:
点击页面上的
发表
,效果如下。留言测试结果页面
可以看到,前面的留言内容中的字符都已经被转义了。
5.2.3 使用Decorator模式包装request对象实现敏感字符过滤
首先,新建一个filter实现类,在初始化方法中,读取敏感词列表,加载到内存。同时,实现一个内部类,包装request类,重新实现获取参数值的方法。
com.lfqy.web.filter.DirtyWordsFilter
:
package com.lfqy.web.filter;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.util.ArrayList;
import java.util.List;
/**
* Created by chengxia on 2019/11/13.
*/
public class DirtyWordsFilter implements Filter {
private FilterConfig config = null;
private List<String> dirtyWords;
@Override
public void init(FilterConfig filterConfig) throws ServletException {
this.config = filterConfig;
dirtyWords = getDirtyWords();
}
@Override
public void doFilter(ServletRequest req, ServletResponse resp,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) resp;
DirtyWordsRequest dirtyRequest = new DirtyWordsRequest(request);
chain.doFilter(dirtyRequest, response);
}
@Override
public void destroy() {
}
/**
* @Method: getDirtyWords
* @Description: 获取敏感字符
* @Anthor:SpaceCatt
*
* @return
*/
private List<String> getDirtyWords(){
List<String> dirtyWords = new ArrayList<String>();
String dirtyWordPath = config.getInitParameter("DirtyWordsFilePath");
InputStream inputStream = config.getServletContext().getResourceAsStream(dirtyWordPath);
InputStreamReader is = null;
try {
is = new InputStreamReader(inputStream,"UTF-8");
} catch (UnsupportedEncodingException e2) {
e2.printStackTrace();
}
BufferedReader reader = new BufferedReader(is);
String line;
try {
while ((line = reader.readLine())!= null) {//如果 line为空说明读完了
dirtyWords.add(line);
}
} catch (IOException e) {
e.printStackTrace();
}
return dirtyWords;
}
/**
* @ClassName: DirtyWordsRequest
* @Description: 使用Decorator模式包装request对象,实现敏感字符过滤功能
* @author: spacecat
* @date: 2019-12-6 上午08:56:35
*
*/
class DirtyWordsRequest extends HttpServletRequestWrapper {
private HttpServletRequest request;
public DirtyWordsRequest(HttpServletRequest request) {
super(request);
this.request = request;
}
/* 重写getParameter方法,实现对敏感字符的过滤
* @see javax.servlet.ServletRequestWrapper#getParameter(java.lang.String)
*/
@Override
public String getParameter(String name) {
String value = this.request.getParameter(name);
if(value==null){
return null;
}
for(String dirtyWord : dirtyWords){
if(value.contains(dirtyWord)){
System.out.println("内容中包含敏感词:"+dirtyWord+",将会被替换成****");
//替换敏感字符
value = value.replace(dirtyWord, "****");
}
}
return value;
}
}
}
然后,写一个测试页面,提交带敏感词的表单内容。
DirtyWordsFilterTest.jsp
:
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<!DOCTYPE HTML>
<html>
<head>
<title>Html Character Escape Test</title>
</head>
<body>
<form action="${pageContext.request.contextPath}/HTMLCharacterEscapeTestServlet" method="post">
留言:
<textarea rows="8" cols="70" name="message">
这是我的留言,包含敏感词1,敏感词2等敏感词。哈哈
</textarea>
<input type="submit" value="发表">
</form>
</body>
</html>
这里表单的数据直接提交到com.lfqy.web.servlet.HTMLCharacterEscapeTestServlet
即可,无需新写。
最后修改配置文件。
WEB-INF/web.xml
:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<filter>
<filter-name>FilterTrial01</filter-name>
<filter-class>com.lfqy.web.filter.FilterTrial01</filter-class>
</filter>
<filter>
<filter-name>FilterInitParamTrial</filter-name>
<filter-class>com.lfqy.web.filter.FilterInitParamTrial</filter-class>
<init-param>
<description>测试Filter配置参数01</description>
<param-name>FilterName</param-name>
<param-value>PaopaoFilter</param-value>
</init-param>
<init-param>
<description>测试Filter配置参数02</description>
<param-name>Function</param-name>
<param-value>Test filter init param...</param-value>
</init-param>
</filter>
<filter>
<filter-name>CharacterEncodingFilter</filter-name>
<filter-class>com.lfqy.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<description>设置编码信息</description>
<param-name>charset</param-name>
<param-value>utf-8</param-value>
</init-param>
</filter>
<filter>
<filter-name>DirtyWordsFilter</filter-name>
<filter-class>com.lfqy.web.filter.DirtyWordsFilter</filter-class>
<init-param>
<description>设置包含敏感词的配置文件路径</description>
<param-name>DirtyWordsFilePath</param-name>
<param-value>/WEB-INF/DirtyWords.txt</param-value>
</init-param>
</filter>
<filter>
<filter-name>HTMLCharacterEscapeFilter</filter-name>
<filter-class>com.lfqy.web.filter.HTMLCharacterEscapeFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>FilterTrial01</filter-name>
<!-- "/*"表示拦截所有的请求 -->
<url-pattern>*.jsp</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>FilterInitParamTrial</filter-name>
<!-- "/*"表示拦截所有的请求 -->
<url-pattern>*.html</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>CharacterEncodingFilter</filter-name>
<!-- "/*"表示拦截所有的请求 -->
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>HTMLCharacterEscapeFilter</filter-name>
<!-- "/*"表示拦截所有的请求 -->
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>DirtyWordsFilter</filter-name>
<!-- "/*"表示拦截所有的请求 -->
<url-pattern>/*</url-pattern>
</filter-mapping>
<servlet>
<servlet-name>EncodingTestServlet</servlet-name>
<servlet-class>com.lfqy.web.servlet.EncodingTestServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>EncodingTestServlet</servlet-name>
<url-pattern>/EncodingTestServlet</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>PostUrlParamTestServlet</servlet-name>
<servlet-class>com.lfqy.web.servlet.PostUrlParamTestServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>PostUrlParamTestServlet</servlet-name>
<url-pattern>/PostUrlParamTestServlet</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>HTMLCharacterEscapeTestServlet</servlet-name>
<servlet-class>com.lfqy.web.servlet.HTMLCharacterEscapeTestServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>HTMLCharacterEscapeTestServlet</servlet-name>
<url-pattern>/HTMLCharacterEscapeTestServlet</url-pattern>
</servlet-mapping>
</web-app>
启动tomcat之后,访问http://localhost:8080/DirtyWordsFilterTest.jsp
,效果如下:
点击
发表
,可以看到关键词已经都被替换:留言敏感词测试结果页面
在实际中,我们经常将上面的三个过滤器合并成一个。如下:
com.lfqy.web.filter.TripleInOneFilter
:
package com.lfqy.web.filter;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.util.ArrayList;
import java.util.List;
/**
* Created by chengxia on 2019/11/13.
*/
public class TripleInOneFilter implements Filter {
private FilterConfig config = null;
private List<String> dirtyWords;
//设置默认的字符编码
private String defaultCharset = "UTF-8";
@Override
public void init(FilterConfig filterConfig) throws ServletException {
this.config = filterConfig;
dirtyWords = getDirtyWords();
}
@Override
public void doFilter(ServletRequest req, ServletResponse resp,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) resp;
//得到在web.xml中配置的字符编码
String charset = config.getInitParameter("charset");
if(charset==null){
charset = defaultCharset;
}
request.setCharacterEncoding(charset);
response.setCharacterEncoding(charset);
response.setContentType("text/html;charset="+charset);
TripleInOneRequest filteredRequest = new TripleInOneRequest(request);
chain.doFilter(filteredRequest, response);
}
@Override
public void destroy() {
}
/**
* @Method: getDirtyWords
* @Description: 获取敏感字符
* @Anthor:SpaceCat
*
* @return
*/
private List<String> getDirtyWords(){
List<String> dirtyWords = new ArrayList<String>();
String dirtyWordPath = config.getInitParameter("DirtyWordsFilePath");
InputStream inputStream = config.getServletContext().getResourceAsStream(dirtyWordPath);
InputStreamReader is = null;
try {
is = new InputStreamReader(inputStream,"UTF-8");
} catch (UnsupportedEncodingException e2) {
e2.printStackTrace();
}
BufferedReader reader = new BufferedReader(is);
String line;
try {
while ((line = reader.readLine())!= null) {//如果 line为空说明读完了
dirtyWords.add(line);
}
} catch (IOException e) {
e.printStackTrace();
}
return dirtyWords;
}
/**
* @ClassName: TripleInOneRequest
* @Description: 使用Decorator模式包装request对象,实现敏感字符过滤功能
* @author: spacecat
* @date: 2019-12-6 上午08:56:35
*
*/
class TripleInOneRequest extends HttpServletRequestWrapper {
private HttpServletRequest request;
public TripleInOneRequest(HttpServletRequest request) {
super(request);
this.request = request;
}
/* 重写getParameter方法,实现对敏感字符的过滤
* @see javax.servlet.ServletRequestWrapper#getParameter(java.lang.String)
*/
@Override
public String getParameter(String name) {
//获取参数的值
String value= this.request.getParameter(name);
if(value==null){
return null;
}
//如果不是以get方式提交数据的,就直接返回获取到的值;如果是get方法,就需要进行编码转换
if(!this.request.getMethod().equalsIgnoreCase("get")) {
return value;
}else{
try {
//如果是以get方式提交数据的,就对获取到的值进行转码处理
value = new String(value.getBytes("ISO8859-1"),this.request.getCharacterEncoding());
}catch (Exception e){
e.printStackTrace();
}
}
//html字符转义
value = filter(value);
//敏感词过滤
for(String dirtyWord : dirtyWords){
if(value.contains(dirtyWord)){
System.out.println("内容中包含敏感词:"+dirtyWord+",将会被替换成****");
//替换敏感字符
value = value.replace(dirtyWord, "****");
}
}
return value;
}
public String filter(String value) {
if (value == null){
return null;
}
char content[] = new char[value.length()];
value.getChars(0, value.length(), content, 0);
StringBuffer result = new StringBuffer(content.length + 50);
for (int i = 0; i < content.length; i++) {
switch (content[i]) {
case '<':
result.append("<");
break;
case '>':
result.append(">");
break;
case '&':
result.append("&");
break;
case '"':
result.append(""");
break;
default:
result.append(content[I]);
}
}
return (result.toString());
}
}
}
在配置文件中添加的配置信息如下:
WEB-INF/web.xml
:
<filter>
<filter-name>DirtyWordsFilter</filter-name>
<filter-class>com.lfqy.web.filter.DirtyWordsFilter</filter-class>
<init-param>
<description>设置编码信息</description>
<param-name>charset</param-name>
<param-value>utf-8</param-value>
</init-param>
<init-param>
<description>设置包含敏感词的配置文件路径</description>
<param-name>DirtyWordsFilePath</param-name>
<param-value>/WEB-INF/DirtyWords.txt</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>TripleInOneFilter</filter-name>
<!-- "/*"表示拦截所有的请求 -->
<url-pattern>/*</url-pattern>
</filter-mapping>
5.2.4 使用Decorator设计模式包装response对象实现压缩响应正文内容
通过filter向目标页面传递一个自定义的response对象。在自定义的response对象中,重写getOutputStream方法和getWriter方法,使目标资源调用此方法输出页面内容时,获得的是我们自定义的ServletOutputStream对象。在我们自定义的ServletOuputStream对象中,重写write方法,使写出的数据写出到一个buffer中(这里实际上是写到了java.io.ByteArrayOutputStream
中,这个流自带一个自动增长的buffer,所有到输出到该流的数据都会先到buffer)。当页面完成输出后,通过调用自定义response对象的getBuffer()方法,在filter中就可得到页面写出的数据,从而我们可以调用GzipOuputStream对数据进行压缩后再写出给浏览器,以此完成响应正文件压缩功能。
首先,实现一个自定义的过滤器类。
com.lfqy.web.filter.GzipFilter
:
package com.lfqy.web.filter;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import java.io.*;
import java.util.ArrayList;
import java.util.List;
import java.util.zip.GZIPOutputStream;
/**
* Created by chengxia on 2019/11/13.
*/
public class GzipFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest req, ServletResponse resp,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) resp;
BufferedResponse bufResp = new BufferedResponse(response);
chain.doFilter(request, bufResp);
//拿出缓存中的数据,压缩后再打给浏览器
byte out[] = bufResp.getBuffer();
System.out.println("原始大小:" + out.length);
ByteArrayOutputStream bout = new ByteArrayOutputStream();
//压缩输出流中的数据
GZIPOutputStream gout = new GZIPOutputStream(bout);
gout.write(out);
gout.close();
byte gzip[] = bout.toByteArray();
System.out.println("压缩后的大小:" + gzip.length);
response.setHeader("content-encoding", "gzip");
response.setContentLength(gzip.length);
response.getOutputStream().write(gzip);
}
@Override
public void destroy() {
}
/**
* @ClassName: BufferedResponse
* @Description:
* 使用Decorator模式包装response对象,输出到带缓存的ByteArrayOutputStream功能
* 同时,实现了getBuffer方法,使能够拿到缓冲区的内容
* @author: spacecat
* @date: 2019-12-6 上午08:56:35
*
*/
class BufferedResponse extends HttpServletResponseWrapper {
private ByteArrayOutputStream bout = new ByteArrayOutputStream();
private PrintWriter pw;
private HttpServletResponse response;
public BufferedResponse(HttpServletResponse response) {
super(response);
this.response = response;
}
/**
* 这个方法是我们压缩之后,再向浏览器写数据的时候,要调用的。
* */
@Override
public ServletOutputStream getOutputStream() throws IOException {
return new MyServletOutputStream(bout);
}
/**
* 这个方法是框架输出到response对象时调用的
* */
@Override
public PrintWriter getWriter() throws IOException {
pw = new PrintWriter(new OutputStreamWriter(bout,this.response.getCharacterEncoding()));
return pw;
}
public byte[] getBuffer(){
try{
if(pw!=null){
pw.close();
}
if(bout!=null){
bout.flush();
return bout.toByteArray();
}
return null;
}catch (Exception e) {
throw new RuntimeException(e);
}
}
}
class MyServletOutputStream extends ServletOutputStream{
private ByteArrayOutputStream bout;
public MyServletOutputStream(ByteArrayOutputStream bout){
this.bout = bout;
}
@Override
public void write(int b) throws IOException {
this.bout.write(b);
}
@Override
public boolean isReady() {
return false;
}
@Override
public void setWriteListener(WriteListener writeListener) {
}
}
}
修改配置文件,说明那些需要压缩。
WEB-INF/web.xml
:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<filter>
<filter-name>FilterTrial01</filter-name>
<filter-class>com.lfqy.web.filter.FilterTrial01</filter-class>
</filter>
<filter>
<filter-name>FilterInitParamTrial</filter-name>
<filter-class>com.lfqy.web.filter.FilterInitParamTrial</filter-class>
<init-param>
<description>测试Filter配置参数01</description>
<param-name>FilterName</param-name>
<param-value>PaopaoFilter</param-value>
</init-param>
<init-param>
<description>测试Filter配置参数02</description>
<param-name>Function</param-name>
<param-value>Test filter init param...</param-value>
</init-param>
</filter>
<filter>
<filter-name>CharacterEncodingFilter</filter-name>
<filter-class>com.lfqy.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<description>设置编码信息</description>
<param-name>charset</param-name>
<param-value>utf-8</param-value>
</init-param>
</filter>
<filter>
<filter-name>DirtyWordsFilter</filter-name>
<filter-class>com.lfqy.web.filter.DirtyWordsFilter</filter-class>
<init-param>
<description>设置编码信息</description>
<param-name>charset</param-name>
<param-value>utf-8</param-value>
</init-param>
<init-param>
<description>设置包含敏感词的配置文件路径</description>
<param-name>DirtyWordsFilePath</param-name>
<param-value>/WEB-INF/DirtyWords.txt</param-value>
</init-param>
</filter>
<filter>
<filter-name>TripleInOneFilter</filter-name>
<filter-class>com.lfqy.web.filter.TripleInOneFilter</filter-class>
<init-param>
<description>设置包含敏感词的配置文件路径</description>
<param-name>DirtyWordsFilePath</param-name>
<param-value>/WEB-INF/DirtyWords.txt</param-value>
</init-param>
</filter>
<filter>
<filter-name>GzipFilter</filter-name>
<filter-class>com.lfqy.web.filter.GzipFilter</filter-class>
</filter>
<filter>
<filter-name>HTMLCharacterEscapeFilter</filter-name>
<filter-class>com.lfqy.web.filter.HTMLCharacterEscapeFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>FilterTrial01</filter-name>
<!-- "/*"表示拦截所有的请求 -->
<url-pattern>*.jsp</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>FilterInitParamTrial</filter-name>
<!-- "/*"表示拦截所有的请求 -->
<url-pattern>*.html</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>CharacterEncodingFilter</filter-name>
<!-- "/*"表示拦截所有的请求 -->
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>HTMLCharacterEscapeFilter</filter-name>
<!-- "/*"表示拦截所有的请求 -->
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>DirtyWordsFilter</filter-name>
<!-- "/*"表示拦截所有的请求 -->
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>TripleInOneFilter</filter-name>
<!-- "/*"表示拦截所有的请求 -->
<url-pattern>/*</url-pattern>
</filter-mapping>
<!--jsp文件的输出的内容都经过压缩过滤器压缩后才输出 -->
<filter-mapping>
<filter-name>GzipFilter</filter-name>
<url-pattern>*.jsp</url-pattern>
<!-- 配置过滤器的拦截方式-->
<!-- 对于在Servlet中通过
request.getRequestDispatcher("jsp页面路径").forward(request, response)
方式访问的Jsp页面的要进行拦截 -->
<dispatcher>FORWARD</dispatcher>
<!--对于直接以URL方式访问的jsp页面进行拦截,过滤器的拦截方式默认就是 REQUEST-->
<dispatcher>REQUEST</dispatcher>
</filter-mapping>
<!--js文件的输出的内容都经过压缩过滤器压缩后才输出 -->
<filter-mapping>
<filter-name>GzipFilter</filter-name>
<url-pattern>*.js</url-pattern>
</filter-mapping>
<!--css文件的输出的内容都经过压缩过滤器压缩后才输出 -->
<filter-mapping>
<filter-name>GzipFilter</filter-name>
<url-pattern>*.css</url-pattern>
</filter-mapping>
<!--html文件的输出的内容都经过压缩过滤器压缩后才输出 -->
<filter-mapping>
<filter-name>GzipFilter</filter-name>
<url-pattern>*.html</url-pattern>
</filter-mapping>
<servlet>
<servlet-name>EncodingTestServlet</servlet-name>
<servlet-class>com.lfqy.web.servlet.EncodingTestServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>EncodingTestServlet</servlet-name>
<url-pattern>/EncodingTestServlet</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>PostUrlParamTestServlet</servlet-name>
<servlet-class>com.lfqy.web.servlet.PostUrlParamTestServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>PostUrlParamTestServlet</servlet-name>
<url-pattern>/PostUrlParamTestServlet</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>HTMLCharacterEscapeTestServlet</servlet-name>
<servlet-class>com.lfqy.web.servlet.HTMLCharacterEscapeTestServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>HTMLCharacterEscapeTestServlet</servlet-name>
<url-pattern>/HTMLCharacterEscapeTestServlet</url-pattern>
</servlet-mapping>
</web-app>
这样,启动tomcat服务器之后,访问http://localhost:8080/DirtyWordsFilterTest.jsp
,页面正常显示,服务器控制台输出如下:
Before the request is handled...
url: http://localhost:8080/DirtyWordsFilterTest.jsp
原始大小:376
压缩后的大小:300
After the request is handled...
5.2.5 使用Decorator设计模式包装response对象实现缓存数据到内存
对于页面中很少更新的数据,例如商品分类,为避免每次都要从数据库查询分类数据,因此可把分类数据缓存在内存或文件中,以此来减轻数据库或者磁盘的压力,提高系统响应速度。
原理上,这里也是用自定义的Response对象将输出先放到缓存,然后,缓存的数据放到一个映射结构中。这样,后续如果在内存中命中,就不用重新查询数据库或者访问磁盘了。
首先写一个自定义过滤器实现类。
com.lfqy.web.filter.WebResourceCachedFilter
:
package com.lfqy.web.filter;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.Map;
import java.util.zip.GZIPOutputStream;
/**
* Created by chengxia on 2019/11/13.
*/
public class WebResourceCachedFilter implements Filter {
//用一个map结构缓存web资源
private Map<String,byte[]> map = new HashMap<String,byte[]>();
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest req, ServletResponse resp,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) resp;
//1.得到用户请求的uri
String uri = request.getRequestURI();
//2.看缓存中有没有uri对应的数据
byte b[] = map.get(uri);
//3.如果缓存中有,直接拿缓存的数据打给浏览器,程序返回
if(b!=null){
//根据字节数组和指定的字符编码构建字符串
String webResourceHtmlStr = new String(b,response.getCharacterEncoding());
System.out.println(webResourceHtmlStr);
response.getOutputStream().write(b);
return;
}
//4.如果缓存没有,让目标资源执行,并捕获目标资源的输出
BufferedResponse myresponse = new BufferedResponse(response);
chain.doFilter(request, myresponse);
//获取缓冲流中的内容的字节数组
byte out[] = myresponse.getBuffer();
//5.把资源的数据以用户请求的uri为关键字保存到缓存中
map.put(uri, out);
//6.把数据打给浏览器
response.getOutputStream().write(out);
}
@Override
public void destroy() {
}
/**
* @ClassName: BufferedResponse
* @Description:
* 使用Decorator模式包装response对象,输出到带缓存的ByteArrayOutputStream功能
* 同时,实现了getBuffer方法,使能够拿到缓冲区的内容
* @author: spacecat
* @date: 2019-12-6 上午08:56:35
*
*/
class BufferedResponse extends HttpServletResponseWrapper {
private ByteArrayOutputStream bout = new ByteArrayOutputStream();
private PrintWriter pw;
private HttpServletResponse response;
public BufferedResponse(HttpServletResponse response) {
super(response);
this.response = response;
}
/**
* 这个方法是我们压缩之后,再向浏览器写数据的时候,要调用的。
* */
@Override
public ServletOutputStream getOutputStream() throws IOException {
return new MyServletOutputStream(bout);
}
/**
* 这个方法是框架输出到response对象时调用的
* */
@Override
public PrintWriter getWriter() throws IOException {
pw = new PrintWriter(new OutputStreamWriter(bout,this.response.getCharacterEncoding()));
return pw;
}
public byte[] getBuffer(){
try{
if(pw!=null){
pw.close();
}
if(bout!=null){
bout.flush();
return bout.toByteArray();
}
return null;
}catch (Exception e) {
throw new RuntimeException(e);
}
}
}
class MyServletOutputStream extends ServletOutputStream{
private ByteArrayOutputStream bout;
public MyServletOutputStream(ByteArrayOutputStream bout){
this.bout = bout;
}
@Override
public void write(int b) throws IOException {
this.bout.write(b);
}
@Override
public boolean isReady() {
return false;
}
@Override
public void setWriteListener(WriteListener writeListener) {
}
}
}
修改配置文件,指定缓存那些页面。
WEB-INF/web.xml
:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<filter>
<filter-name>FilterTrial01</filter-name>
<filter-class>com.lfqy.web.filter.FilterTrial01</filter-class>
</filter>
<filter>
<filter-name>FilterInitParamTrial</filter-name>
<filter-class>com.lfqy.web.filter.FilterInitParamTrial</filter-class>
<init-param>
<description>测试Filter配置参数01</description>
<param-name>FilterName</param-name>
<param-value>PaopaoFilter</param-value>
</init-param>
<init-param>
<description>测试Filter配置参数02</description>
<param-name>Function</param-name>
<param-value>Test filter init param...</param-value>
</init-param>
</filter>
<filter>
<filter-name>CharacterEncodingFilter</filter-name>
<filter-class>com.lfqy.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<description>设置编码信息</description>
<param-name>charset</param-name>
<param-value>utf-8</param-value>
</init-param>
</filter>
<filter>
<filter-name>DirtyWordsFilter</filter-name>
<filter-class>com.lfqy.web.filter.DirtyWordsFilter</filter-class>
<init-param>
<description>设置编码信息</description>
<param-name>charset</param-name>
<param-value>utf-8</param-value>
</init-param>
<init-param>
<description>设置包含敏感词的配置文件路径</description>
<param-name>DirtyWordsFilePath</param-name>
<param-value>/WEB-INF/DirtyWords.txt</param-value>
</init-param>
</filter>
<filter>
<filter-name>TripleInOneFilter</filter-name>
<filter-class>com.lfqy.web.filter.TripleInOneFilter</filter-class>
<init-param>
<description>设置包含敏感词的配置文件路径</description>
<param-name>DirtyWordsFilePath</param-name>
<param-value>/WEB-INF/DirtyWords.txt</param-value>
</init-param>
</filter>
<filter>
<filter-name>GzipFilter</filter-name>
<filter-class>com.lfqy.web.filter.GzipFilter</filter-class>
</filter>
<filter>
<filter-name>WebResourceCachedFilter</filter-name>
<filter-class>com.lfqy.web.filter.WebResourceCachedFilter</filter-class>
</filter>
<filter>
<filter-name>HTMLCharacterEscapeFilter</filter-name>
<filter-class>com.lfqy.web.filter.HTMLCharacterEscapeFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>FilterTrial01</filter-name>
<!-- "/*"表示拦截所有的请求 -->
<url-pattern>*.jsp</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>FilterInitParamTrial</filter-name>
<!-- "/*"表示拦截所有的请求 -->
<url-pattern>*.html</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>CharacterEncodingFilter</filter-name>
<!-- "/*"表示拦截所有的请求 -->
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>HTMLCharacterEscapeFilter</filter-name>
<!-- "/*"表示拦截所有的请求 -->
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>DirtyWordsFilter</filter-name>
<!-- "/*"表示拦截所有的请求 -->
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>TripleInOneFilter</filter-name>
<!-- "/*"表示拦截所有的请求 -->
<url-pattern>/*</url-pattern>
</filter-mapping>
<!--jsp文件的输出的内容都经过压缩过滤器压缩后才输出 -->
<filter-mapping>
<filter-name>GzipFilter</filter-name>
<url-pattern>*.jsp</url-pattern>
<!-- 配置过滤器的拦截方式-->
<!-- 对于在Servlet中通过
request.getRequestDispatcher("jsp页面路径").forward(request, response)
方式访问的Jsp页面的要进行拦截 -->
<dispatcher>FORWARD</dispatcher>
<!--对于直接以URL方式访问的jsp页面进行拦截,过滤器的拦截方式默认就是 REQUEST-->
<dispatcher>REQUEST</dispatcher>
</filter-mapping>
<!--js文件的输出的内容都经过压缩过滤器压缩后才输出 -->
<filter-mapping>
<filter-name>GzipFilter</filter-name>
<url-pattern>*.js</url-pattern>
</filter-mapping>
<!--css文件的输出的内容都经过压缩过滤器压缩后才输出 -->
<filter-mapping>
<filter-name>GzipFilter</filter-name>
<url-pattern>*.css</url-pattern>
</filter-mapping>
<!--html文件的输出的内容都经过压缩过滤器压缩后才输出 -->
<filter-mapping>
<filter-name>GzipFilter</filter-name>
<url-pattern>*.html</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>WebResourceCachedFilter</filter-name>
<!-- 映射需要缓存输出的JSP页面,这几个页面都只是单纯作为输入UI,不会有太多的变化,因此可以缓存输出 -->
<url-pattern>/DirtyWordsFilterTest.jsp</url-pattern>
<url-pattern>/test.jsp</url-pattern>
<url-pattern>/test2.jsp</url-pattern>
</filter-mapping>
<servlet>
<servlet-name>EncodingTestServlet</servlet-name>
<servlet-class>com.lfqy.web.servlet.EncodingTestServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>EncodingTestServlet</servlet-name>
<url-pattern>/EncodingTestServlet</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>PostUrlParamTestServlet</servlet-name>
<servlet-class>com.lfqy.web.servlet.PostUrlParamTestServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>PostUrlParamTestServlet</servlet-name>
<url-pattern>/PostUrlParamTestServlet</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>HTMLCharacterEscapeTestServlet</servlet-name>
<servlet-class>com.lfqy.web.servlet.HTMLCharacterEscapeTestServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>HTMLCharacterEscapeTestServlet</servlet-name>
<url-pattern>/HTMLCharacterEscapeTestServlet</url-pattern>
</servlet-mapping>
</web-app>
启动服务器之后,第二次访问http://localhost:8080/DirtyWordsFilterTest.jsp
,服务器控制台输出如下:
Before the request is handled...
url: http://localhost:8080/DirtyWordsFilterTest.jsp
<!DOCTYPE HTML>
<html>
<head>
<title>Html Character Escape Test</title>
</head>
<body>
<form action="/HTMLCharacterEscapeTestServlet" method="post">
留言:
<textarea rows="8" cols="70" name="message">
这是我的留言,包含敏感词1,敏感词2等敏感词。哈哈
</textarea>
<input type="submit" value="发表">
</form>
</body>
</html>
原始大小:376
压缩后的大小:300
After the request is handled...
注意,在最后的两个例子中,并没有调用chain.doFilter(filteredRequest, response);
,也就是说,请求经过这两个filter处理之后,就不会继续往别的过滤器传递了。这里需要特别注意。