10.3示例:AutoCorrectFilter

Web应用中,用户经常在单词的前面或者后面输入空格,更有甚者在单词之间也加入空格。是否很想在应用的每个Servlet中,把多余的空格删除掉呢?本节的AutoCorrectFilter可以帮助你搞定它。该Filter包含了HttpServletRequestWrapper子类AutoCorrectHttpServletRequestWrapper,并重写了返回参数值的方法:getParametergetParameterValuesgetParameterMap

AutoCorrectFilter.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
package filter;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
@WebFilter(
filterName = "AutoCorrectFilter",
//过滤所有的URL
urlPatterns ={"/*"}
)
public class AutoCorrectFilter
implements
Filter
{
@Override
public void init(FilterConfig filterConfig)
throws ServletException
{}
@Override
public void destroy()
{}
@Override
public void doFilter(ServletRequest request,
ServletResponse response,FilterChain filterChain)
throws IOException,ServletException
{
//被装饰的对象
HttpServletRequest httpServletRequest =
(HttpServletRequest) request;
//装饰器
AutoCorrectHttpServletRequestWrapper wrapper =
new AutoCorrectHttpServletRequestWrapper(
httpServletRequest);
//使用装饰器代替请求对象
filterChain.doFilter(wrapper, response);
}
}

代码详解

doFilter方法

这个FilterdoFilter方法非常简单:创建ServletRequest的修饰类实例,然后,把修饰类实例传给doFilter

1
2
3
4
5
6
7
8
9
10
//获取被修饰类的实例
HttpServletRequest httpServletRequest =
(HttpServletRequest) request;
//创建修饰类的实例
AutoCorrectHttpServletRequestWrapper
wrapper = new
AutoCorrectHttpServletRequestWrapper(
httpServletRequest);
//修饰类的实例代替被修饰类的实例
filterChain.doFilter(wrapper, response);

AutoCorrectHttpServletRequestWrapper.java

AutoCorrectHttpServletRequestWrapper.java实现了对HttpServletRequest对象的装饰.如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
package filter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
//装饰器类
class AutoCorrectHttpServletRequestWrapper
extends HttpServletRequestWrapper
{
//原来的请求对象
private HttpServletRequest httpServletRequest;
//构造函数的形式参数设置为要修饰的实例
public AutoCorrectHttpServletRequestWrapper(
HttpServletRequest httpServletRequest)
{
super(httpServletRequest);
this.httpServletRequest = httpServletRequest;
}
//重写原来的getParameter方法:
//- 对原来的方法的返回值做一些处理
//- 在原来方法的前后做一些处理
@Override
public String getParameter(String name)
{
//autoCorrect为修饰方法
return autoCorrect(
//原来的方法
httpServletRequest.getParameter(name)
);
}
@Override
public String[] getParameterValues(String name)
{
//处理原来方法的返回值
return autoCorrect(
//调用原来的方法
httpServletRequest.getParameterValues(name));
}
@Override
public Map<String,String[]> getParameterMap()
{
//获取原有的getParameterMap的返回值
final Map<String,String[]> parameterMap =
httpServletRequest.getParameterMap();
//创建一个新的Map用来存储处理的结果
Map<String,String[]> newMap =
new Map<String,String[]>()
{
@Override
public int size()
{
//使用原有的size方法
return parameterMap.size();
}
@Override
public boolean isEmpty()
{
//不改变
return parameterMap.isEmpty();
}
@Override
public boolean containsKey(Object key)
{
//不改变
return parameterMap
.containsKey(key);
}
@Override
public boolean containsValue(Object value)
{
//与原方法一致,不改变
return parameterMap
.containsValue(value);
}
@Override
public String[] get(Object key)
{
//返回修饰后的get方法
return autoCorrect(
//调用原有的get方法
parameterMap.get(key));
}
@Override
public void clear()
{
// this will throw an IllegalStateException,
// but let the user get the original
// exception
//不改变
parameterMap.clear();
}
@Override
public Set<String> keySet()
{
//不改变
return parameterMap.keySet();
}
@Override
public Collection<String[]> values()
{
//返回修饰后的结果
return autoCorrect(
parameterMap.values());
}
@Override
public Set<Map.Entry<String,String[]>> entrySet()
{
//返回修饰后的结果
return autoCorrect(
parameterMap.entrySet());
}
@Override
public String[] put(String key,String[] value)
{
// this will throw an IllegalStateException,
// but let the user get the original
// exception
//不改变
return parameterMap.put(key, value);
}
@Override
public void putAll(Map<? extends String,? extends String[]> map)
{
// this will throw an IllegalStateException,
// but let
// the user get the original exception
//不改变
parameterMap.putAll(map);
}
@Override
public String[] remove(Object key)
{
// this will throw an IllegalStateException,
// but let
// the user get the original exception
//不改变
return parameterMap.remove(key);
}
};
return newMap;
}
// #### 下面是修饰方法 ############################################################
/**
* 修饰方法,删除参数中的空白符.
* @param value 带有空白符的字符串参数.
* @return 没有空白符的字符串.
*/
private String autoCorrect(String value)
{
if(value == null) {
return null;
}
value = value.trim();
int length = value.length();
StringBuilder temp = new StringBuilder();
boolean lastCharWasSpace = false;
for(int i = 0;i < length;i++) {
char c = value.charAt(i);
if(c == ' ') {
if(!lastCharWasSpace) {
temp.append(c);
}
lastCharWasSpace = true;
} else {
temp.append(c);
lastCharWasSpace = false;
}
}
return temp.toString();
}
/**
* 删除字符串数组中元素中的空白符.
* @param values 字符串数组,数组元素可能带空白符的
* @return 字符串数组,数组元素中没有空白符.
*/
private String[] autoCorrect(String[] values)
{
if(values != null) {
int length = values.length;
for(int i = 0;i < length;i++) {
values[i] = autoCorrect(values[i]);
}
return values;
}
return null;
}
/**
*
* @param valueCollection
* @return
*/
private Collection<String[]> autoCorrect(
Collection<String[]> valueCollection)
{
Collection<String[]> newCollection =
new ArrayList<String[]>();
for(String[] values: valueCollection) {
//
newCollection.add(
//使用修饰后的值
autoCorrect(values)
);
}
return newCollection;
}
private Set<Map.Entry<String,String[]>> autoCorrect(
Set<Map.Entry<String,String[]>> entrySet)
{
Set<Map.Entry<String,String[]>> newSet =
new HashSet<Map.Entry<String,String[]>>();
for(final Map.Entry<String,String[]> entry: entrySet)
{
Map.Entry<String,String[]> newEntry =
new Map.Entry<String,String[]>()
{
@Override
public String getKey()
{
return entry.getKey();
}
@Override
public String[] getValue()
{
//使用修饰后的值
return autoCorrect(
entry.getValue()
);
}
@Override
public String[] setValue(String[] value)
{
return entry.setValue(value);
}
};
newSet.add(newEntry);
}
return newSet;
}
}

修饰方法

在这个Filter背后的任何Servlet获得的HttpServletRequest都将被AutoCorrectHttpServletRequestWrapper所封装。这个封装类很长,但很好理解。简单地说,就是它把所有获取参数方法的返回值都用autoCorrect方法先处理删除其中的空格,然后使用autoCorrect方法处理后的结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
private String autoCorrect(String value) {
if (value == null) {
return null;
}
value = value.trim();
int length = value.length();
StringBuilder temp = new StringBuilder();
boolean lastCharWasSpace = false;
for (int i = 0; i < length; i++) {
char c = value.charAt(i);
if (c == ' ') {
if (!lastCharWasSpace) {
temp.append(c);
}
lastCharWasSpace = true;
} else {
temp.append(c);
lastCharWasSpace = false;
}
}
return temp.toString();
}

测试

测试这个Filter时,可以分别下面的test1.jsp,test2.jsp这两个页面。

test1.jsp页面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<!DOCTYPE HTML>
<html>
<head>
<title>User Form</title>
<style>
table {
font-family: verdana, arial, sans-serif;
font-size: 16px;
color: #333333;
border-width: 1px;
border-color: #a9c6c9;
border-collapse: collapse;
background-color: #1f3de199;
}
table td {
border-width: 1px;
padding: 8px;
border-style: solid;
border-color: #a9c6c9;
}
</style>
</head>
<body>
<form action="test2.jsp" method="post">
<table>
<tr>
<td align="right">Name:</td>
<td><input name="name" /></td>
</tr>
<tr>
<td align="right">Address:</td>
<td><input name="address" /></td>
</tr>
<tr>
<td align="center" colspan="2">
<input type="submit" value="Login" />
</td>
</tr>
</table>
</form>
</body>
</html>

test2.jsp页面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn"%>
<!DOCTYPE HTML>
<html>
<head>
<title>Form Values</title>
<!-- 引入table CSS样式用到JS代码 -->
<script type="text/javascript" src="table.js"></script>
<!-- 引入table的css样式 -->
<link type="text/css" rel="styleSheet" href="table.css">
</head>
<body>
<table class="altrowstable" id="alternatecolor">
<tr>
<td align="right">Name:</td>
<td>
${param.name}(length:${fn:length(param.name)})
</td>
</tr>
<tr>
<td align="right">Address:</td>
<td>
${param.address}(length:${fn:length(param.address)})
</td>
</tr>
</table>
</body>
</html>

运行效果

可以使用如下URL路径访问test1.jsp页面:
http://localhost:8080/app10a/test1.jsp
输入一个带空格的单词,无论是前面、后面,还是在单词之间,然后点击提交。接下来,在显示器上你将看到这些输入单词都被修正过来。如下图所示:

10.2 Servlet装饰类

Servlet装饰类源自于4个实现类,它很少被使用,但是十分强大:ServletRequestWrapperServletResponseWrapper以及HttpServletRequestWrapperHttpServletResponseWrapper
这几个装饰类非常便于使用,因为它提供了每个方法的默认实现:
ServletRequest封闭的配置方法。通过继承ServletRequestWrapper,只需要实现你需要变更的方法就可以了。如果不用ServletRequestWrapper,则需要继承ServletRequest并实现ServletRequest中所有的方法。

装饰器设计模式中ServletRequestWrapper的类图如下图所示。Servlet容器在每次Servlet服务调用时创建ServletRequestContainerImpl直接继承ServletRequestWrapper就可以装饰ServletRequest
这里有一张图片

10.1 装饰器模式

装饰器模式

装饰器模式,顾名思义,就是对已经存在的某些类进行装饰,以此来扩展一些功能

即使你没有该对象的源代码或者该对象标识为final
Decorator模式适用于无法继承该类(例如,对象的实现类使用final标识)或者无法创建该类的实例但从另外的系统中可以取得该类的实现的情况。例如,Servlet容器方法。只有一种方法可以修改ServletRequest或者ServletResponse行为,即在另外的对象中封装该实例。
唯一的限制是,
修饰对象必须继承一个接口
,然后实现接口以封装这些方法。

Decorator模式的URL类图

UML类图如下图所示。
这里有一张图片
图中的类图说明了一个Component接口以及它的实现类ComponentImplComponent接口定义了名为A的方法。为了修饰ComponentImpl的实例,需要创建一个Decorator类,并实现Component接口,然后在子类中扩展Decorator的新行为。在类图中DecoratorA就是Decorator的一个子类。每个Decorator实例需要包含Component的一个实例Decorator类代码如下(注意在构造函数中获取了Component的实例,这意味着创建Decorator对象只能传入Component的实例):

实例

Decorator.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//实现Component,以修饰Component
public class Decorator implements Component {
//声明一个Component实例的引用
private Component decorated;
// 构造函数中传入Component的实例
public Decorator(Component component) {
this.decorated = component;
}
//装饰原有的methodA方法,拓展功能
@Override
public void methodA(args) {
//做一些其他的工作
decorated.methodA(args);
//做一些其他的工作
}
//装饰原有的methodA方法,拓展功能
@Override
public void methodB(args) {
//做一些其他的工作
decorated.methodB(args)
//做一些其他的工作
}
}

Decorator类中,有修饰的方法就是可能在子类中需要修改行为的方法,在子类中不需要修饰的方法可以不需要实现。所有的方法,无论是否需要修饰,都叫作Component中的配对方法。Decorator是一个非常简单的类,便于提供每个方法的默认实现。修改行为在它的子类中。
需要牢记一点,**Decorator类及被修饰对象的类需要实现相同的接口为了实现Decorator,可以在Decorator中封装修饰对象,并把Decorator作为Component的一个实现**。任何Component的实现都可以在Decorator中注入。事实上,你可以把一个修饰的对象传入另一个修饰的对象,以实现双重的修饰。

参考资料

https://www.cnblogs.com/jzb-blog/p/6717349.html

第10章修饰Requests及Responses概述

Servlet API包含4个可修饰的类,用于改变ServletRequest以及Servlet Response。这种修饰允许修改ServletRequest以及ServletResponse或者HTTP中的等价类(即HttpServletRequestHttpServletResponse)中的任务方法。这种修饰遵循Decorator模式或者Wrapper模式,因此在使用修饰前,需要了解一下该模式的内容。

本章从解释Decorator模式开始,说明如何通过修饰HttpServletRequest来修改HttpServletRequest对象的行为。该技术同样适用于修饰HttpServletResponse对象。

9.7 小结

本章介绍了Filter API的相关内容,如Filter接口、FilterConfig接口、FilterChaing接口。通过本章内容,读者能够掌握如何实现一个Filter接口,并且通过@WebFilter的注解或者部署描述来配置它。
每个Filter仅有一个实现,因此如果需要保持或者改变Filter实现中的状态,就要考虑到线程安全问题。最后一个Filter的例子中,有关于该问题的处理方法。

9.6 Filter的触发顺序

**如果多个Filter应用于同一个资源,Filter的触发顺序将变得非常重要,这时就需要使用部署描述来管理Filter**:指定哪个Filter先被触发。例如:Filter 1需要在Filter 2前被触发,那么在部署描述中,Filter 1需要配置在Filter 2之前:

1
2
3
4
5
6
7
8
9
10
11
12
<filter>
<filter-name>Filter1</filter-name>
<filter-class>
the fully-qualified name of the filter class
</filter-class>
</filter>
<filter>
<filter-name>Filter2</filter-name>
<filter-class>
the fully-qualified name of the filter class
</filter-class>
</filter>

通过部署描述之外的配置来指定Filter触发的顺序是不可能的。第13章将会有更多部署描述的说明。

9.5 示例3:下载计数Filter

本例子中,下载计数Filter将会示范如何Filter中计算资源下载的次数。这个示例特别有用,它将会得到文档、音频文件的受欢迎程度。作为简单的示例,这里将数值保存在属性文件中,而不保存在数据库中。其中资源的ULR路径将作为属性名保存在属性文件中。
因为我们把值保存在属性文件中,并且**Filter可以被多线程访问,因此涉及线程安全问题。用户访问一个资源时,Filter需要读取相应的属性值加1,然后保存该值。如果第二个用户在第一个线程完成前同时访问该资源,将会发生什么呢?计算值出错。在本例中,读写的同步锁并不是一个好的解决这个问题的方法,因为它会导致扩展性问题。
本示例中,解决这个线程安全问题是通过Queue以及Executor。如果不熟悉这两个Java类型的话,请看第18章”多线程及线程安全”
简而言之,
进来的Request请求将会保存在单线程Executor的队列中**。替换这个任务十分方便,因为这是一个异步的方法,因此你不需要等待该任务结束。Executor一次从队列中获取一个对象,然后做相应属性值的增加。由于Executor只在一个线程中使用,因此可以消除多个线程同时访问一个属性文件的影响。

DownloadCounterFilter.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
package filter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Properties;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
@WebFilter(
filterName = "DownloadCounterFilter",
urlPatterns={"/*"}
)
public class DownloadCounterFilter
implements
Filter
{
ExecutorService executorService =
Executors.newSingleThreadExecutor();
Properties downloadLog;
File logFile;
@Override
public void init(FilterConfig filterConfig)
throws ServletException
{
System.out.println("DownloadCounterFilter");
//获取项目所在的根目录
String appPath =
filterConfig
.getServletContext()
.getRealPath("/");
//创建日志文件
logFile = new File(appPath, "downloadLog.txt");
if(!logFile.exists()) {
try {
//创建日志文件
logFile.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
}
//创建配置文件
downloadLog = new Properties();
try {
downloadLog
.load(new FileReader(logFile));
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void destroy()
{
if(logFile.exists())
{
//删除日志文件
logFile.delete();
}
executorService.shutdown();
}
@Override
public void doFilter(ServletRequest request,
ServletResponse response, FilterChain filterChain)
throws IOException, ServletException
{
HttpServletRequest httpServletRequest =
(HttpServletRequest) request;
//获取请求的uri
final String uri = httpServletRequest.getRequestURI();
executorService.execute(new Runnable() {
@Override
public void run() {
//读取配置文件该URL中对应的值
String property = downloadLog.getProperty(uri);
//如果没有读到值,这说明这是第一次访问
if (property == null) {
//该url的值设置为1
downloadLog.setProperty(uri, "1");
} else {
int count = 0;
try {
count = Integer.parseInt(property);
} catch (NumberFormatException e) {
// silent
}
//值加一
count++;
//更新配置文件中uri的值
downloadLog.setProperty(uri,
Integer.toString(count));
}
try {
//保存到本地配置文件中
downloadLog
.store(new FileWriter(logFile), "");
} catch (IOException e) {
}
}
});
filterChain.doFilter(request, response);
}
}

代码详解

init方法

如果在当前应用的工作目录中不存在downloadLog.txt文件,这个Filterinit方法就会创建它:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//获取项目所在的根目录
String appPath =
filterConfig
.getServletContext()
.getRealPath("/");
//创建日志文件
logFile = new File(appPath, "downloadLog.txt");
if(!logFile.exists()) {
try {
//创建日志文件
logFile.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
}

接着创建Properties对象,并读取该文件:

1
2
3
4
5
6
7
//创建配置文件
downloadLog = new Properties();
try {
downloadLog.load(new FileReader(logFile));
} catch (IOException e) {
e.printStackTrace();
}

注意,Filter的实现类中引用到了ExecutorServiceExecutor的子类):

1
2
ExecutorService executorService = 
Executors.newSingleThreadExecutor();

destroy方法

且当Filter销毁时,会调用ExecutorServiceshutdown方法:

1
2
3
public void destroy() {
executorService.shutdown();
}

doFilter方法

FilterdoFilter实现中大量地使用到这个Job。每次URL请求都会调用到ExecutorServiceexecute方法,然后才调用FilterChaing.doFilter()。该任务的execute实现非常好理解:它将URL作为一个属性名,从Properties实例中获取该属性的值,然后加1,并调用flush方法写回到指定的日志文件中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public void run() {
//读取配置文件该URL中对应的值
String property = downloadLog.getProperty(uri);
//如果没有读到值,这说明这是第一次访问
if (property == null) {
//该url的值设置为1
downloadLog.setProperty(uri, "1");
} else {
int count = 0;
try {
count = Integer.parseInt(property);
} catch (NumberFormatException e) {
// silent
}
//值加一
count++;
//更新配置文件中uri的值
downloadLog.setProperty(uri,
Integer.toString(count));
}
try {
//保存到本地配置文件中
downloadLog
.store(new FileWriter(logFile), "");
} catch (IOException e) {
}
}

这个FilterurlPatterns设置为*,即过滤所有URL,实际应用中可以修改为过滤某些特定的文件,如:只过滤pdf文件

运行效果

访问项目中的多个jsp页面,然后多刷新几次,然后打开打开downloadLog.txt文件(在Tomcat中的路径E:\apache-tomcat-8.5.35\webapps\app09a\downloadLog.txt)可以看到效果,如下图所示:
这里有一张图片

9.4 示例2:图像文件保护Filter

项目描述

本例中的图像文件保护Filter用于防止用户在浏览器地址栏中直接输入图像的URL路径来下载图像应用中只显示通过<img/>标签链接的图片

原理

Filter的实现原理是检查HTTP Headerreferer值。如果该值为null,就意味着当前的请求中没有referer值,即当前的请求是直接通过输入URL来访问该资源的。如果资源的Header值为非空,将返回Request语法的原始页面作为referer值。

ImageProtectorFilter.java

Filter实现类ImageProtectorFilter.java如下所示,从WebFilterAnnotation中,可以看到该Filter应用于所有的.png.jpg.gif文件后缀。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
package filter;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
@WebFilter(
filterName = "ImageProtetorFilter",
urlPatterns= {"*.png","*.jpg","*.gif"}
)
public class ImageProtectorFilter
implements
Filter
{
@Override
public void init(FilterConfig filterConfig)
throws ServletException
{}
@Override
public void destroy()
{}
@Override
public void doFilter(ServletRequest request,
ServletResponse response,
FilterChain filterChain)
throws IOException,ServletException
{
System.out.println("ImageProtectorFilter");
HttpServletRequest httpServletRequest =
(HttpServletRequest) request;
String referrer =
httpServletRequest.getHeader("referer");
System.out.println("referrer:" + referrer);
if(referrer != null) {
filterChain.doFilter(request, response);
}
else {
throw new ServletException(
"Image not available");
}
}
}

代码详解

这里并没有initdestroy方法。其中doFilter方法读取到Header中的referer值,要确认是要继续处理这个资源还是给个异常:

1
2
3
4
5
6
7
String referrer = httpServletRequest.getHeader("referer");
System.out.println("referrer:" + referrer);
if (referrer != null) {
filterChain.doFilter(request, response);
} else {
throw new ServletException("Image not available");
}

image.jsp

1
2
avatar.png:<br>
<img alt='avatar.png' src='image/avatar.png' />

图片资源

WebContent目录下创建image目录,在image目录中粘贴如下图片(avatar.png):
这里有一张图片
项目结果如下图所示:
这里有一张图片

运行效果

浏览器地址栏中输入地址访问图片

测试该Filter,可以在浏览器地址栏中输入如下ULR路径,尝试访问logo.png图像:

1
http://localhost:8080/app09a/image/avatar.png

将会得到“Image not available”的错误提示。如下图所示:
这里有一张图片

jsp页面中图片的显示效果

接下来,通过image.jsp的页面来访问该图像:
http://localhost:8080/app09a/image.jsp
可以访问到该图像了,如下图所示:
这里有一张图片

这种方法生效的原因是image.jsp页面中包含了通知浏览器下载图像的链接:

1
<img alt='avatar.png' src='image/avatar.png' />

当浏览器通过该链接获取图像资源时,它也将该页面的URL(本示例中为http://localhost:8080/app09a/image/avatar.png)作为Headerreferer值传到服务中。可以在浏览器中按下F12查看该图片的请求头中的Referer信息,如下图所示:
这里有一张图片

9.3示例1:日志Filter

实例简介

作为第1个例子,将做一个简单的Filter

  • app09a的应用中把Request请求的URL记录到日志文本文件中
  • 日志文本文件名通过Filter的初始化参数来配置。
    • 此外,日志的每条记录都会有一个前缀,该前缀也由Filter初始化参数来定义。

通过日志文件,可以获得许多有用的信息,例如在应用中哪些资源访问最频繁;Web站点在一天中的哪个时间段访问量最多。

这个Filter的类名叫LoggingFilter,一般情况下,Filter的类名都以*Filter结尾。

LoggingFilter类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
package filter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.PrintWriter;
import java.text.DateFormat;
import java.util.Date;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.annotation.WebInitParam;
import javax.servlet.http.HttpServletRequest;
@WebFilter(
filterName = "LoggingFilter",
urlPatterns ={"/*"},
initParams = {
@WebInitParam(
name = "logFileName",
value = "log.txt"
),
@WebInitParam(
name = "prefix",
value = "URI: "
)
}
)
public class LoggingFilter
implements
Filter
{
private PrintWriter logger;
private String prefix;
static DateFormat format=
DateFormat.getDateTimeInstance(
DateFormat.LONG,DateFormat.LONG);
@Override
public void init(FilterConfig filterConfig)
throws ServletException
{
//获取初始化参数
prefix = filterConfig.getInitParameter("prefix");
//获取初始化参数
String logFileName =
filterConfig.getInitParameter("logFileName");
String appPath =
filterConfig.getServletContext().getRealPath("/");
// without path info in logFileName, the log file will be
// created in $TOMCAT_HOME/bin
System.out.println(
"LoggingFilter的init()方法被执行"
+ "--->logFileName:"
+ logFileName);
try {
File file=new File(appPath, logFileName);
logger = new PrintWriter(new File(appPath,
logFileName));
System.out.println("日志文件绝对路径:"+
file.getAbsolutePath());
} catch (FileNotFoundException e) {
e.printStackTrace();
throw new ServletException(e.getMessage());
}
}
@Override
public void destroy()
{
System.out.println(
"LoggingFilter的destroy()方法被执行");
if(logger != null) {
logger.close();
}
}
@Override
public void doFilter(ServletRequest request,
ServletResponse response,FilterChain filterChain)
throws IOException,ServletException
{
System.out.println(
"LoggingFilter的doFilter()方法被执行");
HttpServletRequest httpServletRequest =
(HttpServletRequest) request;
logger.println(
format.format(new Date()) + " "
+prefix + httpServletRequest.getRequestURI());
logger.flush();
filterChain.doFilter(request, response);
}
}

下面来仔细分析一下该Filter类。

成员变量

首先,该Filter的类实现了Filter的接口并声明两个成员变量:PrintWriter类型的loggerString类型的prefix

1
2
private PrintWriter logger;
private String prefix:

其中PrintWriter用于记录日志到文本文件,prefix的字符串用于每条日志的前缀。

WebFilter注解

Filter的类使用了@WebFilterAnnotation,将两个参数(logFilteNameprefix)传入到该Filter中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@WebFilter(
filterName = "LoggingFilter",
urlPatterns ={"/ *"},
initParams = {
@WebInitParam(
name = "logFileName",
value = "log.txt"
),
@WebInitParam(
name = "prefix",
value = "URI: "
)
}
)

init方法

获取初始化参数

Filterinit方法中,通过形式参数FilterConfig对象的getInitParameter方法来获取prefixgetFileName的初始化参数。其中把prefix参数中赋给了类变量prefixlogFileName则用于创建一个PrintWriter

1
2
3
prefix = filterConfig.getInitParameter("prefix");
String logFileName = filterConfig
.getInitParameter("logFileName");

工作目录

如果Servlet/JSP应用是通过Servlet/JSP容器启动的,那么当前应用的工作目录是当前JDK所在的目录。
如果是在Tomcat中,该目录是Tomcat的安装目录。
在应用中创建日志文件,可以通过ServletContext.getRealPath来获取工作目录,结合应用工作目录以及初始化参数中的logFilterNmae,就可以得到日志文件的绝对路径:

1
2
3
4
5
6
7
8
9
10
11
String appPath = 
filterConfig.getServletContext().getRealPath("/");
// without path info in logFileName, the log file will be
// created in $TOMCAT_HOME/bin
try {
logger = new PrintWriter(new File(appPath,
logFileName));
} catch (FileNotFoundException e) {
e.printStackTrace();
throw new ServletException(e.getMessage());
}

Filterinit方法被执行时,日志文件就会创建出来。如果在应用的工作目录中该文件已经存在,那么该日志文件的内容将会被覆盖。

destroy方法

当应用关闭时,PrintWriter需要被关闭。因此在Filterdestroy方法中,需要:

1
2
3
if (logger != null) {
logger.close();
}

doFilter方法

FilterdoFilter实现中记录着所有从ServletRequestHttpServletRequestRequest,并调用了它的getRequestURI方法,该方法的返回值将记录通过PrintWriterprintln方法记录下来:

1
2
3
4
HttpServletRequest httpServletRequest = 
(HttpServletRequest) request;
logger.println(new Date() + " " + prefix
+ httpServletRequest.getRequestURI());

每条记录都有一个时间戳以及前缀,这样可以很方便地标识每条记录。接下来 FilterdoFilter实现调用PrintWriterflush方法以及FilterChain.doFilter,以唤起资源的调用:

1
2
logger.flush();
filterChain.doFilter(request, response);

如果使用TomcatFilter的初始化并不会等到第一个Request请求时才触发进行。这点可以在控制台中打印出来的logFileName参数值中可以看到。

test.jsp

1
2
3
4
5
6
7
8
9
10
11
12
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>测试</title>
</head>
<body>
<h2>测试</h2>
</body>
</html>

运行效果

启动项目

启动app09a项目,可以看到控制台部分输出如下图所示:
这里有一张图片
从控制台的输出我们可以知道:

  • 日志文件的绝对路径为:E:\apache-tomcat-8.5.35\webapps\app09a\log.txt.
  • 在项目启动阶段Filter初始化就开始了。

访问JSP页面

然后通过URL调用test.jsp页面
http://localhost:8080/app09a/test.jsp
此时,容器会调用doFilter方法,该方法控制台输出如下:
这里有一张图片

查看日志文件

这时候可以打开日志文件:E:\apache-tomcat-8.5.35\webapps\app09a\log.txt查看日志输出,如下图所示:
这里有一张图片

doFilter的执行次数和JSP页面的访问次数的关系

在该过滤器中,我们设置的过滤规则为:

1
urlPatterns ={"/*"}

这表示过滤所有的JSP页面,所以每访问一次JSP页面,过滤器就会执行一次.
再刷新两次test.jsp页面,则doFilter方法也会再执行两次,日志文件中也会多出两条记录,如下图所示:
这里有一张图片
这里有一张图片