监听器与过滤器保姆级解析
「本文已参与好文召集令活动,点击查看:后端、大前端双赛道投稿,2万元奖池等你挑战!」
一、过滤器概述
1.1、什么是过滤器
Filter 过滤器它是 JavaWeb 的三大组件之一。三大组件分别是:Servlet 程序、Listener 监听器、Filter 过滤器。
Filter 过滤器它是 JavaEE 的规范。也就是接口,它的作用是:拦截请求,过滤响应。
1.2、过滤器的流程
1.3、过滤器的实现
public class AdminFilter implements Filter {
/**
* doFilter 方法,专门用于拦截请求。可以做权限检查
*/
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChainfilterChain) throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
HttpSession session = httpServletRequest.getSession();
Object user = session.getAttribute("user");
// 如果等于 null,说明还没有登录
if (user == null) { servletRequest.getRequestDispatcher("/login.jsp").forward(servletRequest,servletResponse);
return;
}else{
// 让程序继续往下访问用户的目标资源,放行
filterChain.doFilter(servletRequest,servletResponse);
}
}
}
复制代码
随后在web.xml中配置
<!--filter 标签用于配置一个 Filter 过滤器-->
<filter>
<!--给 filter 起一个别名-->
<filter-name>AdminFilter</filter-name>
<!--配置 filter 的全类名-->
<filter-class>com.atguigu.filter.AdminFilter</filter-class>
</filter>
<!--filter-mapping 配置 Filter 过滤器的拦截路径-->
<filter-mapping>
<!--filter-name 表示当前的拦截路径给哪个 filter 使用-->
<filter-name>AdminFilter</filter-name>
<!--url-pattern 配置拦截路径
/ 表示请求地址为:http://ip:port/工程路径/
映射到 IDEA 的 web 目录
/admin/* 表示请求地址为:http://ip:port/工程路径/admin/*
-->
<url-pattern>/admin/*</url-pattern>
</filter-mapping>
复制代码
1.4、Filter 的生命周期
1.5、FilterConfig 类
FilterConfig 类它是 Filter 过滤器的配置文件类。Tomcat 每次创建 Filter 的时候,也会同时创建一个 FilterConfig 类,这里包含了 Filter 配置文件的配置信息。
FilterConfig 类的作用是获取 filter 过滤器的配置内容:
- 获取 Filter 的名称 filter-name 的内容。
- 获取在 Filter 中配置的 init-param 初始化参数
- 获取 ServletContext 对象。
1.6、过滤器链
1.7、Filter的拦截路径
Filter过滤器的拦截有三种路径匹配:
- 精准匹配
- 目录匹配
- 后缀名匹配
1.7.1、精准匹配
<!-- 以上配置的路径,表示请求地址必须为:http://ip:port/工程路径/login.jsp -->
<url-pattern>/login.jsp</url-pattern>
复制代码
1.7.2、目录匹配
<!-- 以上配置的路径,表示请求地址必须为:http://ip:port/工程路径/admin/* -->
<url-pattern>/admin/*</url-pattern>
复制代码
1.7.3、后缀名匹配
<!-- 以上配置的路径,表示请求地址必须以.html 结尾才会拦截到 -->
<url-pattern>*.html</url-pattern>
<!-- 以上配置的路径,表示请求地址必须以.do 结尾才会拦截到 -->
<url-pattern>*.do</url-pattern>
复制代码
Filter 过滤器它只关心请求的地址是否匹配,不关心请求的资源是否存在!!!
二、字符编码过滤器
之前编码处理的代码是直接写 Servlet 中的,那么它存在的问题是代码重复,无法复用,同时增加了维护的成本,我们想要的是一处设置,全局生效。 我们的方案是访问到 Servlet 之前对请求中的编码做处理(Filter)。
2.1、代码实现
我们编写 CharacterEncodingFilter类,统一在过滤器的 doFilter 方法中设置好编码为 UTF-8。
public class CharacterEncodingFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse
servletResponse, FilterChain filterChain) throws IOException, ServletException {
// 设置请求编码
servletRequest.setCharacterEncoding("UTF-8");
// 发行
filterChain.doFilter(servletRequest, servletResponse);
}
@Override
public void destroy() {
s}
}
复制代码
接着在 web.xml 配置 CharacterEncodingFilter这个过滤器。
<filter>
<filter-name>characterEncodingFilter</filter-name>
<filter-class>cn.wolfcode.web.filter.CharacterEncodingFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>characterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
复制代码
2.2、代码优化
在过滤器中,我们将请求参数的编码设值为 UTF-8,此时编码是写死在 Java 代码中的,若后期需要修改,那么这个 UTF-8 就是硬编码,不可以灵活配置。
2.2.1、配置字符编码
Filter 类似 Servlet 一样,也可以配置初始化参数。在 web.xml 配置如下:
<filter>
<filter-name>characterEncodingFilter</filter-name>
<filter-class>cn.wolfcode.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>characterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
复制代码
2.2.2、修改 CharacterEncodingFilter
在初始化方法里从 FilterConfig 对象中获取配置的 encoding 的初始化参数值。
public class CharacterEncodingFilter implements Filter {
private String encoding;
@Override
public void init(FilterConfig filterConfig) throws ServletException {
this.encoding = filterConfig.getInitParameter("encoding");
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse
servletResponse, FilterChain filterChain) throws IOException, ServletException {
if(StringUtil.hasLength(encoding) {
// 设置请求编码
servletRequest.setCharacterEncoding(this.encoding);
}
// 放行
filterChain.doFilter(servletRequest, servletResponse);
}
@Override
public void destroy() {
}
}
复制代码
三、登录校验过滤器实战
3.1、以前的代码
在实际开发中,我们项目中会存在很多的资源都需要在登录之后才能访问,所以我们需要在请求到达这些资源之前先判断当前用户是否登录,如果没有应该跳转到登录页面。之前我们是在需要登录访问的的资源加上登录判断代码。
Object obj = req.getSession().getAttribute("USER_IN_SESSION");
if(obj == null){
resp.sendRedirect("/login.jsp");
return;
复制代码
这段代码是可以满足需求,但是存在大量重复代码,在每个资源的代码中都会编写检查登录的代码,从增加维护成本。完全违背了我们之前使用过滤器的初衷(设置一次,所有 Servlet 共用)。
3.2、过滤器代码实现
3.2.1、编写 CheckLoginFilter
public class CheckLoginFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse
servletResponse, FilterChain filterChain) throws IOException, ServletException {
// 把请求和响应转化为符合 HTTP 协议的对象
HttpServletRequest req = ((HttpServletRequest) servletRequest);
HttpServletResponse resp = ((HttpServletResponse) servletResponse);
Object obj = req.getSession().getAttribute("USER_IN_SESSION");
// 没有登录,重定向到登录页面
if(obj == null) {
resp.sendRedirect("/login.jsp");
return;
}
// 登录过,放行访问
filterChain.doFilter(req, resp);
}
@Override
public void destroy() {
}
}
复制代码
3.2.2、配置 CheckLoginFilter
我们写完了过滤器还需要在 web.xml 配置此过滤器。
<filter>
<filter-name>checkLoginFilter</filter-name>
<filter-class>cn.wolfcode.web.filter.CheckLoginFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>checkLoginFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
复制代码
3.3、匿名访问资源的方法
匿名访问资源指的是不需要登录也可以访问的资源,比如 /login.jsp,/login,/randomCode 和静态资源等等。若不排除,那么当浏览器中请求这些资源,也需要登录,这就会造成这些资源访问不到了。针对上面的情况,只需要指定 CheckLoginFilter 对哪些资源做登录校验处理或者不对哪些资源做登录校验处理。
3.3.1、方式一:
我们可以指定 CheckLoginFilter 不对哪些资源做登录校验处理,只需修改 CheckLoginFilter,配置初始化参数,指定哪些资源不做登录校验。
<filter>
<filter-name>checkLoginFilter</filter-name>
<filter-class>cn.wolfcode.web.filter.CheckLoginFilter</filter-class>
<init-param>
<param-name>unCheckUri</param-name>
<param-value>/login.jsp;/login;/randomCode</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>checkLoginFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
复制代码
public class CheckLoginFilter implements Filter {
private List<String> unCheckUriList;
@Override
public void init(FilterConfig filterConfig) throws ServletException {
this.unCheckUriList =
Arrays.asList(filterConfig.getInitParameter("unCheckUri").split(";"));
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse
servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest req = ((HttpServletRequest) servletRequest);
HttpServletResponse resp = ((HttpServletResponse) servletResponse);
Object obj = req.getSession().getAttribute("USER_IN_SESSION");
// 获取请求的资源路径
String uri = req.getRequestURI();
// 没有登录且访问的不是匿名资源,重定向到登录页面
if(obj == null && !unCheckUriList.contains(uri)) {
resp.sendRedirect("/login.jsp");
return;
}
// 登录过,放行访问
filterChain.doFilter(req, resp);
}
@Override
public void destroy() {
}
}
复制代码
3.3.2、方式二
因为会发现,上面那样处理还是会导致静态资源不可以访问到,而实际开发中,我们需要排除的资源较多的话,尤其是像静态资源,会很麻烦。所以这次指定 CheckLoginFilter 对哪些资源做登录校验处理。将需要受检查的资源通通存放到 check 路径下。 首先先修改 CheckLoginFilter 的过滤路径。
<filter>
<filter-name>checkLoginFilter</filter-name>
<filter-class>cn.wolfcode.web.filter.CheckLoginFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>checkLoginFilter</filter-name>
<!-- 只有在 check 路径下的资源访问会做登录的校验 -->
<url-pattern>/check/*</url-pattern>
</filter-mapping>
复制代码
接着再修改修改 CheckLoginFilter
public class CheckLoginFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse
servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest req = ((HttpServletRequest) servletRequest);
HttpServletResponse resp = ((HttpServletResponse) servletResponse);
Object obj = req.getSession().getAttribute("USER_IN_SESSION");
// 没有登录,重定向到登录页面
if(obj == null) {
resp.sendRedirect("/login.jsp");
return;
}
// 登录过,放行访问
filterChain.doFilter(req, resp);
}
@Override
public void destroy() {
}
}
复制代码
最后只要修改需要做登录校验的资源,加上 /check 路径即可。
四、监听器(Listener)
Listener 是 Java Web 组件之一,主要的作用是用于监听作用域对象的创建和销毁动作以及作用域属性值的改变动作。若用户触发了这些动作,那么会立即执行相应的的监听器的操作。
4.1、监听器的分类
监听器监听的对象是作用域对象,作用域属性,他监听的动作:作用域对象的创建和销毁,作用域属值的增删改。 监听器按照作用域对象可以分为:
- ServletRequestListener
- HttpSessionListener
- ServletContextListener
按作用域属性分:
- ServletRequestAttributeListener
- HttpSessionAttributeListener
- ServletContextAttributeListener
4.2、开发监听器步骤
- 创建一个类,根据需求实现对应的接口。
- 实现其中的方法。
- 将监听器交给 Tomcat 管理。
4.3、实战
我们的需求是做一个网站统计游客的数量。
4.3.1、编写 VisitorListener
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;
public class VisitorListener implements HttpSessionListener {
private static long totalCount = 0;
// Session 对象创建会执行下面的方法
@Override
public void sessionCreated(HttpSessionEvent httpSessionEvent) {
totalCount++;
System.out.println("在线人数:" + totalCount);
}
// Session 对象销毁会执行下面的方法
@Override
public void sessionDestroyed(HttpSessionEvent httpSessionEvent) {
totalCount--;
}
}
复制代码
4.3.2、配置 VisitorListener
对于配置 VisitorListener,我们可以使用注解配置(直接在自己写监听器类上贴 @WebListener 即可),也可以是 XML 配置。
<listener>
<listener-class>cn.wolfcode.web.listener.VisitorListener</listener-class>
</listener>
复制代码