1. Servlet与MVC模式解析
Servlet作为JavaWeb开发的核心技术,本质上是一个运行在服务器端的Java程序,用于处理客户端请求并生成动态响应。与传统的CGI技术相比,Servlet采用线程池处理请求,性能优势明显。在实际项目中,一个典型的Servlet类需要继承HttpServlet并重写doGet()或doPost()方法:
public class UserServlet extends HttpServlet { protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 业务逻辑处理 String username = request.getParameter("username"); UserService service = new UserService(); boolean result = service.register(username); // 响应处理 if(result) { request.setAttribute("message", "注册成功"); } else { request.setAttribute("message", "用户名已存在"); } request.getRequestDispatcher("/result.jsp").forward(request, response); } }这种原始开发方式虽然直接,但随着业务复杂度上升会面临几个典型问题:
- 单个Servlet处理多个功能导致代码臃肿(如UserServlet同时处理登录、注册、修改密码)
- 业务逻辑与视图渲染强耦合,不利于分工协作
- 请求参数处理、数据验证等重复代码充斥在各个方法中
经验之谈:在实际项目中,建议每个Servlet只处理单一业务功能。例如LoginServlet只处理登录相关请求,RegisterServlet只处理注册请求。这样虽然类数量增加,但维护性大幅提升。
2. MVC架构的演进与实践
MVC(Model-View-Controller)模式通过职责分离解决了上述痛点。在JavaWeb中,其典型实现方式为:
Model(模型):业务逻辑与数据访问层,通常包含:
- Service类(如UserService)处理核心业务逻辑
- DAO类(如UserDao)负责数据库操作
- POJO类(如User)作为数据载体
View(视图):展示层技术,传统采用JSP,现代项目更多使用Thymeleaf、FreeMarker等模板引擎:
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>操作结果</title> </head> <body> <h1>${message}</h1> <a href="index.jsp">返回首页</a> </body> </html>- Controller(控制器):接收请求并协调模型与视图,早期使用Servlet,现代框架中演化为@Controller注解类
实际项目中的调用流程通常为:
- 用户提交表单到Servlet(Controller)
- Servlet调用Service处理业务(Model)
- Service通过DAO操作数据库
- 结果通过setAttribute()存入request
- Servlet转发到JSP页面(View)
- JSP通过EL表达式${}展示数据
3. 从Servlet到Spring MVC的演进
虽然原生Servlet可以实现MVC,但存在配置繁琐、功能有限等问题。Spring MVC通过前端控制器模式进行了优化:
- DispatcherServlet:作为统一入口接收所有请求
- HandlerMapping:将请求映射到具体Controller
- Controller:使用@Controller注解的处理器类
- ModelAndView:封装模型数据和视图信息
- ViewResolver:解析逻辑视图名到具体视图技术
对比示例:
// 传统Servlet方式 public class OldServlet extends HttpServlet { protected void doGet(...) { User user = userService.getUser(id); request.setAttribute("user", user); request.getRequestDispatcher("/userDetail.jsp").forward(...); } } // Spring MVC方式 @Controller public class NewController { @GetMapping("/user/{id}") public String getUser(@PathVariable int id, Model model) { model.addAttribute("user", userService.getUser(id)); return "userDetail"; // 对应模板文件userDetail.html } }避坑指南:在迁移旧项目时,建议逐步替换。可以先引入DispatcherServlet处理新功能,旧功能保持Servlet实现,通过URL映射区分新旧路由。
4. 实战:学生管理系统案例设计
基于Servlet和JSP实现MVC的典型项目结构如下:
/project /src /main /java /com.example.controller # Servlet控制器 /com.example.service # 业务逻辑层 /com.example.dao # 数据访问层 /com.example.model # 实体类 /webapp /WEB-INF web.xml # 部署描述符 /views # JSP视图文件 /static /css # 静态资源 /js /target # 构建输出目录关键配置示例(web.xml):
<servlet> <servlet-name>studentServlet</servlet-name> <servlet-class>com.example.controller.StudentServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>studentServlet</servlet-name> <url-pattern>/student/*</url-pattern> </servlet-mapping>分页查询的实现要点:
- DAO层使用LIMIT语句:
public List<Student> findByPage(int pageNo, int pageSize) { String sql = "SELECT * FROM students LIMIT ?,?"; return jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(Student.class), (pageNo-1)*pageSize, pageSize); }- Service层计算分页参数:
public PageInfo<Student> getPage(int pageNo, int pageSize) { int total = studentDao.count(); List<Student> list = studentDao.findByPage(pageNo, pageSize); return new PageInfo<>(pageNo, pageSize, total, list); }- JSP页面显示分页导航:
<c:forEach begin="1" end="${pageInfo.totalPage}" var="i"> <a href="student?action=list&pageNo=${i}">${i}</a> </c:forEach>5. 常见问题排查与性能优化
典型问题1:中文乱码解决方案:
// Servlet中设置编码 request.setCharacterEncoding("UTF-8"); response.setContentType("text/html;charset=UTF-8"); // JSP页面头部添加 <%@ page pageEncoding="UTF-8"%> // 数据库连接字符串配置 jdbc:mysql://localhost:3306/db?useUnicode=true&characterEncoding=UTF-8典型问题2:静态资源被拦截在web.xml中配置放行:
<servlet-mapping> <servlet-name>default</servlet-name> <url-pattern>*.css</url-pattern> </servlet-mapping>性能优化建议:
- 使用连接池(如HikariCP)替代直连数据库
- 对频繁访问的数据添加缓存(如Redis)
- 静态资源启用浏览器缓存
<filter> <filter-name>expiresFilter</filter-name> <filter-class>org.apache.catalina.filters.ExpiresFilter</filter-class> <init-param> <param-name>ExpiresByType image</param-name> <param-value>access plus 1 month</param-value> </init-param> </filter>开发效率提升:
- 使用JSTL标签库替代Scriptlet
<%-- 旧方式 --%> <% for(Student s : studentList) { out.print("<li>"+s.getName()+"</li>"); } %> <%-- 新方式 --%> <c:forEach items="${studentList}" var="s"> <li>${s.name}</li> </c:forEach>- 采用三层架构明确职责边界:
- Controller只负责参数接收和结果转发
- Service处理业务逻辑和事务管理
- DAO专注数据库操作
在团队协作中,这种分层可以让前端开发人员专注JSP/HTML开发,后端开发人员处理业务逻辑,数据库专家优化SQL语句,各司其职提升整体效率。