第一次接触Java Web开发时,我被那些专业术语搞得晕头转向。后来在实际项目中摸爬滚打多年才发现,Web开发本质上就是处理请求和响应的艺术。Java Web开发的核心在于理解浏览器与服务器之间的对话机制——当你在地址栏输入网址时,背后其实发生了一系列精妙的交互过程。
Servlet技术作为Java EE规范的核心组件,扮演着Web应用程序与服务器之间的桥梁角色。与常见的PHP、Python等脚本语言不同,Java通过Servlet提供了更结构化的Web开发方式。我刚开始学习时最大的误区是认为Servlet已经过时,实际上即使在Spring框架盛行的今天,Servlet仍然是底层基石。
典型的Java Web应用架构包含几个关键层次:客户端浏览器发送HTTP请求,Web服务器(如Tomcat)接收请求后交给Servlet容器处理,Servlet根据请求类型调用相应的业务逻辑,最后生成响应返回给客户端。这个过程看似简单,但每个环节都有值得深究的技术细节。
Servlet的生命周期是我在面试初级开发者时必问的问题。一个Servlet从诞生到销毁经历了三个关键阶段:
初始化阶段:当容器首次加载Servlet时,会调用init()方法。这个方法只执行一次,适合进行资源加载等初始化操作。我曾在项目中犯过把耗时操作放在init()中的错误,导致应用启动异常缓慢。
服务阶段:每次请求都会触发service()方法,它根据请求类型(GET/POST等)调用doGet()或doPost()。这里有个性能优化技巧——将这些方法设计为线程安全的,因为Servlet默认是多线程模型。
销毁阶段:容器在卸载Servlet前调用destroy()方法,用于释放资源。常见错误是忘记关闭数据库连接等资源,导致内存泄漏。
java复制// 典型Servlet类结构示例
public class MyServlet extends HttpServlet {
@Override
public void init() throws ServletException {
// 初始化代码
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
// 处理GET请求
}
@Override
public void destroy() {
// 清理资源
}
}
HttpServletRequest和HttpServletResponse这两个对象是Servlet开发中最常用的API。经过多个项目的实践,我总结出几个关键使用技巧:
请求参数处理:getParameter()方法虽然简单,但在处理中文参数时容易出乱码。我现在的标准做法是在获取参数前先设置编码:
java复制request.setCharacterEncoding("UTF-8");
String username = request.getParameter("user");
响应输出优化:直接使用PrintWriter输出HTML不仅低效,还容易引发XSS攻击。推荐使用模板引擎,但若必须直接输出,至少要设置内容类型:
java复制response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
out.println("<html><body>...</body></html>");
请求转发与重定向:这是初学者最容易混淆的概念。RequestDispatcher.forward()是服务器内部跳转,地址栏不变;response.sendRedirect()是客户端重定向,会产生新的请求。在电商项目中,支付成功后应该用重定向避免重复提交。
web.xml配置曾经是Servlet开发的标配,虽然现在流行注解方式,但理解配置原理仍然重要。我在团队代码审查时经常发现映射路径配置不当的问题:
xml复制<!-- 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>
注解方式虽然简便,但要注意几个细节:
java复制@WebServlet(
name = "helloServlet",
urlPatterns = {"/hello", "/hello/*"},
loadOnStartup = 1
)
public class HelloServlet extends HttpServlet {...}
重要提示:urlPatterns中使用通配符时要特别小心顺序,容器会按照最精确匹配优先的原则查找Servlet。
用户登录状态管理是Web开发的基础需求。我经历过因为会话管理不当导致的安全事故,现在对这方面特别谨慎:
Cookie:存储在客户端,有大小限制(通常4KB)。适合存储不敏感的小数据,如用户偏好设置。关键属性:
java复制Cookie cookie = new Cookie("theme", "dark");
cookie.setMaxAge(86400); // 有效期1天
cookie.setHttpOnly(true); // 防止XSS攻击
cookie.setSecure(true); // 仅HTTPS传输
response.addCookie(cookie);
Session:存储在服务端,更安全但消耗服务器内存。典型用法:
java复制HttpSession session = request.getSession();
session.setAttribute("user", userObj);
// 获取时记得做类型转换
User user = (User)session.getAttribute("user");
当应用需要水平扩展时,会话管理变得复杂。我主导的一个电商项目就曾因会话不同步导致用户购物车丢失。解决方案包括:
xml复制<!-- context.xml配置 -->
<Manager className="org.apache.catalina.session.PersistentManager">
<Store className="org.apache.catalina.session.RedisStore"/>
</Manager>
过滤器(Filter)是Servlet规范的瑰宝,能实现横切关注点。我在日志、安全、压缩等方面大量使用过滤器:
java复制@WebFilter("/*")
public class LoggingFilter implements Filter {
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) {
long start = System.currentTimeMillis();
chain.doFilter(req, res); // 必须调用此方法继续链
long duration = System.currentTimeMillis() - start;
System.out.println("请求处理时间:" + duration + "ms");
}
}
过滤器顺序由web.xml中定义的顺序决定,或者使用@WebFilter的filterName属性控制。常见错误是忘记调用chain.doFilter()导致请求被阻断。
监听器(Listenter)适合处理应用生命周期事件。我常用的场景包括:
应用启动时加载缓存:
java复制@WebListener
public class CacheLoader implements ServletContextListener {
public void contextInitialized(ServletContextEvent sce) {
// 初始化缓存
}
}
监控会话活动:
java复制public class SessionTracker implements HttpSessionListener {
private static final AtomicInteger activeSessions = new AtomicInteger();
public void sessionCreated(HttpSessionEvent se) {
activeSessions.incrementAndGet();
}
}
经历过双十一级别的流量冲击后,我总结了这些Servlet优化经验:
线程安全编码:避免使用实例变量,必要时使用同步块
合理使用异步处理(Servlet 3.0+):
java复制@WebServlet(urlPatterns="/async", asyncSupported=true)
public class AsyncServlet extends HttpServlet {
protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
AsyncContext ctx = req.startAsync();
executor.submit(() -> {
// 耗时操作
ctx.complete();
});
}
}
连接池配置:数据库连接池参数要根据实际负载调整,我常用的Druid配置:
properties复制druid.initialSize=5
druid.maxActive=50
druid.maxWait=60000
安全漏洞可能让多年心血毁于一旦。这些是我在代码审查时必查的点:
输入验证:所有用户输入都视为不可信的
java复制String input = request.getParameter("input");
if (!Pattern.matches("[a-zA-Z0-9]+", input)) {
throw new IllegalArgumentException("非法输入");
}
密码存储:永远不要明文存储,使用BCrypt等算法
java复制String hashed = BCrypt.hashpw(password, BCrypt.gensalt());
CSRF防护:使用随机token
java复制String token = UUID.randomUUID().toString();
session.setAttribute("csrfToken", token);
request.setAttribute("csrfToken", token);
虽然现在流行Spring Boot等框架,但理解Servlet底层原理依然重要。我在微服务项目中发现的几个关键点:
最后给初学者的建议:不要被各种框架迷惑,先扎实掌握Servlet这一Java Web基石。我见过太多开发者跳过基础直接学框架,最终遇到问题无从下手。Servlet就像武术中的马步,练好了后续学习各种框架都会事半功倍。