JavaWeb中Servlet核心知识点与实战案例详解

275 阅读4分钟

作为后端Java工程师,Servlet是构建动态Web应用的核心组件。本文将从基础概念到高级特性,结合代码案例系统梳理Servlet知识体系。


一、Servlet基础概念

1. Servlet定义与作用

  • 定义:Servlet是Java EE(现Jakarta EE)规范的一部分,是运行在Web服务器上的Java程序,用于处理HTTP请求并生成动态响应。

  • 作用

    • 接收客户端请求(如HTML表单提交)
    • 执行业务逻辑(如数据库操作)
    • 生成动态响应(如HTML页面、JSON数据)

2. Servlet与CGI对比

特性ServletCGI
进程模型单实例多线程每请求创建新进程
性能高(线程切换开销小)低(进程创建开销大)
资源占用低(共享JVM内存)高(每个进程独立内存)
开发复杂度低(使用Java API)高(需处理进程间通信)

二、Servlet核心组件

1. Servlet生命周期

  • 加载与实例化:首次请求时由容器加载并实例化
  • 初始化(init) :执行一次,用于资源初始化
  • 服务(service) :处理每个请求,调用doGet/doPost等方法
  • 销毁(destroy) :应用关闭时释放资源

示例代码

	import javax.servlet.*;

	import javax.servlet.http.*;

	import java.io.*;

	 

	public class LifeCycleServlet extends HttpServlet {

	    @Override

	    public void init() throws ServletException {

	        System.out.println("Servlet初始化");

	    }

	 

	    @Override

	    protected void doGet(HttpServletRequest req, HttpServletResponse resp) 

	        throws ServletException, IOException {

	        resp.getWriter().println("<h1>Servlet服务中</h1>");

	    }

	 

	    @Override

	    public void destroy() {

	        System.out.println("Servlet销毁");

	    }

	}

2. 请求与响应对象

  • HttpServletRequest:获取请求参数、头信息、会话等
  • HttpServletResponse:设置响应状态、头信息、输出内容

示例代码

	@Override

	protected void doGet(HttpServletRequest req, HttpServletResponse resp) 

	    throws ServletException, IOException {

	    // 获取请求参数

	    String name = req.getParameter("name");

	    

	    // 设置响应内容类型

	    resp.setContentType("text/html;charset=UTF-8");

	    

	    // 输出响应内容

	    PrintWriter out = resp.getWriter();

	    out.println("<h1>Hello, " + name + "!</h1>");

	}

3. 会话管理(HttpSession)

  • 创建会话request.getSession(true)
  • 存储数据session.setAttribute("key", value)
  • 获取数据session.getAttribute("key")
  • 销毁会话session.invalidate()

示例代码

	@Override

	protected void doGet(HttpServletRequest req, HttpServletResponse resp) 

	    throws ServletException, IOException {

	    HttpSession session = req.getSession(true);

	    

	    // 首次访问时设置用户名

	    if (session.getAttribute("username") == null) {

	        session.setAttribute("username", "Guest");

	    }

	    

	    resp.getWriter().println("当前用户: " + session.getAttribute("username"));

	}

三、Servlet配置与部署

1. 传统配置方式(web.xml)

	<servlet>

	    <servlet-name>HelloServlet</servlet-name>

	    <servlet-class>com.example.HelloServlet</servlet-class>

	    <load-on-startup>1</load-on-startup> <!-- 启动时加载 -->

	</servlet>

	<servlet-mapping>

	    <servlet-name>HelloServlet</servlet-name>

	    <url-pattern>/hello</url-pattern>

	</servlet-mapping>

2. 注解配置方式(Servlet 3.0+)

	@WebServlet(

	    name = "HelloServlet",

	    urlPatterns = {"/hello", "/hi"},

	    initParams = {

	        @WebInitParam(name = "adminEmail", value = "admin@example.com")

	    },

	    loadOnStartup = 1

	)

	public class HelloServlet extends HttpServlet {

	    // ...

	}

3. 部署到Tomcat

  1. 打包为WAR文件mvn clean package

  2. 部署到Tomcat

    • 复制WAR文件到$CATALINA_HOME/webapps/
    • 或通过IDE直接运行(如IntelliJ的Tomcat配置)

四、Servlet高级特性

1. 线程安全问题

  • 原因:Servlet实例单例,多线程共享成员变量

  • 解决方案

    • 避免使用成员变量存储请求级数据
    • 使用ThreadLocal存储线程本地变量
    • 对共享资源加锁(如synchronized

错误示例(线程不安全):

	public class UnsafeServlet extends HttpServlet {

	    private int count = 0; // 成员变量,多线程共享

	    

	    @Override

	    protected void doGet(HttpServletRequest req, HttpServletResponse resp) 

	        throws ServletException, IOException {

	        count++; // 线程不安全

	        resp.getWriter().println("Count: " + count);

	    }

	}

修正方案(使用局部变量):

	public class SafeServlet extends HttpServlet {

	    @Override

	    protected void doGet(HttpServletRequest req, HttpServletResponse resp) 

	        throws ServletException, IOException {

	        int count = 0; // 局部变量,线程安全

	        count++;

	        resp.getWriter().println("Count: " + count);

	    }

	}

2. 过滤器(Filter)

  • 作用:拦截请求/响应,实现日志记录、权限校验等
  • 示例代码
	@WebFilter("/*") // 拦截所有请求

	public class LoggingFilter implements Filter {

	    @Override

	    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) 

	        throws IOException, ServletException {

	        System.out.println("请求URI: " + ((HttpServletRequest) request).getRequestURI());

	        chain.doFilter(request, response); // 继续处理

	    }

	}

3. 监听器(Listener)

  • 作用:监听应用、会话、请求的生命周期事件
  • 示例代码
	@WebListener

	public class AppContextListener implements ServletContextListener {

	    @Override

	    public void contextInitialized(ServletContextEvent sce) {

	        System.out.println("应用启动");

	    }

	 

	    @Override

	    public void contextDestroyed(ServletContextEvent sce) {

	        System.out.println("应用关闭");

	    }

	}

五、Servlet实战案例

案例1:用户登录系统

  1. 前端表单
	<form action="/login" method="post">

	    用户名: <input type="text" name="username"><br>

	    密码: <input type="password" name="password"><br>

	    <input type="submit" value="登录">

	</form>
  1. Servlet处理
	@WebServlet("/login")

	public class LoginServlet extends HttpServlet {

	    @Override

	    protected void doPost(HttpServletRequest req, HttpServletResponse resp) 

	        throws ServletException, IOException {

	        String username = req.getParameter("username");

	        String password = req.getParameter("password");

	        

	        // 简单校验(实际项目应连接数据库)

	        if ("admin".equals(username) && "123456".equals(password)) {

	            // 登录成功,设置会话

	            HttpSession session = req.getSession(true);

	            session.setAttribute("user", username);

	            resp.sendRedirect("/welcome");

	        } else {

	            resp.getWriter().println("用户名或密码错误");

	        }

	    }

	}
  1. 欢迎页面
	@WebServlet("/welcome")

	public class WelcomeServlet extends HttpServlet {

	    @Override

	    protected void doGet(HttpServletRequest req, HttpServletResponse resp) 

	        throws ServletException, IOException {

	        HttpSession session = req.getSession(false); // 不创建新会话

	        if (session == null || session.getAttribute("user") == null) {

	            resp.sendRedirect("/login");

	        } else {

	            resp.getWriter().println("欢迎, " + session.getAttribute("user"));

	        }

	    }

	}

案例2:文件上传

  1. 前端表单
	<form action="/upload" method="post" enctype="multipart/form-data">

	    文件: <input type="file" name="file"><br>

	    <input type="submit" value="上传">

	</form>
  1. Servlet处理(需引入Apache Commons FileUpload):
	@WebServlet("/upload")

	@MultipartConfig

	public class UploadServlet extends HttpServlet {

	    @Override

	    protected void doPost(HttpServletRequest req, HttpServletResponse resp) 

	        throws ServletException, IOException {

	        Part filePart = req.getPart("file");

	        String fileName = filePart.getSubmittedFileName();

	        

	        // 保存文件到服务器

	        try (InputStream fileContent = filePart.getInputStream();

	             FileOutputStream out = new FileOutputStream("/uploads/" + fileName)) {

	            IOUtils.copy(fileContent, out); // 使用Apache Commons IO

	        }

	        

	        resp.getWriter().println("文件上传成功: " + fileName);

	    }

	}

六、Servlet与现代框架

  1. 与Spring MVC的关系

    • Servlet是底层协议处理器
    • Spring MVC的DispatcherServlet继承自HttpServlet
    • Spring MVC通过注解(如@Controller)简化Servlet开发
  2. 与Jakarta EE的关系

    • Servlet是Jakarta EE的核心组件
    • Jakarta EE 9+将包名从javax.servlet改为jakarta.servlet

七、总结

Servlet是Java Web开发的基石,掌握其核心原理和实战技巧对后端工程师至关重要。本文系统梳理了:

  1. Servlet生命周期与核心组件
  2. 请求/响应处理与会话管理
  3. 配置方式与部署流程
  4. 线程安全与高级特性
  5. 实战案例(登录、文件上传)

在实际开发中,建议结合Spring Boot等框架简化Servlet开发,但理解底层原理有助于解决复杂问题。后续将分享更多Servlet与微服务架构的集成案例。