'# 小黑子—JavaWeb:第六章 - Filter、Listener、AJAX与JSON
一、背景与问题
在JavaWeb开发中,Filter、Listener、AJAX和JSON是构建现代Web应用的四大核心技术支柱。它们分别解决了请求拦截、事件驱动、异步交互和数据交换等核心问题。然而,这些技术在实际应用中存在诸多复杂场景,例如:
- Filter 的滥用可能导致性能瓶颈
- Listener 的生命周期管理不当可能引发资源泄漏
- AJAX 与 JSON 的结合使用可能引发跨域安全问题
- JSON 数据格式的处理可能引入安全漏洞
本文将深入解析这些技术的工作原理,结合真实开发场景,探讨其最佳实践和常见陷阱。
二、基本原理
1. Filter(过滤器)的工作原理
Filter 是 Servlet API 中用于拦截请求的组件,其核心逻辑如下:
public class AuthFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) {
// 初始化资源
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
// 前置处理
if (validateRequest(request)) {
chain.doFilter(request, response); // 放行请求
} else {
// 拦截处理
}
}
@Override
public void destroy() {
// 清理资源
}
}
关键原理:
- FilterChain 是请求处理的链式结构
- 每个 Filter 可以选择是否放行请求
- Filter 的执行顺序由
web.xml 中的配置决定
2. Listener(监听器)的工作原理
Listener 是事件驱动机制的实现,主要处理以下事件:
public class MyContextListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent sce) {
// 应用启动时初始化资源
ServletContext context = sce.getServletContext();
context.setAttribute("initTime", System.currentTimeMillis());
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
// 应用关闭时释放资源
}
}
关键原理:
- 监听器通过
@WebListener 注解或 web.xml 配置 - 事件类型包括:
ContextInitialized、ContextDestroyed、RequestInitialized 等 - 适用于全局资源管理(如缓存、数据库连接池)
3. AJAX 与 JSON 的协作原理
AJAX 是通过 XMLHttpRequest 实现的异步通信,JSON 是数据交换的标准化格式。其核心流程如下:
// 前端
fetch('/api/data', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ query: 'test' })
})
.then(response => response.json())
.then(data => console.log(data));
// 后端
@WebServlet("/api/data")
public class DataServlet extends HttpServlet {
protected void doPost(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
String json = req.getReader().lines().collect(Collectors.joining());
JSONObject obj = new JSONObject(json);
res.setContentType("application/json");
res.getWriter().print(new JSONObject().put("result", "success"));
}
}
关键原理:
- 前端通过
fetch 发起异步请求 - 后端返回 JSON 格式的响应
- 跨域请求需要配置 CORS 头
三、环境准备
1. 开发环境要求
- JDK 1.8+
- Tomcat 9.x
- Maven 3.x
- IDE: IntelliJ IDEA 或 Eclipse
2. 项目结构示例
src/
├── main/
│ ├── java/
│ │ └── com.example.filter
│ │ ├── AuthFilter.java
│ │ └── MyContextListener.java
│ └── webapp/
│ ├── WEB-INF/
│ │ └── web.xml
│ └── index.jsp
3. Maven 依赖(关键部分)
<dependencies>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
</dependency>
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
<version>20231013</version>
</dependency>
</dependencies>
四、核心实现
1. Filter 的实现与配置
关键代码:
@WebFilter("/auth/*")
public class AuthFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse res = (HttpServletResponse) response;
String token = req.getHeader("X-Token");
if (token == null || !isValidToken(token)) {
res.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
res.setContentType("application/json");
res.getWriter().print("{\"error\": \"Unauthorized\"}");
return;
}
chain.doFilter(request, response);
}
private boolean isValidToken(String token) {
// 实际中应调用 JWT 验证方法
return token.equals("valid_token");
}
}
关键点解释:
@WebFilter 注解替代了传统的 web.xml 配置FilterChain 是请求处理的关键接口- 需要处理
HttpServletResponse 的状态码和内容类型
2. Listener 的实现与配置
关键代码:
@WebListener
public class MyContextListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent sce) {
ServletContext context = sce.getServletContext();
context.setAttribute("initTime", System.currentTimeMillis());
context.log("Application started at " + new Date());
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
ServletContext context = sce.getServletContext();
context.log("Application stopped at " + new Date());
}
}
关键点解释:
@WebListener 是替代 web.xml 的注解方式contextInitialized 用于初始化全局资源- 注意避免在
contextDestroyed 中进行耗时操作
3. AJAX 与 JSON 的完整实现
前端代码(index.jsp):
<%@ page contentType="text/html;charset=UTF-8" %>
<html>
<head>
<title>AJAX Test</title>
</head>
<body>
<button onclick="fetchData()">Fetch Data</button>
<div id="result"></div>
<script>
function fetchData() {
fetch('/api/data', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ query: 'test' })
})
.then(response => {
if (!response.ok) throw new Error('Network response was not OK');
return response.json();
})
.then(data => {
document.getElementById('result').innerText = JSON.stringify(data);
})
.catch(error => {
console.error('Error:', error);
});
}
</script>
</body>
</html>
后端代码(DataServlet.java):
@WebServlet("/api/data")
public class DataServlet extends HttpServlet {
protected void doPost(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
String json = req.getReader().lines().collect(Collectors.joining());
JSONObject obj = new JSONObject(json);
String query = obj.getString("query");
res.setContentType("application/json");
res.setCharacterEncoding("UTF-8");
res.getWriter().print(new JSONObject().put("result", "success").put("query", query));
}
}
关键点解释:
- 使用
fetch 替代 XMLHttpRequest 简化代码 - 后端需要处理 JSON 请求体
- 跨域请求需配置
Access-Control-Allow-Origin 头
五、完整案例:用户登录系统
1. 系统需求
实现一个支持异步登录的系统,要求:
- 使用 Filter 实现权限控制
- 使用 Listener 初始化登录日志
- 使用 AJAX 与 JSON 实现登录接口
2. 项目结构
src/
├── main/
│ ├── java/
│ │ └── com.example.filter
│ │ ├── AuthFilter.java
│ │ └── LoginListener.java
│ └── webapp/
│ ├── WEB-INF/
│ │ └── web.xml
│ └── login.jsp
3. 关键代码
Filter 实现:
@WebFilter("/auth/*")
public class AuthFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse res = (HttpServletResponse) response;
String token = req.getHeader("X-Token");
if (token == null || !isValidToken(token)) {
res.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
res.setContentType("application/json");
res.getWriter().print("{\"error\": \"Unauthorized\"}");
return;
}
chain.doFilter(request, response);
}
private boolean isValidToken(String token) {
// 模拟验证逻辑
return token.equals("valid_token");
}
}
Listener 实现:
@WebListener
public class LoginListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent sce) {
ServletContext context = sce.getServletContext();
context.setAttribute("loginCount", 0);
context.log("Login system initialized");
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
ServletContext context = sce.getServletContext();
context.log("Login system destroyed");
}
}
前端页面(login.jsp):
<%@ page contentType="text/html;charset=UTF-8" %>
<html>
<head>
<title>Login System</title>
</head>
<body>
<h2>Login</h2>
<form id="loginForm">
<input type="text" name="username" placeholder="Username" required>
<input type="password" name="password" placeholder="Password" required>
<button type="submit">Login</button>
</form>
<div id="result"></div>
<script>
document.getElementById('loginForm').addEventListener('submit', function(e) {
e.preventDefault();
const formData = new FormData(this);
const data = {};
for (const [key, value] of formData.entries()) {
data[key] = value;
}
fetch('/api/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
})
.then(response => {
if (!response.ok) throw new Error('Network response was not OK');
return response.json();
})
.then(data => {
document.getElementById('result').innerText = JSON.stringify(data);
})
.catch(error => {
console.error('Error:', error);
});
});
</script>
</body>
</html>
后端接口(LoginServlet.java):
@WebServlet("/api/login")
public class LoginServlet extends HttpServlet {
protected void doPost(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
String json = req.getReader().lines().collect(Collectors.joining());
JSONObject obj = new JSONObject(json);
String username = obj.getString("username");
String password = obj.getString("password");
// 模拟验证逻辑
if (username.equals("admin") && password.equals("123456")) {
res.setContentType("application/json");
res.setCharacterEncoding("UTF-8");
res.getWriter().print(new JSONObject().put("result", "success").put("token", "valid_token"));
} else {
res.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
res.setContentType("application/json");
res.getWriter().print("{\"error\": \"Invalid credentials\"}");
}
}
}
关键点解释:
- 使用 Filter 实现登录态的统一校验
- Listener 用于初始化全局登录计数器
- AJAX 请求与 JSON 返回的完整流程
六、源码解析
1. Filter 的执行流程
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
// 前置处理
if (validateRequest(request)) {
chain.doFilter(request, response); // 执行后续 Filter 或 Servlet
} else {
// 拦截处理
}
}
关键点:
FilterChain 是请求处理的链式结构- 每个 Filter 可以选择是否放行请求
- 需要处理
HttpServletResponse 的状态码和内容类型
2. Listener 的生命周期管理
@Override
public void contextInitialized(ServletContextEvent sce) {
// 初始化资源
ServletContext context = sce.getServletContext();
context.setAttribute("initTime", System.currentTimeMillis());
}
关键点:
contextInitialized 是应用启动时调用contextDestroyed 是应用关闭时调用- 需要避免在
contextDestroyed 中进行耗时操作
3. AJAX 请求的完整流程
fetch('/api/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username: 'admin', password: '123456' })
})
.then(response => response.json())
.then(data => console.log(data));
关键点:
fetch 是现代浏览器支持的 API- 需要处理
Content-Type 头 - 需要处理
response.json() 的解析
七、进阶使用
1. Filter 的进阶使用场景
场景一:日志记录
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
long start = System.currentTimeMillis();
chain.doFilter(request, response);
long duration = System.currentTimeMillis() - start;
logger.info("Request processed in {} ms", duration);
}
场景二:请求限流
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
if (rateLimiter.acquire()) {
chain.doFilter(request, response);
} else {
res.setStatus(HttpServletResponse.SC_TOO_MANY_REQUESTS);
res.getWriter().print("{\"error\": \"Rate limit exceeded\"}");
}
}
2. Listener 的进阶使用场景
场景一:全局缓存初始化
public void contextInitialized(ServletContextEvent sce) {
ServletContext context = sce.getServletContext();
context.setAttribute("cache", new Cache());
}
场景二:配置文件加载
public void contextInitialized(ServletContextEvent sce) {
ServletContext context = sce.getServletContext();
String config = context.getInitParameter("config");
loadConfig(config);
}
3. AJAX 与 JSON 的进阶使用场景
场景一:分页加载
function loadPage(page) {
fetch('/api/data?page=' + page, {
method: 'GET'
})
.then(response => response.json())
.then(data => {
// 渲染分页内容
});
}
场景二:文件上传
function uploadFile(file) {
const formData = new FormData();
formData.append('file', file);
fetch('/api/upload', {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
// 处理上传结果
});
}
八、性能与工程实践
1. 性能优化方法
Filter 优化:
- 避免在 Filter 中进行复杂计算
- 使用缓存机制减少重复校验
Listener 优化:
- 在
contextInitialized 中初始化资源时使用线程安全机制 - 使用
ServletContext 的 getInitParameter 获取配置参数
AJAX 优化:
- 使用 HTTP/2 提升传输效率
- 对于大量数据使用分页加载
2. 安全风险分析
Filter 安全风险:
- 未正确验证 Token 可能导致权限越权
- 未处理异常可能暴露敏感信息
JSON 安全风险:
- 未过滤输入可能导致 XSS 攻击
- 未验证 JSON 格式可能引发数据解析错误
3. 异常处理机制
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
try {
chain.doFilter(request, response);
} catch (Exception e) {
res.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
res.getWriter().print("{\"error\": \"Internal server error\"}");
}
}
关键点:
- 需要捕获所有异常并返回标准错误响应
- 避免在日志中暴露敏感信息
九、常见问题与踩坑
1. Filter 配置错误
错误示例:
@WebFilter("/auth/*")
问题:
- 未指定
urlPatterns 可能导致过滤器未生效 - 需要确保配置的 URL 路径正确
解决办法:
@WebFilter(urlPatterns = "/auth/*")
2. Listener 未注册
错误示例:
public class MyContextListener implements ServletContextListener {
// 没有实现方法
}
问题:
- 未实现
contextInitialized 或 contextDestroyed 方法 - 导致监听器未被注册
解决办法:
public class MyContextListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent sce) {
// 初始化逻辑
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
// 清理逻辑
}
}
3. AJAX 跨域问题
错误示例:
fetch('http://localhost:8080/api/login', {
method: 'POST'
});
问题:
- 浏览器默认阻止跨域请求
- 需要服务器端配置 CORS 头
解决办法:
@Override
protected void doOptions(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
res.setHeader("Access-Control-Allow-Origin", "*");
res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
res.setHeader("Access-Control-Allow-Headers", "Content-Type, X-Token");
res.setHeader("Access-Control-Allow-Credentials", "true");
res.setStatus(HttpServletResponse.SC_OK);
}
十、最佳实践
1. Filter 使用建议
- 仅用于核心功能:如权限控制、日志记录、性能监控
- 避免复杂业务逻辑:Filter 应专注于请求处理,而非业务逻辑
- 避免阻塞操作:如数据库查询、文件读取等应异步处理
2. Listener 使用建议
- 用于全局资源管理:如缓存、数据库连接池、配置加载
- 避免频繁创建资源:应在
contextInitialized 中初始化,contextDestroyed 中释放 - 确保线程安全:访问共享资源时需加锁或使用线程安全类
3. AJAX 与 JSON 使用建议
- 使用 HTTP/2:提升传输效率
- 避免过度请求:使用分页、懒加载等技术
- 处理异常:在
fetch 中添加 try-catch 块
十一、总结
Filter、Listener、AJAX 和 JSON 是构建现代 JavaWeb 应用的四大核心技术。它们分别解决了请求拦截、事件驱动、异步交互和数据交换等核心问题。在实际开发中,需要根据具体场景选择合适的实现方式:
- Filter 适用于统一处理请求,如权限控制、日志记录
- Listener 适用于全局资源管理,如缓存初始化、配置加载
- AJAX 与 JSON 适用于异步交互,提升用户体验
在使用过程中需要注意以下几点:
- 避免滥用 Filter 和 Listener,防止性能瓶颈
- 处理 AJAX 请求时需考虑跨域和安全问题
- 使用 JSON 时需进行输入验证和安全处理
通过合理使用这些技术,可以构建出高性能、可维护的 JavaWeb 应用。