JavaWeb中的MVC


不使用什么MVC的案例分析:

利用Servlet與jsp實現登陸請求,數據庫查詢,以及頁面的跳轉邏輯

具體流程如下:

image-20191214113351941

不做任何結構上的考慮,可以簡單的做如下實現:

目錄結構

image-20191214113859994

LoginServlet
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.sql.*;

@WebServlet("/Login")
public class LoginServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doGet(req,resp);//轉發到get 執行相同邏輯
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

        //獲取參數
        String name = req.getParameter("username");
        String password = req.getParameter("pwd");


        String error = null; //錯誤信息
        boolean flag = false; //是否登陸成功
        //驗證數據可靠性
        if(name == null || password == null || name.equals("") || password.equals("")){
            error="用戶名和密碼不能為空!";
        }else {
            //查詢數據庫
            try {
                Class.forName("com.mysql.jdbc.Driver");
                Connection connect = DriverManager.getConnection("jdbc:mysql://127.0.0.1/db1?charsetEncoding=utf-8&user=root&password=admin");
                PreparedStatement statement = connect.prepareStatement("select *from user where name = ? and password = ?");
                statement.setObject(1,name);
                statement.setObject(2,password);
                ResultSet set = statement.executeQuery();
                if(set.next()){
                    flag = true;
                }else{
                    error="用戶名和密碼錯誤!";
                }
            } catch (ClassNotFoundException | SQLException e1) {
                error = "服務器忙,請稍后再試!";
            }
        }

        if(flag){
            //刪除已存在的錯誤信息,避免后續登錄頁面展示重復展示
            req.getSession().removeAttribute("error");
            //添加用戶名稱到session
            req.getSession().setAttribute("user",name);
            //跳轉頁面
            req.getRequestDispatcher("./index.jsp").forward(req,resp);
        }else{
            //添加錯誤信息到session
            req.getSession().setAttribute("error",error);
            //跳轉頁面
            req.getRequestDispatcher("./login.jsp").forward(req,resp);
        }
    }
}
login.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
  <head>
    <title>$Title$</title>
  </head>
  <body>
<span style="color: red">${error}</span>
  <form action="./Login" method="post">
    用戶名:<input name="username"/>
    <br>
    密碼:<input name="pwd">
    <br>
    <input type="submit" value="登錄">
  </form>
  </body>
</html>
index.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>主頁</title>
</head>
<body>
<h1>你好${user},歡迎登錄</h1>
</body>
</html>

增加需求

通過簡單的邏輯判斷和JDBC就實現了上述需求,接下來我們繼續增加需求

假設現在要增加注冊功能,可以這樣:

  1. 創建register.jsp
  2. 創建 RegisterServlet
  3. 在RegisterServlet,獲取參數,鏈接數據庫,處理注冊邏輯

思考,這樣寫有什么問題?

  • 按照這樣的思路,一個功能就需要一個Servlet,而Servlet的生命周期是非常長的,將會大量消耗內存
  • 如果不創建其他Servlet,那只能將所有URL交個同一個Servlet處理,這樣的話就不可避免的需要對請求路勁進行判斷,以確定客戶端要執行的功能
  • 每個功能都要重復的進行數據庫的相關操作,例如連接數據庫,提取結果等,出現了大量重復代碼

上述列出的問題,在程序開發中是經常遇到的,所以我們不得不對程序的整體結構進行重新思考和設計

MVC

MVC是一種軟件構架模式,所謂模式,就是前輩們給我們總結出的針對固定類型問題的已有解決方案,這寫方案是經過大量時間,以及大量項目實踐,最終總結出的良好的方案;給開發者,提供了非常好的指導思想,在漫長的技術發展過程中,開發者們總結出了,很多好的模式,如<<23中常見模式>>一書中講到;

注意:嚴格來說MVC不屬於設計模式,而是構架模式,因為MVC是為程序划分層了次結構,使得程序結構更規范,更合理,降低了耦合度

針對web項目,MVC就是非常通用的構架模式,到底如何去設計一款MVC的程序呢?

MVC分層

為了降低各個功能間的耦合度,提高代碼的可維護性,MVC將程序分為了三個層次

  • M:Model 模型,用於作為數據的載體的Bean,通常不包含復雜邏輯,一個Bean對應數據庫一條記錄
  • V:View 視圖,用於展示Model中的數據,和處理用戶交互
  • C :Controller 控制器,接受View的輸入,根據需要調用Mode,獲取數據后再交給View層進行展示

補充POJO和Bean的區別,POJO更加簡單只要求由set/get,而Bean為了滿足可重用性,有更多的規范要求

圖示:

image-20191214123329331

注意:

View與Model層之間不允許直接交互,必須由Controller來調度

Service層

按照這樣的層次結構,將servlet用於Controller,在增加一個Bean類,來裝載數據,看起來不錯,但是沒有解決根本的問題,業務邏輯和數據庫操作依然擠在Servlet中,必須在對其進行拆分,於是在MVC基礎結構上增加Service層,用於處理業務邏輯,你也可以叫它業務邏輯層(business)

看起來就像下面這樣:

image-20191214131855074

你可能會問,Service屬於M還是C,它既不是C也不是M,是我們在MVC基礎上擴展出來的,我們經常會在已有模式上擴展新的內容這很正常

使用MVC

現在對之前的程序進行改造

  • 增加JavaBean,其中的屬性與對應的表相同,擁有ID,name,password
  • 增加service包,並創建UserService類,用作業務邏輯層
  • 在控制器Servlet中,調用Service來完成業務功能
  • 根據Service的返回結果來決定頁面的跳轉地址
項目結構:

image-20191214151229600

UserLoginServlet:
package com.xx.controller;

import com.xx.exceptions.LoginException;
import com.xx.models.UserBean;
import com.xx.services.UserService;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.sql.SQLException;

@WebServlet("/Login")
public class UserLoginServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request,response);
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
      	//獲取參數
        String name = request.getParameter("username");
        String pwd = request.getParameter("pwd");
        UserBean reqBean = new UserBean(name,pwd);
				//實例化業務邏輯對象
        UserService service = new UserService();

        try {
          	//調用對應方法 根據結構跳轉頁面
            UserBean resBean = service.CheckLogin(reqBean);
            request.getSession().setAttribute("user",resBean.getName());
            response.sendRedirect(request.getServletContext().getContextPath() +"/index.jsp");
        } catch (LoginException e) {
            request.getSession().setAttribute("error",e.getMessage());
            response.sendRedirect(request.getServletContext().getContextPath() +"/login.jsp");
        }
    }
}
UserService:
package com.xx.services;

import com.xx.exceptions.LoginException;
import com.xx.models.UserBean;

import java.sql.*;

public class UserService {

    public UserBean CheckLogin(UserBean userBean) throws LoginException {
        //驗證參數有效性
        if (userBean.getPassword() == null || userBean.getPassword() == null ||
            userBean.getPassword().equals("") || userBean.getName().equals("")){
            throw new LoginException("用戶名和密碼不能為空");
        }
        UserBean bean = null;
        //查詢數據庫
        try {
            Class.forName("com.mysql.jdbc.Driver");
            Connection connection = DriverManager.getConnection("jdbc:mysql://localhost/db1?characterEncoding=utf8&user=root&password=admin");
            PreparedStatement preparedStatement = connection.prepareStatement("select *from user where name  = ? and password = ?");
            preparedStatement.setObject(1, userBean.getName());
            preparedStatement.setObject(2, userBean.getPassword());
            ResultSet set = preparedStatement.executeQuery();
            if (set.next()) {
                bean = new UserBean();
                bean.setId(set.getInt(1));
                bean.setName(set.getString(2));
                bean.setPassword(set.getString(3));
            }else {
                throw new LoginException("用戶名或密碼錯誤!");
            }
        }catch (SQLException | ClassNotFoundException e){
            e.printStackTrace();
            throw new LoginException("服務器忙,(數據庫炸了)",e);
        }
        return bean;
    }
}

UserBean僅包含構造器,和setget方法

LoginException,也只是簡單的繼承了Exception

小結:

MVC是Web應用常用的設計模式,其增強了代碼的擴展性,可維護性,降低了各組件的耦合度

在上述案例中通過MVC分層,代碼結構得到了優化,但是細心的你可能已經發現了,

  • 業務邏輯層Service中夾雜着與數據庫相關的所有代碼,導致代碼冗余.重復代碼問題依然沒有得到解決
  • UserLoginServlet依然只能處理用戶的登錄請求


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM