SpringBoot-三层架构
前言
通过简单的示例介绍SpringBoot的三层架构。
版本:
- Maven:3.6.1
- JDK:17
- SpringBoot:3.3.2
- API测试工具:Postman
概述
三层架构
后端的处理逻辑大体可以分为三个部分:
- 数据访问
- 逻辑处理
- 接收请求,响应数据
而在开发过程中,需要尽量让每一个部分的职责更加单一(单一职责原则),便于后期的拓展维护等操作。
由此出现了开发的三层架构:
- Controller:控制层。接收前端发送的请求,对请求进行处理,并响应数据。
- Service:业务逻辑层。处理具体的业务逻辑。
- DAO(Data Access Object):数据访问层(持久层)。负责数据访问操作,包括数据的增、删、改、查。
设计原则
有了三层架构的设计思想,我们便可以遵循这一思想进行业务开发。
但是这里会遇到一个问题:尽管分模块进行代码编写,但免不了要进行模块与模块之间(层与层)的数据交互。
举个最简单的例子,Controller层要想拿到Service层处理过后的数据并返回给前端,最起码也要创建一个Service层的对象。假设最初的业务逻辑用的是
A方案
,那么在Controller层就需要创建一个ServiceA
对象,但是后面需求有改变,要求使用业务逻辑的B方案
,那么Controller层就需要改变创建ServiceB
对象。
private Service service = new ServiceA()
—>private Service service = new ServiceB()
很明显,目前的方式在改动Service层代码的同时需要额外修改Controller层的代码(Service层和DAO层亦是如此),称层与层之间存在耦合
。自然我们是希望尽量降低这种耦合,以避免改动一个模块的同时影响另一个模块,这就引出了软件设计原则:高内聚低耦合
。
高内聚低耦合
-
内聚:软件中各个功能模块内部的功能联系。
-
耦合:衡量软件中各个层/模块之间的依赖、关联的程度。
-
软件设计原则:
高内聚低耦合
。
IOC(控制反转)&DI(依赖注入)
SpringBoot为层与层提供了一个解耦方案(以Controller层和Service层交互为例):
首先构造一个容器,用于存储Bean
对象,Service层可以把目前业务逻辑所需要创建的对象放在容器中,而在程序运行时,Controller层需要一个Service层的对象,它就会从容器中寻找看是否有一个Service类型的对象,如果有则直接取出调用。如此一来,如果Service层的业务逻辑发生变化,也不影响Controller层。
那么Bean
对象如何交给容器管理?容器又如何为模块提供对应的Bean
对象?这就引出了SpringBoot中重要的两个概念:IOC(控制反转)和DI(依赖注入)。
- 控制反转(Inversion Of Control,IOC):对象的创建控制权由程序自身转移到外部(容器),这种思想称为控制反转。
- 依赖注入(Dependency Injection,DI):容器为应用程序提供运行时所需要的依赖资源,称为依赖注入。
注解实现
-
@Component
:用于实现IOC操作。表示将当前类交给IOC容器管理,成为IOC容器中的Bean。Spring框架为了更好地标识Web应用开发中Bean对象到底归属于哪一层,又提供了
@Component
的三个衍生注解:注解 说明 位置 @Component
声明Bean的基础注解 不属于以下三类时,用此注解 @Controller
@Component
的衍生注解标注在控制器类上(Controller)(一般用 @RestController
替代)@Service
@Component
的衍生注解标注在业务类上(Service) @Repository
@Component
的衍生注解标注在数据访问类上(DAO)(由于与MyBatis整合,很少使用) -
@Autowired
:用于实现DI操作。运行时,IOC容器会提供该类型的Bean对象,并赋值给该变量。
项目实例构建
通过一个简单的实例说明SpringBoot的三层架构,具体项目目录如下:
这里先说明一下:POJO层(Plain Ordinary Java Object):存放JavaBeans,本示例直接通过DAO层创建POJO层的JavaBeans进行数据访问,忽略数据库操作,因此也没设计Mapper层。
Lombok
在编写类时,使用lombok
插件(新版IDEA已集成)简化书写。
Lombok是一个实用的Java类库,能通过注解的形式自动生成构造器、getter/setter、equals、hashcode、toString等方法,并可以自动化生成日志变量,简化java开发、提高效率。
lombok的坐标:
1 | <dependency> |
tips:可以在类中直接引用lombok的注解,再通过IDEA自动引入。
lombok注解说明:
注解 | 作用 |
---|---|
@Getter /@Setter |
为所有的属性提供getter() 、setter() 方法 |
@ToString |
会给类自动生成易阅读的toString() 方法 |
@EqualsAndHashCode |
根据类所拥有的非静态字段自动重写equals() 方法和hashCode() 方法 |
@Data |
相当于@Getter +@Setter +@ToString +@EqualsAndHashCode |
@NoArgsConstructor |
为实体类生成无参的构造器方法 |
@AllArgsConstructor |
为实体类生成除了static修饰的字段之外带有各参数的构造器方法 |
User
类:
1
2
3
4
5
6
7
8
public class User {
private String name;
private Integer age;
private Address address;
}
Address
类:
1
2
3
4
5
6
7
public class Address {
private String province;
private String city;
}
Result
类:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Result {
private Integer code;
private String msg;
private Object data;
// 用于快速返回Result对象
public static Result success(Object data) {
return new Result(1, "success", data);
}
public static Result success() {
return new Result(1, "success", null);
}
public static Result error(String msg) {
return new Result(0, msg, null);
}
}额外说明:以DAO层的实现为例,本层的实例是要被Service层所调用,而DAO的实现方式可能有很多(可能访问本地文件的数据,可能访问数据库的数据等),而想要灵活地切换各种实现,可以采用面向接口的方式编程,因此在实现之前就需要创建一个接口,方便Service层调用。(Service层亦是如此)。
三层架构实现
DAO层
首先在dao
目录下创建UserDao
接口:
1 | public interface UserDao { |
然后在dao
目录下创建impl.UserDaoImpl
实现类:
1 | //将当前类交给IOC容器管理,成为IOC容器中的bean |
这里创建三个User
对象,并添加至list
集合中返回。
Service层
首先在service
目录中创建UserService
接口:
1 | public interface UserService { |
然后在service
目录下创建impl.UserServiceImpl
实现类:
1 | //将当前类交给IOC容器管理,成为IOC容器中的bean |
这里删除list
集合中的最后一个User
对象,并返回list
集合。
Controller层
在controller
目录中创建UserController
类:
1 | //@Controller |
接收前端发来的请求并返回数据。
说明:
@RestController
=@Controller
+@ResponseBody
(因此这里直接使用@RestController
)
访问http://localhost:8080/getUserList
进行测试:
一个包含两个User
对象的list
集合以JSON格式成功返回。
后记
大一暑期实训的时候就写过基于Servlet
的三层架构的Web应用,那时还一知半解,只是照着老师的代码敲。通过几年的开发经历,对于三层架构诞生构想、实现过程有了更深的理解。