MVC模型和JavaWeb模型入門_簡單的註冊登入案例
很久沒有寫部落格了,主要的原因還是因為懶惰…前些日子上課講到了一個簡單的註冊登入案例;
雖然案例本身需求並不複雜,但因為老師將MVC的基本思想和JavaWeb的分層模型思想加入到裡面,讓這個小案例有了很多可以咀嚼的地方,特此做個記錄;
小生水平有限,不足之處望多多交流:)
基礎知識:
MVC模型和JavaWeb分層思想;
MVC模型;
控制器Controller:對請求進行處理,負責請求轉發;
檢視View:介面設計人員進行圖形介面設計;
模型Model:程式編寫程式應用的功能(實現演算法等等)、資料庫管理;
這是一個設計思想,具體可以Google;按照我的理解,這是一個將整個工程劃分為三個部分的設計思想;
M層— 資料模型層;
這一層主要與資料打交道,結合JDBC等實現可以從程式碼層面完成和資料庫的聯動,從而實現通過程式碼對資料庫程序操作;另外很多具體的資料實體也可以分到這一層;
總而言之,這個層面直接面對資料;
V層— 檢視層;
我最不感興趣的一層,就個人而言感覺這層不那麼有趣(請前端攻城獅輕拍);這一層主要和使用者打交道,主要用於資料的表示層;在M層對資料的操作和處理結果都會反饋到這一層,而這一層提交的對資料的請求也會由C層進行控制並聯合M層進行操控;
簡言之,這一層主要使用者資料顯示;
C層— 控制層;
據說是最複雜的一層;
這一層主要負責對M層和V層的行為掌控,由V層提交的資料訪問請求經過處理之後傳達給M層,並且當M層完成資料初六之後再返回到V層;這一系列的操作都需要由C層來進行操作和主導;
簡言之,這一層主要負責對請求的處理和轉發;
下圖是MVC的示意圖:
參考資料:崔希凡老師筆記(內部);
JavaWeb
相比MVC,JavaWeb的模型層劃分更加細緻,二者之間有聯絡,但不能單純的認為MVC模型就是JavaWeb資料模型層;要知道在除開web工程,很多軟體模型也使用了MVC分層思想;
所謂的JavaWeb三層框架,主要分為web層,業務層和實體層;
WEB層:包含JSP和Servlet等與WEB相關的內容;
業務層:業務層中不包含JavaWeb API,它只關心業務邏輯;
資料層:封裝了對資料庫的訪問細節;
WEB層:
這層主要負責web方面內容,包括HTML和servlet,由於目前沒有學到SSH三大框架,暫時無法使用action,所以控制器(C層)方面基本由servlet負責,這也是容易和MVC混淆的地方;需要了解的是,嚴格意義上的WEB層之負責和它名字相關的部分,剩下的業務邏輯和資料訪問等資訊都是管的;而在MVC模型中,C層和V層是分開的,這是兩者最大的區別;另外,WEB層依賴於業務層;
業務層:
這層主要負責業務邏輯,只負責具體的業務流程和實現,而對於web層的相關資訊則不予理睬,它主要和資料層打交道,負責完成業務邏輯中的相關操作,同時需要訪問資料層;這一層依賴於資料層;
資料層:
這一層主要負責資料操作,有點類似於MVC中的M層,但依然不能混淆;同時這層當中似乎還包括實體層;這層還需要和資料庫打交道;
下圖是JavaWeb的示意圖;
簡單的註冊登入案例
需求分析:
完成一個簡單的註冊登入介面;
在服務端對資料進行驗證,其中包括空使用者名稱,空密碼,密碼不匹配和資料格式不符合要求等;
在註冊介面進行註冊,將請求介面的使用者資訊封裝存入資料庫;
另外需要設計簡單的友好提示,用於處理使用者誤操作的情況;
專案的基本結構;
簡要說明:
資料庫為MySQL;
Dao包負責資料模型層;
Domai包負責實體層,存放UserBean;
Service包負責業務邏輯;
Web.servlect包負責控制層;
V層由jsp組成;
Utils和test包提供對dao層和bean進行操作的工具類;
開發流程:
在這個案例之前,因為還沒有學習到資料庫,暫時歇了一個用XML檔案做資料儲存的案例,其他層完全一樣,由此也可以體現出MVC模型的強大和靈活之處,在這個案例中,只需要簡單的將資料訪問層的配置檔案更改即可完成從XML儲存到MYSQL的切換;下面是之前的流程圖,在本案例中將XML部分替換為資料庫即可;
開發前期準備;
在實際開發中,有些重複性的工作是不需要每次都做的;所以一般會先準備一些工具包;
首先是jar包
commons-beanutils-1.8.3.jar
commons-logging-1.1.1.jar
jaxen-1.1-beta-6.jar
mysql-connector-java-5.0.8-bin.jar
說實話是不是都需要我忘了,因為這些部分是直接Copy來的,但並不影響最後效果;
然後是一些不需要手寫的工具類和實體類;
timescript.com.domain
User.java
timescript.com.utils
BeanFactory.java
JdbcUtil.java
WebUtils.java
Timescript.com.exception
UserExistException.java
Timescript.com.web.form
UserFormBean.java
bean.properties
Jdbccfg.properties
需要知道的是,至少在這個案例中,資料的流向和開發的步驟是相反的;按照上面的流程圖所示,最開始準備的應該是資料庫;
那麼開始吧 —
資料庫建表資訊;
create table user(
username varchar(20),
password varchar(16),
email varchar(50),
birthday datetime
);
建立DAO介面和其實現類;
Timescript.com.dao
Timescript.com.dao.impl
這一層完成後可以使用單元測試驗證一下資料是否能夠新增到資料庫當中;
package timescript.com.dao;
import timescript.com.domain.User;
/**
這是dao層介面;
*/
public interface UserDao {
/*
* 將一個指定的使用者新增到資料庫;
*/
public void addUser(User user);
/*
* 根據使用者名稱和密碼進行查詢;
*/
public User getUserByUsernameAndPassword(String username,String password);
/*
* 根據使用者名稱進行查詢;
*/
public User getUserByUsername(String username);
}
package timescript.com.dao.impl;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import timescript.com.dao.UserDao;
import timescript.com.domain.User;
import timescript.com.utils.JdbcUtil;
/**
這是dao層介面的實現類;
*/
public class UserMySqlDaoImpl implements UserDao {
@Override
public void addUser(User user) {
Connection con = null;// 定義一個連線;
PreparedStatement st = null;// 定義SQL語句物件;
try {
con = JdbcUtil.getConnection();
String sql = "insert into user value(?,?,?,?)";
st = con.prepareStatement(sql);
st.setString(1, user.getUsername());// 依次對不同欄位的屬性進行賦值;
st.setString(2, user.getPassword());
st.setString(3, user.getEmail());
st.setDate(4, new java.sql.Date(user.getBirthday().getTime()));
// 因為日期型別比較特殊所以需要轉換一下;
st.executeUpdate();// 更新資料;
} catch (Exception e) {
e.printStackTrace();
} finally {
JdbcUtil.release(null, st, con);
// 無論操作是否成功,都需要釋放資源;
}
}
// 使用者登入方法;
@Override
public User getUserByUsernameAndPassword(String username, String password) {
Connection con = null;
PreparedStatement st = null;
ResultSet rs = null;
User user = null;
try {
con = JdbcUtil.getConnection();// 獲取連線;
String sql = "select * from user where username=?";
// 定義查詢使用者名稱的sql語句;
if (password != null) {// 避免使用者使用空密碼登入情況;
sql += "and password=?";
}
System.out.println(sql);
// 建立傳送和執行sql語句的物件;
st = con.prepareStatement(sql);
// 為第一個佔位符賦值為username
st.setString(1, username);
if (password != null && !"".equals(password)) {
// 如果密碼不是空字串則對第二個佔位符賦值為password;
st.setString(2, password);
}
// 對查詢結果進行處理;
rs = st.executeQuery();
if (rs.next()) {
//如果有下一條,則說明使用者名稱和密碼在記錄表中存在,則將結果集封裝到UserBean當中
user = new User(rs.getString("username"),
rs.getString("password"),
rs.getString("email"),
rs.getDate("birthday"));
}
} catch (Exception e) {
e.printStackTrace();
}
return user;
}
@Override
public User getUserByUsername(String username) {
return getUserByUsernameAndPassword(username, null);
}
}
這一層完成後可以使用單元測試驗證一下資料是否能夠新增到資料庫當中;
建立service介面和其實現類;
timescript.com.service
Timescript.com.service.impl
package timescript.com.service;
import timescript.com.domain.User;
import timescript.com.exception.UserExistException;
/**
* 業務層介面
*/
public interface UserService {
/**
* 用於實現使用者登入功能;
*/
public User login(String username, String password);
/**
* 用於實現註冊功能;
*/
public void register(User user)throws UserExistException;
}
package timescript.com.service.impl;
import timescript.com.dao.UserDao;
import timescript.com.domain.User;
import timescript.com.exception.UserExistException;
import timescript.com.service.UserService;
import timescript.com.utils.BeanFactory;
public class UserServiceImpl implements UserService {
//通過工廠建立資料層物件;
private UserDao dao= BeanFactory.getInstance().getUserDaoInstance();
@Override
public User login(String username, String password) {
//返回資料層呼叫登入方法獲取的結果;
return dao.getUserByUsernameAndPassword(username, password);
}
@Override
public void register(User user) throws UserExistException {
//通過資料層呼叫方法對資料庫進行查詢,看註冊使用者是否存在;
User u= dao.getUserByUsername(user.getUsername());
if(u==null){
//如過使用者不存在則呼叫註冊方法進行註冊;
dao.addUser(user);
}else{
//否則丟擲使用者已存在異常;
throw new UserExistException();
}
}
}
建立ControllerServlet完成控制功能;
Timescript.com.web.servlet
package timescript.com.web.servlet;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.InvocationTargetException;
import java.util.Date;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.beanutils.ConvertUtils;
import org.apache.commons.beanutils.locale.converters.DateLocaleConverter;
import timescript.com.domain.User;
import timescript.com.exception.UserExistException;
import timescript.com.service.UserService;
import timescript.com.service.impl.UserServiceImpl;
import timescript.com.utils.WebUtils;
import timescript.com.web.form.UserFormBean;
public class ControllerServlet extends HttpServlet {
public void destroy() {
super.destroy();
}
private UserService us = new UserServiceImpl();
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 解決瀏覽器亂碼問題
request.setCharacterEncoding("UTF-8");
response.setContentType("text/html;charset= UTF-8");
// 獲取一個輸出流物件
PrintWriter out = response.getWriter();
// 獲取請求介面引數名為“op”的引數值;
// 並根據該值呼叫相應的函式進行操作;
String op = request.getParameter("op");
if ("login".equals(op)) {
login(request, response);
} else if ("register".equals(op)) {
register(request, response);
} else if ("logout".equals(op)) {
logout(request, response);
} else {
out.write("該功能尚在開發當中,2秒後返回主介面");
response.setHeader("Refresh", "2;URL=" + request.getContextPath());
}
}
// 登出的操作
private void logout(HttpServletRequest request, HttpServletResponse response)
throws IOException {
// 獲取到session物件;
HttpSession hs = request.getSession();
// 獲取一個輸入流用於列印提示資訊;
PrintWriter out = response.getWriter();
hs.removeAttribute("user");// 移除user的session;
hs.invalidate();// 銷燬session;
out.write("登出成功,2秒後返回首頁");
response.setHeader("Refresh", "2;URL=" + request.getContextPath());
}
// 註冊操作;
private void register(HttpServletRequest request,
HttpServletResponse response) throws IOException, ServletException {
PrintWriter out = response.getWriter();
// 將請求介面傳入的引數封裝到bean當中;
UserFormBean bean = WebUtils.fillBean(request, UserFormBean.class);
if (!bean.validate()) {
// 如果驗證方法返回值不為空,則說明使用者的註冊資訊填寫有誤;
// 儲存使用者已填寫的資訊,然後跳回註冊介面;
request.setAttribute("bean", bean);
request.getRequestDispatcher("/register.jsp").forward(request,
response);
return;
}
// 註冊一個日期轉換器;
ConvertUtils.register(new DateLocaleConverter(), Date.class);
// 建立一個使用者物件
User user = new User();
try {
// 通過beanutils的方法將bean當中的資料封裝回UserBean當中;
BeanUtils.copyProperties(user, bean);
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("轉換資料失敗");
}
try {
us.register(user);// 呼叫業務層的方法進行註冊;
out.write("註冊成功,2秒後跳轉到主頁;");
response.setHeader("Refresh", "2;URL="+request.getContextPath());
} catch (UserExistException e) {
// 如果捕獲了同名異常則進行提示並跳回註冊介面;
bean.getErrorMsg().put("username", "使用者名稱已存在");
request.setAttribute("bean", bean);
request.getRequestDispatcher("/register.jsp").forward(request,
response);
}
}
// 登入操作;
private void login(HttpServletRequest request, HttpServletResponse response)
throws IOException {
PrintWriter out = response.getWriter();
// 獲取請求引數中的具體值;
String username = request.getParameter("username");
String password = request.getParameter("password");
//呼叫業務層的方法進行登入;
User user = us.login(username, password);
if (user != null) {
//如果user例項物件不為空,則說明登入成功;
//則將使用者資訊儲存到session當中;
request.getSession().setAttribute("user", user);
response.sendRedirect(request.getContextPath());
} else {
out.write("登入失敗,2秒後返回登入介面");
response.setHeader("Refresh", "2;URL" + request.getContextPath()
+ "/login.jsp");
}
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
this.doGet(request, response);
}
public void init() throws ServletException {
}
}
需要注意的是,上面的步驟沒有包含已經實現準備好的類似於UserBean這樣的實體類的建立;
不過每一段程式碼的註釋都足夠詳細,可以參考瞭解整個業務邏輯和資料訪問的具體實現;
小結和收穫;
需要承認的是,到目前為止對於這整個模型的掌握程度還根本不夠;英語所造成的阻礙已經越發明顯;其實在目前階段,很多方法的名字都非常直白;但只因為看不懂英語,導致整個開發過程很痛苦;另外需要注意的是對錯誤的排查,MyEclipse至少目前看來對於錯誤的提示還是比較準確,基本能夠達到看懂意思就能發現錯誤所在的地方;不過需要注意的是字串的拼寫和URL地址的錯誤填寫,這樣的錯誤MyEclipse無法檢測出來,在除錯的過程中也很花費時間;按照老師的建議,複製貼上確實不失為一種手段;
不過最大的問題還是練習的不足;其實到目前為止,就像在web階段老師說的那樣,還沒有遇到過什麼太過於複雜的問題,大部分都是單詞拼寫錯誤或者採用自動完成時出現的導錯包或者是方法的許可權錯誤等等;
但經過半天的思考和練習,對MVC和JavaWeb的結構有了深一層的瞭解,日後需要學習到的其他技術,大概也不會脫離這個基本框架;只會在不同的部分進行新增補齊;