过滤器
Servlet过滤器与Servlet十分相似,但它具有拦截客户端(浏览器)请求的功能,Servlet过滤器可以改变请求中的内容,来满足实际开发中的需要。对于程序开发人员而言,过滤器实质上就是在Web应用服务器上的一个Web应用组件,用于拦截客户端(浏览器)与目标资源的请求,并对这些请求进行一定过滤处理再发送给目标资源。
在Web容器中部署了过滤器以后,不仅客户端发送的请求会经过过滤器的处理,而且请求在发送到目标资源处理以后,请求的回应信息也同样要经过过滤器。
如果一个Web应用中使用一个过滤器不能解决实际中的业务需要,那么可以部署多个过滤器对业务请求进行多次处理,这样做就组成了一个过滤器链。Web容器在处理过滤器链时,将按过滤器的先后顺序对请求进行处理。
过滤器的使用
使用过滤器需要实现Filter接口。除这个接口外,与过滤器相关的对象还有FilterConfig对象与FilterChain对象,这两个对象也同样是接口对象,分别为过滤器的配置对象与过滤器的传递工具。
步骤
- 实现Filter接口,重写doFilter()方法
- 在doFilter方法中使用request和response对象完成具体操作
- 最后使用FilterChain对象的doFilter()方法将请求或响应传递给下一个过滤器。如果此过滤器已经是过滤器链中的最后一个过滤器,请求将传送给目标资源。
- 在xml文件中对过滤器进行配置,说明拦截URL的范围
过滤器的配置
- 单个过滤器使用注解配置比较简单:
完整版:@WebFilter(filterName = "MyFirstFilter",urlPatterns = "/*")
简化版:@webFilter("/*")
- 过滤器链需要配置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>
过滤器的生命周期
- 初始化 - Filter.init()
- 提供服务 - Filter.doFilter()
- 销毁 - 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;
("/*")
public class CharsetFilter implements Filter {
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;
("/*")
public class CharsetFilter implements Filter {
private String encoding;
public void init(FilterConfig filterConfig) throws ServletException {
encoding = filterConfig.getInitParameter("encoding"); //获取初始化参数
}
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这样的,就会按照顺序执行。
设备适配过滤器
案例需求:为电脑、手机编写不同的网页,通过电脑和手机访问可以分别显示。
步骤:
- 编写不同的页面,一个位于 desktop 目录下,另一个位于 mobile 目录下,文件名都是 index.html
- 获取user-agent并判断
- 如果是电脑端,在请求uri中添加
/desktop
- 如果是手机端,在请求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;
("/*")
public class DeviceAdapterFilter implements Filter {
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
使用
- 实现不同对象的Listener接口
- 在web.xml配置 <listener>
package com.example.listener;
import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.annotation.*;
public class FirstListener implements ServletContextListener {
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被初始化了");
}
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
监听接口
属性监听:
- ServletContextAttributeListener
- HttpSessionAttributeListener
- ServletRequestAttributeListener
请求流量统计
监听器最适合的应用场景是统计网站的访问了。
步骤:
- 在ServletContext 中保存两个属性,记录时间和访问量。
- 通过ServletRequest监听器监听请求,每请求一次就记录下当前时间并把访问量增加一次
- 编写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;
public class WebStatisticListener implements ServletContextListener, ServletRequestListener {
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);
}
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;
("/ss")
public class StatisticServlet extends HttpServlet {
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>");
}
}