三层架构
定义
控制层(Controller)
- 控制层接收前端发送的请求,对请求进行处理,并响应数据。
业务逻辑层(Service)
- 业务逻辑层处理具体的业务逻辑。
数据访问层(DAO - Data Access Object)
- 数据访问层(持久层)负责数据访问操作,包括数据的增、删、改、查。
Java Web应用程序中的执行顺序
在Java Web应用程序中,请求的处理流程通常遵循以下顺序:
-
前端请求:
- 前端发送请求至服务器。
-
控制层(Controller):
- 接收请求:控制层首先接收前端发送的请求。
- 预处理:可能进行一些初步的数据验证或预处理。
- 调用业务逻辑:根据请求类型和参数,调用相应的业务逻辑层方法。
-
业务逻辑层(Service):
- 处理业务逻辑:执行具体的业务逻辑操作,如计算、决策、事务管理等。
- 数据访问需求:如果业务逻辑处理中需要访问数据库,则调用数据访问层的方法。
-
数据访问层(DAO - Data Access Object):
- 数据库交互:执行SQL语句(或者其他获取数据的技术)或使用ORM框架进行数据的增、删、改、查操作。
- 返回结果:将操作结果返回给业务逻辑层。
-
业务逻辑层(Service):
- 继续处理:根据数据访问层返回的结果,继续执行剩余的业务逻辑。
-
控制层(Controller):
- 响应前端:将业务逻辑层处理的结果封装成响应数据,返回给前端。
-
前端响应:
- 前端接收到控制层返回的数据,并据此更新用户界面。
简而言之,执行顺序为:
前端请求 → 控制层(Controller) → 业务逻辑层(Service) → 数据访问层(DAO) → 业务逻辑层(Service) → 控制层(Controller) → 前端响应
代码实现
初始代码如下:
这段代码复用性差,难以维护
我们要对下面的代码进行优化(拆分
package com.example.springboot_start.controller;
import com.example.springboot_start.pojo.Emp;
import com.example.springboot_start.pojo.Result;
import com.example.springboot_start.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(){
String file = this.getClass().getClassLoader().getResource("emp.xml").getFile();
System.out.println(file);
List<Emp> empList = XmlParserUtils.parse(file, Emp.class);
empList.stream().forEach(emp -> {
String gender = emp.getGender();
if ("1".equals(gender)){
emp.setGender("男");
} else if ("2".equals(gender)) {
emp.setGender("nv");
}
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 Result.success(empList);
}
}
把以上代码分成三层架构:
然后把三层架构的包给新建出来:
为了功能的灵活性,采用面向接口的方法
dao包的实现
先弄个接口EmpDao,然后在弄个实现类文件夹impl。
实现类文件夹中再新建一个实现类EmpDaoA
这个A的意思就是之后还可能有其他实现类来实现这个接口
接口:
package com.example.springboot_start.dao;
import com.example.springboot_start.pojo.Emp;
import java.util.List;
public interface EmpDao {
public List<Emp> listEmp();
}
实现类:
(实际上就是上面那个图中的“数据访问”那一块代码
package com.example.springboot_start.dao.impl;
import com.example.springboot_start.dao.EmpDao;
import com.example.springboot_start.pojo.Emp;
import com.example.springboot_start.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;
}
}
service层
接口:
package com.example.springboot_start.service;
import com.example.springboot_start.pojo.Emp;
import java.util.List;
public interface EmpService {
//先获取员工列表(从DAO那里
public List<Emp> listEmp();
}
实现类:
package com.example.springboot_start.service.impl;
import com.example.springboot_start.dao.EmpDao;
import com.example.springboot_start.dao.impl.EmpDaoA;
import com.example.springboot_start.pojo.Emp;
import com.example.springboot_start.service.EmpService;
import java.util.List;
public class EmpServiceA implements EmpService {
//获取DAO
private EmpDao empDao = new EmpDaoA();
@Override
public List<Emp> listEmp() {
List<Emp> empList = empDao.listEmp();
//调用dao来获取数据
empList.stream().forEach(emp -> {
String gender = emp.getGender();
if ("1".equals(gender)){
emp.setGender("男");
} else if ("2".equals(gender)) {
emp.setGender("nv");
}
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中的数据
controller层
这个就不必用什么接口或者实现类了
代码:
@RestController
public class EmpController {
//创建service对象(面向接口
private EmpService empService = new EmpServiceA();
@RequestMapping("/listEmp")
public Result list(){
//调用service获取数据
List<Emp> empList = empService.listEmp();
return Result.success(empList);
}
}
同理,controller层也必须获取service层中数据
分层解耦
定义
内聚
- 定义:内聚是指软件中各个功能模块内部的功能联系。它衡量了一个模块内部各个元素之间相互依赖、相互关联的紧密程度。
耦合
- 定义:耦合是衡量软件中各个层/模块之间依赖、关联程度的指标。它描述了不同模块之间相互依赖的关系强度。
所以我们刚才进行的拆分就是使代码变得更 高内聚,低耦合 的过程
这也是软件设计过程中非常重要的原则:高内聚,低耦合
以刚才的代码为例
**高内聚:**在员工管理的service中,仅仅存在与员工管理相关的逻辑处理
**低耦合:**尽量降低层与层之间或模块与模块之间的依赖,关联程度
引入IOC & DI
但是刚才咱拆分之后的代码还是存在耦合
比如说:
上面这个图就是controller层的代码
但是如标红所示
EmpController
类直接依赖于 EmpServiceA
这个具体实现类,而不是依赖于 EmpService
接口。这意味着 EmpController
与 EmpServiceA
的实现细节紧密相关。
同时,EmpServiceA
被硬编码为 EmpService
的实现,这限制了 EmpController
的灵活性。如果将来需要更换 EmpService
的实现,必须修改 EmpController
类的代码。
所以我们必须把那块标红的搞掉:
但这声明了变量但是没赋值,会报错,咋办呢
这个时候我们就要用IOC和DI了
这个就是对控制反转(IOC)和依赖注入(DI)的图解
IoC是有专门一个容器来创建这些对象,即由Ioc容器来控制对象的创建以及外部资源获取(不只是对象包括比如文件等)。
反转则是由容器来帮忙创建及注入依赖对象:由容器帮我们查找及注入依赖对象,对象只是被动的接受依赖对象,所以是反转,依赖对象的获取被反转了
概念如下:
控制反转 (Inversion Of Control, 简称 IoC)
- 定义:控制反转是一种设计原则,指对象的创建控制权由程序自身转移到外部(容器)。
依赖注入 (Dependency Injection, 简称 DI)
- 定义:依赖注入是实现控制反转的一种方法,容器为应用程序提供运行时所依赖的资源。
Bean对象
- 定义:在IoC容器中创建、管理的对象称为bean。
代码实现
@Component:
- 将当前类交给IOC容器管理,成为IOC容器中的bean
@Autowired
- 运行时,IOC容器会提供该类型的bean对象,并赋值给该变量——依赖注入
这样就完成解耦操作了~
IOC讲解
注解 | 说明 | 位置 |
---|---|---|
@Component | 声明bean的基础注解 | 不属于以下三类时,用此注解 |
@Controller | @Component的衍生注解 | 标注在控制器类上 |
@Service | @Component的衍生注解 | 标注在业务类上 |
@Repository | @Component的衍生注解 | 标注在数据访问类上(由于与mybatis整合,用的少) |
在实际开发中,建议用下面三个注解
比如说我们刚才的代码(看蓝色字体)
注意@RestController
是由@Controller
和@ResponseBody
组成的
所以不用额外加@Controller
了
查看Bean
在图中位置查看bean:
bean的名字的默认值就是bean所在类的名字(首字母变成小写
更改bean的名字:
就是通过value属性更改
@Respository(value = " daoA ")
//属性名字也可以省略
@Respository(" daoA ")
//这俩都是一样的
DI(依赖注入)详解
存在多个相同类型的bean咋办?
以上代码中,我们注入了EmpDaoA 👇
@Repository
public class EmpDaoA implements EmpDao {
那如果还有一个EmpDaoB也注解了@Repository👇
@Repository
public class EmpDaoB implements EmpDao {
那咋整呢?到底注入谁呢?
在这种情况下运行实际上会报错的
like this 👇
那咋办呢?
@Primary注解
就是多个注解在上面
如下:
@Primary
@Repository
public class EmpDaoA implements EmpDao {
这样运行起来就没毛病了
@Qualifier
在 被注入 的地方加入注释
然后在括号里面加入bean的名字
注意不是类的名字哦,是bean的捏(默认是类名首字母小写)
@Autowired
@Qualifier("empDaoA")
private EmpDao empDao;
这样运行起来就没毛病了
Resource
在被注入的位置把@Autowired删掉,然后再加入这个注解
@Resource(name = "empDaoA")
private EmpDao empDao;
注意这个是以名字注入的,但是其他的是以类型注入的捏
注解 | 定义 |
---|---|
@Autowired | 是Spring框架提供的注解 |
@Resource | 是JDK提供的注解 |
特点 | @Autowired | @Resource |
---|---|---|
默认注入方式 | 按照类型注入 | 默认按照名称注入 |