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依然只能处理用户的登录请求
posted @ 2019-12-14 15:25  CoderJerry  阅读(...)  评论(...编辑  收藏