1.Filter过滤器
Servlet API中提供了一个Filter接口,在开发Web应用时,如果编写的Java类实现了这个接口,则把这个Java类称为过滤器Filter。
通过Filter技术,开发人员可以实现用户在访问某个目标资源之前,对访问的请求和响应进行拦截。
简单来说,就是浏览器请求服务器资源或服务器响应浏览器时,过滤器可以将这些请求和响应拦截下来,完成某些相应的操作。
一般用于完成通用的操作,例如:
①登录验证,只有登录成功后,才能访问资源。
②统一编码处理,在获取数据前,进行编码以解决乱码问题。
③敏感字符过滤。
1.1.Filter快速入门
实现步骤:
(1)定义一个Java类并实现Filter接口;
(2)在这个Java类中复写doFilter()方法;
(3)配置拦截路径:web.xml配置和注解配置两种方法,选其中一种即可。
1.2.Filter的注解配置和web.xml配置
(1)注解配置。
导入javax.servlet.annotation.WebFilter;
包,然后在类上使用注解@WebFilter("/*")
即可。/*
表示访问所有资源之前,都会执行该过滤器。
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;
@WebFilter("/*")
public class FilterDemo implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("FilterDemo....");
// 考虑是否放行
filterChain.doFilter(servletRequest, servletResponse);
}
@Override
public void destroy() {
}
}
(2)web.xml配置。
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<filter>
<filter-name>demo</filter-name>
<filter-class>com.atlantis.filter.FilterDemo</filter-class>
</filter>
<filter-mapping>
<filter-name>demo</filter-name>
<!-- 拦截路径 -->
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
1.3.Filter过滤器执行流程和生命周期
方法 | 描述 |
---|---|
init() | 服务器启动后会创建一个Filter对象,调用init()方法。服务器帮你完成,只执行一次,多用于加载资源 |
doFilter() | 每一次请求被拦截资源时,都会被执行。执行多次 |
destroy() | 服务器关闭后会销毁Filter对象,如果服务器正常关闭,则会执行destroy()方法,只执行一次,多用于释放资源 |
1.4.Filter过滤器的配置详情
(1)配置拦截路径,@WebFilter("拦截路径")
拦截方式 | 配置方法 | 描述 |
---|---|---|
拦截具体资源路径 | @WebFilter("/index.jsp") | 只有访问index.jsp资源时,过滤器才会被执行 |
拦截指定目录下资源 | @WebFilter("/user/*") | 访问 /user下的所有资源时,过滤器都会被执行 |
拦截指定后缀名资源 | @WebFilter("*.jsp") | 访问后缀名为jsp资源时,过滤器都会被执行 |
拦截所有资源 | @WebFilter("/*") | 访问所有资源时,过滤器都会被执行 |
(2)配置拦截方式,即拦截资源被访问的方式
①注解配置,设置dispatcherTypes属性。
dispatcherTypes属性中的属性值 | 描述 |
---|---|
REQUEST | 默认值,浏览器直接请求资源 |
FORWARD | 转发方式来访问资源 |
INCLUDE | 包含方式访问资源 |
ERROR | 错误跳转方式访问资源 |
ASYNC | 异步方式访问资源 |
②web.xml配置
在对应的filter-mapping
标签下,设置<dispatcher></dispatcher>
标签内的属性值即可。
1.5.Filter过滤器链
过滤器链:配置的多个过滤器。在这里,我们只需要了解在过滤器链中的每个过滤器的执行顺序即可。
下面案例:存在两个过滤器,FilterDemo和FilterDemo2。则它们的执行顺序是,FilterDemo1–>FilterDemo2–>访问资源(index.jsp)–>FilterDemo2–>FilterDemo1。
那么问题来了,为什么是FilterDemo先执行,FilterDemo2后执行?为什么不是FilterDemo2先执行呢。
所以,我们需要了解过滤器的先后顺序问题:
①注解配置下,按照类名的字符串比较规则(按照每个字符进行比较,a比b小,1比2小等等)进行比较,值小的先执行。如果还不能理解的话,看项目工程的文件目录结构,从上到下依次执行。
②web.xml配置下,按照定义的顺序来,谁先定义谁先开始执行。因为web.xml加载时,是从上往下加载,这样先遇到的过滤器就会被先执行。
1.6.Filter过滤器的简单案例
(1)登录验证
分析一下:访问项目的某个资源时,先验证是否登录。若登录过,则直接放行;若没有登录,则跳转登录界面,提示"请先登录"。
package com.atlantis.filter;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
@WebFilter("/*")
public class LoginFilter implements Filter {
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
// 1.将ServletRequest和ServletResponse强转
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) resp;
// 2.获取请求路径
String uri = request.getRequestURI();
// 3.判断是否包含登录相关资源路径,要注意排除css/js/图片等一些静态资源
if (uri.contains("/login.jsp") || uri.contains("/login")) {
// 4-1放行登录操作
chain.doFilter(req, resp);
} else {
// 4-2不是登录操作,拦截下来
// 获取session
Object username = request.getSession().getAttribute("username");
if (username != null) {
// 已经登录过,则放行
chain.doFilter(req, resp);
} else {
// 没有登录,则跳转到登录界面
request.setAttribute("login_msg","请先登录!");
request.getRequestDispatcher("/login.jsp").forward(request,response);
}
}
}
public void destroy() {
}
public void init(FilterConfig config) throws ServletException {
}
}
(2)过滤敏感词汇(代理模式,动态代理)
分析一下:对录入的数据根据SensitiveWords.txt来进行敏感词汇过滤,如果是敏感词则替换为**表示。
我们首先需要对request对象进行增强,增强获取参数相关方法。然后放行传递代理对象。
package com.atlantis.filter;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.List;
/**
* 敏感词汇过滤器
*/
@WebFilter("/*")
public class SensitiveWordsFilter implements Filter {
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
// 1.创建代理对象,增强getParameter方法
ServletRequest proxy_req = (ServletRequest) Proxy.newProxyInstance(req.getClass().getClassLoader(), req.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 2.判断是否是getParameter方法
if ("getParameter".equals(method.getName())) {
// 3.增强getParameter方法的返回值
// 获取返回值
req.setCharacterEncoding("utf-8");
String value = (String) method.invoke(req, args);
if (value != null) {
for (String str : SensitiveWordsList) {
// 将敏感词汇替换
if (value.contains(str)) {
value = value.replaceAll(str, "**");
}
}
}
// 返回过滤后的敏感词汇
return value;
}
// 判断是否是getParameterMap方法
// 判断是否是getParameterValue方法
// 若不满足,则原样返回数据
return method.invoke(req, args);
}
});
// 放行传递代理对象proxy_req
chain.doFilter(proxy_req, resp);
}
// 敏感词汇的List集合
private List<String> SensitiveWordsList = new ArrayList<String>();
public void init(FilterConfig config) throws ServletException {
try {
// 获取SensitiveWords.txt的真实路径
ServletContext context = config.getServletContext();
String realPath = context.getRealPath("WEB-INF/classes/SensitiveWords.txt");
// 读取SensitiveWords.txt文件,BufferedReader默认GBK编码
BufferedReader bf = new BufferedReader(new FileReader(realPath));
// 将文件每一行数据添加到敏感词汇的List集合SensitiveWordsList中
String line = null;
while ((line = bf.readLine()) != null) {
SensitiveWordsList.add(line);
}
bf.close();
System.out.println("敏感词汇:" + SensitiveWordsList);
} catch (Exception e) {
e.printStackTrace();
}
}
public void destroy() {
}
}
2.Listener监听器
Listener监听器用于监听web应用中某些对象、信息的创建、销毁、增加,修改,删除等动作的发生,然后作出相应的响应处理。当范围对象的状态发生变化的时候,服务器自动调用监听器对象中的方法。常用于统计在线人数和在线用户,系统加载时进行信息初始化,统计网站的访问量等等。
按监听的对象划分,可以分为:
①ServletContext对象监听器
②HttpSession对象监听器
③ServletRequest对象监听器
按监听的事件划分:
①对象自身的创建和销毁的监听器
②对象中属性的创建和消除的监听器
③session中的某个对象的状态变化的监听器
这里主要讲解一下ServletContextListener:监听ServletContext对象的创建和销毁。ServletContextListener一般用于加载整个项目的资源和配置文件等。
描述 | 方法 | 返回类型 |
---|---|---|
ServletContext对象被销毁前调用该方法 | contextDestroyed(ServletContextEvent sce) | void |
ServletContext对象创建后调用该方法 | contextInitialized(ServletContextEvent sce) | void |
2.1.ServletContextListener监听器的使用步骤
(1)定义一个Java类,实现ServletContextListener接口;
(2)复写方法;
(3)配置:web.xml配置和注解配置两种方法,选其中一种即可。
在web.xml中配置监听器。注意:监听器>过滤器>serlvet,配置的时候要注意先后顺序:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<listener>
<listener-class>com.atlantis.listener.ContextLoaderListener</listener-class>
</listener>
</web-app>
注解配置,监听器的配置可以直接在代码中通过注释来完成。
package com.atlantis.listener;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;
@WebListener()
public class ContextLoaderListener implements ServletContextListener {
// 监听ServletContext对象创建,ServletContext对象在服务器启动后自动创建
@Override
public void contextInitialized(ServletContextEvent servletContextEvent) {
// 一般加载资源文件
// 1.获取ServletContext对象
ServletContext servletContext = servletContextEvent.getServletContext();
// 2.加载资源文件
String contextConfigLocation = servletContext.getInitParameter("contextConfigLocation");
// 3.获取真实路径
String realPath = servletContext.getRealPath(contextConfigLocation);
try {
// 4.加载进内存
FileInputStream fis = new FileInputStream(realPath);
System.out.println(fis);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("ServletContext对象被创建了...");
}
// 监听ServletContext对象销毁,服务器正常关闭后该方法被调用
@Override
public void contextDestroyed(ServletContextEvent servletContextEvent) {
System.out.println("ServletContext对象被销毁了...");
}
}