閱讀504 返回首頁    go 人物


【Servlet】根據MVC思想設計用戶登陸、用戶注冊、修改密碼係統

MVC不是一種像C、JAVA的編程語言,也不是一種像Ajax,Servlet的技術,隻是一種如同麵向對象一樣編程思想。近年來MVC一直很火,讚者批者有之,然後大篇幅的文章很多,可是簡明扼要的簡單MVC的例子幾乎沒有。在JSP領域一直向鼓風機地勐吹SSH如何如何地好,一直怒批JSP,Servlet等如何如何差。其實使用JSP+Servlet同樣可以利用MVC思想來完成一個係統。下麵用一個爛得不能再爛的例子,你步入網頁編程必須學會的東西,登陸、注冊、修改密碼係統,來說明這種編程思想。


一、基本目標

在一個form.jsp頁麵有三個表單,分別是登陸係統、注冊係統、修改密碼係統,不同情況給出不同的信息,如下圖:


處理Servlet,也就是那些Java文件,是不可以通過直接輸入網址來訪問的。



二、基本思想

網站目錄結構如下圖所示,輸入表單的頁麵form.jsp就是所謂的View層,單擊go!之後提交到的處理Java文件登陸係統的login.java,注冊係統的register.java,修改密碼係統的update.java裏麵不允許有任何操作數據庫的動作,它們隻能通過form.jsp發送過來的信息,構造相應的sql語句到dbDAO.java,在dbDAO.java同一操作數據庫,這些java文件就是所謂的Controller層,然後dbDAO.java就是所謂的處於JavaBean,Java容器下的DAO組件,什麼業務邏輯的狗屁抽象東西了,也就是Model層了。


MVC的編程思想,在一開始寫程序是最蛋疼的,其在最初始的開發速度不如直接寫出Java語句操作數據庫,你所想即所得當然寫得比起你又要把操作數據庫的東西抽象出來放在一個dbDAO.java裏麵快,但是在你開發完一個登陸係統之後,隨後的用戶注冊係統、修改密碼係統很快就能夠完成了,因為所有的東西都被你抽象好了,改起來非常好改。這就是現在一些企業嚴格要求MVC去編程的原因,首先第一個分工明確,第二個是以後改起來好改,最重要的是他們可以利用一些技術不讓你看到dbDAO.java,你隻管開發View層或者Controller就能不完全掌握它們底層的數據庫是什麼。甚至一些專門寫Model的程序員,他們根本就不知道數據庫裏麵的一堆數據用來幹什麼。隻有核心高管才知道整個程序的流程。他管你程序難寫不好寫,不會寫請滾粗。

MVC編程思想有好有不好,這裏不再討論,關鍵是你要會。這是一道從低級程序猿進階到中級程序猿的坎。下麵使用Servlet說明一下怎麼同時實現這三個係統的。不采用複雜SSH搞得一頭霧水了。

這東西真的不難,請不要給那些大篇幅的東西嚇到,隻是我此前討論過的JDBC技術《【Mysql】Java中對Mysql數據庫的增刪改查、Java的System類》(點擊打開鏈接)與Servlet技術《【Servlet】最簡單的Servlet JavaWeb程序》(點擊打開鏈接)的指示再現而已。


三、製作過程


1、首先還是有一個test數據庫,裏麵有一張用戶信息表usertable,還是如下圖經典的配置,主鍵、用戶名、密碼,


在表裏麵已經存在幾個用戶信息,如下圖,按理說,這裏的密碼應該通過SHA-1的編碼再存放的,但是為了避免太複雜,這裏先用明文存放:



2、新建一個eclipse工程,在lib文件夾,放入javax.servlet-api-3.1.0.jar,防止一些來路不明的Tomcat不支持Servlet,這東西超過3.0就可以,還有所謂的mysqlJDBC數據源mysql-connector-java-5.1.32.jar,這東西超過5.0即可。就兩個Jar包,網上一大堆,擺入lib文件夾就完事。



3、配置好Servlet,在web.xml文件寫入如下的代碼,表示/login的域名就跳到根目錄下的login.java處理,其餘如此類推。

[html] view plain copy
  1. <?xml version="1.0" encoding="UTF-8"?>  
  2. <web-app xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"  
  3.     xmlns="https://java.sun.com/xml/ns/javaee"  
  4.     xsi:schemaLocation="https://java.sun.com/xml/ns/javaee https://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"  
  5.     version="2.5">  
  6. <!-- 用戶登錄 -->  
  7.     <servlet>  
  8.         <servlet-name>login</servlet-name>  
  9.         <servlet-class>login</servlet-class>  
  10.     </servlet>  
  11.     <servlet-mapping>  
  12.         <servlet-name>login</servlet-name>  
  13.         <url-pattern>/login</url-pattern>  
  14.     </servlet-mapping>  
  15. <!-- 用戶注冊 -->   
  16.     <servlet>  
  17.         <servlet-name>register</servlet-name>  
  18.         <servlet-class>register</servlet-class>  
  19.     </servlet>  
  20.     <servlet-mapping>  
  21.         <servlet-name>register</servlet-name>  
  22.         <url-pattern>/register</url-pattern>  
  23.     </servlet-mapping>  
  24. <!-- 用戶修改密碼 -->     
  25.     <servlet>  
  26.         <servlet-name>update</servlet-name>  
  27.         <servlet-class>update</servlet-class>  
  28.     </servlet>  
  29.     <servlet-mapping>  
  30.         <servlet-name>update</servlet-name>  
  31.         <url-pattern>/update</url-pattern>  
  32.     </servlet-mapping>  
  33. </web-app>  


4、之後MVC先寫哪個都沒有關係,我習慣先寫V,因為V最簡單,就一個表示層,不用怎麼思考,然後用戶會產生什麼變量你寫完V就能夠明確。也就是那個form.jsp,裏麵實際上就三張簡單得不能再簡單的表單了。放一段jsp語句,檢查是否存在提示信息,如果有則輸出。

[html] view plain copy
  1. <%@ page language="java" contentType="text/html; charset=utf-8"  
  2.     pageEncoding="utf-8"%>  
  3. <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "https://www.w3.org/TR/html4/loose.dtd">  
  4. <html>  
  5. <head>  
  6. <meta http-equiv="Content-Type" content="text/html; charset=utf-8">  
  7. <title>login</title>  
  8. </head>  
  9. <body>  
  10.     <div style="color: red;">  
  11.         <strong>   
  12.             <%  
  13.             if(request.getAttribute("errmsg")!=null){  
  14.                 out.println(request.getAttribute("errmsg"));  
  15.             }  
  16.             %>  
  17.         </strong>  
  18.     </div>  
  19.     用戶登錄:<br />  
  20.     <form method="post" action="login">  
  21.         username:<input type="text" name="username" /><br />  
  22.         password:<input  type="password" name="password" /><br />  
  23.         <input type="submit" value="go!" /><br />  
  24.     </form>  
  25.     用戶注冊:<br />  
  26.     <form method="post" action="register">  
  27.         username:<input type="text" name="username" /><br />  
  28.         password:<input  type="password" name="password" /><br />  
  29.         passwordAgain:<input type="password" name="passwordAgain" /><br />  
  30.         <input type="submit" value="go!" /><br />  
  31.     </form>  
  32.     修改密碼:<br />  
  33.     <form method="post" action="update">  
  34.         username:<input type="text" name="username" /><br />  
  35.         password:<input  type="password" name="password" /><br />  
  36.         newpassword:<input type="password" name="newpassword" /><br />  
  37.         newpasswordAgain:<input  type="password" name="newpasswordAgain" /><br />  
  38.         <input type="submit" value="go!" /><br />  
  39.     </form>  
  40. </body>  
  41. </html>  
按理說,這段jsp也應該省去,使得V層是純粹的V層,全部都是前端代碼,應該直接利用ajax技術去完成錯誤信息的輸出,使用Dwr或者jqueryAjax什麼的javascript前端技術,這裏為了不搞太複雜,還是用回簡單的jsp代碼來說明,同時驗證兩次密碼是否一致,也應該用去《【JavaScript】表單即時驗證,不成功不讓提交》(點擊打開鏈接)做的,但是,同樣為了簡單一點,放到C層去處理吧。理解了MVC思想之後,大家慢慢改。


5、再寫M,操作數據庫的動作抽象出來,把連接數據庫的動作放在構造函數,把關閉數據庫的動作放在析構函數,以後在C層連con.close也省了,別告訴我Java沒有析構函數,protected void finalize()與嚴格意義上的析構函數,得到的實際結果是沒有任何區別,隻有概念上的繁瑣區別,然後比如查詢、插入、修改數據,一般不用寫刪除,因為習慣設置標誌位偽刪除。關閉數據的時候,先看看是否現在是連接數據庫的。

這個所謂的持久層,一般寫好就不用改了。你隻需要不停寫C去調用M層,返回結果給V層。

[java] view plain copy
  1. import java.sql.*;  
  2.   
  3. public class dbDAO {  
  4.     private Connection con;  
  5.   
  6.     // 構造函數,連接數據庫  
  7.     public dbDAO() throws Exception {  
  8.         String dburl = "jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8&useOldAliasMetadataBehavior=true";  
  9.         String dbusername = "root";  
  10.         String dbpassword = "root";  
  11.         Class.forName("com.mysql.jdbc.Driver");  
  12.         this.con = DriverManager.getConnection(dburl, dbusername, dbpassword);  
  13.     }  
  14.   
  15.     // 執行查詢  
  16.     public ResultSet query(String sql, Object... args) throws Exception {  
  17.         PreparedStatement ps = con.prepareStatement(sql);  
  18.         for (int i = 0; i < args.length; i++) {  
  19.             ps.setObject(i + 1, args[i]);  
  20.         }  
  21.         return ps.executeQuery();  
  22.     }  
  23.   
  24.     // 執行插入  
  25.     public boolean insert(String sql, Object... args) throws Exception {  
  26.         PreparedStatement ps = con.prepareStatement(sql);  
  27.         for (int i = 0; i < args.length; i++) {  
  28.             ps.setObject(i + 1, args[i]);  
  29.         }  
  30.         if (ps.executeUpdate() != 1) {  
  31.             return false;  
  32.         }  
  33.         return true;  
  34.     }  
  35.   
  36.     // 執行修改  
  37.     public boolean modify(String sql, Object... args) throws Exception {  
  38.         PreparedStatement ps = con.prepareStatement(sql);  
  39.         for (int i = 0; i < args.length; i++) {  
  40.             ps.setObject(i + 1, args[i]);  
  41.         }  
  42.         if (ps.executeUpdate() != 1) {  
  43.             return false;  
  44.         }  
  45.         return true;  
  46.     }  
  47.   
  48.     // 析構函數,中斷數據庫的連接  
  49.     protected void finalize() throws Exception {  
  50.         if (!con.isClosed() || con != null) {  
  51.             con.close();  
  52.         }  
  53.     }  
  54. }  
這裏與此前討論過的JDBC技術《【Mysql】Java中對Mysql數據庫的增刪改查、Java的System類》(點擊打開鏈接)沒有任何區別,隻是注意這裏新增了一點點的小東西,通過《【Java】JDK1.5以後新型的泛型參數傳遞方法Object...args》(點擊打開鏈接)傳遞一個sql語句,與一個Object數組過來,裏麵有字符串String與整形int,這裏是為了配合setObject的方法,比如傳遞過來的sql語句是:

[sql] view plain copy
  1. update userTable set password=? where username=?  

兩個Object參數是字符串11與22,通過:

[java] view plain copy
  1. for (int i = 0; i < args.length; i++) {  
  2.     ps.setObject(i + 1, args[i]);  
  3. }  
它會自動把?替換按順序替換成你傳遞過來的Object參數,形成出一條完整的sql語句,供你之後的插入、修改、刪除方法executeUpdate()或者查詢方法executeQuery()使用:
[java] view plain copy
  1. update userTable set password=‘11‘ where username=’22‘  
如果傳遞過來的是整形,它也會自動把引號去掉,以構造出一條完整的語句。所謂的PreparedStatement對象也就是這個意思了!

直接強調插入、修改、刪除方法與查詢方法在JDBC裏麵是不同的,前者是executeUpdate(),後者是executeQuery(),那是因為前者都是沒有返回值,後者有返回值的緣故啊!當然,封裝好的asp與php就另當別論。

通過上麵的方法,現在不管你傳遞一個什麼的東西過來,都能夠查詢了。


6、最後寫C,這是最難的一步,有了M的基礎與V的基礎,才能把兩部分連接起來。這裏的Servlet不能通過一個service方法把doGet與doPost合起來,恰恰是把它們分開防止用戶直接輸入網址訪問此Servlet,因為用戶直接輸入網址訪問此Servlet是調用doGet方法,通過表單直接傳遞過來的是doPost方法,因為我再表單早就注明了。


(1)首先是login.java,登陸的思想沒什麼好說的。把此用戶名在數據庫對應的密碼與用戶輸入的密碼比對,是此成功,否此失敗,查不到就是失敗了。

可以明顯看到,這裏沒有任何操作的數據庫的東西。

[java] view plain copy
  1. import java.io.*;  
  2. import java.sql.*;  
  3.   
  4. import javax.servlet.*;  
  5. import javax.servlet.http.*;  
  6.   
  7. public class login extends HttpServlet {  
  8.     // 防止用戶直接輸入網址訪問此Servlet  
  9.     protected void doGet(HttpServletRequest request,  
  10.             HttpServletResponse response) throws ServletException, IOException {  
  11.         PrintStream out = new PrintStream(response.getOutputStream());  
  12.         response.setContentType("text/html;charSet=utf-8");  
  13.         out.print("請正常打開此頁");  
  14.     }  
  15.   
  16.     protected void doPost(HttpServletRequest request,  
  17.             HttpServletResponse response) throws ServletException {  
  18.         String errmsg = "";  
  19.         //獲取用戶輸入的東西  
  20.         String username = request.getParameter("username");  
  21.         String password = request.getParameter("password");  
  22.         try {  
  23.             //構造操作數據庫的語句  
  24.             dbDAO db = new dbDAO();           
  25.             ResultSet rs = db  
  26.                     .query("select password from userTable where username=?",  
  27.                             username);  
  28.             //根據不同的查詢結果的,返回不同的結果到View層  
  29.             if (rs.next()) {  
  30.                 if (rs.getString("password").equals(password)) {  
  31.                     HttpSession session = request.getSession();  
  32.                     session.setAttribute("username", username);  
  33.                     request.getRequestDispatcher("/welcome.jsp").forward(  
  34.                             request, response);  
  35.                 } else {  
  36.                     errmsg = "密碼錯誤!";  
  37.                     request.setAttribute("errmsg", errmsg);  
  38.                     request.getRequestDispatcher("/form.jsp").forward(request,  
  39.                             response);  
  40.                 }  
  41.             } else {  
  42.                 errmsg = "用戶名不存在!";  
  43.                 request.setAttribute("errmsg", errmsg);  
  44.                 request.getRequestDispatcher("/form.jsp").forward(request,  
  45.                         response);  
  46.             }  
  47.         } catch (Exception e) {  
  48.             e.printStackTrace();  
  49.         }  
  50.   
  51.     }  
  52. }  
這裏的錯誤信息僅僅存在request對象,它在Servlet返回結果給form.jsp之後就消息了,以後在任何頁麵用request.getAttribute("errmsg")都拿不到此次請求的返回的提示消息了。如果登陸成功,則把用戶名存放在session對象,這個對象默認是用戶不關閉這個瀏覽器都不會消失的,一直存在的。登陸成功,用戶不關閉瀏覽器,在任何頁麵利用session.getAttribute("username")都能拿到它的用戶名,除非有語句設置這個session的生存時間或者立即死亡。


(2)之後的register.java的開發就快了,你可以比對login.java代碼幾乎一樣,如果不使用MVC則要把操作數據庫大改特改,甚至如果你直接寫在jsp頁麵,就像php與asp開發那樣,不是自己去改的話很蛋疼的,思想還是那個思想,先比對用戶輸入的密碼是否一致,然後查詢是否有這個用戶,沒有就在數據庫插入這條信息。

[java] view plain copy
  1. import java.io.*;  
  2. import java.sql.*;  
  3.   
  4. import javax.servlet.*;  
  5. import javax.servlet.http.*;  
  6.   
  7. public class register extends HttpServlet {  
  8.     // 防止用戶直接輸入網址訪問此Servlet  
  9.     protected void doGet(HttpServletRequest request,  
  10.             HttpServletResponse response) throws ServletException, IOException {  
  11.         PrintStream out = new PrintStream(response.getOutputStream());  
  12.         response.setContentType("text/html;charSet=utf-8");  
  13.         out.print("請正常打開此頁");  
  14.     }  
  15.   
  16.     protected void doPost(HttpServletRequest request,  
  17.             HttpServletResponse response) throws ServletException {  
  18.         String errmsg = "";  
  19.         String username = request.getParameter("username");  
  20.         String password = request.getParameter("password");  
  21.         String passwordAgain = request.getParameter("passwordAgain");  
  22.   
  23.         try {  
  24.             if (password.equals(passwordAgain)) {  
  25.                 dbDAO db = new dbDAO();  
  26.                 ResultSet rs = db.query(  
  27.                         "select username from userTable where username=?",  
  28.                         username);  
  29.                 if (!rs.next()) {  
  30.                     db.insert(  
  31.                             "insert into userTable(username,password) values(?,?)",  
  32.                             username, password);  
  33.                     errmsg = "注冊成功!";  
  34.                     request.setAttribute("errmsg", errmsg);  
  35.                     request.getRequestDispatcher("/form.jsp").forward(request,  
  36.                             response);  
  37.                 } else {  
  38.                     errmsg = "用戶名已存在!";  
  39.                     request.setAttribute("errmsg", errmsg);  
  40.                     request.getRequestDispatcher("/form.jsp").forward(request,  
  41.                             response);  
  42.                 }  
  43.             } else {  
  44.                 errmsg = "兩次輸入的密碼不一致";  
  45.                 request.setAttribute("errmsg", errmsg);  
  46.                 request.getRequestDispatcher("/form.jsp").forward(request,  
  47.                         response);  
  48.             }  
  49.         } catch (Exception e) {  
  50.             e.printStackTrace();  
  51.         }  
  52.   
  53.     }  
  54. }  

(3)修改密碼係統,這個有了上麵一大輪的基礎,已經易如反掌,你隻要改成,看用戶兩次輸入的密碼是否一致,再看用戶是否能登陸成功,最後修改這個用戶名所對應的密碼。

[java] view plain copy
  1. import java.io.*;  
  2. import java.sql.*;  
  3.   
  4. import javax.servlet.*;  
  5. import javax.servlet.http.*;  
  6.   
  7. public class update extends HttpServlet {  
  8.     // 防止用戶直接輸入網址訪問此Servlet  
  9.     protected void doGet(HttpServletRequest request,  
  10.             HttpServletResponse response) throws ServletException, IOException {  
  11.         PrintStream out = new PrintStream(response.getOutputStream());  
  12.         response.setContentType("text/html;charSet=utf-8");  
  13.         out.print("請正常打開此頁");  
  14.     }  
  15.   
  16.     protected void doPost(HttpServletRequest request,  
  17.             HttpServletResponse response) throws ServletException {  
  18.         String errmsg = "";  
  19.         String username = request.getParameter("username");  
  20.         String password = request.getParameter("password");  
  21.         try {  
  22.             dbDAO db = new dbDAO();  
  23.             ResultSet rs = db  
  24.                     .query("select password from userTable where username=?",  
  25.                             username);  
  26.             if (rs.next()) {  
  27.                 if (rs.getString("password").equals(password)) {  
  28.                     String newpassword = request.getParameter("newpassword");  
  29.                     String newpasswordAgain = request.getParameter("newpasswordAgain");  
  30.                     if(newpassword.equals(newpasswordAgain)){  
  31.                         db.modify("update userTable set password=? where username=?", newpassword,username);  
  32.                         errmsg = "密碼修改成功!";  
  33.                         request.setAttribute("errmsg", errmsg);  
  34.                         request.getRequestDispatcher("/form.jsp").forward(request,  
  35.                                 response);  
  36.                     }  
  37.                     else{  
  38.                         errmsg = "兩次輸入密碼不一致!";  
  39.                         request.setAttribute("errmsg", errmsg);  
  40.                         request.getRequestDispatcher("/form.jsp").forward(request,  
  41.                                 response);  
  42.                     }  
  43.                 } else {  
  44.                     errmsg = "密碼錯誤!";  
  45.                     request.setAttribute("errmsg", errmsg);  
  46.                     request.getRequestDispatcher("/form.jsp").forward(request,  
  47.                             response);  
  48.                 }  
  49.             } else {  
  50.                 errmsg = "用戶名不存在!";  
  51.                 request.setAttribute("errmsg", errmsg);  
  52.                 request.getRequestDispatcher("/form.jsp").forward(request,  
  53.                         response);  
  54.             }  
  55.         } catch (Exception e) {  
  56.             e.printStackTrace();  
  57.         }  
  58.   
  59.     }  
  60. }  

四、總結與展望

通過上麵的介紹,你可以看到MVC的優點與缺點,這東西的代碼比平時多了一倍,還多了分層、建立持久層,也就是dbDAO.java的思考,但是你打好M的基礎之後,以後要加新功能,你隻要在web.xml加東西,加C層的java,查詢數據庫的語句統一變成了幾個方法query,insert,modify,連接數據庫,關閉數據庫也不用你做了。與其說是一個新的編程模式,不如是麵向對象OO編程的思想進一步深化而已。這要求你對變量的傳遞更加地熟悉。

最後更新:2017-11-18 15:03:45

  上一篇:go  Netty應用篇
  下一篇:go  如何使用高速通道實現跨VPC NAT公網共享