SpringBoot 分层解耦

1. 三层架构

1.1介绍

在程序开发时,尽可能让每一个接口、类、方法的职责更单一些(单一职责原则)。

单一职责原则:一个类或一个方法,就只做一件事情,只管一块功能。

这样就可以让类、接口、方法的复杂度更低,可读性更强,扩展性更好,也更利用后期的维护。

之前开发的程序呢,并不满足单一职责原则。下面我们来分析下之前的程序:

那其实我们上述案例的处理逻辑呢,从组成上看可以分为三个部分:

  • 数据访问:负责业务数据的维护操作,包括增、删、改、查等操作。
  • 逻辑处理:负责业务逻辑处理的代码。
  • 请求处理、响应数据:负责,接收页面的请求,给页面响应数据。

按照上述的三个组成部分,在我们项目开发中呢,可以将代码分为三层:

  • Controller:控制层。接收前端发送的请求,对请求进行处理,并响应数据。
  • Service:业务逻辑层。处理具体的业务逻辑。
  • Dao:数据访问层(Data Access Object),也称为持久层。负责数据访问操作,包括数据的增、删、改、查。

基于三层架构的程序执行流程:

1.2 代码拆分

三层架构的好处:

  1. 复用性强
  2. 便于维护
  3. 利用扩展

2. 分层解耦

2.1 耦合问题

首先需要了解软件开发涉及到的两个概念:内聚和耦合。

  • 内聚:软件中各个功能模块内部的功能联系。
  • 耦合:衡量软件中各个层/模块之间的依赖、关联的程度。

软件设计原则:高内聚低耦合。

高内聚指的是:一个模块中各个元素之间的联系的紧密程度,如果各个元素(语句、程序段)之间的联系程度越高,则内聚性越高,即 "高内聚"。

低耦合指的是:软件中各个层、模块之间的依赖关联程序越低越好。

程序中高内聚的体现:

  • EmpServiceA类中只编写了和员工相关的逻辑处理代码

程序中耦合代码的体现:

  • 把业务类变为EmpServiceB时,需要修改controller层中的代码

高内聚、低耦合的目的是使程序模块的可重用性、移植性大大增强。

2.2 解耦思路

之前我们在编写代码时,需要什么对象,就直接new一个就可以了。 这种做法呢,层与层之间代码就耦合了,当service层的实现变了之后, 我们还需要修改controller层的代码。

那应该怎么解耦呢?

  • 首先不能在EmpController中使用new对象。代码如下:
  • 此时,就存在另一个问题了,不能new,就意味着没有业务层对象(程序运行就报错),怎么办呢?我们的解决思路是:

    • 提供一个容器,容器中存储一些对象(例:EmpService对象)
    • controller程序从容器中获取EmpService类型的对象

3.Spring的IOC&DI

1 IoC和DI介绍

  • 控制反转: Inversion Of Control,简称IOC。对象的创建控制权由程序自身转移到外部(容器),这种思想称为控制反转。

    对象的创建权由程序员主动创建转移到容器(由容器创建、管理对象)。这个容器称为:IOC容器或Spring容器

  • 依赖注入: Dependency Injection,简称DI。容器为应用程序提供运行时,所依赖的资源,称之为依赖注入。

    程序运行时需要某个资源,此时容器就为其提供这个资源。

    例:EmpController程序运行时需要EmpService对象,Spring容器就为其提供并注入EmpService对象

IOC容器中创建、管理的对象,称之为:bean对象

2. IOC&DI入门

需求

完成Controller层、Service层、Dao层的代码解耦

思路

  1. 删除Controller层、Service层中new对象的代码

  2. Service层及Dao层的实现类,交给IOC容器管理

  3. 为Controller及Service注入运行时依赖的对象

    • Controller程序中注入依赖的Service层对象
    • Service程序中注入依赖的Dao层对象

第1步:删除Controller层、Service层中new对象的代码

第2步:Service层及Dao层的实现类,交给IOC容器管理

  • 使用Spring提供的注解:@Component ,就可以实现类交给IOC容器管理

第3步:为Controller及Service注入运行时依赖的对象

  • 使用Spring提供的注解:@Autowired ,就可以实现程序运行时IOC容器自动注入需要的依赖对象

代码

  • Controller层:
kotlin 复制代码
@RestController
public class EmpController {
​
    @Autowired //运行时,从IOC容器中获取该类型对象,赋值给该变量
    private EmpService empService ;
​
    @RequestMapping("/listEmp")
    public Result list(){
        //1. 调用service, 获取数据
        List<Emp> empList = empService.listEmp();
​
        // 响应数据
        return Result.success(empList);
    }
}
  • Service层:
typescript 复制代码
@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;
    }
}

Dao层:

kotlin 复制代码
@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;
    }
}

3. IOC详解

IoC是什么:Inversion of Control,控制反转。反转的是创建对象的控制权

  • 原来:需要什么对象,就得自己主动new创建对象
  • 现在:Spring创建对象放到容器里。我们需要什么对象,Spring就给我什么对象

使用IoC的步骤:

  1. 哪个类需要由Spring来创建对象,就在哪个类上加@Component
  2. 需要什么对象,就在成员变量上添加 @Autowired

3.1 bean的声明

哪个类需要让Spring帮我们创建对象,就给类上加注解,Spring将会帮我们创建对象放到容器。这个过程叫:bean的注册或者bean的声明

bean:就是Spring创建出来的对象,起的称呼

3.1.1 声明bean的注解

在之前的入门案例中,要把某个对象交给IOC容器管理,需要在类上添加一个注解:@Component 。其实SpringBoot根据分层思想,提供了多个不同的注解: 语义化

注解 说明 位置
@Component 声明bean的基础注解 加到类上,Spring扫描到以后就会创建对象放到容器里
@Controller @Component的衍生注解 标注在web层的类上
@Service @Component的衍生注解 标注在service层的类上
@Repository @Component的衍生注解 标注在dao层的类上。将来使用Mybatis框架后,不用这个注解了

3.1.2 使用示例

  • Controller层:
kotlin 复制代码
@RestController  //@RestController = @Controller + @ResponseBody
public class EmpController {
​
    @Autowired //运行时,从IOC容器中获取该类型对象,赋值给该变量
    private EmpService empService ;
​
    @RequestMapping("/listEmp")
    public Result list(){
        //1. 调用service, 获取数据
        List<Emp> empList = empService.listEmp();
​
        // 响应数据
        return Result.success(empList);
    }
}
  • Service层:
typescript 复制代码
@Service
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;
    }
}

Dao层:

kotlin 复制代码
@Repository
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;
    }
}

3.1.3 bean的名称

在IOC容器中,每一个Bean都有一个属于自己的名字,可以通过注解的value属性指定bean的名字。如果没有指定,默认为类名首字母小写。

注意事项:

  • 声明bean的时候,可以通过value属性指定bean的名字,如果没有指定,默认为类名首字母小写。
  • 使用以上四个注解都可以声明bean,但是在springboot集成web开发中,声明控制器bean只能用@Controller。

3.2 组件扫描@ComponentScan

如果一个类上加了@Component或其衍生注解,并不意味着Springboot一定会创建这个类的对象。因为SpringBoot还需要:扫描类,发现哪个类上有@Component或衍生注解,才会给这个类创建对象并放到IoC容器里。

原始的Spring框架提供了扫描bean的注解:@ComponentScan("com.itheima"),可以用于设置扫描的范围。

而SpringBoot作为对Spring再封装的框架,它的引导类上添加的@SpringBootApplication注解已经包含了@ComponentScan。扫描范围是:引导类在哪个包,就扫描哪个包下的类。

所以SpringBoot项目,默认就已经实现了组件扫描,只要求我们:把所有类都放到 引导类所在包 下即可

注意:引导类 不要直接放到类路径(src\main\java)里,必须放到某个package里

4. DI详解

DI是什么:Dependency Inject,依赖注入。

  • 需要什么对象,就添加注解,由Spring帮我们查找对象并注入进来

4.1 DI相关注解

依赖注入相关的注解:

注解 说明
@Autowired【常用】 byType注入,根据依赖项的类型,从容器里查找bean对象并注入进来
@Autowired + @Primary 如果从容器中找到多个符合的bean,将会把有@Primary注解的bean对象注入进来
@Autowired + @Qualifier 如果从容器中找到多个符合的bean,将会把@Qualifier指定名称的bean注入进来
@Resource【常用】 byName注入,根据依赖项的名称,从容器中查找bean对象注入进来

4.2 注解使用说明

使用@Primary注解:当存在多个相同类型的Bean注入时,加上@Primary注解,来确定默认的实现。

使用@Qualifier注解:指定当前要注入的bean对象。 在@Qualifier的value属性中,指定注入的bean的名称。

  • @Qualifier注解不能单独使用,必须配合@Autowired使用

使用@Resource注解:是按照bean的名称进行注入。通过name属性指定要注入的bean的名称。

@Autowird 与 @Resource的区别

  • @Autowired 是spring框架提供的注解,而@Resource是JDK提供的注解
  • @Autowired 默认是按照类型注入,而@Resource是按照名称注入
相关推荐
长栎12 分钟前
写 for 循环写了十年,你却从没用过迭代器模式最狠的那一面
后端
LiaCode16 分钟前
Redis 在生产项目的使用
前端·后端
用户5598224812221 分钟前
Docker Compose Down 导致容器数据误删——ext4 日志恢复全记录
后端
LiaCode21 分钟前
一天学完 redis 的爽翻版核心知识总结
前端·后端
大刚测试开发实战23 分钟前
如何内网穿透访问本地私有化部署的TestHub
前端·后端·github
xiaodaoluanzha41 分钟前
迄今為止,最簡單的編程語言 Nolang
前端·后端
Csvn42 分钟前
Docker 容器管理入门 — 从镜像到容器编排
后端
用户762352425911 小时前
ShardingJDBC
后端
行者全栈架构师1 小时前
IDEA 中 Maven 项目的 15 个红色报错快速解决方法
java·后端
Colin草率地做慢慢地改1 小时前
关于QuickStore这个项目的重构(2)- 数据库建表文件
后端·面试·架构