MVC三层架构

本文介绍了MVC设计模式及其在JavaWeb中的应用,包括Struts2框架的使用。接着讲述了JavaWeb的开发模型从JSP Model1到Model2的演进,强调Model2的MVC结构。然后详细探讨了基于MVC的三层架构实现,分为web层、service层和dao层,并提供了项目准备、前端页面编写和java代码组织的步骤。最后讨论了如何防止表单重复提交,推荐利用Session实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

MVC简介

MVC开始是存在于桌面程序中的,M是指业务模型,V是指用户界面,C则是控制器,使用MVC的目的是将M和V的实现代码分离,从而使同一个程序可以使用不同的表现形式。比如一批统计数据可以分别用柱状图、饼图来表示。C存在的目的则是确保M和V的同步,一旦M改变,V应该同步更新。 [1-2]
模型-视图-控制器(MVC)是Xerox PARC在二十世纪八十年代为编程语言Smalltalk-80发明的一种软件设计模式,已被广泛使用。后来被推荐为Oracle旗下Sun公司Java EE平台的设计模式,并且受到越来越多的使用ColdFusion和PHP的开发者的欢迎。模型-视图-控制器模式是一个有用的工具箱,它有很多好处,但也有一些缺点。

虽然MVC并不是Java当中独有的,但是现在几乎所有的B/S的架构都采用了MVC框架模式。

  • 控制器Controller:控制器即是控制请求的处理逻辑,对请求进行处理,负责请 求转发和重定向;
  • 视图View:视图即是用户看到并与之交互的界面,比如HTML(静态资源),JSP(动态资源)等等。
  • 模型Model:模型代表着一种企业规范,就是业务流程/状态的处理以及业务规则的规定。业务流程的处理过程对其他层来说是不透明的,模型接受的请求,并返回最终的处理结果。业务模型的设计可以说是MVC的核心。
1.3 MVC高级框架应用

​ MVC模式被广泛用于Java的各种框架中,比如Struts2、spring MVC等等都用到了这种思想。

Struts2是基于MVC的轻量级的web应用框架。基于MVC,说明基于Struts2开发的Web应用自然就能实现MVC,也说明Struts2着力于在MVC的各个部分为我们的开发提供相应帮助。

第二节 JSP开发模型

2.1 JavaWeb经历两个时期
2.1.1 JSP Model1

JSP Model1是JavaWeb早期的模型,它适合小型Web项目,开发成本低!Model1第一代时期,服务器端只有JSP页面,所有的操作都在JSP页面中,连访问数据库的API也在JSP页面中完成。也就是说,所有的东西都耦合在一起,对后期的维护和扩展极为不利。

在这里插入图片描述

JSP Model1的优化(Model1第二代)

JSP Model1优化后有所改进,把业务逻辑和数据访问的内容放到了JavaBean(狭义JavaBean:实体类,广义JavaBean:实体类,dao,service,工具类)中,而JSP页面负责显示以及请求调度的工作。虽然第二代比第一代好了些,但还让JSP做了过多的工作,JSP中把视图工作和请求调度(控制器)的工作耦合在一起了。

在这里插入图片描述

2.1.2 JSP Model2

JSP Model2模式已经可以清晰的看到MVC完整的结构了。

JSP:视图层,用来与用户打交道。负责接收数据,以及显示数据给用户;

Servlet:控制层,负责找到合适的模型对象来处理业务逻辑,转发到合适的视图;

JavaBean:模型层,完成具体的业务工作,例如:开启事务、转账等。

在这里插入图片描述
小结:这就是javaweb经历的两个时期,JSP Model2适合多人合作开发大型的Web项目,各司其职,互不干涉,有利于开发中的分工,有利于组件的重用。但是,Web项目的开发难度加大,同时对开发人员的技术要求也提高了。

2.2基于MVC的三层架构的实现

虽然MVC把程序分成三部分,每个部分负责不同的功能,但是这只是逻辑的分离,实际代码并没有真正分离,特别是Model(包括业务、数据访问和实体类、工具类等)部分的代码,为了增强代码的维护性和降低代码耦合性,需要把代码分层管理,于是就有了三层架构:

分别是:web层(表示|界面层)、service层(业务逻辑层)、dao层(数据访问层、持久层)

在这里插入图片描述

web层对应MVC中的Servlet和JSP

其他层都属于MVC中的Model

在这里插入图片描述

案例演示:

web层
    com.qf.web.servlet
    jsp页面放在 WebContent 或  WEB-INF
    
Service层
    com.qf.service  放服务层接口
    com.qf.service.impl  放服务层实现
    
Dao  层
    com.qf.dao   方法 dao接口
    com.qf.dao.impl  放dao实现
    

实体类  
    com.qf.domain   | com.qf.beans  | com.qf.entity | com.qf.pojo

工具类 
    com.qf.commons  | com.qf.utils


    #### 第三节 MVC结合事务练习

通过结合事务和MVC知识,练习一个转账demo

ThreadLocal:线程局部变量:作用实现把数据绑定到线程中,从而实现线程安全。

3.1 项目准备
  • 创建web项目

  • 导入需要jar包

    mysql驱动

​ druid.jar

​ commons-beanutils-1.8.3.jar

​ commons-collections.jar

​ commons-dbutils-1.4.jar

​ commons-logging-1.1.1.jar

  • 添加数据库配置文件

    db.properties

      #连接设置
      driverClassName=com.mysql.jdbc.Driver
      url=jdbc:mysql://localhost:3306/myschool
      username=root
      password=root
      #<!-- 初始化连接 -->
      initialSize=10
      #最大连接数量
      maxActive=50
      #<!-- 最小空闲连接 -->
      minIdle=5
      #<!-- 超时等待时间以毫秒为单位 6000毫秒/1000等于60秒 -->
      maxWait=5000
    
3.2 编写前端页面
  • 转账页面

    <%@ page language=“java” import=“java.util.*” pageEncoding=“UTF-8”%>
    <%
    String path = request.getContextPath();
    String basePath = request.getScheme()+"?/"+request.getServerName()+":"+request.getServerPort()+path+"/";
    %>

      <title>My JSP 'transfer.jsp' starting page</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">
      <!--
      <link rel="stylesheet" type="text/css" href="styles.css">
      -->
    
    </head>
    
    <body>
        
        <form action="${pageContext.request.contextPath }/servlet/AccountServlet" method="post" >
           
               转出方:<input type="text"  name="outaccount"/><br/>
               转入方:<input type="text"  name="intaccount"/><br/>        
               金额<input type="text"  name="money"/><br/>
       <input type="submit"  value="转账"/><br/>    
        </form>
    </body>
    
3.3 准备编写java代码

因为要使用到mvc模式,要对项目代码进行分包

3.3.1 项目分包

​ com.qf.dao

​ com.qf.dao.impl

​ com.qf.service

​ com.qf.service.impl

​ com.qf.web.servlet

​ com.qf.utils

​ com.qf.domain

3.3.2 编写DataSourceUtils工具类

DatasoutceUtils工具类,优化获取连接,优化事务操作

ThreadLocal的实例代表了一个线程局部的变量,每条线程都只能看到自己的值,并不会意识到其它的线程中也存在该变量。它采用采用空间来换取时间的方式,解决多线程中相同变量的访问冲突问题。


package com.itqf.utils;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

import javax.sql.DataSource;

import com.qf.utils.DruidUtils;

public class DataSourceUtils {
	private static DruidDataSource ds=null;
	
	private static ThreadLocal<Connection> tl=new ThreadLocal<Connection>();
   //静态代码块
    static {
        InputStream is = DruidUtils.class.getClassLoader().getResourceAsStream("db.properties");
        Properties properties=new Properties();
        try {
            properties.load(is);
            dataSource= (DruidDataSource) DruidDataSourceFactory.createDataSource(properties);
        } catch (Exception e) {
            e.printStackTrace();
        }

    }
	/**
	 * 获取数据源
	 * @return 连接池
	 */
	public static DataSource getDataSource(){
		return ds;
	}
	
	/**
	 * 从当前线程上获取连接
	 * @return 连接
	 * @throws SQLException
	 */
	public static Connection getConnection() throws SQLException{
		//-- 从线程获取链接
		Connection conn = tl.get();
		if(conn==null){
			//第一次获取 创建一个连接 和当前的线程绑定
			 conn=ds.getConnection();
			 
			 //----绑定
			 tl.set(conn);
		}
		return conn;
	}
	
	/**
	 * ---释放资源
	 * 
	 * @param conn
	 *            连接
	 * @param st
	 *            语句执行者
	 * @param rs
	 *            结果集
	 */
	public static void closeResource(Connection conn, Statement st, ResultSet rs) {
		closeResource(st, rs);
		closeConn(conn);
	}
	
	 
	public static void closeResource(Statement st, ResultSet rs) {
			closeResultSet(rs);
			closeStatement(st);
	}

	/**
	 * 释放连接
	 * 
	 * @param conn
	 *            连接
	 */
	public static void closeConn(Connection conn) {
		if (conn != null) {
			try {
              	//----和当前的线程解绑
				tl.remove();
				conn.close();
			} catch (SQLException e) {
				e.printStackTrace();
			}
			conn = null;
		}

	}

	/**
	 * 释放语句执行者
	 * 
	 * @param st
	 *            语句执行者
	 */
	public static void closeStatement(Statement st) {
		if (st != null) {
			try {
				st.close();
			} catch (SQLException e) {
				e.printStackTrace();
			}
			st = null;
		}

	}

	/**
	 * 释放结果集
	 * 
	 * @param rs
	 *            结果集
	 */
	public static void closeResultSet(ResultSet rs) {
		if (rs != null) {
			try {
				rs.close();
			} catch (SQLException e) {
				e.printStackTrace();
			}
			rs = null;
		}

	}
	
	/**
	 *---- 开启事务
	 * @throws SQLException
	 */
	public static void startTransaction() throws SQLException{
		//获取连接//开启事务
		getConnection().setAutoCommit(false);;
	}
	
	/**
	 *--- 事务提交
	 */
	public static void commitAndClose(){
		try {
			//获取连接
			Connection conn = getConnection();
			//提交事务
			conn.commit();
			//释放资源
			conn.close();
			//解除绑定
			tl.remove();
		} catch (SQLException e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * ----事务回滚
	 */
	public static void rollbackAndClose(){
		try {
			//获取连接
			Connection conn = getConnection();
			//事务回滚
			conn.rollback();
			//释放资源
			conn.close();
			//解除绑定
			tl.remove();
		} catch (SQLException e) {
			e.printStackTrace();
		}
	}
}
3.3.3 编写Dao层代码

dao层进行具体数据库操作

package com.itqf.dao;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

import org.apache.commons.dbutils.DbUtils;
import org.apache.commons.dbutils.QueryRunner;

import com.itqf.utils.DataSourceUtils;
import com.itqf.utils.JdbcUtils;

public class AccountDaoImpl{

	
	/**
	 * 转出钱
	 * @param from
	 * @param money
	 * @throws SQLException 
	 */
	public void out(String from, String money) throws SQLException {
		// TODO Auto-generated method stub
		//创建 queryrunner
		QueryRunner queryRunner = new QueryRunner();
		//编写sql
		String sql ="update account set money = money - ? where name = ?";
		
		//执行sql
		//手动传入
		queryRunner.update(DataSourceUtils.getConnection(),sql,money,from);
		//不要调DButils操作
		//DbUtils.close(conn);
	}
    
	/*
	 * 转入操作
	 */
	public void in(String to, String money) throws SQLException {
		// TODO Auto-generated method stub
		QueryRunner queryRunner = new QueryRunner();
		//编写sql
		String sql ="update account set money = money + ? where name = ?";
		
		//执行sql
		//手动传入
		queryRunner.update(DataSourceUtils.getConnection(),sql,money,to);
		//不要调DButils操作
	}

}

3.3.4 编写业务层代码

package com.itqf.service;

import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Savepoint;

import com.itqf.dao.AccountDao;
import com.itqf.dao.AccountDaoDButis;
import com.itqf.dao.AccountDaoLocal;
import com.itqf.utils.DataSourceUtils;
import com.itqf.utils.JdbcUtils;
/**
 * jdbc+threadlocal
 * 
 * @author Administrator
 *
 */
public class AccountServiceImpl {
    
	/**
	 * 转账业务逻辑
	 * @param from
	 * @param to
	 * @param money
	 * @throws Exception 
	 */
	public  void transfer(String from, String to,String money) throws Exception {
		// TODO Auto-generated method stub
		AccountDaoDButis accountDao = new AccountDaoDButis();
		
		
		try {
			//开启事务
			DataSourceUtils.startTransaction();
			//1.转出
			accountDao.out(from,money);
			int z = 1/0;
			//2.转入
			accountDao.in(to,money);
			DataSourceUtils.commitAndClose();
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			
			DataSourceUtils.rollbackAndClose();
			throw e; //接着向外抛
		}
	}

}

3.3.5 编写servlet代码

package com.itqf.web.servlet;

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.itqf.service.AccountService;
import com.itqf.service.AccountServiceDButil;
import com.itqf.service.AccountServiceLocal;

public class AccountServlet extends HttpServlet {

	public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

		 //设置编码格式
		  request.setCharacterEncoding("UTF-8");
		  response.setContentType("text/html;charset=UTF-8");	
		  PrintWriter writer = response.getWriter();
		 //获取表单数据
		 String from = request.getParameter("outaccount");
		 String  to   = request.getParameter("intaccount");
		 String money = request.getParameter("money");
		//调用业务逻辑
		 AccountServiceDButil accountService = new AccountServiceDButil();
		 try {
			 accountService.transfer(from,to,money);
			//分发转向
			writer.print("转账成功!");
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			writer.print("转账失败!");
		};
		 
		
	}

	/**
		 * The doPost method of the servlet. <br>
		 *
		 * This method is called when a form has its tag value method equals to post.
		 * 
		 * @param request the request send by the client to the server
		 * @param response the response send by the server to the client
		 * @throws ServletException if an error occurred
		 * @throws IOException if an error occurred
		 */
	public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

		  doGet(request, response);
	}
}
3.4防止表单重复提交

常见用两种方式

1 通过JavaScript屏蔽提交按钮(不推荐)

通过js代码,当用户点击提交按钮后,屏蔽提交按钮使用户无法点击提交按钮或点击无效,从而实现防止表单重复提交。

缺点:js代码很容易被绕过。比如用户通过刷新页面方式,或使用postman等工具绕过前段页面仍能重复提交表单。因此不推荐此方法。

		<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
    <!DOCTYPE HTML>
    <html>
    <head>
     <title>表单</title>
        <script type="text/javascript">
        //默认提交状态为false
        var commitStatus = false;
        function dosubmit(){
              if(commitStatus==false){
            //提交表单后,将提交状态改为true
              commitStatus = true;  
              return true;
             }else{
              return false;
          }
         }
  </script>
 </head>

    <body>
        <form action="/path/post" onsubmit="return dosubmit()" method="post" target="_blank">
         用户名:<input type="text" name="username">
        <input type="submit" value="提交" id="submit">
        </form>
    </body>
</html>

2 利用Session防止表单重复提交(推荐)

实现原理:
服务器返回表单页面时,会先生成一个subToken保存于session,并把该subToken传给表单页面。当表单
提交时会带上subToken,服务器判断session中的subToken和表单提交subToken是否一致。若不一致或
session的subToken为空或表单未携带subToken则不通过。

首次提交表单时session的subToken与表单携带的subToken一致走正常流程,然后服务器会删除session保存
的subToken。当再次提交表单时由于session的subToken为空则不通过。从而实现了防止表单重复提交。

代码如下:

TokenProccessor.java


package com.qf.utils;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Random;

/**
 * wgy 2019/4/23 11:47
 */
public class TokenProccessor {
    private TokenProccessor(){}
    private static  final  TokenProccessor instance=new TokenProccessor();
    public static TokenProccessor getInstance(){
        return instance;
    }
    public String makeToken(){
        String str=System.currentTimeMillis()+new Random().nextInt(999999999)+"";

        try {
            //1使用md5加密
            MessageDigest digest=MessageDigest.getInstance("md5");
            digest.update(str.getBytes());
            byte[] digest1 = digest.digest();
            //2使用base64
            return Base64.getEncoder().encodeToString(digest);

        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}

TokenTools.java

package com.qf.utils;

import javax.servlet.http.HttpServletRequest;

/**
 * wgy 2019/4/23 11:54
 */
public class TokenTools {

    //把token放入session
    public static void createToken(HttpServletRequest request,String tokenkey){
        String token=TokenProccessor.getInstance().makeToken();
        request.getSession().setAttribute(tokenkey, token);
    }
    //把token从session删除
    public static void removeToken(HttpServletRequest request,String tokenkey){
        request.getSession().removeAttribute(tokenkey);
    }
}

tranview.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>转账</title>

    <script type="text/javascript">
        //默认提交状态为false
        var commitStatus = false;
        function dosubmit(){
            if(commitStatus==false){
                //提交表单后,将提交状态改为true
                commitStatus = true;
                document.getElementById("btnsubmit").disabled=true;
                return true;
            }else{
                return false;
            }
        }
     </script>
</head>
<body>
   <%
      //向session中添加令牌
      TokenTools.createToken(request, "mytoken");
    %>
<h2>转账</h2>
    <form action="${pageContext.request.contextPath}/trans" onsubmit="return dosubmit()" method="post" target="_blank">
        <input type="text" name="from" placeholder="请输入转出账号"><br/>
        <input type="text" name="to" placeholder="请输入转入账号"><br/>
        <input type="text" name="money" placeholder="请输入金额"><br/>
        <input type="hidden" name="mytoken" value="${mytoken}">
        <input id="btnsubmit" type="submit" value="转账">
    </form>
</body>
</html>

TransServlet.java

package com.qf.web.servlet;

import com.qf.domain.Account;
import com.qf.service.AccountService;
import com.qf.service.impl.AccountServiceImpl;
import com.qf.utils.TokenTools;

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.math.BigDecimal;

/**
 * wgy 2019/4/23 11:05
 */
@WebServlet(name = "TransServlet",urlPatterns = "/trans")
public class TransServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        request.setCharacterEncoding("utf-8");
        response.setContentType("text/html;charset=utf-8");

        //判断是否是重复提交
        boolean b=isRepeatSubmit(request, "mytoken");
        if(b){
            response.getWriter().write("不好意思,重复提交了...");
            return;
        }
        //通过验证
        TokenTools.createToken(request, "mytoken");

        //接受数据
        String from = request.getParameter("from");
        String to = request.getParameter("to");
        String money = request.getParameter("money");
        //验证金额
        BigDecimal m=new BigDecimal(money);
        if(m.doubleValue()<=0){
            response.getWriter().write("转账金额不能小于0");
            return;
        }
        //转账
        AccountService accountService=new AccountServiceImpl();
        Account accfrom=new Account(Integer.parseInt(from),"宁宁",new BigDecimal(0));
        Account accto=new Account(Integer.parseInt(to),"春春",new BigDecimal(0));
        try {
            accountService.transMoney(accfrom, accto, m);
            response.getWriter().write("转账成功");
        } catch (Exception e) {
            e.printStackTrace();
            response.getWriter().write("转账失败");
        }
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doPost(request, response);
    }

    public boolean isRepeatSubmit(HttpServletRequest request,String clientTokeyKey){
        //1 判断session有没有
        String serverToken = (String) request.getSession().getAttribute(clientTokeyKey);
        if(serverToken==null){
            return true;
        }

        //2 判断客户端有没有token
        String clientToken=request.getParameter(clientTokeyKey);
        if(clientToken==null){
            return true;
        }

        //3 两个是否相同
        if(!serverToken.equals(clientToken)){
            return  true;
        }
        return  false;
    }
}
总结

1 MVC Model模型 View 视图 Controller 控制:是一种设计模式,属于架构模式,把程序逻辑上分成三部分

Model: 负责获取、处理数据

View: 负责展示数据

Controller:负责接受请求,调用Model, 转发或重定向

2 Jsp开发模式

​ Model1 Jsp+Javaban

​ Model2 Servlet+JSP+JavaBean

3 基于MVC三层的实现

​ web 层 :servlet+jsp

​ 业务层:负责业务处理 service

​ 持久层 :负责数据库的访问 dao

​ utils:工具类

​ domain:实体类

4 使用javaweb实现事务转账

​	TheadLocal:线程局部变量   key线程  value:数据

5 防止重复提交

​	session	token(令牌)

​	TokenProccessor

​	TokenTools	
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值