过滤器

Servlet过滤器与Servlet十分相似,但它具有拦截客户端(浏览器)请求的功能,Servlet过滤器可以改变请求中的内容,来满足实际开发中的需要。对于程序开发人员而言,过滤器实质上就是在Web应用服务器上的一个Web应用组件,用于拦截客户端(浏览器)与目标资源的请求,并对这些请求进行一定过滤处理再发送给目标资源。

在Web容器中部署了过滤器以后,不仅客户端发送的请求会经过过滤器的处理,而且请求在发送到目标资源处理以后,请求的回应信息也同样要经过过滤器。

Filter和Listener_ide

如果一个Web应用中使用一个过滤器不能解决实际中的业务需要,那么可以部署多个过滤器对业务请求进行多次处理,这样做就组成了一个过滤器链。Web容器在处理过滤器链时,将按过滤器的先后顺序对请求进行处理。

Filter和Listener_ide_02

过滤器的使用

使用过滤器需要实现Filter接口。除这个接口外,与过滤器相关的对象还有FilterConfig对象与FilterChain对象,这两个对象也同样是接口对象,分别为过滤器的配置对象与过滤器的传递工具。

步骤

  1. 实现Filter接口,重写doFilter()方法
  2. 在doFilter方法中使用request和response对象完成具体操作
  3. 最后使用FilterChain对象的doFilter()方法将请求或响应传递给下一个过滤器。如果此过滤器已经是过滤器链中的最后一个过滤器,请求将传送给目标资源。
  4. 在xml文件中对过滤器进行配置,说明拦截URL的范围

过滤器的配置

  1. 单个过滤器使用注解配置比较简单:
    完整版:@WebFilter(filterName = "MyFirstFilter",urlPatterns = "/*")
    简化版:@webFilter("/*")
  2. 过滤器链需要配置web.xml

<!-- 指定具体的过滤器 -->
<filter>
<filter-name>firstFilter</filter-name>
<filter-class>com.example.filter.FirstFilter</filter-class>
</filter>
<!-- 指定过滤器拦截的URL的范围 -->
<filter-mapping>
<filter-name>firstFilter</filter-name>
<url-pattern>/*</url-pattern><!-- 这样设置的话,所有的包括静态页面都会被拦截 -->
</filter-mapping>


过滤器的生命周期

  1. 初始化 - Filter.init()
  2. 提供服务 - Filter.doFilter()
  3. 销毁 - Filter.destroy()

过滤器在服务器启动的时候被初始化,在服务器关闭的时候被销毁。过滤器对象是单例的,通过多线程的方式提供多用户环境。

字符集过滤器

通过过滤器可以统一解决中文乱码问题。

package com.example.filter;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebFilter("/*")
public class CharsetFilter implements Filter {

@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
//1.类型转换
HttpServletRequest request = (HttpServletRequest)servletRequest;
HttpServletResponse response = (HttpServletResponse)servletResponse;
//2.设置字符集
request.setCharacterEncoding("utf-8");
response.setContentType("text/html;charset=utf-8");

//3.放行
filterChain.doFilter(servletRequest,servletResponse);
}
}


过滤器参数化

上面的字符集过滤器需要设置字符集为 utf-8 ,我们把编码写在了代码中,修改起来很不方便。JavaEE 为了增加灵活性,允许将配置信息放在web.xml中,在 <init-param>中设置参数。

web.xml:

<filter>
<filter-name>CharsetFilter</filter-name>
<filter-class>com.example.filter.CharsetFilter</filter-class>
<!-- 初始化参数配置在下面,可以通过FilterConfig对象读取-->
<init-param>
<param-name>encoding</param-name>
<param-value>utf-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CharsetFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>


Filter:

package com.example.filter;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebFilter("/*")
public class CharsetFilter implements Filter {

private String encoding;

@Override
public void init(FilterConfig filterConfig) throws ServletException {
encoding = filterConfig.getInitParameter("encoding"); //获取初始化参数
}

@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
//1.类型转换
HttpServletRequest request = (HttpServletRequest)servletRequest;
HttpServletResponse response = (HttpServletResponse)servletResponse;
//2.设置字符集
request.setCharacterEncoding(encoding);
response.setContentType("text/html;charset="+encoding);

//3.放行
filterChain.doFilter(servletRequest,servletResponse);
}
}


url-pattern设置过滤范围

​/index.jsp​​ - 资源精确匹配

​/servlet/*​​ - 前缀模糊匹配

​*.jsp​​ - 后者模糊匹配

一个过滤器可以设置多个 url-pattern,来匹配多种资源

过滤器链

过滤器的执行顺序以 <filter-mapping>的编写顺序为准。

如果是通过注解配置,那么执行顺序以类名的字符串排序来执行。可以起名像Filter1、Filter2这样的,就会按照顺序执行。

设备适配过滤器

案例需求:为电脑、手机编写不同的网页,通过电脑和手机访问可以分别显示。

步骤:

  1. 编写不同的页面,一个位于 desktop 目录下,另一个位于 mobile 目录下,文件名都是 index.html
  2. 获取user-agent并判断
  3. 如果是电脑端,在请求uri中添加​​/desktop​
  4. 如果是手机端,在请求uri中添加​​/mobile​

Filter:

package com.example.filter;

import com.sun.tools.javac.jvm.Code;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Locale;

@WebFilter("/*")
public class DeviceAdapterFilter implements Filter {

@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest)servletRequest;
HttpServletResponse resp = (HttpServletResponse)servletResponse;

//获取请求uri。uri很简洁,没有前面的localhost:8080/虚拟路径,只有/index.html
String uri = req.getRequestURI();
//如果uri包含了desktop或者mobile就不用处理,直接放行
if(uri.startsWith("/desktop")||uri.startsWith("/mobile")){
filterChain.doFilter(servletRequest,servletResponse);
}else{
//读取user-agent,全部转为小写
String ua = req.getHeader("user-agent").toLowerCase();

if(ua.contains("android")||ua.contains("iphone")){
//生成新的uri
String targetURI = "/mobile"+uri;
System.out.println("移动端设备正在访问,重新跳转uri:"+targetURI);
//请求重定向
resp.sendRedirect(targetURI);
}else{
String targetURI = "/desktop"+uri;
System.out.println("PC端设备正在访问,重新跳转uri:"+targetURI);
resp.sendRedirect(targetURI);
}

}
}
}


监听器

可以对web应用对象的行为进行监控。一旦监听到了对象状态发生变化,可以自动触发操作。有三种监听对象:ServletContext、HttpSession、ServletRequest

使用

  1. 实现不同对象的Listener接口
  2. 在web.xml配置 <listener>
package com.example.listener; 

import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.annotation.*;

@WebListener
public class FirstListener implements ServletContextListener {

@Override
public void contextInitialized(ServletContextEvent sce) {
/* This method is called when the servlet context is initialized(when the Web application is deployed). */
System.out.println("ServletContext被初始化了");
}

@Override
public void contextDestroyed(ServletContextEvent sce) {
/* This method is called when the servlet Context is undeployed or Application Server shuts down. */
System.out.println("ServletContext被销毁了");
}
}


配置web.xml

<listener>
<listener-class>com.example.listener.FirstListener</listener-class>
</listener>


或者使用注解配置:​​@WebListener​

监听接口

属性监听:

  1. ServletContextAttributeListener
  2. HttpSessionAttributeListener
  3. ServletRequestAttributeListener

请求流量统计

监听器最适合的应用场景是统计网站的访问了。

步骤:

  1. 在ServletContext 中保存两个属性,记录时间和访问量。
  2. 通过ServletRequest监听器监听请求,每请求一次就记录下当前时间并把访问量增加一次
  3. 编写Servlet动态显示网站访问量,使用了JS的echarts库。

监听器代码:

package com.example.listener; 

import javax.servlet.*;
import javax.servlet.annotation.*;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

@WebListener
public class WebStatisticListener implements ServletContextListener, ServletRequestListener {

@Override
public void contextInitialized(ServletContextEvent sce) {
/* This method is called when the servlet context is initialized(when the Web application is deployed). */
//在ServletContext中记录访问量和访问时间,为了连续的展示,我们选择了列表
List<String> timeList = new ArrayList<>();
List<Integer> valueList = new ArrayList<>();
sce.getServletContext().setAttribute("timeList",timeList);
sce.getServletContext().setAttribute("valueList",valueList);
}

@Override
public void requestInitialized(ServletRequestEvent sre) {
//当监听到了一次请求,就记录下访问时间并把访问量加一

//1.获取ServletContext中保存的访问时间和访问量列表
List<String> timeList =(List)sre.getServletContext().getAttribute("timeList");
List<Integer> valueList = (List)sre.getServletContext().getAttribute("valueList");

//2.获取当前时间并格式化
Date date = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm");
String time = sdf.format(date);

//3.如果时间列表里有当前时间
if(timeList.contains(time)){
//4.1 就把当前时间对应的访问量加一
int index = timeList.indexOf(time);
valueList.set(index,valueList.get(index)+1);
}else{
//4.2 如果时间列表里没有当前时间,把当前时间加入时间列表,同时给对应的访问量加一。
timeList.add(time);
valueList.add(1);
}

//5.最后把修改后的时间列表和访问量列表重新设放回应用域
sre.getServletContext().setAttribute("timeList",timeList);
sre.getServletContext().setAttribute("valueList",valueList);

}
}


显示Servlet代码:

package com.example.listener;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;

@WebServlet("/ss")
public class StatisticServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//这个Servlet用来动态显示网站

List<String> timeList =(List)getServletContext().getAttribute("timeList");
List<Integer> valueList = (List)getServletContext().getAttribute("valueList");

resp.getWriter().println("<h1>"+timeList+"</h1>");
resp.getWriter().println("<h1>"+valueList+"</h1>");
}
}