开启你的编程学习之旅

云课堂提供高质量的编程课程,从入门到精通,助你成为技术大牛

立即开始学习

Java Web 开发实战

作者: 钱老师 更新: 2024-03-20 阅读: 42367 难度: 中级
学习工具

2. Servlet入门与Web开发基础

Servlet 是 Java Web 开发的核心技术,用于处理客户端请求并生成动态 Web 内容。本章将介绍 Servlet 的基本概念、生命周期和开发流程。

Maven 依赖配置

首先需要在 pom.xml 中添加 Servlet API 依赖:

pom.xml
<dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>4.0.1</version> <scope>provided</scope> </dependency>

第一个 Servlet 程序

下面是一个简单的 Servlet 示例:

HelloServlet.java
import java.io.*; import javax.servlet.*; import javax.servlet.http.*; import javax.servlet.annotation.*; @WebServlet("/hello") public class HelloServlet extends HttpServlet { @Override public void init() throws ServletException { // Servlet 初始化代码 System.out.println("HelloServlet 初始化"); } @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 设置响应内容类型 response.setContentType("text/html;charset=UTF-8"); // 获取请求参数 String name = request.getParameter("name"); if (name == null || name.trim().isEmpty()) { name = "访客"; } // 获取输出流 PrintWriter out = response.getWriter(); // 生成 HTML 响应 out.println("<!DOCTYPE html>"); out.println("<html>"); out.println("<head>"); out.println("<title>Hello Servlet</title>"); out.println("</head>"); out.println("<body>"); out.println("<h1>Hello, " + name + "!</h1>"); out.println("<p>当前时间: " + new java.util.Date() + "</p>"); out.println("<a href=\"index.jsp\">返回首页</a>"); out.println("</body>"); out.println("</html>"); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } @Override public void destroy() { // Servlet 销毁代码 System.out.println("HelloServlet 销毁"); } }

Servlet 生命周期详解

Servlet 生命周期包括初始化、服务和销毁三个阶段:

Servlet 生命周期示例
import java.io.*; import javax.servlet.*; import javax.servlet.http.*; import javax.servlet.annotation.*; @WebServlet("/lifecycle") public class LifecycleServlet extends HttpServlet { private int requestCount; // 1. 初始化方法 @Override public void init() throws ServletException { super.init(); requestCount = 0; System.out.println("Servlet 初始化完成,请求计数器已重置"); // 获取初始化参数 ServletConfig config = getServletConfig(); String initParam = config.getInitParameter("appName"); System.out.println("初始化参数 appName: " + initParam); } // 2. 服务方法 - 处理所有请求 @Override protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 在服务方法开始前执行 long startTime = System.currentTimeMillis(); // 调用父类的service方法,它会根据请求方法调用相应的doXxx方法 super.service(request, response); // 在服务方法结束后执行 long endTime = System.currentTimeMillis(); long processingTime = endTime - startTime; System.out.println("请求处理完成,耗时: " + processingTime + "ms"); } // 3. 处理GET请求 @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 增加请求计数 synchronized(this) { requestCount++; } response.setContentType("text/html;charset=UTF-8"); PrintWriter out = response.getWriter(); out.println("<!DOCTYPE html>"); out.println("<html>"); out.println("<head><title>Servlet 生命周期</title></head>"); out.println("<body>"); out.println("<h1>Servlet 生命周期演示</h1>"); out.println("<p>总请求次数: " + requestCount + "</p>"); out.println("<p>Servlet 信息: " + getServletInfo() + "</p>"); out.println("<p>服务器信息: " + getServletContext().getServerInfo() + "</p>"); out.println("</body>"); out.println("</html>"); } // 4. 处理POST请求 @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 获取表单数据 String username = request.getParameter("username"); String email = request.getParameter("email"); response.setContentType("text/html;charset=UTF-8"); PrintWriter out = response.getWriter(); out.println("<!DOCTYPE html>"); out.println("<html>"); out.println("<head><title>表单处理结果</title></head>"); out.println("<body>"); out.println("<h1>表单提交成功</h1>"); out.println("<p>用户名: " + username + "</p>"); out.println("<p>邮箱: " + email + "</p>"); out.println("<a href=\"lifecycle\">返回</a>"); out.println("</body>"); out.println("</html>"); } // 5. 销毁方法 @Override public void destroy() { super.destroy(); System.out.println("Servlet 销毁,总请求次数: " + requestCount); System.out.println("释放资源..."); } // 6. 获取Servlet信息 @Override public String getServletInfo() { return "LifecycleServlet - 演示Servlet生命周期"; } }

请求和响应对象

HttpServletRequest 和 HttpServletResponse 是 Servlet 开发中最重要的两个对象:

请求和响应处理示例
import java.io.*; import java.util.*; import javax.servlet.*; import javax.servlet.http.*; import javax.servlet.annotation.*; @WebServlet("/request-info") public class RequestInfoServlet 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("<!DOCTYPE html>"); out.println("<html>"); out.println("<head>"); out.println("<title>请求信息</title>"); out.println("<style>"); out.println("table { border-collapse: collapse; width: 100%; }"); out.println("th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }"); out.println("th { background-color: #f2f2f2; }"); out.println("</style>"); out.println("</head>"); out.println("<body>"); out.println("<h1>HTTP 请求信息</h1>"); // 基本请求信息 out.println("<h2>基本请求信息</h2>"); out.println("<table>"); out.println("<tr><th>项目</th><th>值</th></tr>"); out.println("<tr><td>请求方法</td><td>" + request.getMethod() + "</td></tr>"); out.println("<tr><td>请求URL</td><td>" + request.getRequestURL() + "</td></tr>"); out.println("<tr><td>请求URI</td><td>" + request.getRequestURI() + "</td></tr>"); out.println("<tr><td>协议</td><td>" + request.getProtocol() + "</td></tr>"); out.println("<tr><td>服务器名</td><td>" + request.getServerName() + "</td></tr>"); out.println("<tr><td>服务器端口</td><td>" + request.getServerPort() + "</td></tr>"); out.println("<tr><td>客户端IP</td><td>" + request.getRemoteAddr() + "</td></tr>"); out.println("<tr><td>客户端主机</td><td>" + request.getRemoteHost() + "</td></tr>"); out.println("<tr><td>查询字符串</td><td>" + request.getQueryString() + "</td></tr>"); out.println("</table>"); // 请求头信息 out.println("<h2>请求头信息</h2>"); out.println("<table>"); out.println("<tr><th>头名称</th><th>头值</th></tr>"); Enumeration<String> headerNames = request.getHeaderNames(); while (headerNames.hasMoreElements()) { String headerName = headerNames.nextElement(); String headerValue = request.getHeader(headerName); out.println("<tr><td>" + headerName + "</td><td>" + headerValue + "</td></tr>"); } out.println("</table>"); // 请求参数 out.println("<h2>请求参数</h2>"); out.println("<table>"); out.println("<tr><th>参数名</th><th>参数值</th></tr>"); Map<String, String[]> parameterMap = request.getParameterMap(); for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) { String paramName = entry.getKey(); String[] paramValues = entry.getValue(); out.println("<tr><td>" + paramName + "</td><td>" + String.join(", ", paramValues) + "</td></tr>"); } out.println("</table>"); // 属性信息 out.println("<h2>请求属性</h2>"); out.println("<table>"); out.println("<tr><th>属性名</th><th>属性值</th></tr>"); Enumeration<String> attributeNames = request.getAttributeNames(); while (attributeNames.hasMoreElements()) { String attrName = attributeNames.nextElement(); Object attrValue = request.getAttribute(attrName); out.println("<tr><td>" + attrName + "</td><td>" + attrValue + "</td></tr>"); } out.println("</table>"); out.println("</body>"); out.println("</html>"); } }

Servlet 配置和注解

Servlet 3.0 引入了注解配置,简化了部署描述符的使用:

Servlet 配置示例
import java.io.*; import javax.servlet.*; import javax.servlet.http.*; import javax.servlet.annotation.*; // 使用注解配置Servlet @WebServlet( name = "ConfigServlet", urlPatterns = { "/config", "/configure" }, loadOnStartup = 1, initParams = { @WebInitParam(name = "databaseURL", value = "jdbc:mysql://localhost:3306/mydb"), @WebInitParam(name = "maxConnections", value = "100"), @WebInitParam(name = "timeout", value = "30") } ) public class ConfigServlet extends HttpServlet { private String databaseURL; private int maxConnections; private int timeout; @Override public void init() throws ServletException { super.init(); // 获取初始化参数 ServletConfig config = getServletConfig(); databaseURL = config.getInitParameter("databaseURL"); maxConnections = Integer.parseInt(config.getInitParameter("maxConnections")); timeout = Integer.parseInt(config.getInitParameter("timeout")); System.out.println("数据库URL: " + databaseURL); System.out.println("最大连接数: " + maxConnections); System.out.println("超时时间: " + timeout); } @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html;charset=UTF-8"); PrintWriter out = response.getWriter(); out.println("<!DOCTYPE html>"); out.println("<html>"); out.println("<head><title>Servlet 配置</title></head>"); out.println("<body>"); out.println("<h1>Servlet 配置信息</h1>"); out.println("<ul>"); out.println("<li>数据库URL: " + databaseURL + "</li>"); out.println("<li>最大连接数: " + maxConnections + "</li>"); out.println("<li>超时时间: " + timeout + "秒</li>"); out.println("<li>Servlet名称: " + getServletName() + "</li>"); out.println("</ul>"); out.println("</body>"); out.println("</html>"); } }

Servlet 上下文和会话管理

ServletContext 和 HttpSession 用于在多个请求间共享数据:

上下文和会话管理示例
import java.io.*; import java.util.concurrent.atomic.AtomicInteger; import javax.servlet.*; import javax.servlet.http.*; import javax.servlet.annotation.*; @WebServlet("/session-demo") public class SessionDemoServlet extends HttpServlet { private static final String VISIT_COUNT = "visitCount"; private static final String LAST_VISIT_TIME = "lastVisitTime"; // 应用级别的访问计数器 private AtomicInteger totalVisits; @Override public void init() throws ServletException { super.init(); // 初始化应用级别的计数器 ServletContext context = getServletContext(); totalVisits = (AtomicInteger) context.getAttribute("totalVisits"); if (totalVisits == null) { totalVisits = new AtomicInteger(0); context.setAttribute("totalVisits", totalVisits); } } @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html;charset=UTF-8"); PrintWriter out = response.getWriter(); // 获取或创建会话 HttpSession session = request.getSession(true); // 会话级别的访问计数 Integer visitCount = (Integer) session.getAttribute(VISIT_COUNT); if (visitCount == null) { visitCount = 1; } else { visitCount++; } session.setAttribute(VISIT_COUNT, visitCount); session.setAttribute(LAST_VISIT_TIME, new java.util.Date()); // 应用级别的访问计数 int appVisitCount = totalVisits.incrementAndGet(); out.println("<!DOCTYPE html>"); out.println("<html>"); out.println("<head>"); out.println("<title>会话管理</title>"); out.println("<style>"); out.println(".info { background-color: #f0f8ff; padding: 10px; margin: 10px 0; }"); out.println("</style>"); out.println("</head>"); out.println("<body>"); out.println("<h1>Servlet 会话管理演示</h1>"); // 会话信息 out.println("<div class=\"info\">"); out.println("<h2>会话信息</h2>"); out.println("<p>会话ID: " + session.getId() + "</p>"); out.println("<p>创建时间: " + new java.util.Date(session.getCreationTime()) + "</p>"); out.println("<p>最后访问时间: " + new java.util.Date(session.getLastAccessedTime()) + "</p>"); out.println("<p>最大不活动间隔: " + session.getMaxInactiveInterval() + "秒</p>"); out.println("<p>是否新会话: " + session.isNew() + "</p>"); out.println("</div>"); // 访问统计 out.println("<div class=\"info\">"); out.println("<h2>访问统计</h2>"); out.println("<p>本次会话访问次数: " + visitCount + "</p>"); out.println("<p>最后访问时间: " + session.getAttribute(LAST_VISIT_TIME) + "</p>"); out.println("<p>应用总访问次数: " + appVisitCount + "</p>"); out.println("</div>"); // 操作按钮 out.println("<div>"); out.println("<form method=\"post\" action=\"session-demo\">"); out.println("<button type=\"submit\" name=\"action\" value=\"refresh\">刷新页面</button>"); out.println("<button type=\"submit\" name=\"action\" value=\"invalidate\">销毁会话</button>"); out.println("</form>"); out.println("</div>"); out.println("</body>"); out.println("</html>"); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String action = request.getParameter("action"); if ("invalidate".equals(action)) { HttpSession session = request.getSession(false); if (session != null) { session.invalidate(); } response.sendRedirect("session-demo"); } else { doGet(request, response); } } }

过滤器 (Filter) 示例

过滤器用于在请求到达 Servlet 之前或响应发送到客户端之前进行处理:

日志过滤器示例
import java.io.*; import javax.servlet.*; import javax.servlet.annotation.*; import javax.servlet.http.*; @WebFilter("/*") public class LoggingFilter implements Filter { private FilterConfig filterConfig; @Override public void init(FilterConfig filterConfig) throws ServletException { this.filterConfig = filterConfig; System.out.println("LoggingFilter 初始化"); } @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(); // 记录请求信息 String clientIP = httpRequest.getRemoteAddr(); String requestURI = httpRequest.getRequestURI(); String method = httpRequest.getMethod(); System.out.println(String.format("请求开始 - IP: %s, 方法: %s, URI: %s, 时间: %s", clientIP, method, requestURI, new java.util.Date())); // 继续处理请求 chain.doFilter(request, response); long endTime = System.currentTimeMillis(); long processingTime = endTime - startTime; // 记录响应信息 System.out.println(String.format("请求完成 - URI: %s, 处理时间: %dms, 状态: %d", requestURI, processingTime, httpResponse.getStatus())); } @Override public void destroy() { System.out.println("LoggingFilter 销毁"); } }

关键知识点总结

  • Servlet 生命周期:init() → service() → destroy()
  • 请求处理:doGet(), doPost(), doPut(), doDelete() 等方法
  • 注解配置:@WebServlet, @WebFilter, @WebListener
  • 会话管理:HttpSession 用于跟踪用户状态
  • 上下文对象:ServletContext 用于应用级别数据共享
  • 过滤器:用于请求和响应的预处理和后处理

最佳实践

  1. 使用注解配置简化部署
  2. 合理管理会话数据,避免存储大量数据
  3. 及时释放资源,特别是在 destroy() 方法中
  4. 使用过滤器处理通用功能(如日志、认证、编码设置)
  5. 设置合适的字符编码防止中文乱码
  6. 合理使用重定向和请求转发

注意:在实际项目中,Servlet 通常与 JSP、MVC 框架(如 Spring MVC)结合使用,以更好地分离表现层和业务逻辑。

提示: 这是一个重要的概念,需要特别注意理解和掌握。
注意: 这是一个常见的错误点,请避免犯同样的错误。