详解MVC架构模式
1. 不使用MVC架构模式开发的缺陷
在一个普通的银行转账程序中,通常一个Servlet
通常要完成如下的工作
- 负责数据的接收
- 负责核心的业务逻辑
- 负责数据库表中的CRUD操作。
- 如果使用了
jsp
技术,那么还要负责页面的展示
缺点
- 代码的重用性很差,没有进行职能分工,没有独立组件的概念,代码和代码之间耦合度太高
- 耦合度高,导致代码很难扩展
- 操作数据库的代码和业务逻辑混杂在一起,编写代码的时候,很难专注于业务代码开发
2. 什么是MVC架构模式
简单来说就是进行分层,让专人做专事,职能分工要明确,这样可以让代码的耦合度降低,扩展力增强
。
M(Model)
:数据/业务的实际处理。它会将业务结果/数据处理结果交还给控制器。V(View)
:展示,负责页面展示。接收到实际来自Model的实际数据,直接交接人是Controller
,它将数据显示给用户。C(Contoller)
:核心,是控制器,处理业务/数据的请求分发,起到一个调度的作用。
3. DAO模式简介
DAO
:DataAccessObject是数据访问对象,是一种设计模式,属于JavaEE
的设计模式,只负责底层数据库表的CRUD,没有任何的业务逻辑。
一般情况下domain
中定义的实体类属性类型设置为包装类型(引用数据类型)
,防止因为null
值无法赋给基本数据类型。
一般情况下,一张表对应一个DAO
的对象
public interface AccountDAO {
Integer update(Account account);
void insert(Account account);
void deleteById(String id);
Account selectById(String id);
}
public class AccountDAOImpl implements AccountDAO {
//jdbc代码暂时为空
@Override
public Integer update(Account account){return 1;}
@Override
public void insert(Account account){}
@Override
public void deleteById(String id){}
@Override
public Account selectById(String id){}
}
4. 基本模式概念
pojo:普通简单的对象被称为
pojo
对象,Plain Ordinary Java Object
:简单的Java对象bean:专门数据的对象
domain:领域模型对象
entity:实体类
实际上都是一种东西
5.Service层
Service
:专注于处理相关域业务的一个类模型,在该类中只编写业务代码,希望只专注业务
起名是xxxBiz
或者xxxService
public interface AccountService {
void transfer(String from,String to,Double balance);
}
public class AccountServiceImpl implements AccountService {
private AccountDAO accountDAO = new AccountDAOImpl();//调用底层的DAO对象执行数据库的增删查改
@Override
public void transfer(String from, String to, Double balance) {
}
}
6. MVC架构模式与三层架构的关系
- 经过上面的DAO层封装和service封装之后,对数据模型进行分发
@WebServlet("/transfer")
public class AccountTransferServlet extends HttpServlet {
AccountService accountService = new AccountServiceImpl();
//在这里的话Servlet相当于一个controller
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//负责处理业务分发
String from = request.getParameter("from");
String to = request.getParameter("to");
Double balance = Double.valueOf(request.getParameter("balance"));
try{
accountService.transfer(from,to,balance);
response.getWriter().write(1);//1代表成功,可以用一个Result来封装相关信息
}catch (Exception e){
response.getWriter().write(0);
}finally {
response.getWriter().close();
}
}
}
最终经过上述的分层之后,就形成了这样的项目结构。可以图解为:
Model
包含了有pojo
,service
,dao
至于表现层/web层
包含了有html/css/javascript/Controller(单独一个层)
谈谈你对MVC架构和三层架构的理解
首先MVC架构是基于三个分层的,这三个分层分别是负责请求处理的分发核心层
Controller
,还有负责业务/数据处理的Model
层,还有负责数据/业务处理结果展示的View
层。用户发送请求后首先会到达Controller
层,Controller
层将请求需求分发到Model
层进行数据处理,数据处理结束后返回到Controller
层,Controller
层将数据分发到View
层,View
将数据进行展示与渲染回显给用户。至于三层架构,三层架构是基于
MVC
模式进行的再次划分,对于后端开发需求,将Model
层细分为了业务逻辑层
、数据持久层
以及底层数据存储设备DB
,对于Controller
和View
将其划分为了一个层面,也就是表现层或者web层,在三层架构模式认为表现层是直接与用户进行交互的,因此划分为了一个层面。
在后面学习会学习到Spring
:它是整个项目的”管家”,它负责项目所有重要对象的创建以及维护对象和对象之间的关系,它并不存在于某一个具体分层中。
7. 分层中的事务控制
事务
一定是在service
层进行事务的控制的,一般来说是一个方法对应一个完整的事务,因此在业务逻辑执行前开启事务,在业务逻辑结束后提交事务。
同时要注意,如果对象中获取到的connection
对象不是同一个connection
对象 ,那么事务就是隔离的。两个connection
描述对象的执行操作不是同一个配置。
这就好像你两个后端,你两个人都去连接数据库获取connection
对象,一个后端想要事务,另一个后端不想要事务,如果是一致配置的话,那就乱套了。
ThreadLocal
1.底层数据结构
在一些业务场景中,我们希望同一个工作线程总能共享一些对象,这些对象应该是跨类的,一种解决思路是提供一个静态的变量,而且为它提供静态访问方法,让其他类来共享这个变量,这样可以解决问题,但是破坏了类的层次结构,也不符合开闭原则。
还有一种做法是提供一个map集合,这个map集合存储在应用上下文之中。这个map的key是Thread
对象,value
是要存储的共享变量,那么这样做的话,我们就能够在同一个工作线程之中,根据工作线程这个对象来取到对应的存储共享变量。
public class MyThreadLocal {
//本质原理,实际上还需要考虑多线程访问的问题。
//所有需要和当前线程绑定的数据都要放到这个容器当中
Map<Thread,Object> map = new ConcurrentHashMap<>();
public Object get(){
return map.get(Thread.currentThread());
}
public void set(Object obj){
map.put(Thread.currentThread(),obj);
}
public void remove(){
map.remove(Thread.currentThread());
}
}
2. 实现原理
要使用当前线程域的上下文,如果是同一个线程发送请求,那么获取上下文内容肯定是一样的。同时要避免取不到值的问题,这时候需要做一个初始化的特判
Object obj = local.get();
if(obj == null){
obj = new Object();
local.set(obj);
}
return obj;