主要内容:
- filter的部署
- 包装设计模式对request和response进行包装增强
一、Filter的部署-注册Filter
<filter-name>
:指定过滤器的名字,内容不能为空。<filter-class>
: 指定过滤器的完整的限定类名<init-param>
指定初始化参数,其子元素<param-name>
指定参数的名字,<param-value>
指定参数的值。可以使用FilterConfig
接口对象来访问初始化参数。-
<filter-mapping>
设置一个Filter所负责拦截的资源。一个Filter拦截的资源可以通过两种方式来指定:Servlet名称和资源访问的请求路径。-
<filter-name>
子元素用于设置Filter注册名称,和上面一样。 -
<url-pattern>
设置Filter所拦截的请求路径;/表示拦截所有,.jsp表示拦截jsp文件。等等。 -
<dispatcher>
指定过滤器所拦截的资源被Servlet容器调用的方式,可以是REQUEST,INCLUDE,FORWARD,ERROR
之一(必须是大写),默认是REQUEST
。用户可以设置多个<dispatcher>
子元素来指定Filter对资源的多种调用方式进行拦截。
-
-
<dispatcher>
子元素可以设置的值及其意义-
REQUEST
:当用户直接访问页面时,web容器将会调用过滤器。如果目标资源是通过RequestDispatcher
的include或forward方法访问时,那么该过滤器就不会被调用。 -
INCLUDE
:如果目标资源是通过RequestDispatcher
的include方法访问时,那么该过滤器将被调用,除此之外,该过滤器将不会被调用。 -
FORWARD
:如果目标资源是通过RequestDispatcher
的forward方法访问时,那么该过滤器将被调用,除此之外,该过滤器将不会被调用。 -
ERROR
:如果目标资源是通过声明式异常处理机制调用时,那么该过滤器将被调用。除此之外,过滤器不会被调用。
-
示例:
FilterDemo4.java
package cn.itcast.web.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;
public class FilterDemo4 implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
System.out.println("hahah");
}
@Override
public void destroy() {
}
}
配置:
<filter>
<filter-name>FilterDemo4</filter-name>
<filter-class>cn.itcast.web.filter.FilterDemo4</filter-class>
</filter>
<filter-mapping>
<filter-name>FilterDemo4</filter-name>
<url-pattern>/*</url-pattern>
<dispatcher>ERROR</dispatcher>
</filter-mapping>
<error-page>
<exception-type>java.lang.ArithmeticException</exception-type>
<location>/2.jsp</location>
</error-page>
1.jsp
<body>
<%
int x = 1/0;
%>
</body>
2.jsp
<body>
error page
</body>
说明:
- 1.本来我们是在
1.jsp
中配置errorPage="/2.jsp"
,但是这样配置不起作用。于是我们在web.xml
中进行配置。试验时我们访问1.jsp
,那么就会被过滤器拦截下来,不会到达2.jsp
。 - 2.在以后的开发中我们经常要拦截forward转发的资源,注意在配置文件中进行配置,不然是不会起作用的。在上个例子中我们可以看到。
二、filter高级开发
由于开发人员在Filter中可以得到代表用户请求和响应的request、response对象,因此在编程中可以使用Decorator(装饰器)模式对这些对象进行包装,再把包装后的对象传给目标资源,从而实现一些特殊需求。
注意:有四种方式访问web资源,正常的通过浏览器直接访问(request方式)、forward方式、include方式和error方式。
2.1回顾包装开发模式
之前我们在笔记20中讲到过包装开发模式,这里再次回顾一下。使用包装设计模式对BufferedReader类进行包装增强。
BufferedReaderWrapper.java
package cn.itcast.demo;
import java.io.BufferedReader;
import java.io.IOException;
//使用包装设计模式对BufferedReader类进行包装增强
/*
* 1.实现与被增强对象相同的接口,如果接口方法太多,也可以继承一个类
* 2.定义一个变量记住被增强对象
* 3.定义一个构造器,接收被增强对象
* 4.覆盖需要增强的方法
* 5.对于不想增强的方法,直接调用被增强对象的方法
*
* */
import java.io.Reader;
public class BufferedReaderWrapper extends BufferedReader {
private BufferedReader br;
private int linenum = 1;
public BufferedReaderWrapper(BufferedReader br) {
super(br);
// 子类在使用构造函数的时候会调用父类的构造函数,但是
// 这里不知道调用父类哪个构造函数,于是就调用无参构造
// 函数,但是父类又没有无参构造函数,这样就会报错,所
// 以这里我们要指定调用父类哪个构造函数
this.br = br;
}
@Override
public String readLine() throws IOException {// 增强此方法
String line = br.readLine();
if (line == null) {
return line;
}
return linenum++ + ":" + line;
}
}
测试:TestBufferedReaderWrapper.java
package cn.itcast.demo;
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
public class TestBufferedReaderWrapper {
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new FileReader("src/cn/itcast/demo/BufferedReaderWrapper.java"));
BufferedReaderWrapper wrapper = new BufferedReaderWrapper(br);
/*String line = null;
while((line = wrapper.readLine()) != null ){
System.out.println(line);
}*/
FileWriter fw = new FileWriter("D:\\1.java");
String line = null;
while((line = wrapper.readLine()) != null ){
fw.write(line + "\r\n");//注意要换行
}
fw.close();
wrapper.close();
br.close();
}
}
说明:BufferedReader类中的readLine方法可以读取一行文本,这里我们想让readLine读取一行文本时还在本行最前面加上行号,于是我们使用包装设计模式对此方法进行包装。注意:这里当此方法读到空行的时候返回的是"",而不是null。
记住:能用包装设计模式就不要用子类的方式
2.2Decorator(包装)设计模式
-
当某个对象的方法不适应业务需求时,通常有两种方式可以对方法进行增强:
- 编写子类,覆盖需要增强的方法
- 使用Decorator设计模式对方法进行增强
- 使用动态代理(这里先不讲)
-
在实际开发中遇到需要增强对象的方法时,到底选择用哪种方式
- 没有具体的定式,不过有一种情况下,必须使用Decorator设计模式,即被增强对象,开发人员只能得到它的对象,无法得到它的class文件。
- 比如request、response对象,开发人员之所以在Servlet中能通过sun公司定义的
HttpServletRequest\HttpServletResponse
接口去操作这些对象,是因为tomcat服务器厂商编写了request、response接口的实现类。Web服务器在调用Servlet时,会用这些接口的实现类创建出对象,然后传递给Servlet程序。 - 此种情况下,由于开发人员根本不知道服务器厂商编写的request、response接口的实现类是哪个,在程序中只能拿到其提供的对象,因此就只能采用Decorator设计模式对这些对象进行增强。
Decorator设计模式的实现
1.首先看需要被增强对象继承了什么接口或父类,编写一个类也去继承这些接口或父类。
2.在类中定义一个变量,变量类型即需要增强对象类型。
3.在类中定义一个构造函数,接收需要增强的对象。
4.覆盖需要增强的方法,编写增强的代码。
使用此设计模式为BufferedReader类的readLine方法添加行号的功能。在上面的例子中我们可以看到。
三、对request对象的增强(工程day18_2
)
- ServletAPI中提供了一个request对象的Decorator设计模式的默认实现类
HttpServletRequestWrapper
(HttpServletRequestWrapper
类实现了request接口中的所有方法,但这些方法的内部实现都是仅仅调用了一下所包装的request对象的对应方法)以避免用户在对request对象进行增强时需要实现request接口中的所有方法(这样太麻烦)。 - 使用Decorator模式包装request对象,完全解决get、post请求方式下的乱码问题(前面有过这样一个例子,但是那个例子中只能解决post方式下的乱码问题)。
3.1 包装request对象,解决乱码问题
过滤器:CharacterEncodingFilter.java
public class CharacterEncodingFilter 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;
// 这里我们先解决post方式的乱码问题
request.setCharacterEncoding("UTF-8");
response.setCharacterEncoding("UTF-8");
response.setContentType("text/html;charset=UTF-8");
// 解决get方式的乱码问题
MyCharacterEncodingRequest requestWrapper = new MyCharacterEncodingRequest(
request);
chain.doFilter(requestWrapper, response);
}
@Override
public void destroy() {
}
}
class MyCharacterEncodingRequest extends HttpServletRequestWrapper {
// 这里我们使用一个变量记住Servlet传递的request对象。
private HttpServletRequest request;
public MyCharacterEncodingRequest(HttpServletRequest request) {
super(request);
this.request = request;
}
@Override
// 增强此方法,此方法得到表单提交的数据
public String getParameter(String name) {
String value = this.request.getParameter(name);
if (value == null) {// 如果为空直接返回空即可
return null;
}
if (!this.request.getMethod().equalsIgnoreCase("get")) {
// 如果不是get方式则没必要转换
return value;
}
try {
/*
* value = new
* String(value.getBytes("ISO8859-1"),"UTF-8");//手工转换,不要写死
*/
value = new String(value.getBytes("ISO-8859-1"),
this.request.getCharacterEncoding());// 和Request设置的编码一致
return value;// 这里使用ISO-8859-1没有解决乱码,这里浏览器
// 提交的本就是UTF-8编码,不需要转换
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
说明:由于使用request.setCharacterEncoding("UTF-8");
方式对get方式提交的数据无效,所以之前那个过滤器只能解决post方式提交的数据乱码问题。既然这种方式不能解决get方式提交的数据的乱码问题,那么我们可以将HttpServletRequest包装之后再给用户使用。这里我们主要是对其方法getParameter
进行增强。这样就解决了get方式提交的乱码问题。
3.2 包装request对象,实现html标签转义功能
(E:\apache\apache-tomcat-8.0.28-src\webapps\examples\WEB-INF\classes\util\HTMLFilter.java
提供相应的例子)
HtmlFilter .java
public class HtmlFilter 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;
MyHtmlRequest myHtmlRequest = new MyHtmlRequest(request);
chain.doFilter(myHtmlRequest, response);
}
@Override
public void destroy() {
}
}
class MyHtmlRequest extends HttpServletRequestWrapper{
private HttpServletRequest request;
public MyHtmlRequest(HttpServletRequest request) {
super(request);
this.request = request;
}
@Override
public String getParameter(String name) {
String value = this.request.getParameter(name);
if(value == null){
return null;
}
return filter(value);
}
public static String filter(String message) {
if (message == null)
return (null);
char content[] = new char[message.length()];
message.getChars(0, message.length(), content, 0);
StringBuilder result = new StringBuilder(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());
}
}
3.3 包装request对象,实现对脏话进行过滤
DirtyFilter.java
public class DirtyFilter 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;
DirtyRequest dirtyRequest = new DirtyRequest(request);
chain.doFilter(dirtyRequest, response);
}
@Override
public void destroy() {
}
}
class DirtyRequest extends HttpServletRequestWrapper{
private List<String> dirtyWords = Arrays.asList("sb","畜生");
private HttpServletRequest request ;
public DirtyRequest(HttpServletRequest request) {
super(request);
this.request = request;
}
@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)){
value = value.replace(dirtyWord, "***");
}
}
return value;
}
}
四、包装response对象
4.1 包装response对象,实现压缩响应
GzipFilter.java
package cn.itcast.web.filter;
//解决全站的压缩问题,对Response进行增强,但是这个过滤器只是解决压缩字符,需要把数据都写到内存中去,如果是下载一个大文件,内存可能会崩,这里我们可以在
//配置文件中设定只对字符压缩有效,这拦截jsp,js,css,html
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.util.zip.GZIPOutputStream;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.WriteListener;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
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;
BufferResponse myResponse = new BufferResponse(response);
chain.doFilter(request, myResponse);
byte out[] = myResponse.getBuffer();//先获得数据
ByteArrayOutputStream bout = new ByteArrayOutputStream();
GZIPOutputStream gout = new GZIPOutputStream(bout);//进行压缩
gout.write(out);
gout.close();//一定要关闭,这样数据才会从缓存中写入到底层流中去
byte gzip[] = bout.toByteArray();//从底层流中取得数据
response.setHeader("content-encoding", "gzip");//这里需要告诉浏览器这是一个压缩数据
response.setContentLength(gzip.length);
response.getOutputStream().write(gzip);//写出到浏览器
}
@Override
public void destroy() {}
}
/*OutputStream out = response.getOutputStream();
out.write("aaaaaa".getBytes());
*之后servlet在使用Response的时候其实是使用我们自己定义的Response,而调用的getOutputStream方法也是我们自己定义的,这个方法返回的是
*MyServletOutputStream,然后调用write方法也是调用我们自己定义的write方法,此方法是将数据写到ByteArrayOutputStream底层流中。
*
*/
class BufferResponse extends HttpServletResponseWrapper{
private HttpServletResponse response;
private ByteArrayOutputStream bout = new ByteArrayOutputStream();//字节流
private PrintWriter pw;
public BufferResponse(HttpServletResponse response) {
super(response);
this.response = response;
}
@Override
public ServletOutputStream getOutputStream() throws IOException {
//这里我们对此方法进行了增强,不管是图片还是文本等数据都会进行压缩,但是如果直接访问jsp却不会,因为jsp一般是调用getWriter
//方法,所以这里我们需要对getWriter方法进行增强
return new MyServletOutputStream(bout);
}
@Override
public PrintWriter getWriter() throws IOException {
/*return new PrintWriter(bout);//因为PrintWriter有接受一个底层流的构造函数,所以这里我们不需要重写一个,但是这个方法也是一个包装类
//这个类当缓存没有写满的时候是不会讲数据写到底层流中去,所以这里我们需要强制关闭此类*/
//pw = new PrintWriter(bout);//jsp中的汉字是一个字符流,这里会将其先转换为字节流,查的码表是gb2312的码表,但是我们设置的码表是UTF-8
//而PrintWriter有一个接受一个字符流的方法,而字符流就会有设置码表的方法,而OutputStreamWriter是字符流到字节流的一个转换流,里面就可以指定码表
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 arg0) throws IOException {
this.bout.write(arg0);
}
@Override
public boolean isReady() {
return false;
}
@Override
public void setWriteListener(WriteListener listener) {}
}
说明:
- 这个包装类的实现有点难理解,这里我们详细说明一下。这里是实现数据的压缩之后再输出给浏览器,而我们完全可以在servlet中先从缓存中拿到数据压缩之后再输出,但是那样的话每个需要压缩资源的servlet都需要编写重复的代码,所以这里我们使用过滤器进行简化。
- 首先服务器将资源写给浏览器的时候(注意这里和request过滤器的方向是反的),会被这个浏览拦截到。拦截到之后我们对response进行增强。
- 而一般会调用
getOutputStream
方法和getWriter
方法向浏览器中写数据,于是这里我们对这两个方法进行增强。 - 方法
getOutputStream
会返回一个ServletOutputStream
流,我们需要增强,我们要让此方法返回一个我们自己定义的一个流,于是对此类也进行包装。 - 方法
getWriter
中我们将自己定义的流传递给PrintWriter
方法。 - 我们将数据压缩之后存入到底层流中,之后用户在调用
getOutputStream
方法和getWriter
时拿到的数据就是我们压缩之后的数据。 - 其实整个过程就是当用户调用方法向浏览器输出数据的时候我们将response的相关方法进行增强(实现数据压缩)之后再去调用真正response的方法进行输出,这样就可以实现压缩。
配置:
<filter>
<filter-name>GzipFilter</filter-name>
<filter-class>cn.itcast.web.filter.GzipFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>GzipFilter</filter-name>
<url-pattern>*.jsp</url-pattern>
<dispatcher>FORWARD</dispatcher>
<dispatcher>REQUEST</dispatcher>
</filter-mapping>
<filter-mapping>
<filter-name>GzipFilter</filter-name>
<url-pattern>*.js</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>GzipFilter</filter-name>
<url-pattern>*.css</url-pattern>
</filter-mapping>
注意:这里我们需要配置FORWARD和REQUEST,用于拦截forward请求。因为大多数时候我们都是转发过来的请求。
4.2 包装response对象,缓存数据到内存
CacheFiltet.java
public class CacheFilter implements Filter {
//实际开发中我们可以使用一些专业的缓存工具
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){
response.getOutputStream().write(b);
return ;
}
//4.如果没有,让目标资源执行,并捕获目标资源的输出
BufferResponse1 myresResponse1 = new BufferResponse1(response);
chain.doFilter(request, myresResponse1);
byte out[] = myresResponse1.getBuffer();
//5.把资源的数据以用户请求的uri的关键字保存到缓存中
map.put(uri, out);
//6.把数据送给浏览器
response.getOutputStream().write(out);
}
@Override
public void destroy() {}
}
class BufferResponse1 extends HttpServletResponseWrapper{
private HttpServletResponse response;
private ByteArrayOutputStream bout = new ByteArrayOutputStream();//字节流
private PrintWriter pw;
public BufferResponse1(HttpServletResponse response) {
super(response);
this.response = response;
}
@Override
public ServletOutputStream getOutputStream() throws IOException {
return new MyServletOutputStream1(bout);
}
@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不为空,则我们需要关闭一下,让其将数据从缓存写到底层流中去
pw.close();
}
if(bout != null){
bout.flush();
return bout.toByteArray();
}
return null;
}catch (Exception e) {
throw new RuntimeException(e);
}
}
}
class MyServletOutputStream1 extends ServletOutputStream{
private ByteArrayOutputStream bout ;
public MyServletOutputStream1(ByteArrayOutputStream bout) {
this.bout = bout;
}
@Override
public void write(int arg0) throws IOException {
//其实write方法有三种重载形式,但是其内部都是调用的这种形式,所以我们只需要重载这种形式即可
this.bout.write(arg0);
}
@Override
public boolean isReady() {
return false;
}
@Override
public void setWriteListener(WriteListener listener) {}
}
说明:
- 对于页面中很少更新的数据,例如商品分类,为避免每次都要从数据库查询分类数据,因此可以把分类数据缓存在内存或文件中,以此来减轻数据库压力,提高系统响应速度。相关书名直接看程序中的注释即可,这里不再细说。
五、动态代理
在java里,每个对象都有一个类与之对应。
现在要生成某一个对象的代理对象,这个代理对象也要通过一个类来生成,所以首先要编写用于生成代理对象的类。
-
如何编写生成代理对象的类,两个要素:
- 代理谁
- 如何生成代理对象
代理谁?
设计一个类变量,以及一个构造函数,记住代理类代理哪个对象。如何生成代理对象?
设计一个方法生成代理对象(在方法内编写代码生成代理对象是此处编程的难点)-
java提供了一个Proxy类,调用它的newInstance方法可以生成某个对象的代理对象,使用该方法生成代理对象时,需要三个参数:
- 1.生成代理对象使用哪个类装载器
- 2.生成哪个对象的代理对象,通过接口指定
- 3.生成的代理对象的方法里干什么事,由开发人员编写handler接口的实现来指定。
-
初学者必须理解(记住)
- Proxy类负责创建代理对象时,如果指定了handler(处理器),那么不管用户调用代理对象的什么方法,该方法都是调用处理器的invoke方法。
- 由于invoke方法被调用需要三个参数:代理对象、方法、方法的参数,因此不管代理对象哪个方法调用处理器的invoke方法,都必须把自己所在的对象、自己(调用invoke方法的方法)、方法的参数传递进来。
六、动态代理应用
在动态代理技术里,由于不管用户调用代理对象的什么方法,都是调用开发人员编写的处理器的invoke方法(这相当于invoke方法拦截到了代理对象的方法调用)。
并且,开发人员通过invoke方法的参数,才可以在拦截的同时,知道用户调用的是什么方法,因此利用这两个特征,就可以实现一些特殊需求。例如:拦截用户的访问请求,以检查用户是否有访问权限、动态为某个对象添加额外的功能。