过滤器(Filter)

一、过滤器概述

1、什么是过滤器?

   过滤器是JavaWeb的三大组件之一,它与Servlet相似,不过过滤器是用来拦截请求的,而不是处理请求的。

   当用户请求某个Servlet时,会先执行部署在这个请求上的Filter,如果Filter放行,那么会继续执行用户请求的Servlet;如果Filter不放行,那么就不会执行用户请求的的Filter。

   其实可以这样理解,当用户请求某个Servlet时,Tomcat会去执行注册在这个请求上的Filter,然后是否放行由Filter来决定。可以理解为,Filter来决定是否调用Servlet,当执行完Servlet的代码后,还会执行Filter后面的代码。

  

2、过滤器之HelloWord

   其实过滤器和Servlet很相似,写过滤器就是写一个类,实现Filter接口:

public class HelloFilter implements Filter{
   public void init(FilterConfig filterconfig) throws ServletException{}
   public void doFilter(ServletRequest resquest,ServletResponse response,FilterChain chain)throws IOException,ServletException{
      System.out.println("Hello Filter!");
   }
   public void destroy(){}
}

   第二步也和Servlet一样,在web.xml文件中部署Filter:

<filter>
   <filter-name>helloFilter</filter-name>
   <filter-class>cn.itcast.filter.HelloFilter</filter-class>
</filter>
<filter-mapping>
   <filter-name>helloFilter</filter-name>
   <url-pattern>/index.jsp</url-pattern>
</filter-mapping>

   当用户访问index.jsp页面时,会执行HelloFilter的doFilter()方法。在我们时实例中,index.jsp是不会被执行的,如果想要执行index.jsp页面,那么我们需要放行:

public void doFilter(ServletRequest request,ServletResponse response,FilterChain chain)throws IOException,ServletException{
   System.out.println("filter start...");
   chain.doFilter(request,response);
   System.out.println("filter end...");
}

  

二、过滤器详细

1、过滤器的生命周期

   我们已经学习过Servlet的声明周期,,那么Filter的生命周期就没什么难度了。

  • init(FilterConfig  filterConfig):在服务器启动时会创建Filter实例,并且每个类型的Filter只创建一个实例,从此不再创建。在创建完Filter实例后,会马上调用init()方法完成初始化工作,这个方法只会被执行一次;
  • doFilter(ServletRequest  req,ServletResponse  res,FilterChain chain):这个方法会在用户每次访问 " 目标资源(<url-pattern>index.jsp</url-pattern>) " 时执行,如果需要放行,那么要调用FilterChain对象的doFilter(ServletRequest  req,ServletResponse  res)方法,如果不调用FilterChain的doFilter()方法,那么目标资源无法执行;
  • destroy():服务器会在创建Filter对象之后,把Filter放到缓存中一直使用,通常不会被销毁。一般会在服务器关闭时销毁Filter对象,在销毁Filter对象之前,服务器会调用Filter对象的destroy()方法。

2、FilterConfig

   已经知道,Filter接口中的init()方法的参数类型是FilterConfig类型。它的功能与ServletConfig相似,与web.xml文件中的配置信息对应。下面是FilterConfig的功能介绍:

  • ServletContext  getServletContext():获取ServletContext的方法;
  • String  getFilterName():获取Filter的配置名称,与<filter-name>元素对应;
  • String  getInitParameter():获取Filter的初始化配置,与<init-param>元素对应;
  • Enumeration  getInitParameterNames():获取所有初始化参数的名称;

3、FilterChain

   doFilter()方法的参数中有一个类型为FilterChain的参数,它只有一个doFilter(ServletRequest  res,ServletResponse  res)方法。

   前面我们说doFilter()的放行,让请求流访问目标资源。这么说不严密,其实调用该方法的意思是,当前Filter放行了,不代表其他Filter也放行了。

   也就是说,一个目标资源上,可以部署多个过滤器,所以代表FilterChain类的doFilter()方法表示的是执行下一个过滤器的doFilter()方法,或者执行目标资源。

   如果当前过滤器是最后一个过滤器,那么调用doFilter()方法表示执行目标资源,   而不是最后一个过滤器,那么chain.doFilter()表示执行执行下一个过滤器的doFilter()方法。

4、多个过滤器执行顺序

   一个目标资源可以指向多个过滤器,过滤器的执行顺序是web.xml文件中的部署顺序:

<filter>
  	<filter-name>myFilter1[因为MyFilter1配置在前面,所以先执行MyFilter1的doFilter()方法。]</filter-name>
  	<filter-class>cn.itcast.filter.MyFilter1</filter-class>
</filter>
<filter-mapping>
  	<filter-name>myFilter1</filter-name>
  	<url-pattern>/index.jsp</url-pattern>
</filter-mapping>
<filter>
  	<filter-name>myFilter2</filter-name>
  	<filter-class>cn.itcast.filter.MyFilter2</filter-class>
</filter>
<filter-mapping>
  	<filter-name>myFilter2</filter-name>
  	<url-pattern>/index.jsp</url-pattern>
</filter-mapping>

public class MyFilter1 extends HttpFilter {
	public void doFilter(HttpServletRequest request, HttpServletResponse response,
			FilterChain chain) throws IOException, ServletException {
		System.out.println("filter1 start...");
		chain.doFilter(request, response);//放行,执行MyFilter2的doFilter()方法
		System.out.println("filter1 end...");
	}
}

<p><span style="color:#7f055;">public</span><span style="color:#000000;"> </span><span style="color:#7f055;">class</span><span style="color:#000000;"> MyFilter2 </span><span style="color:#7f055;">extends</span><span style="color:#000000;"> HttpFilter {</span></p><p><span style="color:#7f055;">        public</span><span style="color:#000000;"> </span><span style="color:#7f055;">void</span><span style="color:#000000;"> doFilter(HttpServletRequest request, HttpServletResponse response,</span></p><p><span style="color:#000000;">                        FilterChain chain) </span><span style="color:#7f055;">throws</span><span style="color:#000000;"> IOException, ServletException {</span></p><p><span style="color:#000000;">                System.</span><span style="color:#00c0;">out</span><span style="color:#000000;">.println(</span><span style="color:#2a0ff;">"filter2 start..."</span><span style="color:#000000;">);</span></p><p><span style="color:#000000;">                chain.doFilter(request, response);</span><span style="color:#3f7f5f;">//<span style="font-family:宋体;">放行,执行目标资源</span></span></p><p><span style="color:#000000;">                System.</span><span style="color:#00c0;">out</span><span style="color:#000000;">.println(</span><span style="color:#2a0ff;">"filter2 end..."</span><span style="color:#000000;">);</span></p><p><span style="color:#000000;">        }</span></p><p><span style="color:#000000;">}</span></p>

<body>
    This is my JSP page. <br>
    <h1>index.jsp</h1>
    <%System.out.println("index.jsp"); %>
  </body>

   当有用户访问index.jsp页面时,输出如下结果:

   filter1 start...

   filter2 start...

   index.jsp

   filter2 end...

   filter1 end...

5、四种拦截方式

   我们来做个测试,写一个过滤器,指定过滤的资源为b.jsp,我们在浏览器上直接访问b.jsp,会发现过滤器执行了。

   但是,我们在a.jsp中request.getRequestDispacher( " b.jsp " ).forward( request , response ) 时,就不会再执行过滤器了。

   也就是说,默认情况下,只能直接访问目标资源才会执行过滤器,而forward执行目标资源,不会后执行过滤器。

public class MyFilter extends HttpFilter {
	public void doFilter(HttpServletRequest request,
			HttpServletResponse response, FilterChain chain)
			throws IOException, ServletException {
		System.out.println("myfilter...");
		chain.doFilter(request, response);
	}
}	

<filter>
	<filter-name>myfilter</filter-name>
	<filter-class>cn.itcast.filter.MyFilter</filter-class>
</filter>
<filter-mapping>
	<filter-name>myfilter</filter-name>
	<url-pattern>/b.jsp</url-pattern>
</filter-mapping>

<body>
   <h1>b.jsp</h1>
</body>

<h1>a.jsp</h1>
    <%
    	request.getRequestDispatcher("/b.jsp").forward(request, response);
    %>
</body>


   其实过滤器有四种拦截方式,分别是:REQUEST、FORWARD、INCLUDE、ERROR 

  • REQUEST:直接访问目标资源时执行过滤器,包括在地址栏中直接访问、表单提交、超链接、重定向,只要在地址栏中可以看到目标资源的路径就是;
  • FORWARD:转发访问执行过滤器,包括RequestDispatcher.forward()方法、<jsp:forward>标签;
  • INCLUDE:包含访问过滤器,包括RequestDispatcher.include()方法、<jsp:include>标签;
  • ERROR:当前资源在web.xml中配置为<error-page>中时,并且真的出现了异常,转发到目标资源时,会执行过滤器。

   可以在<filter-mapping>中添加 0~N 个<dispatcher>子元素,来说明当前访问的拦截方式。

<filter-mapping>
	<filter-name>myfilter</filter-name>
	<url-pattern>/b.jsp</url-pattern>
	<dispatcher>REQUEST</dispatcher>[b.jsp为目标资源,当直接请求b.jsp时,会执行过滤器]
	<dispatcher>FORWARD</dispatcher>[当转发到b.jsp页面时,会执行过滤器]
</filter-mapping>

<filter-mapping>
	<filter-name>myfilter</filter-name>
	<url-pattern>/b.jsp</url-pattern>
</filter-mapping>[当没有给出拦截方式时,那么默认为REQUEST]

<filter-mapping>
	<filter-name>myfilter</filter-name>
	<url-pattern>/b.jsp</url-pattern>
	<dispatcher>FORWARD</dispatcher>[当转发到b.jsp页面时,会执行过滤器!因为已经给出了<dispatcher>FORWARD</dispatcher>了,那么就没有默认的REQUEST了!所以只有在转发到b.jsp时才会执行过滤,而转发到b.jsp时,不会执行b.jsp]
</filter-mapping>

   其实最为常用的就是REQUEST和FORWARD两种拦截方式,而INCLUDE和ERROR都比较少用。其中INCLUDE比较好理解,我们这里不再给出代码。而ERROR方式不容易理解,下面给出ERROR拦截方式的例子:

<filter-mapping>
	<filter-name>myfilter</filter-name>
	<url-pattern>/b.jsp</url-pattern>
	<dispatcher>ERROR</dispatcher>[拦截方式为ERROR]
</filter-mapping>
<error-page>
	<error-code>500</error-code>
	<location>/b.jsp</location>[把b.jsp执行为500的错误页面]
</error-page>

<body>
  <h1>a.jsp</h1>
   <%
   if(true)
   	throw new RuntimeException("嘻嘻~");[当用户访问a.jsp页面时会抛出异常,即500了!这时服务器会转发到b.jsp,在这之前会执行过滤器!]
   %>
  </body>

6、过滤器的应用场景

  • 执行目标资源起前做预处理工作,例如设置编码,这种通常都会放行,只是在目标资源执行之前做一些准备工作;
  • 通过条件判断是否放行,例如校验当前用户是否已经登陆,或者用户IP是否已经被禁用;
  • 在目标资源执行之后,做一些后续的特殊处理工作,例如把目标资源输出的数据进行处理。

7、设置目标资源

   在web.xml文件中部署Filter时,可以通过" * "来执行目标资源:

<filter-mapping>
		<filter-name>myfilter</filter-name>
		<url-pattern>/*</url-pattern>[表示过滤所有资源]
	</filter-mapping>

   这一特征与Servlet完全相同,通过这一特性我们可以在用户访问敏感资源时,执行过滤器,例如:<url-pattern>/admin/*</url-pattern>,可以把所有管理员才能访问的资源放到/admin路径下,这时可以通过过滤器来检验用户身份。

   还可以为filter-mapping指定目标资源位某个Servlet,例如:

<servlet>
		<servlet-name>myservlet</servlet-name>
		<servlet-class>cn.itcast.servlet.MyServlet</servlet-class>
	</servlet>
	<servlet-mapping>
		<servlet-name>myservlet</servlet-name>
		<url-pattern>/abc</url-pattern>
	</servlet-mapping>
	<filter>
		<filter-name>myfilter</filter-name>
		<filter-class>cn.itcast.filter.MyFilter</filter-class>
	</filter>
	<filter-mapping>
		<filter-name>myfilter</filter-name>
		<servlet-name>myservlet</servlet-name>[这里没有指定<url-pattern>,而是指定<servlet-name>!注意,它与某个Servlet的配置名称相同!]
	</filter-mapping>

   当访问http://localhost:8080/filtertest/abc时,会执行名为myservlet的Servlet,这时会执行过滤器。

三、过滤器应用案例

(一)、分IP统计网站的访问次数

   统计工作需要在所有资源之前都执行,那么就可以放到Filter中了。

   我们这个过滤器不打算做拦截操作,因为我们只是用来做统计的。

   用什么东西来装载统计的数据。Map<String,Integer>,整个网站只需要一个Map即可!

   Map在什么时间创建(使用ServletContextListner,在服务器启动时完成创建,并只在到ServletContext中),Map保存到哪里?(Map保存到ServletContext中)

  • Map需要在Filter中用来保存数据;
  • Map需要在页面使用,打印Map中的数据;

  1、说明

      网站统计每个IP地址访问本网站的次数。

   2、分析

      因为一个网站可能有多个页面,无论哪个页面被访问,都要统计访问次数,所以使用过滤器最为方便。

      因为需要分IP统计,所以可以在过滤器中创建一个Map,使用IP为key,访问次数为value。当有用户访问时,获取请求的IP,如果IP在Map中存在,说明以前访问过,那么在访问次数上加1,即可,IP在Map中不存在,那么设置一次数为1。

      把这个Map 放到ServletContext中。

   3、代码

      index.jsp

<body>
   <h1>分IP统计访问次数<h1>
   <table align="center" width="50%" border="1">
      <tr>
         <th>IP地址</th>
<pre name="code" class="html">         <th>次数</th>
</tr>
<c:forEach items="${applicationScope.ipCountMap}" var="entry">
<tr>
<td>${entry.key}</td>
            <td>${entry.value}</td>
</tr>
</c:forEach>
</table>
</body>

 

      IPFilter

public class IPFilter impelements Filter{
   private ServletContext context;
   public void init(FilterConfig fConfig) throws ServletException{
      context = fConfig.getServletContext();
      Map<String,Integer> ipCountMap = Collections.synchronizedMap(new LinkedHashMap<String,Integer>());
      context.setAttribute("ipCountMap",ipCountMap);
   }
   public void doFilter(ServletRequest request,ServletResponse response,FilterChain chain) throws IOException,ServletException{
      HttpServletRequest req = (HttpServletRequest)request;
      String ip = req.getRemoteAddr();
      Map<String,Integer> ipCountMap = (Map<String,Integer>)context.getAttribute("ipCountMap");
      Integer count = ipCountMap.get(ip);
      if(count == null){
         count+1;
      }else{
         count += 1;
      }
      ipCountMap.put(ip,count);
      context.setAttribute("ipCountMap",ipCountMap);
      chain.doFilter(request,response);
   }
   public void destory{}
}

   web.xml

<filter>
   <display-name>IPFilter<display-name>
   <filter-name>IPFilter<filter-name>
   <filter-class>cn.itcast.ip.IPFilter<filter-class>
</filter>
<filter-mapping>
   <filter-name>IPFilter<filter-name>
   <url-pattern>/*<url-pattern>
<filter-mapping>

(二)、粗粒度权限控制

1、说明

   我们给出三个页面:index.jsp、user.jsp、admin.jsp

  • index.jsp:谁都可以访问,没有限制;
  • user,jsp:只有登录用户才能访问;
  • admin.jsp:只有管理员才能访问;

2、分析

   设计User类:username、password、grade,其中grade为用户等级,1表示普通用户,2表示管理员。

   当登录成功后,把user保存到session中。

   创建LoginFilter,他有两种过滤方式:

  • 如果访问的是user.jsp,查看session中是否存在user;
  • 如果访问的是admin.jsp,查看session中是否存在user,并且user的grade等于2;

3、代码

   User.java

public class User{
   private String username;
   private String password;
   private int grade;
   ...
}

UserService.java

public class UserService{
   private static Map<String,User> users = new HashMap<String,User>();
   static{
      users.put("zhangsan",new User("zhangsan","123",1));<pre name="code" class="html">      users.put("lisi",new User("lisi","123",1));
}
public User login(String username,String password){
User user = users.get(username);
if(username == null){
return null;
}
return user.getPassword().equals(password)?user:null;
}
}

 login.jsp 

  <body>
  <h1>登录</h1>
  	<p style="font-weight: 900; color: red">${msg }[当登录出错时返回到login.jsp页面,显示“用户名或密码错误”]</p>
    <form action="<c:url value='/LoginServlet'/>" method="post">
    	用户名:<input type="text" name="username"/><br/>
    	密 码:<input type="password" name="password"/><br/>
    	<input type="submit" value="登录"/>
    </form>
  </body>

index.jsp

<body>
    <h1>主页</h1>
    <h3>${user.username }</h3>
    <hr/>
    <a href="<c:url value='/login.jsp'/>">登录</a><br/>
    <a href="<c:url value='/user/user.jsp'/>">用户页面</a><br/>
    <a href="<c:url value='/admin/admin.jsp'/>">管理员页面</a>
  </body>

/user/user.jsp

<body>
<h1>用户页面</h1>
<h3>${user.username }</h3>
<hr/>
</body>

/admin/admin.jsp

<body>
  <h1>管理员页面</h1>
  <h3>${user.username }</h3>
  <hr/>
</body>

LoginServlet

public class LoginSerlet extends HttpServlet{
   public void doPost(HttpServletRequest request,HttpServletResponse response)throws ServletException,IOException{
      request.setCharactorEncoding("utf-8");
      response.setContentType("text/html;charset=utf-8");

      String username = request.getParameter("username");
      String password = request.getParameter("password");
      UserService userService = new UserService();
      User user = userService.login(username,password);
      if(user == null){
         request.setAttribute("msg","用户名或密码错误");
         request.getRequestDispacher("/login.jsp").forward(request,response);
      }else{
         request.getSession().setAttribute("user",user);
         request.getRequestDispacher("/index.jsp").forward(request,response);
      }
   }
}

LoginUserFilter.java

public class LoginUserFilter implements Filter{
   public void destroy(){}
   public void init(FilterConfig fConfig) throws ServletException{}
   public void doFilter(ServletRequest request,ServletResponse response,FilterChain chain) throws ServletException,IOException{
      response.setContentType("text/html;charset=utf-8");
      HttpServletRequest req = (HttpServletRequest)request;
      User user = (User)req.getSession().getAttribute("user");
      if(user==null){
         response.getWriter().print("您还没有登录");
         return;
      }
      chain.doFilter(request,response);
   }
}

LoginAdminFilter.java

public class LoginAdminFilter implements Filter {
	public void destroy() {}
	public void init(FilterConfig fConfig) throws ServletException {}

	public void doFilter(ServletRequest request, ServletResponse response,
			FilterChain chain) throws IOException, ServletException {
		response.setContentType("text/html;charset=utf-8");
		HttpServletRequest req = (HttpServletRequest) request;
		User user = (User) req.getSession().getAttribute("user");[获取session中的user]
		if(user == null) {
			response.getWriter().print("您还没有登录!");
			return;
		}[如果user为null,说明用户没有登录]
		if(user.getGrade() < 2) {
			response.getWriter().print("您的等级不够!");
			return;
		}[如果用户等级小于2,说明是普通用户,而不是管理员用户]
		chain.doFilter(request, response);[放行]
	}
}

(三)、禁用资源缓存

   浏览器只要缓存页面,这对我们在开发测试很不方便,所以我们可以过滤所有资源,然后添加去除所有缓存

public class NoCacheFilter extends HttpFilter{
   public void doFilter(ServletRequest request,ServletResponse response,FilterChain chain)throws ServletException,IOException{
      response.setHeader("cache-control","no-chache")<pre name="code" class="html">      response.setHeader("pragma","no-chache");
<pre name="code" class="html"><pre name="code" class="html">      response.setHeader("expires","0");

 chain.doFilter(request,response); 
 } 

}
    但是要注意,有些浏览器可能不理会你的设置,还是会缓存的。这时就哎哟使用时间戳来处理了。 

(四)、解决全站字符乱码(POST和GET中文编码问题)

1、说明

   乱码问题:

  • 获取请求参数中的乱码问题

             > POST请求: request.setCharactorEncoding(" utf-8 ")

             > GET请求:new String(request.getParameter(" xxx ").getBytes(" iso-8859-1 ")," utf-8 ")

  • 响应的乱码问题

             > responsesetContentType(" text/html;charset=utf-8 ")

   基本上在每个Servlet中都要处理乱码的问题,所以应该把这个工作放到过滤器中完成。

2、分析

   其实全站乱码问题的难点就是处理GET请求参数的问题。

   如果只处理POST请求的编码问题,以及相应编码问题,那么过滤器就是太简单了。

public class EncodingFilter extends HttpFilter{
   public void doFilter(ServletRequest request,ServletResponse response,FilterChain chain)throws ServletException,IOException{
      String charset = this.getInitParameter("charset");
      if(charset == null || charset.isEmpty()){
         charset = "utf-8";
      }
      request.setRequestEncoding(charset);
      response.setContextType("text/html;charset="+charset);
      chain.doFilter(request,response);
   }
}

   如果是PSOT请求,当执行目标Servlet时,Servlet中调用request.getParameter()方法时,就会根据request.setCharactorEncoding()设置编码来转码。这说明在过滤器中调用request.setCharactorEncoding()方法会影响在目标Servlet中的request.getParameter()方法的行为。

   但是如果是GET请求,我们又如何影响request.getParameter()方法的行为呢?这是不好做到的。我们不可能先调用request.getParameter()方法获取参数,然后动手转码后,然后在施加到request中。因为request只有getParameter(),而没有setParameter()方法。

   处理GET请求参数编码问题,需要在Filter中放行时,把request对象给掉包了,也就是让目标Servlet使用我们掉包之后的request对象。这说明我们需要保证掉包之后的request对象所有方法与掉包之前一样可以使用,并且getParameter方法还要有能力返回转码之后的参数。

   这让你想起了继承,但是这里不能用继承,而是装饰者模式,下面是三种对A对象进行增强的手段:

  • 继承:AA类继承a对象的类型A类,然后重写fun1()方法,其中重写的fun1()方法就是被增强的方法。但是继承必须要知道a对象的真是类型,然后才能去继承。如果我们不知道a对象的确切类型,而只知道a对象是IA接口的实现类对象,那么就无法使用继承来增强a对象了。
  • 装饰者模式:AA类去实现a对象相同的接口(IA接口),还需要给AA类传递a对象,然后在AA类中所有的方法实现都是通过a对象的相同方法完成的,只有fun1()方法在代理a对象相同方法的前后添加了一些内容,这就是fun1()方法进行了增强。
  • 动态d代理:动态代理与装饰者模式比较相似,而且是通过反射来完成的。

   在request对象进行增强的条件,刚好符合装饰者模式的特点。因为我们不知道request对象的具体类型,但我们知道request对象是HttpServletRequest接口的实现类。这说明我们写一个EncodingRequest,去实现HttpServletRequest接口,然后在把原来的request传递给EncodingRequest类。在EncodingRequest类中对HttpServletRequest接口中的所有方法的实现都是通过代理原来的request对象完成,只有对getParameter()方法添加了增强。

   JavaEE已经给我们提供了一个HttpServletRequest类,他就是HttpServletRequest的装饰类,但它不是直接用来使用的,而是用来让我们继承的。当我们想写一个装饰类时,还要对所有不需要增强的方法做一次实现,是很烦的事情,但如果你去继承HttpServletRequestWraper类,那么只需要重写需要增强的方法即可。

3、代码

EncodingRequest

public class EncodingRequest extends HttpServletRequestWrapper [包含HttpServletRequst]{
	private String charset;
	public EncodingRequest[创建本类对象时,需要提供底层request,以及字符集](HttpServletRequest request, String charset) {
		super(request);
		this.charset = charset;
	}

	public String getParameter[重写getParameter()方法](String name) {
		HttpServletRequest request [把底层对象转换成HttpServletRequest]= (HttpServletRequest) getRequest();
		
		String method = request.getMethod()[获取请求方法];
		if(method.equalsIgnoreCase("post[如果是post请求]")) {
			try {
				request.setCharacterEncoding(charset);[设置编码,OK!]
			} catch (UnsupportedEncodingException e) {}
		} else if(method.equalsIgnoreCase("get[如果是GET请求]")) {
			String value = request.getParameter(name);[通过底层对象获取参数]
			try {
				value = new String(name.getBytes("ISO-8859-1"), charset);[把参数转码]
			} catch (UnsupportedEncodingException e) {
			}
			return value[返回转码后的参数值];
		}
		return request.getParameter(name);
	}
}


EncodingFilter

public class EncodingFilter extends HttpFilter {
	public void doFilter(HttpServletRequest request,
			HttpServletResponse response, FilterChain chain)
			throws IOException, ServletException {
		String charset = this.getInitParameter("charset");[获取初始化参数]
		if(charset == null || charset.isEmpty()) {
			charset = "UTF-8";
		}[如果没有配置初始化参数,那么把字符集设置为utf-8]
		response.setCharacterEncoding(charset);
		response.setContentType("text/html;charset=" + charset);
		EncodingRequest res = new EncodingRequest(request, charset);[创建EncodingRequest对象,使用request为底层对象]
		chain.doFilter(res, response);[放行!这时用户获取到的request就是EncodingRequest对象了!]
	}
}


web.xml

<filter>
  	<filter-name>EncodingFilter</filter-name>
  	<filter-class>cn.itcast.filter.EncodingFilter</filter-class>
  	<init-param>
  		<param-name>charset</param-name>
  		<param-value>UTF-8</param-value>
  	</init-param>
  </filter>
  <filter-mapping>
  	<filter-name>EncodingFilter</filter-name>
  	<url-pattern>/*</url-pattern>
  </filter-mapping>


四、页面静态化

1、说明

   你到当当网搜索最多的是什么分类?没错,就是Java分类。你猜猜,你去搜索Java分类时,当当会不会去查询数据库呢?当然会了,不查询数据库怎么获取Java分类下的图书呢。其实每天很多人去搜索Java分类的图书,每次都去访问数据库,这回有性能上的缺失,如果是在访问静态页面就会快多了。静态页面本身就就比动态页面快很多倍,而且动态页面总是要去数据库查询,这会更加降低速度。

   页面静态化就是把动态页面生成的html保存到服务器的文件上,然后再有相同请求时,不再去执行动态页面,而是直接给用户响应上次访问的静态页面。而且静态页面还有助于搜索引擎找到你。

2、查看图书分类

   我们先来写一个小例子,用来查看不同分类的图书,然后我们再去思考如何让动态页面静态化的问题。

index.jsp

<body>
   <a href="<c:url value='/BookServlet'/>">全部图书</a><br/>
   <a href="<c:url value='/BookServlet?category=1'/>">JavaSE分类</a><br/>
   <a href="<c:url value='/BookServlet?category=2'/>">JavaEE分类</a><br/>
   <a href="<c:url value='/BookServlet?category=3'/>">Java框架分类</a><br/>
</body>

BookServlet.java

public class BookServlet extends HttpServlet{
   public void doGet(HttpServletRequest request,HttpServletResponse response)throws ServletException,IOException{
      BookService bookService = new BookService();
      List<Book> bookList = null;
      String param = request.getParameter("catagory");
      <span style="color:#7f055;">if</span><span style="color:#000000;">(param == </span><span style="color:#7f055;">null</span><span style="color:#000000;"> || param.isEmpty()) {</span><p><span style="color:#000000;">         bookList = bookService.findAll();</span></p><p><span style="color:#000000;">      } </span><span style="color:#7f055;">else</span><span style="color:#000000;"> {</span></p><p><span style="color:#7f055;">         int</span><span style="color:#000000;"> category = Integer.</span><span style="color:#000000;">parseInt</span><span style="color:#000000;">(param);</span></p><p><span style="color:#000000;">         bookList = bookService.findByCategory(category);</span></p><p><span style="color:#000000;">      }</span></p><p><span style="color:#000000;">      request.setAttribute(</span><span style="color:#2a0ff;">"bookList"</span><span style="color:#000000;">, bookList);</span></p><p><span style="color:#000000;">      request.getRequestDispatcher(</span><span style="color:#2a0ff;">"/show.jsp"</span><span style="color:#000000;">).forward(request, response);</span></p>   }
}

show.jsp

<table border="1" align="center" width="50%">
	<tr>
		<th>图书名称</th>
		<th>图书单价</th>
		<th>图书分类</th>
	</tr>
	
  <c:forEach items="${bookList }" var="book">
	<tr>
		<td>${book.bname }</td>
		<td>${book.price }</td>
		<td>
			<c:choose>
				<c:when test="${book.category eq 1}"><p style="color:red;">JavaSE分类</p></c:when>
				<c:when test="${book.category eq 2}"><p style="color:blue;">JavaEE分类</p></c:when>
				<c:when test="${book.category eq 3}"><p style="color:green;">Java框架分类</p></c:when>
			</c:choose>
		</td>
	</tr>
  </c:forEach>
</table>

3、分析

   我们的目标是在用户第一次访问页面时生成静态页面,然后让请求重定向到静态页面中去,当用户再次访问时,直接重定向到静态页面去。

   我们需要为不同的请求生成静态页面,例如用户访问BookServlet?categroy=1时,我们需要生成静态页面,当用户访问BookServlet?categroy=2时,也要生成静态页。即不同的参数生成不同的页面。

   我们可以使用category为key,静态页面的路径为value,保存到一个Map中,然后再把Map保存到ServletContext中。没有对应的静态页面时,我们生成静态页面,再重定向到静态页面,如果存在静态页面,那么直接重定向即可。

  

  

   staticResponse.java

public class StaticResponse extends HttpServletResponseWrapper {
	private PrintWriter pw;

	public StaticResponse(HttpServletResponse response, String filepath)
			throws FileNotFoundException, UnsupportedEncodingException {
		super(response);
		pw = new PrintWriter(filepath, "UTF-8");[使用路径创建流!当使用该流写数据时,数据会写入到指定路径的文件中。]
	}

	public PrintWriter getWriter[jsp页面会调用本方法获取这个流!使用它来写入页面中的数据。这些数据都写入到指定路径的页面中去了,即写入到静态页面中。]() throws IOException {
		return pw;
	}

	public void close() throws IOException {
		pw.close();[刷新流,使缓冲区中的数据也写入到目标!]
	}
}

staticFilter.java

public class StaticFilter implements Filter {
	private ServletContext sc;
	
	public void destroy() {
	}

	public void doFilter(ServletRequest request, ServletResponse response,
			FilterChain chain) throws IOException, ServletException {
		HttpServletRequest req = (HttpServletRequest) request;
		HttpServletResponse res = (HttpServletResponse) response;

		String key = "key_" + request.getParameter("category");[获取分类参数,分类参数可能是:1,2,3,null。使用分类参数做key]
				
		Map<String,String> map = (Map<String, String>) sc.getAttribute("pages");[在ServletContext中获取Map,首次访问这个Map不存在。]
		if(map == null) {
			map = new HashMap<String,String>();
			sc.setAttribute("pages", map);
		}[如果Map不存在,那么创建一个Map,并存放到ServletContext中,这样下次访问Map就存在了。]
		
		if(map.containsKey(key)) {
			res.sendRedirect(req.getContextPath() + "/staticPages/" + map.get(key));
			return;
		}[查看Map中是否存在这个key,如果存在,那么获取值,值就是这个参数对应的静态页面路径。然后直接重定向到静态页面!]

		String html = key + ".html";[如果当前请求参数对应的静态页面不存在,那么就生成静态页面,首先静态页面的名称为key,容颜名为html]
		String realPath = sc.getRealPath("/staticPages/" + html);[生成真实路径,下面会使用这个路径创建静态页面。]
		StaticResponse sr = new StaticResponse(res, realPath);[创建自定义的response对象,传递待生成的静态页面路径]
		chain.doFilter(request, sr);[放行]
		sr.close();[这个方法的作用是刷新缓冲区!]

		res.sendRedirect(req.getContextPath() + "/staticPages/" + html);[这时静态页面已经生成,重定向到静态页面]
		map.put(key, html);[把静态页面保存到map中,下次访问时,直接从map中获取静态页面,不用再次生成。]
	}

	public void init(FilterConfig fConfig) throws ServletException {
		this.sc = fConfig.getServletContext();
	}
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值