当前位置: 首页 > news >正文

【Spring Boot 快速入门】三、分层解耦

目录

  • 分层解耦
    • 案例:将 emp.xml 中的数据解析并响应
    • 三层架构
    • 分层解耦
    • IOC & DI 入门
    • IOC 详解
    • DI 详解

分层解耦

案例:将 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>

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;}@Overridepublic 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);}@Overridepublic 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.xmlString 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,持久层),负责数据访问操作,包括数据的增删改查

在这里插入图片描述

回顾上述案例,会发现请求控制、业务逻辑处理和数据访问都在同一个类中,这样就会使代码的复用性差以及难以维护,所以按照三层架构改为以下内容:

  1. dao 层接口 EmpDao:

    import com.example.demo.pojo.Emp;
    import java.util.List;public interface EmpDao {// 获取员工列表数据public List<Emp> listEmp();
    }
    
  2. 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 {@Overridepublic List<Emp> listEmp() {// 1. 加载并解析emp.xmlString file = this.getClass().getClassLoader().getResource("emp.xml").getFile();List<Emp> empList = XmlParserUtils.<Emp>parse(file, Emp.class);return empList;}
    }
    
  3. service 层接口 EmpService:

    import com.example.demo.pojo.Emp;
    import java.util.List;public interface EmpService {public List<Emp> listEmp();
    }
    
  4. 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();@Overridepublic 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;}
    }
    
  5. 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 入门

操作步骤:

  1. service 层及 dao 层的实现类,交给 IOC 容器管理,使用 @Component 注解
  2. 为 controller 层及 service 层注入运行时依赖的对象,使用 @Autowired 注解
  3. 运行测试

代码做以下修改:

  1. 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 {@Autowiredprivate EmpService empService;@RequestMapping("/listEmp")public Result list() {List<Emp> empList = empService.listEmp();//3. 响应数据return Result.success(empList);}
    }
    
  2. 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 {@Autowiredprivate EmpDao empDao;@Overridepublic 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;}
    }
    
  3. 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 {@Overridepublic List<Emp> listEmp() {// 1. 加载并解析emp.xmlString 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.classAction: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 {@Autowiredprivate EmpDao empDao;@Overridepublic List<Emp> listEmp() {//...}
    }
    
  • @Qualifier:放在 Bean 内,与 @Autowired 配合使用,@Qualifier(“Bean 的名称”)

    @Service
    public class EmpServiceA implements EmpService {@Qualifier("daoA")@Autowiredprivate EmpDao empDao;@Overridepublic List<Emp> listEmp() {// ...}
    }
    
  • @Resource:用 @Resource(name = “Bean 的名称”) 来替换 @Autowired

    @Service
    public class EmpServiceA implements EmpService {@Resource(name = "daoA")private EmpDao empDao;@Overridepublic List<Emp> listEmp() {// ...}
    }
    

@Resource 和 @Autowired 的区别:

  • @Autowired 是 Spring 框架提供的注解,而 @Resource 是 JDK 提供的注解
  • @Autowired 是默认按照类型注入,而 @Resource 是默认按照名称注入
http://www.lryc.cn/news/603569.html

相关文章:

  • 飞算JavaAI:数据库插件安装与表结构设计的智能革命
  • 室内环境具身智能语义建图研究综述:进展、挑战与未来方向
  • SpringBoot整合RocketMQ(阿里云ONS)
  • GC8870 3.6A 刷式直流电机驱动器深度解析——规格、应用与实测数据全指南
  • 网络安全的变革:深入洞察 Web3 与传统网络模型
  • 【Linux我做主】探秘进程状态
  • 橡胶制品加工:塑造生活的柔韧力量
  • protobuf2.5.0 arm_linux
  • 嵌入向量与向量数据库:AI时代的语义搜索革命
  • 力扣30 天 Pandas 挑战(3)---数据操作
  • 【go】语言的匿名变量如何定义与使用
  • 算法【1】
  • Three.js实现银河螺旋星云粒子特效——原理、实现
  • Makefile 与 CMake 关系指南
  • Java设计模式之《备忘录模式》
  • 怎么理解使用MQ解决分布式事务 -- 以kafka为例
  • 【EDA】Calma--早期版图绘制工具商
  • Kafka运维实战 16 - kafka 分区重新分配【实战】
  • Javaweb————揭秘404 not found(HTTP常用响应码)
  • 【数据结构】真题 2016
  • STM32--DHT11(标准库)驱动开发
  • JVM 崩溃(Fatal Error)解决方法
  • 26考研11408数据结构
  • 【Docker】 Docker镜像瘦身终极指南:多阶段构建+Alpine优化+分层策略深度解析
  • 飞机大战小游戏
  • 第十六章 Java基础-拼图小游戏
  • 【Unity编辑器扩展】Unity 笔记编辑器开发详解(支持多页面、重命名、持久化保存)
  • 项目历程—生命数组游戏(两版本)
  • Unity 编辑器开发 之 Excel导表工具
  • 游戏盾从哪些方面保护网站业务?