本文記錄一個基於 Spring 3.1 + JdbcTemplate + MySQL 的經典 MVC 實戰項目 SpringTest,實現用戶註冊、登錄、登出功能。涵蓋專案搭建、配置文件、分層代碼、視圖頁面與運行驗證,適合 Spring 初學者理解請求處理全流程。
| 組件 | 版本/選型 |
|---|---|
| 構建工具 | MyEclipse 10 (無 Maven,手動管理 JAR) |
| 核心框架 | Spring 3.1 Core + Spring MVC |
| 數據訪問 | Spring JdbcTemplate |
| 資料庫 | MySQL 5.7.13 |
| Web 容器 | Tomcat 7.0 (32位) |
| JDK | 1.6.0_13 (32位) |
| 視圖技術 | JSP + JSTL |
┌─────────────┐ ┌──────────────┐ ┌─────────────┐ ┌──────────────┐
│ Browser │────▶│DispatcherServlet│────▶│ Controller │────▶│ Service/DAO │
│ (JSP View) │◀────│ (Front Ctrl) │◀────│ (Action/Form)│◀────│ (JdbcTemplate)│
└─────────────┘ └──────────────┘ └─────────────┘ └──────────────┘
│
┌──────┴──────┐
│ ViewResolver │
│ (InternalResource)│
└─────────────┘
<web-app version="3.0" xmlns="https://java.sun.com/xml/ns/javaee" ...>
<!-- 編碼過濾器 -->
<filter>
<filter-name>encodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param><param-name>encoding</param-name><param-value>UTF-8</param-value></init-param>
</filter>
<filter-mapping><filter-name>encodingFilter</filter-name><url-pattern>*.do</url-pattern></filter-mapping>
<!-- Spring MVC 前端控制器 -->
<servlet>
<servlet-name>spring</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping><servlet-name>spring</servlet-name><url-pattern>*.do</url-pattern></servlet-mapping>
<welcome-file-list><welcome-file>index.jsp</welcome-file></welcome-file-list>
</web-app>
<beans xmlns="https://www.springframework.org/schema/beans" ...>
<!-- 數據源 (DBCP 連接池) -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/demo_test"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</bean>
<!-- 事務管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- DAO 定義 -->
<bean id="userDAO" class="com.liu.spring.dao.UserDAO">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 事務代理 (舊版 Spring AOP 方式) -->
<bean id="UserDAOProxy" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
<property name="transactionManager" ref="transactionManager"/>
<property name="target" ref="userDAO"/>
<property name="transactionAttributes">
<props>
<prop key="insert*">PROPAGATION_REQUIRED</prop>
<prop key="*">PROPAGATION_REQUIRED,readOnly</prop>
</props>
</property>
</bean>
<!-- 視圖解析器 -->
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="viewClass" value="org.springframework.web.servlet.view.InternalResourceView"/>
<property name="prefix" value="/"/>
<property name="suffix" value=".jsp"/>
</bean>
<!-- URL 映射 (SimpleUrlHandlerMapping) -->
<bean id="loginMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<property name="mappings">
<props><prop key="/login.do">loginAction</prop></props>
</property>
</bean>
<bean id="logoutMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<property name="mappings"><props><prop key="/logout.do">logoutAction</prop></props></property>
</bean>
<bean id="registerMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<property name="mappings"><props><prop key="/register.do">registerAction</prop></props></property>
</bean>
<!-- Action 定義 (繼承 SimpleFormController) -->
<bean id="loginAction" class="com.liu.spring.actions.LoginAction">
<property name="commandClass" value="com.liu.spring.forms.LoginForm"/>
<property name="userDAO" ref="userDAO"/>
<property name="formView" value="login"/>
<property name="successView" value="welcome"/>
</bean>
<bean id="logoutAction" class="com.liu.spring.actions.LogoutAction">
<property name="commandClass" value="com.liu.spring.forms.LoginForm"/>
<property name="successView" value="login"/>
</bean>
<bean id="registerAction" class="com.liu.spring.actions.RegisterAction">
<property name="commandClass" value="com.liu.spring.forms.RegisterForm"/>
<property name="userDAO" ref="userDAO"/>
<property name="formView" value="register"/>
<property name="successView" value="login"/>
</bean>
</beans>
// LoginForm.java
public class LoginForm {
private String username;
private String password;
// getter/setter 省略
}
// RegisterForm.java
public class RegisterForm {
private String username;
private String password1;
private String password2;
private String email;
// getter/setter 省略
}
// User.java (POJO)
public class User {
private String id;
private String username;
private String password;
private String email;
// getter/setter 省略
}
// UserDAO.java
public class UserDAO {
private DataSource dataSource;
public void setDataSource(DataSource dataSource) { this.dataSource = dataSource; }
public boolean isValid(String username, String password) {
JdbcTemplate jt = new JdbcTemplate(dataSource);
List list = jt.query("SELECT * FROM userInfo WHERE username=? AND password=?",
new Object[]{username, password}, new RowMapper() {
public User mapRow(ResultSet rs, int rowNum) throws SQLException {
User u = new User();
u.setId(rs.getString("ID"));
u.setUsername(rs.getString("username"));
u.setPassword(rs.getString("password"));
u.setEmail(rs.getString("email"));
return u;
}
});
return !list.isEmpty();
}
public boolean isExist(String username) {
JdbcTemplate jt = new JdbcTemplate(dataSource);
Integer count = jt.queryForObject("SELECT COUNT(*) FROM userInfo WHERE username=?",
new Object[]{username}, Integer.class);
return count > 0;
}
public void insertUser(User user) {
JdbcTemplate jt = new JdbcTemplate(dataSource);
jt.update("INSERT INTO userInfo(username,password,email) VALUES(?,?,?)",
user.getUsername(), user.getPassword(), user.getEmail());
}
}
// LoginAction.java
public class LoginAction extends SimpleFormController {
private UserDAO userDAO;
public void setUserDAO(UserDAO userDAO) { this.userDAO = userDAO; }
@Override
protected ModelAndView onSubmit(HttpServletRequest request, HttpServletResponse response,
Object command, BindException errors) throws Exception {
LoginForm form = (LoginForm) command;
if (userDAO.isValid(form.getUsername(), form.getPassword())) {
request.getSession().setAttribute("username", form.getUsername());
return new ModelAndView(getSuccessView()); // welcome.jsp
} else {
errors.getModel().put("loginForm", form);
return new ModelAndView(getFormView(), errors.getModel()); // login.jsp
}
}
}
// RegisterAction.java
public class RegisterAction extends SimpleFormController {
private UserDAO userDAO;
public void setUserDAO(UserDAO userDAO) { this.userDAO = userDAO; }
@Override
protected ModelAndView onSubmit(HttpServletRequest request, HttpServletResponse response,
Object command, BindException errors) throws Exception {
RegisterForm form = (RegisterForm) command;
String method = request.getParameter("method");
if ("register".equals(method)) {
if (!userDAO.isExist(form.getUsername())) {
User user = new User();
user.setUsername(form.getUsername());
user.setPassword(form.getPassword1());
user.setEmail(form.getEmail());
userDAO.insertUser(user);
response.sendRedirect("login.do"); // 註冊成功重定向登錄頁
return null;
} else {
errors.getModel().put(getFormSessionAttributeName(), form);
return new ModelAndView(getFormView(), errors.getModel()); // 回註冊頁
}
}
return new ModelAndView(getFormView()); // 初始顯示註冊頁
}
}
// LogoutAction.java
public class LogoutAction extends SimpleFormController {
@Override
protected ModelAndView onSubmit(HttpServletRequest request, HttpServletResponse response,
Object command, BindException errors) throws Exception {
request.getSession().invalidate(); // 銷毀 Session
response.sendRedirect("login.do");
return null;
}
}
public class Constants {
public final static String USERNAME_KEY = "username";
}
<%@ page language="java" pageEncoding="UTF-8"%>
<% String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/"; %>
<html><head><base href="<%=basePath%>"><title>用戶登錄</title></head><body>
<form name="form1" action="login.do" method="post">
<table width="200" border="1">
<tr><td colspan="2">登錄窗口</td></tr>
<tr><td>用戶名</td><td><input type="text" name="username" size="10"></td></tr>
<tr><td>密碼</td><td><input type="password" name="password" size="10"></td></tr>
<tr><td colspan="2">
<input type="submit" name="submit" value="登錄">
<a href="register.do?method=init">註冊新用戶</a>
</td></tr>
</table></form></body></html>
<form name="form1" action="register.do?method=register" method="post">
<table width="200" border="1">
<tr><td colspan="2">註冊窗口</td></tr>
<tr><td>用戶名</td><td><input type="text" name="username" size="10"></td></tr>
<tr><td>密碼</td><td><input type="password" name="password1" size="10"></td></tr>
<tr><td>確認密碼</td><td><input type="password" name="password2" size="10"></td></tr>
<tr><td>Email</td><td><input type="text" name="email" size="10"></td></tr>
<tr><td colspan="2">
<input type="submit" value="註冊"> <a href="login.do">返回</a>
</td></tr>
</table></form>
<form name="form1" action="logout.do" method="post">
<table width="200" border="1">
<tr><td colspan="2">登錄成功</td></tr>
<tr><td>歡迎你,</td><td><%=session.getAttribute("username")%></td></tr>
<tr><td colspan="2"><input type="submit" name="submit" value="退出"></td></tr>
</table></form>
CREATE DATABASE demo_test DEFAULT CHARACTER SET utf8;
USE demo_test;
CREATE TABLE userInfo (
ID INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(50) NOT NULL UNIQUE,
password VARCHAR(50) NOT NULL,
email VARCHAR(100)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
https://localhost:8080/SpringTest/login.jspSpringTest/
├── src/
│ ├── com.liu.spring.actions/ (LoginAction, RegisterAction, LogoutAction)
│ ├── com.liu.spring.dao/ (UserDAO, User)
│ ├── com.liu.spring.forms/ (LoginForm, RegisterForm)
│ └── com.liu.spring.util/ (Constants)
├── WebRoot/
│ ├── WEB-INF/
│ │ ├── web.xml
│ │ ├── spring-servlet.xml
│ │ └── lib/ (spring.jar, mysql-connector.jar, commons-dbcp.jar, ...)
│ ├── login.jsp
│ ├── register.jsp
│ └── welcome.jsp
└── 數據庫腳本.sql
| 原技術 | 現代替代 | 改造要點 |
|---|---|---|
| XML 配置 | Java Config / YAML | @Configuration、@Bean、application.yml |
| SimpleFormController | @Controller + @RequestMapping | POJO 入參、@Valid 驗證、統一異常處理 |
| JdbcTemplate | Spring Data JPA / MyBatis | Repository 介面、實體映射、分頁查詢 |
| DBCP | HikariCP | Spring Boot 2+ 默認連接池 |
| JSP | Thymeleaf / 前後端分離 (Vue/React) | 模板引擎或 REST API + 前端框架 |
| 手動 JAR 管理 | Maven / Gradle | 依賴傳遞、版本衝突解決、構建自動化 |
| 明文密碼 | BCrypt 加密 | BCryptPasswordEncoder、Spring Security |
學習價值:雖然技術棧較老,但 SpringTest 完整展示了 Spring MVC 請求處理核心流程:DispatcherServlet → HandlerMapping → Controller → Service/DAO → ViewResolver → JSP。理解這套流程,對學習現代 Spring Boot、Spring Cloud 微服務有極大幫助。
源碼參考:GitHub - SpringTest