分层解耦
案例:将 emp.xml 中的数据解析并响应
emp.xml 内容如下:
<emps>
<emp>
<name>Tom</name>
<age>18</age>
<gender>1</gender>
<job>1</job>
</emp>
<emp>
<name>Jerry</name>
<age>19</age>
<gender>1</gender>
<job>2</job>
</emp>
<emp>
<name>Mike</name>
<age>20</age>
<gender>1</gender>
<job>3</job>
</emp>
<emp>
<name>Lucy</name>
<age>21</age>
<gender>2</gender>
<job>3</job>
</emp>
</emps>
引入相关依赖:
<dependency>
<groupId>org.dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>2.1.3</version>
</dependency>
XML 文件解析工具类 XmlParserUtils:
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import java.io.File;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.List;
public class XmlParserUtils {
public static <T> List<T> parse(String file, Class<T> targetClass){
ArrayList<T> list = new ArrayList<>();
try {
SAXReader saxReader = new SAXReader();
Document document = saxReader.read(new File(file));
Element rootElement = document.getRootElement();
List<Element> elements = rootElement.elements("emp");
for (Element element : elements) {
String name = element.element("name").getText();
String age = element.element("age").getText();
String gender = element.element("gender").getText();
String job = element.element("job").getText();
Constructor<T> constructor = targetClass.getDeclaredConstructor(String.class, Integer.class, String.class, String.class);
constructor.setAccessible(true);
T object = constructor.newInstance(name, Integer.parseInt(age), gender, job);
list.add(object);
}
} catch (Exception e) {
e.printStackTrace();
}
return list;
}
}
实体类 Emp:
public class Emp {
private String name;
private Integer age;
private String gender;
private String job;
public Emp(String name, Integer age, String gender, String job) {
this.name = name;
this.age = age;
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 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 +
", gender='" + gender + '\'' +
", job='" + job + '\'' +
'}';
}
}
统一响应结果封装类 Result:
/*
* 统一响应结果封装类
*/
public class Result {
private Integer code; // 状态码,200为成功,500为失败
private String message; // 返回消息
private Object data; // 返回数据
public Result(Integer code, String message, Object data) {
this.code = code;
this.message = message;
this.data = data;
}
public Integer getCode() {return code;}
public void setCode(Integer code) {this.code = code;}
public String getMessage() {return message;}
public void setMessage(String message) {this.message = message;}
public Object getData() {return data;}
public void setData(Object data) {this.data = data;}
public static Result success(Object data) {
return new Result(200, "success", data);
}
public static Result success() {
return new Result(200, "success", null);
}
public static Result error(String message) {
return new Result(500, message, null);
}
@Override
public String toString() {
return "Result{" +
"code=" + code +
", message='" + message + '\'' +
", data=" + data +
'}';
}
}
请求处理类 EmpController:
import com.example.demo.pojo.Emp;
import com.example.demo.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();
List<Emp> empList = XmlParserUtils.<Emp>parse(file, Emp.class);
// 2. 对数据进行转换处理
empList.stream().forEach(emp -> {
String gender = emp.getGender();
if ("1".equals(gender)) {
emp.setGender("男");
} else if ("2".equals(gender)) {
emp.setGender("女");
}
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);
}
}
响应结果为:
三层架构
controller:控制层,接收前端发送的请求,对请求进行处理,并响应数据
service:业务逻辑层,处理具体的业务逻辑
dao:数据访问层(Data Access Object,持久层),负责数据访问操作,包括数据的增删改查
回顾上述案例,会发现请求控制、业务逻辑处理和数据访问都在同一个类中,这样就会使代码的复用性差以及难以维护,所以按照三层架构改为以下内容:
dao 层接口 EmpDao:
import com.example.demo.pojo.Emp; import java.util.List; public interface EmpDao { // 获取员工列表数据 public List<Emp> listEmp(); }
dao 层实现类 EmpDaoA:
import com.example.demo.dao.EmpDao; import com.example.demo.pojo.Emp; import com.example.demo.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(); List<Emp> empList = XmlParserUtils.<Emp>parse(file, Emp.class); return empList; } }
service 层接口 EmpService:
import com.example.demo.pojo.Emp; import java.util.List; public interface EmpService { public List<Emp> listEmp(); }
service 层实现类 EmpServiceA:
import com.example.demo.dao.EmpDao; import com.example.demo.dao.impl.EmpDaoA; import com.example.demo.pojo.Emp; import com.example.demo.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. 对数据进行转换处理 empList.stream().forEach(emp -> { String gender = emp.getGender(); if ("1".equals(gender)) { emp.setGender("男"); } else if ("2".equals(gender)) { emp.setGender("女"); } 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 层实现类 EmpController:
import com.example.demo.pojo.Emp; import com.example.demo.service.EmpService; import com.example.demo.service.impl.EmpServiceA; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.List; @RestController public class EmpController { private EmpService empService = new EmpServiceA(); @RequestMapping("/listEmp") public Result list() { List<Emp> empList = empService.listEmp(); //3. 响应数据 return Result.success(empList); } }
这样就完成了三层架构的分层,便于后期的管理与维护。
分层解耦
内聚:指一个模块(类、方法、函数等)内部各个元素(如方法、属性)之间的关联紧密程度。内聚度越高,说明模块内部的功能越集中、单一,“做一件事并做好”;内聚度低则意味着模块功能混乱,职责不清晰。
耦合:指不同模块之间的依赖紧密程度。耦合度越低,模块之间的独立性越强,一个模块的修改对其他模块的影响越小;耦合度高则意味着模块 “牵一发而动全身”,难以维护。
按照案例中三层架构的流程,controller 中要调用 service 都需要创建一个 service 层的对象,controller 层与 service 层耦合;service 中要调用 dao 都需要创建一个 dao 层的对象,service 层与 dao 层耦合。若后续需要更换 EmpService 的实现类(比如新增 EmpServiceB),必须修改 EmpController 的代码;当 Dao 层实现变更(如换成 EmpDaoB),得改动 EmpServiceA 代码,耦合度高,不利于代码扩展和维护。
为了解决这个问题,就要引入三个概念:控制反转、依赖注入、Bean 对象:
- 控制反转:Inversion Of Control,简称 IOC,对象的创建控制权由程序自身转移到外部(容器),这种思想成为控制反转。
- 依赖注入:Dependency Injection,简称 DI,容器为应用程序提供运行时所依赖的资源,称为依赖注入。
- Bean 对象:IOC 容器中创建、管理的对象,称为 Bean。
IOC & DI 入门
操作步骤:
- service 层及 dao 层的实现类,交给 IOC 容器管理,使用 @Component 注解
- 为 controller 层及 service 层注入运行时依赖的对象,使用 @Autowired 注解
- 运行测试
代码做以下修改:
controller 层
import com.example.demo.pojo.Emp; import com.example.demo.service.EmpService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.List; @RestController public class EmpController { @Autowired private EmpService empService; @RequestMapping("/listEmp") public Result list() { List<Emp> empList = empService.listEmp(); //3. 响应数据 return Result.success(empList); } }
service 层
import com.example.demo.dao.EmpDao; import com.example.demo.pojo.Emp; import com.example.demo.service.EmpService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.util.List; @Component public class EmpServiceA implements EmpService { @Autowired private EmpDao empDao; @Override public List<Emp> listEmp() { // 1. 调用dao层方法获取数据 List<Emp> empList = empDao.listEmp(); // 2. 对数据进行转换处理 empList.stream().forEach(emp -> { String gender = emp.getGender(); if ("1".equals(gender)) { emp.setGender("男"); } else if ("2".equals(gender)) { emp.setGender("女"); } 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 层
import com.example.demo.dao.EmpDao; import com.example.demo.pojo.Emp; import com.example.demo.utils.XmlParserUtils; import org.springframework.stereotype.Component; import java.util.List; @Component public class EmpDaoA implements EmpDao { @Override public List<Emp> listEmp() { // 1. 加载并解析emp.xml String file = this.getClass().getClassLoader().getResource("emp.xml").getFile(); List<Emp> empList = XmlParserUtils.<Emp>parse(file, Emp.class); return empList; } }
这样就完成了模块间的解耦操作
IOC 详解
Bean 的声明
要把某个对象交给 IOC 容器管理,需要在对应的类上加上如下注解之一:
注解 | 说明 | 位置 |
---|---|---|
@Component | 声明 Bean 的基础注解 | 不属于以下三类时,用此注解 |
@Controller | @Component 的衍生注解 | 标注在控制器类上(@RestController 已包含) |
@Service | @Component 的衍生注解 | 标注在业务类上 |
@Repository | @Component 的衍生注解 | 标注在数据访问类上(由于与 Mybatis 整合,用的少) |
声明控制器 Bean 只能用 @Controller
在 IOC 容器中,每个 Bean 都有唯一标识,就是类名首字母小写
当然也可以自己定义 Bean 的名称,只需要在注解后面加上 value
@Repository(value = "daoA") // value也可以不写
public class EmpDaoA implements EmpDao {
//...
}
添加之后,Bean 的名称就变为自己定义的
Bean 的组件扫描:
- 前面声明 Bean 的四大注解,若要想生效,需要被组件扫描注解 @ComponentScan 扫描
- @ComponentScan 注解虽然没有显示配置,但实际上已包含在启动类声明注解 @SpringBootApplication 中,默认扫描的范围是启动类所在包及其子包
DI 详解
@Autowired 注解,默认是按照类型进行,如果存在多个相同类型的 Bean,将会报出如下错误:
Field empService in com.itheima.controller.EmpController required a single bean, but 2 were found:
- empServiceA: defined in file [E:\springboot - web - req - resp\target\classes\com\itheima\service\impl\EmpServiceA.class
- empServiceB: defined in file [E:\springboot - web - req - resp\target\classes\com\itheima\service\impl\EmpServiceB.class
Action:
Consider marking one of the beans as @Primary, updating the consumer to accept multiple beans, or using @Qualifier to identify the bean
可以通过以下几个方案来解决:
@Primary:如果有多个相同类型的 Bean,@Primary 加在哪个 Bean 上,哪个 Bean 就生效
@Primary @Service public class EmpServiceA implements EmpService { @Autowired private EmpDao empDao; @Override public List<Emp> listEmp() { //... } }
@Qualifier:放在 Bean 内,与 @Autowired 配合使用,@Qualifier(“Bean 的名称”)
@Service public class EmpServiceA implements EmpService { @Qualifier("daoA") @Autowired private EmpDao empDao; @Override public List<Emp> listEmp() { // ... } }
@Resource:用 @Resource(name = “Bean 的名称”) 来替换 @Autowired
@Service public class EmpServiceA implements EmpService { @Resource(name = "daoA") private EmpDao empDao; @Override public List<Emp> listEmp() { // ... } }
@Resource 和 @Autowired 的区别:
- @Autowired 是 Spring 框架提供的注解,而 @Resource 是 JDK 提供的注解
- @Autowired 是默认按照类型注入,而 @Resource 是默认按照名称注入