Web后端(2)-三层架构
EmplistA
在前面的前端学习中, 我们学习了 vue 框架 和 axios 技术, 并在离线(也即使用vue.js而非vue脚手架)的情况下通过向测试服务器(Yapi)请求数据得以将网页的表格完整渲染和展示.
在本模块中, 我们尝试将前端的vue, axios技术结合进来, 使用离线的 xml 员工数据, 通过本地的 springboot 后台服务将网页表格渲染起来, 主要的工作在于如何将二者结合以及后端需要做的工作
steps
- 在 pom.xml 引入 dom4j 依赖 (2.1.3)
- 编写 XMLParseUtils.java, 解析 emp.xml 为 Emp 实体(列表)
- 完成 EmplistApplication.java, 将请求的数据处理好返回给前端, 首次运行注意还需要编写 Emp 实体类, 响应实体类(统一的Result)
- 引入 html, css, js 等静态文件, 利用 axios, vue 进行数据请求和响应数据的展示
// 基本信息:
// 请求路径: /listEmp
// 返回值: 统一为 Result实体对象
// 请求参数: 无
@RequestMapping("/listEmp")
public Result listEmp(){
// 1. 获取原始数据
String file = this.getClass().getClassLoader().getResource("emp.xml").getFile();
System.out.println(file);
List<Emp> empList = XMLParseUtils.parse(file, Emp.class);
// 2. 处理得到响应数据
empList.forEach(emp -> {
String gender = emp.getGender();
emp.setGender(gender.equals("1")? "男": "女");
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);
}
EmplistB
在EmplistA中, 我们成功利用 springboot 使本机作为Web服务器 并响应前端的数据请求. 那么, 从后端代码编写来看, 它还不够优雅, 主要原因是他把所有的操作例如接受请求、查询原始数据、处理得到响应数据以及返回响应数据都放在了一起,不便于扩展和维护。
想一想计算机网络的分层协议, 这里我们借鉴这一思想, 将功能进行分层, 交给不同的包进行处理, 通常的, 我们有:
controller
: 控制层
, 接收前端发送的请求,对请求进行处理,并响应数据
service
: 业务逻辑层
, 处理具体的业务逻辑
dao
: 数据访问层(Data Access 0bject)(持久层
),负责数据访问操作,包括数据的增、删、改、查
steps
在 EmplistA 的基础上, 先注释(或删除)掉原先的接收、处理、响应的代码; 接着将代码实际执行的功能进行划分
- 增加
dao
包, 编写empDao
接口, 该接口具有一个方法, 它返回查询得到的 empList. - 增加
dao.impl
包, 将原 EmplistA 关于访问 xml 获得原始数据的 代码作为empDao
的一个实现 - 增加
service
包, 编写empService
接口, 该接口具有一个方法, 它返回经过处理的作为响应数据(的一部分)的 empList. - 增加
service.impl
包, 将原 EmplistA 关于 将原emp列表的数字转义 代码作为empService
的一个实现 - 在新的
EmplistController
中, 明确请求路径、请求参数, 调用empService
获取得到处理数据, 封装为Result
返回响应数据. - 访问网页, 查看表格是否正常显示
EmplistB2
在 EmplistB 的介绍中, 我们清楚的理解了分层的思想和一个简单的案例实现, 然而, 一个值得注意的细节是:
empService
为了使用 empDao
的数据查询功能, 需要实例化 empDao
的一个具体实现, 例如 empDaoA
.
EmplistController
为了使用empService
的服务以获取相应具体数据, 需要实例化 empService
的一个具体实现, 例如 empServiceA
.
因此, 这三层之间具有两层的耦合关系, 假设empService
需要使用另一个empDao
实体, 例如empDaoB
, 那么需要对empService
的代码进行修改, 同样 EmplistController
需要其他服务时, 也需要修改自己的代码.
Java Bean
- 控制反转:
Inversion of Control
, 简称IC
。对象的创建控制权由程序自身转移到外部(容器),这种思想称为控制反转. - 依赖注入:
Dependency Injection
,简称DI
。容器为应用程序提供运行时所依赖的资源,称之为依赖注入。 - Bean对象: IOC容器中创建、管理的对象,称之为bean。
解耦合
上面提到的思想, 也即借助于媒介使得对象创建交由媒介进行, 实现了解耦合.具体的我们通过 注解: @Component
, @Autowired
来完成这一功能
-
EmplistController
需要empService
对象, 因此在声明语句前加上@Autowired
, 表示请 IOC容器提供这个bean对象; -
empService
作为一个元素被EmplistController
使用, 因此在类创建语句前添加注解@Component
, 表示将自己对象的创建交由IOC容器管理; 另一方面它还使用了empDao
对象, 这需要IOC容器提供, 因此在声明语句前加上@Autowired
; -
empDao
作为一个元素被empService
使用, 因此在类创建语句前添加注解@Component
, 表示将自己对象的创建交由IOC容器管理;
@Component
@Component
是声明bean的基础注解, 鉴于分层思想的考虑, 我们把不同层bean的注解进行了重命名以优化代码可读性:
@Controller
, 标注在控制器类上
@Service
, 标注在业务类上
@Repository
, 标注在数据访问类上(由于与mybatis整合,用的少)
值得注意的是, Javabean 具有包扫描机制, 对于在 Application 之外的bean, 无法直接简单的通过注解引入, 因此, 需要养成良好编程习惯, 在主程序所在的包内创建子包(@SpringBootApplication具有包扫描作用,默认扫描当前包及其子包)
@Autowired
@Autowired
会自动为当前声明的变量分配对象实体, 该对象从JavaBean容器获得, 然而, 当具有多种同类型的bean时, 这将产生冲突. 解决方案是:
若不改变需要依赖注入的类, 那么对于这多个同类型的bean
- 只在一种bean的类前加@Component系列的注解
- 所有的bean都加@Component系列的注解, 表示可以交由容器保管, 然而, 只在其中一个类加
@primary
注解, 表示优先注入该bean的实现
继续思考, 如果出现多对多的情况(例如有多个 controller, 它们使用不尽相同的service), 以上方法又变的不适用了, 这里解决方案是:
所有的bean都加@Component
系列的注解, 表示可以交由容器保管, 并且, 在需要bean注入的类中, 加入 @Resource(name="xxxx")
注解指定bean
code list
/pojo/Emp.java
public class Emp {
private String name;
private Integer age;
private String image; // 表示 图片资源路径
private String gender; // 1, 男; 2, 女
private String job; // 1, 讲师; 2, 班主任; 3, 就业指导
public Emp() {
}
public Emp(String name, Integer age, String image, String gender, String job) {
this.name = name;
this.age = age;
this.image = image;
this.gender = gender;
this.job = job;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getImage() {
return image;
}
public void setImage(String image) {
this.image = image;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public String getJob() {
return job;
}
public void setJob(String job) {
this.job = job;
}
@Override
public String toString() {
return "Emp{" +
"name='" + name + '\'' +
", age=" + age +
", image='" + image + '\'' +
", gender='" + gender + '\'' +
", job='" + job + '\'' +
'}';
}
}
/pojo/Result.java
/**
* 用于统一的返回结果
* code, Integer, 0: failure 1: success
* msg, String, message
* data, Object, response to the front end
*/
public class Result {
private Integer code;
private String msg;
private Object data;
// factory
public static Result success(){
return new Result(1, "success", null);
}
public static Result success(Object data){
return new Result(1, "success", data);
}
public static Result error(){
return new Result(0, "error", null);
}
// getter setter constructor
@Override
public String toString() {
return "Result{" +
"code=" + code +
", msg='" + msg + '\'' +
", data=" + data +
'}';
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
public Result(Integer code, String msg, Object data) {
this.code = code;
this.msg = msg;
this.data = data;
}
public Result() {
}
}
/dao/EmpDao.java
// 创建一个数据访问接口
public interface EmpDao {
// 获取员工列表数据
List<Emp> listEmp();
}
/dao/impl/EmpDaoA.java
@Repository
public class EmpDaoA implements EmpDao {
@Override
public List<Emp> listEmp() {
String file = this.getClass().getClassLoader().getResource("emp.xml").getFile();
System.out.println(file);
return XMLParseUtils.parse(file, Emp.class);
}
}
/service/EmpService.java
public interface EmpService {
// 将数据访问层的数据进行处理, 得到响应数据
List<Emp> listEmp();
}
/service/impl/EmpServiceA.java
@Service
public class EmpServiceA implements EmpService {
// private EmpDao empDao = new EmpDaoA(); // 这里直接实例化了一个具体的 EmpDaoA 对象
@Resource(name = "empDaoA") // 依赖注入(指定了哪一类依赖, 防止多个同类型的依赖造成冲突)
private EmpDao empDao;
@Override
public List<Emp> listEmp() {
List<Emp> empList = empDao.listEmp();
empList.forEach(emp -> {
String gender = emp.getGender();
emp.setGender(gender.equals("1")? "男": "女");
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;
}
}
/dao/impl/EmpDaoA.java
@SpringBootApplication
@RestController
public class EmpListController {
@Resource(name = "empServiceA") // 依赖注入
private EmpService empService;
public static void main(String[] args) {
SpringApplication.run(EmpListController.class, args);
}
@RequestMapping("/listEmp")
public Result listEmp() {
// 调用服务, 获取响应数据
List<Emp> empList = empService.listEmp();
return Result.success(empList);
}
}