案例分析
我们先看一个spring-boot-web开发的接口
package com.shixiaoshi.controller;
import com.shixiaoshi.pojo.Emp;
import com.shixiaoshi.pojo.Result;
import com.shixiaoshi.utils.XmlParserUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
public class EmpController {
@RequestMapping("/listEmp")
public Result list(){
//1. 加载并解析emp.xml
String file = this.getClass().getClassLoader().getResource("emp.xml").getFile();
System.out.println(file);
List<Emp> empList = XmlParserUtils.parse(file, Emp.class);
System.out.println(empList.stream());
//2. 对数据进行转换处理 - gender, job
empList.forEach(emp -> {
//处理 gender 1: 男, 2: 女
String gender = emp.getGender();
if("1".equals(gender)){
emp.setGender("男");
}else if("2".equals(gender)){
emp.setGender("女");
}
//处理job - 1: 讲师, 2: 班主任 , 3: 就业指导
String job = emp.getJob();
if("1".equals(job)){
emp.setJob("讲师");
}else if("2".equals(job)){
emp.setJob("班主任");
}else if("3".equals(job)){
emp.setJob("就业指导");
}
});
//3。响应数据
return Result.success(empList);
}
}
上述代码定义了一个名为listEmp的接口,它对项目中的xml文件进行了解析,然后返回了解析后的数据。
上述代码并没有什么逻辑错误,但如果业务逻辑增加时,所有处理都放在EmpController类中进行处理是愚蠢的。我们需要对这个项目进行优化,让他更适合维护和拓展。
架构优化
什么是三层架构
我们可以将上述代码的功能分成三层结构
我们在设计接口、类或方法时,尽量要让他们的功能单一,只做一件事情,这就是设计模式中的单一职责原则。
单一职责的设计模式会让我们的程序更易维护和拓展,因此,在javaweb开发中,有了三层架构的开发原则。
三层架构的内容
![image.png](https://cdn.nlark.com/yuque/0/2023/png/21865277/1699946109928-03c257f1-6b9b-44b0-b2d0-b98dd8a4f9f0.png#averageHue=%23fddf86&clientId=u430335e7-9b62-4&from=paste&height=119&id=ufc6b669a&originHeight=149&originWidth=734&originalType=binary&ratio=1.25&rotation=0&showTitle=false&size=26545&status=done&style=none&taskId=ufdce5d9c-0022-44fe-9320-6598a52d356&title=&width=587.2)
- controller:控制层,接收前端发送的请求,对请求进行处理,并响应数据。
- service:业务逻辑层,处理具体的业务逻辑。
- dao:数据访问层(Data Access Object)(持久层),负责数据访问操作,包括数据的增、删、改、查。
代码拆分
我们根据三层架构的原则,对原有项目进行拆分优化。首先,和创建dao包service包。注:项目路径一定不能有中文,否则程序执行会报错
dao数据访问层
数据访问层(Data Access Object)(持久层),负责数据访问操作,包括数据的增、删、改、查,最终被service调用。
其访问的数据类型有很多,如文件中的数据、数据库中的数据 、别人接口中的数据。 我们要想灵活的实现这些方式的切换,可以使用面向接口的方式编程。
所以,我们先在dao包内定义一个EmpDao接口,增强其灵活性和可拓展性。
package com.shixiaoshi.dao;
import com.shixiaoshi.pojo.Emp;
import java.util.List;
public interface EmpDao {
//获取员工列表数据 listEmp 是接口中定义的抽象方法,需要子类实现
public List<Emp> listEmp();
}
现在,我们来实现这个接口。首先,我们创建impl.EmpDaoA类,然后实现 EmpDao这个接口
alt + shift + p快捷实现类中的方法,然后从EmpController中复制其对应的数据处理方法
package com.shixiaoshi.dao.imp;
import com.shixiaoshi.dao.EmpDao;
import com.shixiaoshi.pojo.Emp;
import com.shixiaoshi.utils.XmlParserUtils;
import java.util.List;
public class EmpDaoA implements EmpDao {
@Override
public List<Emp> listEmp() {
//1. 加载并解析emp.xml
String file = this.getClass().getClassLoader().getResource("emp.xml").getFile();
System.out.println(file);
List<Emp> empList = XmlParserUtils.parse(file, Emp.class);
return empList;
}
}
现在,我们dao层的数代码逻辑就完成。
service业务逻辑层
service的业务逻辑处理方式和bao层是一致的。我们先在service包内创建一个EmpService接口。
然后,我们创建impl.EmpServiceA这个类实现EmpService接口。同样的,我们从EmpController中复制数据处理逻辑
此时,我们会发现empList及其相关的方法报错了。这很正常,因为empList的值现在在dao层的EmpDaoA类中储存着,我们需要从这个类中获取empList
private EmpDao empDao = new EmpDaoA();
List<Emp> empList = empDao.listEmp();
完整代码如下
package com.shixiaoshi.service.impl;
import com.shixiaoshi.dao.EmpDao;
import com.shixiaoshi.dao.impl.EmpDaoA;
import com.shixiaoshi.pojo.Emp;
import com.shixiaoshi.service.EmpService;
import java.util.List;
public class EmpServiceA implements EmpService {
private EmpDao empDao = new EmpDaoA();
@Override
public List<Emp> listEmp() {
//1. 调用dao, 获取数据
List<Emp> empList = empDao.listEmp();
//2. 对数据进行转换处理 - gender, job
empList.forEach(emp -> {
//处理 gender 1: 男, 2: 女
String gender = emp.getGender();
if("1".equals(gender)){
emp.setGender("男");
}else if("2".equals(gender)){
emp.setGender("女");
}
//处理job - 1: 讲师, 2: 班主任 , 3: 就业指导
String job = emp.getJob();
if("1".equals(job)){
emp.setJob("讲师");
}else if("2".equals(job)){
emp.setJob("班主任");
}else if("3".equals(job)){
emp.setJob("就业指导");
}
});
return empList;
}
}
现在,我们已经将代码已经改造完毕,我们对接口进行测试
总结
上述教程中,我们通过单一职责原理,将代码封装成了三层结构,每层结构只专注自己的事情。相比一开始所有代码写在一起,优化后的代码结构复用性强,利于拓展,便于维护!
但该项目改造后,还是存在一定的缺陷,比如我们的controller层代码依赖service代码
service层代码又依赖数据层代码
这三层结构数据耦合程度比较高,如果其中一个控制层的类名发生变化,对其耦合的数据层也会造成影响。
因此,该项目还可以进一步的进行解耦优化!
解耦优化
什么是高内聚低耦合
软件设计原则:高内聚低耦合。
- 高内聚指的是:一个模块中各个元素之间的联系的紧密程度,如果各个元素(语句、程序段)之间的联系程度越高,则内聚性越高,即 "高内聚"。
- 低耦合指的是:软件中各个层、模块之间的依赖关联程序越低越好。
程序中高内聚的体现:
程序中耦合代码的体现:
把业务类变为EmpServiceB时,需要修改controller层中的代码
高内聚、低耦合的目的是使程序模块的可重用性、移植性大大增强。
解耦思路
之前我们在编写代码时,需要什么对象,就直接new一个就可以了。 这种做法呢,层与层之间代码就耦合了,当service层的实现变了之后, 我们还需要修改controller层的代码
那应该怎么解耦呢?最简单的,就是将serveice层的数据放在一个容器中,contrller层直接调用这个数据就行。
再java中想实现这个操作,就需要了解Spring中的两个核心概念:
- 控制反转: Inversion Of Control,简称IOC。对象的创建控制权由程序自身转移到外部(容器),这种思想称为控制反转。
- 依赖注入: Dependency Injection,简称DI。容器为应用程序提供运行时,所依赖的资源,称之为依赖注入。
Bean对象:IOC容器中创建、管理的对象,称之为bean。
简单来说,就是把service的数据创建放在Bean容器中(IOC),容器为Controller进行数据注入(DI)
解耦的实现
- 删除Controller层、Service层中new对象的代码
- Service层及Dao层的实现类,交给IOC容器管理
- 为Controller及Service注入运行时依赖的对象
- Controller程序中注入依赖的Service层对象
- Service程序中注入依赖的Dao层对象
第1步:删除Controller层、Service层中new对象的代码
第2步:Service层及Dao层的实现类,交给IOC容器管理
- 使用Spring提供的注解:@Component ,就可以实现类交给IOC容器管理
- IOC容器类似一个内部服务,不用进行声明
第3步:为Controller及Service注入运行时依赖的对象
- 使用Spring提供的注解:@Autowired ,就可以实现程序运行时IOC容器自动注入需要的依赖对象
完整的三层代码:
Dao层
@Component //将当前对象交给IOC容器管理,成为IOC容器的bean
public class EmpDaoA implements EmpDao {
@Override
public List<Emp> listEmp() {
//1. 加载并解析emp.xml
String file = this.getClass().getClassLoader().getResource("emp.xml").getFile();
System.out.println(file);
List<Emp> empList = XmlParserUtils.parse(file, Emp.class);
return empList;
}
}
Service层
@Component //将当前对象交给IOC容器管理,成为IOC容器的bean
public class EmpServiceA implements EmpService {
@Autowired //运行时,从IOC容器中获取该类型对象,赋值给该变量
private EmpDao empDao ;
@Override
public List<Emp> listEmp() {
//1. 调用dao, 获取数据
List<Emp> empList = empDao.listEmp();
//2. 对数据进行转换处理 - gender, job
empList.stream().forEach(emp -> {
//处理 gender 1: 男, 2: 女
String gender = emp.getGender();
if("1".equals(gender)){
emp.setGender("男");
}else if("2".equals(gender)){
emp.setGender("女");
}
//处理job - 1: 讲师, 2: 班主任 , 3: 就业指导
String job = emp.getJob();
if("1".equals(job)){
emp.setJob("讲师");
}else if("2".equals(job)){
emp.setJob("班主任");
}else if("3".equals(job)){
emp.setJob("就业指导");
}
});
return empList;
}
}
Controller层
@RestController
public class EmpController {
@Autowired //运行时,从IOC容器中获取该类型对象,赋值给该变量
private EmpService empService ;
@RequestMapping("/listEmp")
public Result list(){
// 调用service, 获取数据
List<Emp> empList = empService.listEmp();
// 响应数据
return Result.success(empList);
}
}
运行测试:
启动SpringBoot引导类,打开浏览器,输入:http://localhost:8080/emp.html