【JavaWeb手册002】深入理解Servlet规范构建

  • 时间:2025-11-28 23:12 作者: 来源: 阅读:9
  • 扫一扫,手机访问
摘要: “如果说Tomcat是Java Web世界的’执法机构’,那么Servlet规范就是这片天地的’根本大法’。它不关心你用什么框架,只定义了一套所有Web容器都必须遵守的行为准则。” 2.1 Servlet接口:生命周期的艺术 在Java Web的世界里,一切组件的生命周期都由Servlet接口严格定义。这不是一个简单的API,而是一套精妙的契约。 // Servlet接口定义了

“如果说Tomcat是Java Web世界的’执法机构’,那么Servlet规范就是这片天地的’根本大法’。它不关心你用什么框架,只定义了一套所有Web容器都必须遵守的行为准则。”

2.1 Servlet接口:生命周期的艺术

在Java Web的世界里,一切组件的生命周期都由Servlet接口严格定义。这不是一个简单的API,而是一套精妙的契约。


// Servlet接口定义了Web组件的完整生命周期
public interface Servlet {
    // 诞生时刻 - 容器在Servlet实例化后立即调用
    void init(ServletConfig config) throws ServletException;
    
    // 服务时刻 - 处理请求的核心方法
    void service(ServletRequest req, ServletResponse res) 
        throws ServletException, IOException;
    
    // 消亡时刻 - 容器在卸载Servlet前调用
    void destroy();
    
    // 身份信息
    ServletConfig getServletConfig();
    String getServletInfo();
}

深入解析生命周期:


public class LifecycleServlet extends HttpServlet {
    private static final Logger logger = LoggerFactory.getLogger(LifecycleServlet.class);
    private List<String> requestHistory = Collections.synchronizedList(new ArrayList<>());
    
    @Override
    public void init() throws ServletException {
        // 注意:这是无参的init(),由GenericServlet提供
        logger.info("🚀 Servlet诞生了!线程: {}", Thread.currentThread().getName());
        
        // 模拟初始化数据库连接、加载配置等一次性操作
        logger.info("📊 初始化数据库连接池...");
        logger.info("🔧 加载系统配置...");
    }
    
    @Override
    public void init(ServletConfig config) throws ServletException {
        // 先调用父类的init(config),它会调用无参的init()
        super.init(config);
        logger.info("⚙️ Servlet配置信息: {}", config.getServletName());
    }
    
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) 
            throws ServletException, IOException {
        
        String requestInfo = String.format("%s %s from %s", 
            req.getMethod(), req.getRequestURI(), req.getRemoteAddr());
        requestHistory.add(requestInfo);
        
        logger.info("🎯 处理请求: {} (历史请求数: {})", 
            requestInfo, requestHistory.size());
        
        // 调用父类的service方法,它会根据请求方法路由到doGet/doPost等
        super.service(req, resp);
    }
    
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) 
            throws ServletException, IOException {
        
        resp.setContentType("text/html;charset=UTF-8");
        PrintWriter out = resp.getWriter();
        
        out.println("<html>");
        out.println("<head><title>Servlet生命周期</title></head>");
        out.println("<body>");
        out.println("<h1>Servlet生命周期演示</h1>");
        out.println("<p>当前时间: " + new Date() + "</p>");
        out.println("<p>请求历史数量: " + requestHistory.size() + "</p>");
        out.println("<ul>");
        for (String history : requestHistory) {
            out.println("<li>" + history + "</li>");
        }
        out.println("</ul>");
        out.println("</body>");
        out.println("</html>");
    }
    
    @Override
    public void destroy() {
        logger.info("💀 Servlet即将消亡...");
        logger.info("📈 统计信息: 总共处理了 {} 个请求", requestHistory.size());
        
        // 模拟清理资源
        requestHistory.clear();
        logger.info("🧹 资源清理完成");
    }
}

生命周期的重要观察:

单例模式:每个Servlet类在容器中只有一个实例多线程环境:同一个Servlet实例同时服务多个请求线程安全挑战:实例变量需要同步控制初始化时机:可以通过 <load-on-startup>控制

2.2 HttpServletRequest/Response:请求响应的本质

这两个对象看似简单,实则是容器为我们封装的强大工具。理解它们的本质,才能真正理解Web编程。


public class RequestInspectionServlet extends HttpServlet {
    
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) 
            throws ServletException, IOException {
        
        response.setContentType("text/html;charset=UTF-8");
        PrintWriter out = response.getWriter();
        
        out.println("<html><head><title>请求解剖</title></head><body>");
        out.println("<h1>HTTP请求的完整解剖</h1>");
        
        // 1. 请求行信息
        out.println("<h2>请求行</h2>");
        out.println("<p>方法: " + request.getMethod() + "</p>");
        out.println("<p>URL: " + request.getRequestURL() + "</p>");
        out.println("<p>URI: " + request.getRequestURI() + "</p>");
        out.println("<p>协议: " + request.getProtocol() + "</p>");
        
        // 2. 请求头信息
        out.println("<h2>请求头</h2>");
        Enumeration<String> headerNames = request.getHeaderNames();
        while (headerNames.hasMoreElements()) {
            String name = headerNames.nextElement();
            String value = request.getHeader(name);
            out.println("<p><b>" + name + "</b>: " + value + "</p>");
        }
        
        // 3. 参数信息
        out.println("<h2>请求参数</h2>");
        Map<String, String[]> params = request.getParameterMap();
        for (Map.Entry<String, String[]> entry : params.entrySet()) {
            out.println("<p><b>" + entry.getKey() + "</b>: " + 
                String.join(", ", entry.getValue()) + "</p>");
        }
        
        // 4. 属性信息(请求域)
        out.println("<h2>请求属性</h2>");
        Enumeration<String> attrNames = request.getAttributeNames();
        while (attrNames.hasMoreElements()) {
            String name = attrNames.nextElement();
            Object value = request.getAttribute(name);
            out.println("<p><b>" + name + "</b>: " + value + "</p>");
        }
        
        out.println("</body></html>");
    }
    
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) 
            throws ServletException, IOException {
        
        // 演示请求体的读取
        StringBuilder body = new StringBuilder();
        try (BufferedReader reader = request.getReader()) {
            String line;
            while ((line = reader.readLine()) != null) {
                body.append(line);
            }
        }
        
        System.out.println("POST请求体: " + body.toString());
        doGet(request, response);
    }
}

深入理解Request/Response的设计:

门面模式(Facade Pattern)


// 实际的HttpServletRequest可能包含多个底层对象
public class HttpServletRequestFacade implements HttpServletRequest {
    private HttpServletRequest request;
    
    public HttpServletRequestFacade(HttpServletRequest request) {
        this.request = request;
    }
    
    // 所有方法都委托给真实的request对象
    public String getMethod() {
        return request.getMethod();
    }
    // ... 其他方法
}

装饰器模式(Decorator Pattern)


// 可以对Request进行装饰,添加功能
public class LoggingHttpServletRequest extends HttpServletRequestWrapper {
    public LoggingHttpServletRequest(HttpServletRequest request) {
        super(request);
    }
    
    @Override
    public String getParameter(String name) {
        String value = super.getParameter(name);
        System.out.println("获取参数: " + name + " = " + value);
        return value;
    }
}

2.3 Filter:责任链模式的典范

Filter是Servlet规范中责任链模式的经典实现,它允许我们在请求到达Servlet之前和响应返回客户端之后插入处理逻辑。


// 一个完整的Filter链演示
@WebFilter("/*")
public class FilterChainDemo implements Filter {
    private static final Logger logger = LoggerFactory.getLogger(FilterChainDemo.class);
    
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        logger.info("🔧 Filter初始化: {}", filterConfig.getFilterName());
    }
    
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, 
                        FilterChain chain) throws IOException, ServletException {
        
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        HttpServletResponse httpResponse = (HttpServletResponse) response;
        
        long startTime = System.currentTimeMillis();
        logger.info("🛡️ Filter开始处理: {} {}", 
                   httpRequest.getMethod(), httpRequest.getRequestURI());
        
        // 1. 预处理 - 在chain.doFilter之前
        httpRequest.setAttribute("startTime", startTime);
        
        // 设置通用响应头
        httpResponse.setHeader("X-Powered-By", "Java-Servlet");
        
        // 2. 将请求传递给下一个Filter或目标Servlet
        chain.doFilter(request, response);
        
        // 3. 后处理 - 在chain.doFilter之后
        long endTime = System.currentTimeMillis();
        long duration = endTime - startTime;
        
        logger.info("⏱️ 请求处理完成: {} {} - 耗时: {}ms", 
                   httpRequest.getMethod(), httpRequest.getRequestURI(), duration);
        
        // 添加处理时间到响应头
        httpResponse.setHeader("X-Processing-Time", duration + "ms");
    }
    
    @Override
    public void destroy() {
        logger.info("🧹 Filter销毁");
    }
}

多个Filter组成的责任链:


// 认证Filter
@WebFilter("/*")
public class AuthenticationFilter implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, 
                        FilterChain chain) throws IOException, ServletException {
        
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        HttpSession session = httpRequest.getSession(false);
        
        if (session == null || session.getAttribute("user") == null) {
            // 未登录,重定向到登录页面
            ((HttpServletResponse) response).sendRedirect("/login");
            return;
        }
        
        logger.info("✅ 用户已认证: {}", session.getAttribute("user"));
        chain.doFilter(request, response);
    }
}

// 日志Filter  
@WebFilter("/*")
public class LoggingFilter implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response,
                        FilterChain chain) throws IOException, ServletException {
        
        // 请求前日志
        logRequest((HttpServletRequest) request);
        
        chain.doFilter(request, response);
        
        // 响应后日志
        logResponse((HttpServletRequest) request, (HttpServletResponse) response);
    }
    
    private void logRequest(HttpServletRequest request) {
        // 记录请求信息
    }
    
    private void logResponse(HttpServletRequest request, HttpServletResponse response) {
        // 记录响应信息
    }
}

// 编码Filter
@WebFilter("/*")  
public class EncodingFilter implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response,
                        FilterChain chain) throws IOException, ServletException {
        
        request.setCharacterEncoding("UTF-8");
        response.setCharacterEncoding("UTF-8");
        response.setContentType("text/html;charset=UTF-8");
        
        chain.doFilter(request, response);
    }
}

Filter执行顺序:


请求 → EncodingFilter → AuthenticationFilter → LoggingFilter → Target Servlet

2.4 Listener:观察者模式的Web实践

Listener允许我们监听Web应用中的各种事件,是观察者模式在Servlet规范中的完美体现。


// 应用生命周期监听器
@WebListener
public class ApplicationLifecycleListener implements ServletContextListener {
    
    @Override
    public void contextInitialized(ServletContextEvent sce) {
        ServletContext context = sce.getServletContext();
        System.out.println("🎉 Web应用启动: " + context.getContextPath());
        
        // 应用启动时的初始化工作
        initializeDatabasePool(context);
        loadConfiguration(context);
        scheduleBackgroundTasks(context);
    }
    
    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        ServletContext context = sce.getServletContext();
        System.out.println("🛑 Web应用关闭: " + context.getContextPath());
        
        // 应用关闭时的清理工作
        shutdownDatabasePool(context);
        cancelBackgroundTasks(context);
    }
    
    private void initializeDatabasePool(ServletContext context) {
        // 初始化数据库连接池
        System.out.println("📊 初始化数据库连接池...");
        context.setAttribute("dbPool", "模拟的连接池");
    }
    
    private void loadConfiguration(ServletContext context) {
        // 加载配置文件
        System.out.println("⚙️ 加载应用配置...");
    }
    
    private void scheduleBackgroundTasks(ServletContext context) {
        // 调度后台任务
        System.out.println("🕒 启动后台任务调度...");
    }
    
    private void shutdownDatabasePool(ServletContext context) {
        // 关闭数据库连接池
        System.out.println("📊 关闭数据库连接池...");
    }
    
    private void cancelBackgroundTasks(ServletContext context) {
        // 取消后台任务
        System.out.println("🕒 停止后台任务调度...");
    }
}

// Session生命周期监听器
@WebListener
public class SessionLifecycleListener implements HttpSessionListener {
    
    private final AtomicInteger activeSessions = new AtomicInteger();
    
    @Override
    public void sessionCreated(HttpSessionEvent se) {
        int count = activeSessions.incrementAndGet();
        HttpSession session = se.getSession();
        
        System.out.println("🆕 Session创建: " + session.getId());
        System.out.println("👥 活跃Session数: " + count);
        
        // 设置Session超时时间(秒)
        session.setMaxInactiveInterval(30 * 60); // 30分钟
    }
    
    @Override
    public void sessionDestroyed(HttpSessionEvent se) {
        int count = activeSessions.decrementAndGet();
        HttpSession session = se.getSession();
        
        System.out.println("🗑️ Session销毁: " + session.getId());
        System.out.println("👥 剩余活跃Session数: " + count);
        
        // 清理Session相关资源
        cleanupSessionResources(session);
    }
    
    private void cleanupSessionResources(HttpSession session) {
        // 清理Session相关的临时文件、缓存等
        System.out.println("🧹 清理Session资源: " + session.getId());
    }
}

// 请求生命周期监听器
@WebListener
public class RequestLifecycleListener implements ServletRequestListener {
    
    private final ThreadLocal<Long> startTime = new ThreadLocal<>();
    
    @Override
    public void requestInitialized(ServletRequestEvent sre) {
        startTime.set(System.currentTimeMillis());
        
        HttpServletRequest request = (HttpServletRequest) sre.getServletRequest();
        System.out.println("📨 请求到达: " + request.getMethod() + " " + request.getRequestURI());
    }
    
    @Override
    public void requestDestroyed(ServletRequestEvent sre) {
        Long start = startTime.get();
        if (start != null) {
            long duration = System.currentTimeMillis() - start;
            
            HttpServletRequest request = (HttpServletRequest) sre.getServletRequest();
            System.out.println("📤 请求完成: " + request.getMethod() + " " + 
                             request.getRequestURI() + " - 耗时: " + duration + "ms");
        }
        startTime.remove();
    }
}

2.5 会话管理:HttpSession的深度探索

Session机制是Web开发中状态管理的核心,理解它的工作原理至关重要。


@WebServlet("/session-demo")
public class SessionDemoServlet extends HttpServlet {
    
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) 
            throws ServletException, IOException {
        
        response.setContentType("text/html;charset=UTF-8");
        PrintWriter out = response.getWriter();
        
        // 获取Session,如果不存在则创建
        HttpSession session = request.getSession();
        
        out.println("<html>");
        out.println("<head><title>Session深度探索</title></head>");
        out.println("<body>");
        out.println("<h1>Session机制深度解析</h1>");
        
        // Session基本信息
        out.println("<h2>Session基本信息</h2>");
        out.println("<p>Session ID: " + session.getId() + "</p>");
        out.println("<p>创建时间: " + new Date(session.getCreationTime()) + "</p>");
        out.println("<p>最后访问: " + new Date(session.getLastAccessedTime()) + "</p>");
        out.println("<p>最大空闲时间: " + session.getMaxInactiveInterval() + "秒</p>");
        out.println("<p>是否新Session: " + session.isNew() + "</p>");
        
        // Session属性操作
        Integer visitCount = (Integer) session.getAttribute("visitCount");
        if (visitCount == null) {
            visitCount = 1;
        } else {
            visitCount++;
        }
        session.setAttribute("visitCount", visitCount);
        
        out.println("<p>访问次数: " + visitCount + "</p>");
        
        // 显示所有Session属性
        out.println("<h2>Session中的所有属性</h2>");
        Enumeration<String> attributeNames = session.getAttributeNames();
        while (attributeNames.hasMoreElements()) {
            String name = attributeNames.nextElement();
            Object value = session.getAttribute(name);
            out.println("<p><b>" + name + "</b>: " + value + "</p>");
        }
        
        // Session操作按钮
        out.println("<h2>Session操作</h2>");
        out.println("<form method='post'>");
        out.println("<button type='submit' name='action' value='invalidate'>使Session失效</button>");
        out.println("<button type='submit' name='action' value='addAttribute'>添加属性</button>");
        out.println("</form>");
        
        out.println("</body>");
        out.println("</html>");
    }
    
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) 
            throws ServletException, IOException {
        
        String action = request.getParameter("action");
        HttpSession session = request.getSession();
        
        if ("invalidate".equals(action)) {
            session.invalidate();
            System.out.println("Session已被手动失效: " + session.getId());
        } else if ("addAttribute".equals(action)) {
            String key = "key_" + System.currentTimeMillis();
            String value = "value_" + (int)(Math.random() * 1000);
            session.setAttribute(key, value);
            System.out.println("添加Session属性: " + key + " = " + value);
        }
        
        response.sendRedirect(request.getRequestURI());
    }
}

Session的工作原理深度解析:


// 模拟Session管理器的实现
public class SimpleSessionManager {
    private final Map<String, HttpSession> sessions = new ConcurrentHashMap<>();
    private final ScheduledExecutorService cleaner = Executors.newScheduledThreadPool(1);
    
    public SimpleSessionManager() {
        // 定时清理过期Session
        cleaner.scheduleAtFixedRate(this::cleanExpiredSessions, 1, 1, TimeUnit.MINUTES);
    }
    
    public HttpSession getSession(String sessionId, boolean create) {
        if (sessionId != null) {
            HttpSession session = sessions.get(sessionId);
            if (session != null && !session.isExpired()) {
                session.access(); // 更新最后访问时间
                return session;
            }
        }
        
        if (create) {
            return createSession();
        }
        
        return null;
    }
    
    private HttpSession createSession() {
        String sessionId = generateSessionId();
        HttpSession session = new SimpleHttpSession(sessionId);
        sessions.put(sessionId, session);
        return session;
    }
    
    private void cleanExpiredSessions() {
        Iterator<Map.Entry<String, HttpSession>> it = sessions.entrySet().iterator();
        while (it.hasNext()) {
            Map.Entry<String, HttpSession> entry = it.next();
            if (entry.getValue().isExpired()) {
                it.remove();
                System.out.println("清理过期Session: " + entry.getKey());
            }
        }
    }
    
    private String generateSessionId() {
        return UUID.randomUUID().toString();
    }
}

2.6 JSP的本质:Servlet的"语法糖"

很多人认为JSP是独立的技术,实际上它只是Servlet的另一种表现形式。

JSP到Servlet的转换过程:


<%-- sample.jsp --%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>JSP示例</title>
</head>
<body>
    <h1>欢迎来到JSP世界</h1>
    
    <%-- 脚本片段 --%>
    <% 
        String name = request.getParameter("name");
        if (name == null) name = "游客";
    %>
    
    <%-- 表达式 --%>
    <p>你好, <%= name %>!</p>
    
    <%-- 声明 --%>
    <%!
        private int visitCount = 0;
        
        public String getWelcomeMessage() {
            return "这是第" + (++visitCount) + "次访问";
        }
    %>
    
    <p><%= getWelcomeMessage() %></p>
    
    <%-- 指令 --%>
    <%@ include file="footer.jsp" %>
</body>
</html>

转换后的Servlet代码(简化版):


public class sample_jsp extends HttpServlet {
    private int visitCount = 0;
    
    public String getWelcomeMessage() {
        return "这是第" + (++visitCount) + "次访问";
    }
    
    protected void service(HttpServletRequest request, HttpServletResponse response) 
            throws ServletException, IOException {
        
        response.setContentType("text/html;charset=UTF-8");
        PrintWriter out = response.getWriter();
        
        // JSP转换后的代码
        out.write("<html>");
        out.write("<head><title>JSP示例</title></head>");
        out.write("<body>");
        out.write("<h1>欢迎来到JSP世界</h1>");
        
        // 脚本片段转换
        String name = request.getParameter("name");
        if (name == null) name = "游客";
        
        // 表达式转换
        out.write("<p>你好, ");
        out.print(name);
        out.write("!</p>");
        
        // 声明的方法调用
        out.write("<p>");
        out.print(getWelcomeMessage());
        out.write("</p>");
        
        // include指令转换
        out.write("<!-- footer内容 -->");
        
        out.write("</body>");
        out.write("</html>");
    }
}

总结

本章我们深入探索了Servlet规范这个Java Web世界的"宪法"。从Servlet的生命周期,到Request/Response的本质,再到Filter、Listener的设计模式应用,我们看到了一个完整而优雅的设计体系。

关键洞察:

契约精神:Servlet规范定义了容器和组件之间的明确契约设计模式:Filter的责任链、Listener的观察者模式体现了优秀的设计生命周期管理:明确的生命周期让资源管理变得可控状态管理:Session机制巧妙地解决了HTTP无状态的问题

“理解Servlet规范,就像理解交通规则。无论你开什么车(使用什么框架),都要遵守同样的交通规则。这保证了整个生态的互操作性和一致性。”


思考题与答案

思考题1:为什么Servlet是单例的?如果我们需要在Servlet中保存用户特定的数据,应该怎么做?

答案:

为什么采用单例模式:

性能优化:避免频繁创建和销毁对象的开销资源节约:大量并发请求时,内存占用保持稳定设计哲学:Servlet应该专注于业务逻辑,而不是状态管理

正确保存用户特定数据的方法:


// ❌ 错误方式:使用实例变量(线程不安全)
public class UnsafeServlet extends HttpServlet {
    private String currentUser; // 多个用户会相互覆盖
    
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
        currentUser = req.getParameter("user"); // 竞态条件!
        // ... 使用currentUser
    }
}

// ✅ 正确方式1:使用局部变量(线程安全)
public class SafeServlet1 extends HttpServlet {
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
        String currentUser = req.getParameter("user"); // 局部变量,线程安全
        // ... 使用currentUser
    }
}

// ✅ 正确方式2:使用Session(用户级别状态)
public class SafeServlet2 extends HttpServlet {
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
        HttpSession session = req.getSession();
        session.setAttribute("user", req.getParameter("user"));
        String currentUser = (String) session.getAttribute("user");
        // ... 使用currentUser
    }
}

// ✅ 正确方式3:使用ThreadLocal(请求级别状态)
public class SafeServlet3 extends HttpServlet {
    private static final ThreadLocal<String> currentUser = new ThreadLocal<>();
    
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
        try {
            currentUser.set(req.getParameter("user"));
            // ... 使用currentUser.get()
        } finally {
            currentUser.remove(); // 重要:清理ThreadLocal,避免内存泄漏
        }
    }
}

思考题2:Filter链中,如果某个Filter没有调用FilterChain.doFilter()方法,会发生什么?

答案:

如果Filter没有调用 chain.doFilter(),请求处理链会在该Filter处中断,后续的Filter和目标Servlet都不会被执行。

这种机制的实际应用场景:


public class AuthenticationFilter implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response,
                        FilterChain chain) throws IOException, ServletException {
        
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        HttpServletResponse httpResponse = (HttpServletResponse) response;
        
        // 检查用户是否登录
        if (!isAuthenticated(httpRequest)) {
            // 未认证,中断Filter链,直接返回401错误
            httpResponse.sendError(401, "需要身份认证");
            return; // 注意:这里没有调用chain.doFilter()
        }
        
        // 已认证,继续执行后续Filter和Servlet
        chain.doFilter(request, response);
    }
    
    private boolean isAuthenticated(HttpServletRequest request) {
        HttpSession session = request.getSession(false);
        return session != null && session.getAttribute("user") != null;
    }
}

public class LoggingFilter implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response,
                        FilterChain chain) throws IOException, ServletException {
        
        System.out.println("请求到达: " + ((HttpServletRequest) request).getRequestURI());
        
        chain.doFilter(request, response);
        
        System.out.println("请求完成: " + ((HttpServletRequest) request).getRequestURI());
    }
}

执行流程:


请求 → AuthenticationFilter → [如果未认证] → 返回401,流程结束
     ↓ [如果已认证]
     → LoggingFilter → Target Servlet

思考题3:Session是如何在集群环境中实现的?

答案:

在单机环境中,Session存储在Web容器的内存中。但在集群环境中,需要解决Session共享问题。

集群Session的解决方案:

Session粘滞(Sticky Session)


// 负载均衡器通过Cookie或URL重写将同一用户的请求总是路由到同一台服务器
// 优点:实现简单,性能好
// 缺点:缺乏容错性,服务器宕机导致Session丢失

Session复制(Session Replication)


// 所有服务器间同步Session数据
// 配置示例(Tomcat):
// server.xml:
<Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster"/>

// web.xml:
<distributable/>

// 优点:容错性好
// 缺点:网络开销大,扩展性差

集中式Session存储(推荐)


// 使用外部存储如Redis、数据库等集中管理Session
public class RedisSessionManager {
    private JedisPool jedisPool;
    
    public void setAttribute(String sessionId, String key, Object value) {
        try (Jedis jedis = jedisPool.getResource()) {
            jedis.hset("session:" + sessionId, key, serialize(value));
            jedis.expire("session:" + sessionId, 1800); // 30分钟过期
        }
    }
    
    public Object getAttribute(String sessionId, String key) {
        try (Jedis jedis = jedisPool.getResource()) {
            String data = jedis.hget("session:" + sessionId, key);
            return deserialize(data);
        }
    }
}

// 通过Filter实现Session的统一管理
public class DistributedSessionFilter implements Filter {
    private RedisSessionManager sessionManager;
    
    @Override
    public void doFilter(ServletRequest request, ServletResponse response,
                        FilterChain chain) throws IOException, ServletException {
        
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        HttpServletResponse httpResponse = (HttpServletResponse) response;
        
        // 包装Request,提供分布式Session支持
        DistributedSessionRequest wrappedRequest = 
            new DistributedSessionRequest(httpRequest, sessionManager);
        
        chain.doFilter(wrappedRequest, response);
    }
}

无状态设计(现代微服务架构推荐)


// 使用JWT等Token机制,完全避免服务器端Session
public class StatelessAuthentication {
    public String createToken(User user) {
        return JWT.create()
            .withSubject(user.getId())
            .withExpiresAt(new Date(System.currentTimeMillis() + 3600000))
            .sign(Algorithm.HMAC256("secret"));
    }
    
    public User validateToken(String token) {
        // 验证Token并返回用户信息
        // 不需要服务器端存储Session
    }
}

最佳实践建议:

中小型集群:Session粘滞 + 简单的故障转移大型分布式系统:集中式Session存储(Redis)现代微服务:无状态设计 + JWT

(下一章预告:我们将探索Spring框架如何基于Servlet规范构建,以及它如何通过控制反转(IoC)和面向切面编程(AOP)等特性,彻底改变Java Web开发的方式。)

  • 全部评论(0)
手机二维码手机访问领取大礼包
返回顶部