欢迎来到我的主页:【一只认真写代码的程序猿】
本篇文章收录于专栏【JSP相关】
如果这篇文章对你有帮助,希望点赞收藏加关注啦~
本文所有内容相关代码都可在以下仓库中找到:
https://github.com/Echo-Nie/JSPLearn
1 过滤器Filter
1.1 Filter机理
Filter,用于在 Servlet之外对 Request 或者 Response 进行修改。用于对用户请求进行预处理,也可以对 HttpServletResponse 进行后处理。
Filter完整流程: Filter 对用户请求进行预处理,接着将请求交给 Serviet进行处理并生成响应,最后 Filter 再 对服务器响应进行后处理。在一个 web 应用中,可以开发编写多个 Filter,这些 Filter 组合 起来称之为一个 Filter 链。
大白话:Filter 作为快递站点的安检环节。包裹(用户请求)到达站点后,先要经过安检(Filter 预处理),检查是否有违禁品等。安检通过后,包裹才会被送到仓库(Servlet 处理请求)。
包裹从仓库发出后,再次经过安检(Filter 后处理),这次检查包装是否完好等,确保送到客户手中的包裹是合格的。多个安检环节就构成了安检链,类似于多个 Filter 组成的 Filter 链。
对于多个过滤器:先配置的先执行(请求时的执行顺序);响应的时候顺序相反。如下图:
在 HttpServletRequest 到达 Servlet 之前,拦截客户的 HttpServletRequest。根据需要检查HttpServletRequest,也可以修改 HttpServletRequest 头和数据。
在HttpServletResponse 到达客户端之前,拦截 HttpServletResponse。根据需要检查HttpServletResponse,也可以修改 HttpServletResponse头和数据。
1.2 FilterTest实现
通过实现一个叫做javax.servlet.Fileter的接口来实现一个过滤器,其中定义了 三个方法,init()、doFilter()、destroy()分别在相应的时机执行。后期观察生命周期。
Step1:编写 java 类实现 Filter接口,并实现其 doFiter 方法, Step2:通过@WebFilter注解设置它所能拦截的资源。
package com.ynu.edu.filter;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;
/**
* @ClassName FilterTest1
* @Description 过滤器测试
* @Author Echo-Nie
* @Date 2025/1/17 21:48
* @Version V1.0
*/
@WebFilter("/ser01")
public class FilterTest1 implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("FilterTest1 init...");
}
/**
* @Author Echo-Nie
* @Description 过滤方法
* @Date 21:51 2025/1/17
* @Param [servletRequest, servletResponse, filterChain]
* @return void
**/
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
//放行资源
filterChain.doFilter(servletRequest,servletResponse);
//如果不放行的话这里就一直被拦截,永远不会进入Servlet01
}
@Override
public void destroy() {
System.out.println("FilterTest1 destroy...");
}
}
package com.ynu.edu.servlet;
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;
/**
* @ClassName Servlet01
* @Description
* @Author Echo-Nie
* @Date 2025/1/17 21:52
* @Version V1.0
*/
@WebServlet("/ser01")
public class Servlet01 extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("Servlet01 start...");
}
}
package com.ynu.edu.servlet;
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;
/**
* @ClassName Servlet01
* @Description
* @Author Echo-Nie
* @Date 2025/1/17 21:52
* @Version V1.0
*/
@WebServlet("/ser02")
public class Servlet02 extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("Servlet02 start...");
}
}
如上图:对于FilterTest1就会有init出现,但是FilterTest2没有,因为拦截器现在只拦截了FilterTest1;带*号就可以拦截所有,实际开发中一般都是带星号
package com.ynu.edu.filter;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;
/**
* @ClassName FilterTest1
* @Description 过滤器测试
* @Author Echo-Nie
* @Date 2025/1/17 21:48
* @Version V1.0
*/
@WebFilter("/*")
public class FilterTestAll implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("FilterTest1 init...");
}
/**
* @Author Echo-Nie
* @Description 过滤方法
doFilter放行方法前,做请求拦截
doFilter放行方法后,做响应拦截
* @Date 21:51 2025/1/17
* @Param [servletRequest, servletResponse, filterChain]
* @return void
**/
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
//放行资源
filterChain.doFilter(servletRequest,servletResponse);
//如果不放行的话这里就一直被拦截,永远不会进入Servlet01
}
@Override
public void destroy() {
System.out.println("FilterTest1 destroy...");
}
}
1.3 LoginFilter
-
预处理:
在用户登录请求到达
UserServlet
之前,检查用户是否已经登录。如果用户已经登录(即
session
中存在user
对象),则直接重定向到首页index.jsp
,避免重复登录。 -
后处理:
在响应返回给客户端之前,设置响应头,防止浏览器缓存页面内容。
package com.ynu.edu.filter;
import com.ynu.edu.vo.MessageModel;
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;
/**
* @ClassName LoginFilter
* @Description 登录过滤器
* @Author Echo-Nie
* @Date 2025/1/17 15:00
* @Version V1.0
*/
@WebFilter("/login")
public class LoginFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
// 初始化
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
// 将ServletRequest和ServletResponse转换为HttpServletRequest和HttpServletResponse
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
// 1. 预处理:检查用户是否已经登录
HttpSession session = httpRequest.getSession();
if (session.getAttribute("user") != null) {
// 如果用户已经登录,直接重定向到首页,避免重复登录
httpResponse.sendRedirect("index.jsp");
return; // 结束过滤链,不再继续执行后续的Filter和Servlet
}
// 2. 继续执行后续的Filter和Servlet
chain.doFilter(request, response);
// 3. 后处理:在响应返回给客户端之前,可以做一些额外的工作
httpResponse.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
httpResponse.setHeader("Pragma", "no-cache"); // HTTP 1.0
httpResponse.setDateHeader("Expires", 0);
}
@Override
public void destroy() {
// 销毁
}
}
1.4 非法访问拦截
需要放行的资源:
-
指定页面,放行 (无需登录即可访问的页面 例如:登录页面、注册页面等)
-
静态资源,放行 (image、js、css文件等)
-
指定操作,放行(无需登录即可执行的操作 例如:登录操作、注册操作)3.
-
登录状态,放行(判断session中用户信息是否为类
package com.ynu.edu.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;
import java.util.Arrays;
import java.util.List;
/**
* @ClassName AuthFilter
* @Description 用户登录验证过滤器
* @Author Echo-Nie
* @Date 2025/1/18
* @Version V1.0
*/
@WebFilter("/*") // 拦截所有请求
public class AuthFilter implements Filter {
// 放行的指定页面
private static final List<String> ALLOWED_PAGES = Arrays.asList(
"/login.jsp", "/register.jsp"
);
// 放行的静态资源路径
private static final List<String> ALLOWED_RESOURCES = Arrays.asList(
"/css/", "/js/", "/images/"
);
// 放行的指定操作
private static final List<String> ALLOWED_ACTIONS = Arrays.asList(
"/login", "/register"
);
@Override
public void init(FilterConfig filterConfig) throws ServletException {
// 初始化方法,可以在这里做一些初始化工作
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
// 将 ServletRequest 和 ServletResponse 转换为 HttpServletRequest 和 HttpServletResponse
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
// 获取请求的 URI
String requestURI = httpRequest.getRequestURI();
String contextPath = httpRequest.getContextPath();
String path = requestURI.substring(contextPath.length());
// 检查是否为放行的指定页面
if (ALLOWED_PAGES.contains(path)) {
chain.doFilter(request, response);
return;
}
// 检查是否为放行的静态资源
if (isAllowedResource(path)) {
chain.doFilter(request, response);
return;
}
// 检查是否为放行的指定操作
if (ALLOWED_ACTIONS.contains(path)) {
chain.doFilter(request, response);
return;
}
// 检查用户是否已登录
HttpSession session = httpRequest.getSession(false); // 如果不存在 Session,则返回 null
boolean isLoggedIn = (session != null && session.getAttribute("user") != null);
if (isLoggedIn) {
// 用户已登录,放行请求
chain.doFilter(request, response);
} else {
// 用户未登录,重定向到登录页面
httpResponse.sendRedirect(contextPath + "/login.jsp");
}
}
@Override
public void destroy() {
// 销毁方法,可以在这里做一些清理工作
}
/**
* 检查是否为放行的静态资源
*/
private boolean isAllowedResource(String path) {
for (String resource : ALLOWED_RESOURCES) {
if (path.startsWith(resource)) {
return true;
}
}
return false;
}
}
2 监听器Listener
监听器是Servlet 中一种的特殊的类,能帮助监听 web 中的特定事件,比如 ServletContextHttpSession,ServletRequest 的创建和销毁;变量的创建、销毁和修改等。可以在某些动作前后增加处理,实现监控。
2.1 监听器的作用
监听器用于监听 Web 应用中的事件,例如:
-
请求的创建和销毁。
-
Session 的创建和销毁。
-
应用上下文(ServletContext)的初始化和销毁。
-
属性的添加、修改和删除。
通过监听器,开发者可以在这些事件发生时执行自定义的逻辑,例如初始化资源、记录日志、统计在线用户等。
2.2 监听器的分类
监听器分为三类,共八种:
(1) 监听生命周期
这类监听器用于监听对象的创建和销毁事件。
-
ServletRequestListener
:监听 HTTP 请求的创建和销毁。
主要方法:
requestInitialized(ServletRequestEvent sre)
:请求创建时触发。requestDestroyed(ServletRequestEvent sre)
:请求销毁时触发。 -
HttpSessionListener
:监听 Session 的创建和销毁。
主要方法:
sessionCreated(HttpSessionEvent se)
:Session 创建时触发。sessionDestroyed(HttpSessionEvent se)
:Session 销毁时触发。 -
ServletContextListener
:监听应用上下文(ServletContext)的初始化和销毁。
主要方法:
contextInitialized(ServletContextEvent sce)
:应用启动时触发。contextDestroyed(ServletContextEvent sce)
:应用关闭时触发。
(2) 监听值的变化
这类监听器用于监听属性的添加、修改和删除事件。
-
ServletRequestAttributeListener
:监听请求属性(
request.setAttribute()
)的变化。主要方法:
attributeAdded(ServletRequestAttributeEvent srae)
:属性添加时触发。attributeRemoved(ServletRequestAttributeEvent srae)
:属性删除时触发。attributeReplaced(ServletRequestAttributeEvent srae)
:属性修改时触发。 -
HttpSessionAttributeListener
:监听 Session 属性(
session.setAttribute()
)的变化。主要方法:
attributeAdded(HttpSessionBindingEvent se)
:属性添加时触发。attributeRemoved(HttpSessionBindingEvent se)
:属性删除时触发。attributeReplaced(HttpSessionBindingEvent se)
:属性修改时触发。 -
ServletContextAttributeListener
:监听应用上下文属性(
servletContext.setAttribute()
)的变化。主要方法:
attributeAdded(ServletContextAttributeEvent scae)
:属性添加时触发。attributeRemoved(ServletContextAttributeEvent scae)
:属性删除时触发。attributeReplaced(ServletContextAttributeEvent scae)
:属性修改时触发。
(3) 针对 Session 中的对象
这类监听器用于监听 Session 中 Java 对象(JavaBean)的绑定和解绑事件。
-
HttpSessionBindingListener
:监听 Java 对象绑定到 Session 或从 Session 中解绑的事件。
需要 JavaBean 直接实现该接口。
主要方法:
valueBound(HttpSessionBindingEvent event)
:对象绑定到 Session 时触发。valueUnbound(HttpSessionBindingEvent event)
:对象从 Session 中解绑时触发。 -
HttpSessionActivationListener
:监听 Session 中 Java 对象的激活和钝化事件(在分布式环境中使用)。
需要 JavaBean 直接实现该接口。
主要方法:
sessionWillPassivate(HttpSessionEvent se)
:对象钝化时触发。sessionDidActivate(HttpSessionEvent se)
:对象激活时触发。
2.3 监听器场景
-
统计在线用户:
使用
HttpSessionListener
监听 Session 的创建和销毁,统计在线用户数量。 -
初始化全局资源:
使用
ServletContextListener
在应用启动时初始化全局资源(如数据库连接池)。 -
记录请求日志:
使用
ServletRequestListener
记录每个请求的访问日志。 -
监听属性变化:
使用
HttpSessionAttributeListener
监听 Session 中属性的变化,实现特定业务逻辑。 -
对象绑定和解绑事件:
使用
HttpSessionBindingListener
监听 Java 对象在 Session 中的绑定和解绑事件。
2.4 监听器的配置
-
注解配置:
在 Servlet 3.0 及以上版本中,可以使用
@WebListener
注解配置监听器。@WebListener public class MySessionListener implements HttpSessionListener { @Override public void sessionCreated(HttpSessionEvent se) { System.out.println("Session created: " + se.getSession().getId()); } @Override public void sessionDestroyed(HttpSessionEvent se) { System.out.println("Session destroyed: " + se.getSession().getId()); } }
-
XML 配置:
在
web.xml
中配置监听器。<listener> <listener-class>com.ynu.edu.listener.MySessionListener</listener-class> </listener>
2.5 测试样例
Listener类:
package com.ynu.edu.listener;
import javax.servlet.annotation.WebListener;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;
/**
* @ClassName Listener01
* @Description 监听器01号
* @Author Echo-Nie
* @Date 2025/1/17 23:04
* @Version V1.0
*/
@WebListener
public class Listener01 implements HttpSessionListener {
@Override
public void sessionCreated(HttpSessionEvent se) {
System.out.println("Session Created...");
}
@Override
public void sessionDestroyed(HttpSessionEvent se) {
System.out.println("Session Destroy...");
}
}
创建Session:
package com.ynu.edu.servlet;
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 javax.servlet.http.HttpSession;
import java.io.IOException;
/**
* @ClassName ServletListener01
* @Description
* @Author Echo-Nie
* @Date 2025/1/17 23:06
* @Version V1.0
*/
@WebServlet("/s01")
public class ServletListener01 extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("Servlet-Listener01 Create...");
HttpSession httpSession = req.getSession();
}
}
销毁Session:
package com.ynu.edu.servlet;
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 javax.servlet.http.HttpSession;
import java.io.IOException;
/**
* @ClassName ServletListener01
* @Description
* @Author Echo-Nie
* @Date 2025/1/17 23:06
* @Version V1.0
*/
@WebServlet("/s02")
public class ServletListener02 extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("Servlet-Listener01 Destroy...");
req.getSession().invalidate();
}
}
3 在线人数监控
3.1 开发逻辑
在线人数统计:
当用户访问网站时,会创建一个 Session,OnlineUserListener
监听到 Session 创建事件,增加在线人数。
当用户退出或 Session 超时时,OnlineUserListener
监听到 Session 销毁事件,减少在线人数。
显示在线人数:
用户可以通过点击链接访问 /onlineUsers
,查看当前在线人数。
退出功能:
用户点击“退出”按钮后,LogoutServlet
会销毁当前 Session 并减少在线人数,然后重定向到首页。
代码编写:
核心工具类:计数功能( OnlineUserCounter
)
监听器:实时更新逻辑( OnlineUserListener
)
Servlet:业务逻辑登录登出( OnlineUserServlet
和 LogoutServlet
)
前端页面:用户交互( index.jsp
)
测试和调试
3.2 代码层
index.jsp
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>在线人数监控</title>
</head>
<body>
<h1>欢迎访问在线人数监控系统</h1>
<p><a href="onlineUsers">查看当前在线人数</a></p>
<form action="logout" method="post">
<button type="submit">退出</button>
</form>
</body>
</html>
LogoutServlet:
package com.ynu.edu.servlet;
import com.ynu.edu.util.OnlineUserCounter;
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 javax.servlet.http.HttpSession;
import java.io.IOException;
@WebServlet("/logout")
public class LogoutServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 获取当前 Session
HttpSession session = req.getSession(false);
if (session != null) {
// 销毁 Session
session.invalidate();
// 减少在线人数
OnlineUserCounter.decrement();
}
// 重定向到首页
resp.sendRedirect("index.jsp");
}
}
OnlineUserListener:
package com.ynu.edu.listener;
import com.ynu.edu.util.OnlineUserCounter;
import javax.servlet.annotation.WebListener;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;
@WebListener
public class OnlineUserListener implements HttpSessionListener {
@Override
public void sessionCreated(HttpSessionEvent se) {
// Session 创建时,增加在线人数
OnlineUserCounter.increment();
System.out.println("Session 创建,当前在线人数: " + OnlineUserCounter.getOnlineUsers());
}
@Override
public void sessionDestroyed(HttpSessionEvent se) {
// Session 销毁时,减少在线人数
OnlineUserCounter.decrement();
System.out.println("Session 销毁,当前在线人数: " + OnlineUserCounter.getOnlineUsers());
}
}
OnlineUserCounter:
package com.ynu.edu.util;
import java.util.concurrent.atomic.AtomicInteger;
public class OnlineUserCounter {
private static final AtomicInteger onlineUsers = new AtomicInteger(0);
// 增加在线人数
public static void increment() {
onlineUsers.incrementAndGet();
}
// 减少在线人数
public static void decrement() {
onlineUsers.decrementAndGet();
}
// 获取当前在线人数
public static int getOnlineUsers() {
return onlineUsers.get();
}
}