基于Spring框架的分层解耦详解
- 博客主页:誓则盟约
- 系列专栏:Java Web
- 关注博主,后期持续更新系列文章
- 如果有错误感谢请大家批评指出,及时修改
- 感谢大家点赞👍收藏⭐评论✍
Java Web 三层架构:
Java Web可以大致被分为三层架构:
- controller:控制层
- service:业务逻辑层
- dao:数据访问层
Controller层:
controller层也称为控制层,只要功能是接收前端发送的请求,对请求进行处理,并响应数据。
作为应用程序的入口点之一,Controller 层负责接收来自客户端(如浏览器、移动设备等)的 HTTP 请求。这些请求可以是 GET、POST、PUT、DELETE 等不同类型的请求,例如,在 Web 应用中,当用户在浏览器中输入一个 URL 或者提交一个表单时,请求会被发送到相应的 Controller。
它会解析请求中的参数,如查询字符串参数(对于 GET 请求)或者表单数据(对于 POST 请求)。例如,在一个登录功能中,Controller 会获取用户输入的用户名和密码参数。
注:Controller 层并不直接实现业务逻辑,而是调用 Service 层(业务逻辑层)的方法来处理业务。
Service层:
service层也称为业务逻辑层,在 Java 应用架构(特别是遵循 MVC 或类似分层架构的应用)中,Service 层(业务逻辑层)扮演着核心的角色,其中主要是处理具体的业务逻辑。
Service 层包含了应用的复杂且核心的业务逻辑。例如,在一个银行系统中,转账业务逻辑就会在 Service 层实现。它需要考虑诸如账户余额检查、转账金额的合法性、更新相关账户余额等操作。
除此之外,Service通常还负责管理事务。在涉及多个数据库操作的业务场景中,如订单处理(包括创建订单、扣减库存、更新用户积分等多个数据库操作),Service 层确保这些操作要么全部成功(提交事务),要么全部失败(回滚事务)。
在架构中Service 层为 Controller 层提供业务处理方法。Controller 层调用 Service 层的方法来完成具体的业务操作。
Dao层:
Dao(Data Access Object)层也称为数据访问层,主要负责数据访问操作,包括数据的增删改查。
Dao 层的主要目的是将数据库操作抽象出来,为上层(通常是 Service 层)提供统一的访问数据库的接口。这样,上层业务逻辑层不需要关心具体的数据库类型(如 MySQL、Oracle 等)以及数据库的底层操作细节(如 SQL 语句的编写、数据库连接的管理等)。例如,在一个企业级应用中,无论是使用关系型数据库还是非关系型数据库,Service 层只需要调用 Dao 层提供的方法(如findUserById
、saveUser
等)就可以实现对数据的操作。
除此之外,Dao层还负责执行数据的持久化操作,包括将数据保存到数据库(插入操作)、从数据库中查询数据、更新数据库中的数据以及删除数据库中的数据等操作。因此,Dao层也被称为持久层。
分层解耦:
首先要知道内聚和耦合两个概念:
- 内聚:软件中各个功能模块内部的功能联系。
- 耦合:衡量软件中各个层/模块之间的依赖、关联的程度。
在开发过程中,我们要朝着高内聚低耦合的方向实施,最好可以让层与层之间解除耦合,让他们不产生依赖,这样我们程序的灵活性和可扩展性会更佳。
下面我们以Spring框架为例讲解如何实现解耦操作。在 Spring 框架中,解耦主要通过控制反转(Inversion of Control,IOC)和依赖注入(Dependency Injection,DI)的机制来实现。
控制反转(IOC):
在没有使用 Spring 等框架进行解耦之前,对象之间的依赖关系通常是由对象自己创建和管理的。例如,在一个业务逻辑类中,如果需要调用数据访问层的方法来获取数据,它可能会直接实例化数据访问层的对象。
public class BusinessLogicClass {
public void doSomeBusinessLogic() {
DataAccessClass dataAccess = new DataAccessClass();
// 使用 dataAccess 对象进行数据操作
}
}
这种方式存在的问题是,业务逻辑类与数据访问类紧密耦合,当数据访问层的实现发生变化时,业务逻辑类也需要进行相应的修改。
而Spring 框架引入了控制反转的概念,即对象的创建和依赖关系的管理不再由对象自己负责,而是交给一个外部的容器(通常是 Spring 容器)来管理。而在IOC容器中创建、管理的对象,称之为bean。
这样在 Spring 中,对象只需要声明自己所需要的依赖,而不需要关心这些依赖是如何创建和初始化的。例如:
public class BusinessLogicClass {
private DataAccessInterface dataAccess;
public BusinessLogicClass(DataAccessInterface dataAccess) {
this.dataAccess = dataAccess;
}
public void doSomeBusinessLogic() {
// 使用 dataAccess 对象进行数据操作
}
}
这里,BusinessLogicClass
不再自己创建DataAccessClass
的实例,而是通过构造函数接收一个实现了DataAccessInterface
接口的对象。这样,BusinessLogicClass
只依赖于接口,而不依赖于具体的实现类,实现了解耦。
依赖注入(DI):
容器为应用程序提供运行时,所依赖的资源,称之为依赖注入。这些资源一般取自Spring容器中的bean对象。
Spring 框架通过依赖注入的方式来实现控制反转。依赖注入有多种方式,常见的有构造函数注入、Setter 注入和字段注入。
构造函数注入:在对象创建时,通过构造函数将依赖对象传递进去。例如上面的例子中,BusinessLogicClass
通过构造函数接收DataAccessInterface
的实现对象。
Setter 注入:通过设置方法将依赖对象注入到对象中。
字段注入:使用注解(如@Autowired
)直接在字段上进行依赖注入。例如:
import org.springframework.beans.factory.annotation.Autowired;
public class BusinessLogicClass {
@Autowired
private DataAccessInterface dataAccess;
public void doSomeBusinessLogic() {
// 使用 dataAccess 对象进行数据操作
}
}
解耦的好处:
1.可维护性
由于对象之间的依赖关系通过接口进行解耦,当一个模块的实现发生变化时,只需要修改对应的实现类,而不会影响到其他模块。例如,如果数据访问层的实现从使用一种数据库切换到另一种数据库,只需要修改数据访问层的实现类和 Spring 配置文件,业务逻辑层不需要做任何修改。
2.可测试性
解耦后的代码更容易进行单元测试。在测试业务逻辑类时,可以通过注入模拟的依赖对象(如使用模拟框架创建的模拟数据访问对象)来隔离其他模块的影响,只专注于测试业务逻辑本身。
3.可扩展性
当需要添加新的功能模块时,可以很容易地将新模块集成到系统中,只需要在 Spring 配置文件中定义新模块的 bean,并将其注入到需要的地方即可。例如,添加一个新的业务逻辑模块,只需要在配置文件中定义新的 bean,并将其注入到现有的业务逻辑类中,实现功能的扩展。