準備工作
開發(fā)環(huán)境準備
- 開發(fā)工具:eclipse 4.3
- 技術(shù)語言:Java SE 1.7
- 依賴控制:Maven 3.0.4
- 服務(wù)器:Tomcat 7.0.68
- 操作系統(tǒng):Windows 10
- 版本管理:項目源碼地址
搭建一個web項目
參考文檔:快速搭建web項目
項目需求
實現(xiàn)一個標準的用戶登錄注冊功能,詳細需求見easyPassport需求說明。
開發(fā)實現(xiàn)
設(shè)計架構(gòu)思想

如上圖所示,由Servlet提供后臺服務(wù),由JSP完成前端的數(shù)據(jù)展示,JavaBean作為數(shù)據(jù)存儲和傳輸?shù)慕橘|(zhì),并最終持久化至數(shù)據(jù)庫。
模版層設(shè)計實現(xiàn)
創(chuàng)建用戶信息表:
用戶信息表用來保存和記錄用戶的信息
create table users{
id int,
userid varchar(50),
username varchar(50),
email varchar(50),
phone varchar(11),
password varchar(50),
status varchar(2),
create_datet varchar(20),
mod_datet varchar(20),
func_arr varchar(20) default '00000000000000000000'
}
對應(yīng)的數(shù)據(jù)字典:
| 字段 | 數(shù)據(jù)類型 | 字段說明 | 是否可以為空 | 備注 |
|---|---|---|---|---|
| id | int | 數(shù)據(jù)庫主鍵ID | 否 | 數(shù)據(jù)自動生成 |
| userid | varchar(50) | 業(yè)務(wù)主鍵ID | 否 | 根據(jù)業(yè)務(wù)規(guī)則生成的主鍵ID |
| username | varchar(50) | 用戶名稱 | 否 | 用戶注冊時輸入的用戶名 |
| varchar(50) | 用戶郵箱 | 否 | 用戶注冊時輸入的郵箱 | |
| phone | varchar(11) | 手機號 | 是 | 手機號的相關(guān)功能本期暫不涉及 |
| password | varchar(50) | 密碼 | 否 | 密碼保存用戶密碼 |
| status | varchar(2) | 用戶狀態(tài):0-正常;1停用 | 否 | |
| create_datet | varchar(20) | 用戶創(chuàng)建時間 | 否 | |
| mod_datet | varchar(20) | 用戶修改時間 | 是 | |
| func_arr | varchar(20) | 特殊狀態(tài)位 | 是 | 用來區(qū)分特殊也屬性的用戶 |
JavaBean、JDBC和DAO
JavaBean是數(shù)據(jù)傳輸?shù)慕橘|(zhì),JDBC是Java應(yīng)用連接數(shù)據(jù)庫的標準實現(xiàn),DAO(Data Access Objects)則提供了對原子表的增刪查改等功能。三者共同組成了一個Java應(yīng)用的模版層(也叫持久層)。
JavaBean的實現(xiàn)
public class User implements Serializable{
private static final long serialVersionUID = 1L;
private long id;
private String userId;
private String userName;
private String email;
private String phone;
private String password;
private String status;
private String createDatet;
private String modDatet;
private String funcArr;
// get、set方法省略
....
}
使用JDBC技術(shù)訪問數(shù)據(jù)庫
JDBC是Java EE標準規(guī)范的一部分,是Java應(yīng)用操作數(shù)據(jù)庫時最重要的方法(甚至可以說是唯一的方法,ODBC/OCI等一堆東西除非極端情況,已經(jīng)基本沒人使用了),基于JDBC技術(shù)衍生出很多框架,在這里就不做演示了。
一個web項目使用JDBC連接數(shù)據(jù)庫的標準步驟如下:
- 引入必要的jar包
<!-- 省略pom.xml文件中的其他內(nèi)容 -->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>1.4.191</version>
</dependency>
- 編寫JDBC程序
//引入必要的包
import java.sql.*;
public class JDBCDemo {
// JDBC 驅(qū)動名及數(shù)據(jù)庫 URL(以H2為例)
static final String JDBC_DRIVER = "org.h2.Driver";
static final String DB_URL = "jdbc:h2:tcp://localhost/~/test";
// 數(shù)據(jù)庫的用戶名與密碼,需要根據(jù)自己的設(shè)置
static final String USER = "sa";
static final String PASS = "sa";
public static void main(String[] args) {
Connection conn = null;
Statement stmt = null;
try{
// 注冊 JDBC 驅(qū)動
Class.forName(JDBC_DRIVER);
// 打開鏈接
System.out.println("連接數(shù)據(jù)庫...");
conn = DriverManager.getConnection(DB_URL,USER,PASS);
// 執(zhí)行查詢
System.out.println(" 實例化Statement對...");
stmt = conn.createStatement();
String sql;
sql = "SELECT * FROM USERS";
ResultSet rs = stmt.executeQuery(sql);
// 展開結(jié)果集數(shù)據(jù)庫
while(rs.next()){
// 通過字段檢索
int id = rs.getInt("id");
String name = rs.getString("username");
// 輸出數(shù)據(jù)
System.out.print("ID: " + id);
System.out.print(", 用戶名稱: " + name);
System.out.print("\n");
}
// 完成后關(guān)閉
rs.close();
stmt.close();
conn.close();
}catch(SQLException se){
// 處理 JDBC 錯誤
se.printStackTrace();
}catch(Exception e){
// 處理 Class.forName 錯誤
e.printStackTrace();
}finally{
// 關(guān)閉資源
try{
if(stmt!=null) stmt.close();
}catch(SQLException se2){
}// 什么都不做
try{
if(conn!=null) conn.close();
}catch(SQLException se){
se.printStackTrace();
}
}
System.out.println("Goodbye!");
}
}
使用DAO提供基礎(chǔ)功能
DAO通常有兩部分組成,一個是通用的接口,提供標準的服務(wù);一個是對應(yīng)接口的實現(xiàn)類,方便擴展和更新。
接口類的設(shè)計如下:
import java.util.List;
import java.util.Map;
import javabean.User;
public interface UserDao {
// 用戶信息查詢
public User findById(long id);
public User findOne(User user);
public User findOne(Map<String, Object> paramMap);
public List<User> findList(User user);
public List<User> findList(Map<String, Object> paramMap);
public Map<String, Object> findMap(User user);
public Map<String, Object> findMap(Map<String, Object> paramMap);
// 用戶信息新增
public int save(User user);
public int save(Map<String, Object> paramMap);
// 用戶信息修改
public int update(User user);
public int update(Map<String, Object> paramMap);
}
DAO接口的實現(xiàn)類如下:
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import java.util.Map;
import javabean.User;
public class UserDaoImpl implements UserDao {
private static final String JDBC_DRIVER = "org.h2.Driver";
private static final String DB_URL = "jdbc:h2:tcp://localhost/~/test";
private static final String USER = "sa";
private static final String PASS = "sa";
private Connection conn = null;
private static String findUserById = " select id, userid, username, email, phone, password, status, create_datet, mod_datet, func_arr from users where id = ? ";
private static String findUsers = " select id, userid, username, email, phone, password, status, create_datet, mod_datet, func_arr from users ";
// 創(chuàng)建獲取數(shù)據(jù)庫鏈接的方法
private Connection getConnection() {
if (null != conn) {
return conn;
} else {
try {
Class.forName(JDBC_DRIVER);
conn = DriverManager.getConnection(DB_URL, USER, PASS);
} catch (Exception e) {
e.printStackTrace();
}
return conn;
}
}
@Override
public User findById(long id) {
if(null == conn)
conn = this.getConnection();
User user = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
ps = conn.prepareStatement(findUserById);
ps.setLong(1, id);
rs = ps.executeQuery();
while(rs.next()){
user = new User(
rs.getLong(1), // id
rs.getString(2), // userid
rs.getString(3), // userName
rs.getString(4), // email
rs.getString(5), // phone
rs.getString(6), // password
rs.getString(7), // status
rs.getString(8), // createDatet
rs.getString(9), // modDatet
rs.getString(10)); // funcArr
}
rs.close();
ps.close();
} catch (Exception e) {
e.printStackTrace();
} finally {
// 確保資源關(guān)閉
try {
if (rs != null) rs.close();
} catch (SQLException e) {
}
try {
if (ps != null) ps.close();
} catch (SQLException e) {
}
}
return user;
}
......
}
控制層設(shè)計實現(xiàn)
控制層的功能主要由Servlet實現(xiàn),通過分析需求文檔可以得出,要實現(xiàn)本系統(tǒng),至少需要Servlet提供以下服務(wù):
- 用戶信息驗證
- 用戶信息查詢
- 用戶信息新增
- 用戶信息修改
- 密碼重置
使用eclipse新建Servlet類
以實現(xiàn)“用戶信息驗證”服務(wù)為例,控制層實現(xiàn)應(yīng)首先新建Servlet類。按以下步驟操作“File” -> “New” -> “Servlet”得到如下彈出框。

選擇包結(jié)構(gòu),輸入自定義的Servlet名稱,點擊“Next”,輸入對這個Servlet類的描述,如果有需要,可以添加初始化參數(shù)。

點擊“Next”,根據(jù)自己需要擴展Servlet類,最后點擊“Finish”完成類的新建。

此時eclipse會自動將上述配置的內(nèi)容轉(zhuǎn)換為web.xml里的配置,接下來我們需要做的,就是在新增的Servlet類中,實現(xiàn)對應(yīng)的服務(wù)。
<!-- web.xml里新增的內(nèi)容 -->
<servlet>
<description></description>
<display-name>UserCheckServlet</display-name>
<servlet-name>UserCheckServlet</servlet-name>
<servlet-class>servlet.UserCheckServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>UserCheckServlet</servlet-name>
<url-pattern>/UserCheckServlet</url-pattern>
</servlet-mapping>
實現(xiàn)業(yè)務(wù)功能
下述代碼簡單描述了“用戶信息驗證”的業(yè)務(wù)流程,即獲取參數(shù) -> 獲取后臺數(shù)據(jù) -> 對比用戶信息與后臺保存是否一致 -> 跳轉(zhuǎn)到指定頁面。
/**
* 用戶信息驗證
*/
public class UserCheckServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
// 默認調(diào)用doPost方法,免得同一段代碼寫多次
this.doPost(request, response);
}
protected void doPost(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
// 獲取用戶輸入的信息
String userName = (String)request.getParameter("userName");
String passWord = (String)request.getParameter("passWord");
// 將用戶名稱作為參數(shù)傳遞給后臺DAO
UserDao userDao = new UserDaoImpl();
Map<String, Object> paramMap = new HashMap<>();
paramMap.put("userName", userName);
// DAO完成用戶用戶信息檢索
User user = userDao.findOne(paramMap);
// 根據(jù)查詢的結(jié)果判斷用戶是否真實存在,并跳轉(zhuǎn)到指定的頁面
if(user.getPassword().equals(passWord)){
response.sendRedirect("登錄成功跳轉(zhuǎn)到首頁");
} else {
response.sendRedirect("登陸不成功則停留在登錄頁面");
}
}
}
JSP實現(xiàn)前端頁面展示
eclipse新建一個JSP文件時會有很多問題,比如默認不支持中文,要解決這類問題就要設(shè)置一下eclipse的默認配置。
按照以下步驟“Windows -> “Preferences” -> “Web” -> “JSP File” -> “Editor” -> “Templates”,找到對應(yīng)的頁面。

點擊“Editor”編輯JSP文件模版,這樣再次新建文件時就不會有問題了。
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<base href="<%=basePath%>">
<title>標題</title>
<meta http-equiv="pragma" content="no-cache">
<meta http-equiv="cache-control" content="no-cache">
<meta http-equiv="expires" content="0">
<meta http-equiv="keywords" content="keyword1,keyword2,keyword3">
<meta http-equiv="description" content="This is my page">
</head>
<body>
${cursor}
</body>
</html>
總結(jié)
至此,我們已經(jīng)基本實現(xiàn)了用戶登錄注冊系統(tǒng)涉及的功能(項目源碼地址)。但一個完善的,工業(yè)級別的系統(tǒng)遠非僅僅“實現(xiàn)功能”這么簡單,以本項目為例,應(yīng)至少滿足以下幾點要求才能最終上線。
- 系統(tǒng)的性能要足夠好
- 系統(tǒng)的安全性要有保障
性能是衡量一個軟件產(chǎn)品的終極指標,世界上任何一款軟件產(chǎn)品都會在滿足所以業(yè)務(wù)功能基礎(chǔ)上,盡力提升性能,提高程序的運轉(zhuǎn)效率。JSP和Servlet技術(shù)是Java Web技術(shù)的基礎(chǔ),它有著上手簡單,文檔資源豐富等優(yōu)點,可以讓稍有Java基礎(chǔ)的同學(xué)在搜索引擎的幫助下,快速實現(xiàn)功能。但也正是因為它“太基礎(chǔ)”,很多性能相關(guān)的內(nèi)容都需要程序員自己設(shè)計實現(xiàn),而這些內(nèi)容,恰恰是網(wǎng)上“稀有資源”。
web應(yīng)用通常會暴露在公網(wǎng)環(huán)境中,安全性也不容忽視。安全問題是相對的,不存在“絕對安全”一說,但一個需要正式上線運行的產(chǎn)品一定要有對應(yīng)的安全規(guī)格和標準。這么做既是對產(chǎn)品負責,更是對用戶負責。
所以,這個“登錄注冊系統(tǒng)”下一階段的目標,就是提升性能表現(xiàn),提高安全等級。