【前端学java】SpringBootWeb极速入门-分层解耦(03)

简介: 【8月更文挑战第13天】SpringBootWeb极速入门-分层解耦(03)

案例分析

我们先看一个spring-boot-web开发的接口

package com.shixiaoshi.controller;

import com.shixiaoshi.pojo.Emp;
import com.shixiaoshi.pojo.Result;
import com.shixiaoshi.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();
        System.out.println(file);
        List<Emp> empList = XmlParserUtils.parse(file, Emp.class);
        System.out.println(empList.stream());
        //2. 对数据进行转换处理 - gender, job
        empList.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("就业指导");
            }
        });

        //3。响应数据
        return Result.success(empList);
    }
}

上述代码定义了一个名为listEmp的接口,它对项目中的xml文件进行了解析,然后返回了解析后的数据。
GIF 2023-11-14 15-01-47.gif
上述代码并没有什么逻辑错误,但如果业务逻辑增加时,所有处理都放在EmpController类中进行处理是愚蠢的。我们需要对这个项目进行优化,让他更适合维护和拓展。

架构优化

什么是三层架构

我们可以将上述代码的功能分成三层结构
image.png
我们在设计接口、类或方法时,尽量要让他们的功能单一,只做一件事情,这就是设计模式中的单一职责原则
单一职责的设计模式会让我们的程序更易维护和拓展,因此,在javaweb开发中,有了三层架构的开发原则。

三层架构的内容

             ![image.png](https://cdn.nlark.com/yuque/0/2023/png/21865277/1699946109928-03c257f1-6b9b-44b0-b2d0-b98dd8a4f9f0.png#averageHue=%23fddf86&clientId=u430335e7-9b62-4&from=paste&height=119&id=ufc6b669a&originHeight=149&originWidth=734&originalType=binary&ratio=1.25&rotation=0&showTitle=false&size=26545&status=done&style=none&taskId=ufdce5d9c-0022-44fe-9320-6598a52d356&title=&width=587.2)
  • controller:控制层,接收前端发送的请求,对请求进行处理,并响应数据。
  • service:业务逻辑层,处理具体的业务逻辑。
  • dao:数据访问层(Data Access Object)(持久层),负责数据访问操作,包括数据的增、删、改、查。

    代码拆分

    我们根据三层架构的原则,对原有项目进行拆分优化。首先,和创建dao包service包。

    注:项目路径一定不能有中文,否则程序执行会报错

GIF 2023-11-14 15-30-47.gif

dao数据访问层

数据访问层(Data Access Object)(持久层),负责数据访问操作,包括数据的增、删、改、查,最终被service调用。
其访问的数据类型有很多,如文件中的数据、数据库中的数据 、别人接口中的数据。 我们要想灵活的实现这些方式的切换,可以使用面向接口的方式编程。

所以,我们先在dao包内定义一个EmpDao接口,增强其灵活性和可拓展性。
GIF 2023-11-14 16-08-27.gif

package com.shixiaoshi.dao;
import com.shixiaoshi.pojo.Emp;
import java.util.List;

public interface EmpDao {
   
   
    //获取员工列表数据  listEmp 是接口中定义的抽象方法,需要子类实现
    public List<Emp> listEmp();
}

现在,我们来实现这个接口。首先,我们创建impl.EmpDaoA类,然后实现 EmpDao这个接口
GIF 2023-11-14 16-26-54.gif
alt + shift + p快捷实现类中的方法,然后从EmpController中复制其对应的数据处理方法
GIF 2023-11-14 16-33-31.gif

package com.shixiaoshi.dao.imp;

import com.shixiaoshi.dao.EmpDao;
import com.shixiaoshi.pojo.Emp;
import com.shixiaoshi.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;
    }
}

现在,我们dao层的数代码逻辑就完成。

service业务逻辑层

service的业务逻辑处理方式和bao层是一致的。我们先在service包内创建一个EmpService接口。
image.png
然后,我们创建impl.EmpServiceA这个类实现EmpService接口。同样的,我们从EmpController中复制数据处理逻辑image.png
此时,我们会发现empList及其相关的方法报错了。这很正常,因为empList的值现在在dao层的EmpDaoA类中储存着,我们需要从这个类中获取empList

private EmpDao empDao = new EmpDaoA();

List<Emp> empList = empDao.listEmp();

完整代码如下

package com.shixiaoshi.service.impl;

import com.shixiaoshi.dao.EmpDao;
import com.shixiaoshi.dao.impl.EmpDaoA;
import com.shixiaoshi.pojo.Emp;
import com.shixiaoshi.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. 对数据进行转换处理 - gender, job
        empList.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;
    }
}

现在,我们已经将代码已经改造完毕,我们对接口进行测试
GIF 2023-11-14 17-13-02.gif

总结

上述教程中,我们通过单一职责原理,将代码封装成了三层结构,每层结构只专注自己的事情。相比一开始所有代码写在一起,优化后的代码结构复用性强,利于拓展,便于维护!
image.png

但该项目改造后,还是存在一定的缺陷,比如我们的controller层代码依赖service代码
image.png
service层代码又依赖数据层代码
image.png
这三层结构数据耦合程度比较高,如果其中一个控制层的类名发生变化,对其耦合的数据层也会造成影响。

因此,该项目还可以进一步的进行解耦优化!

解耦优化

什么是高内聚低耦合

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

  • 高内聚指的是:一个模块中各个元素之间的联系的紧密程度,如果各个元素(语句、程序段)之间的联系程度越高,则内聚性越高,即 "高内聚"。
  • 低耦合指的是:软件中各个层、模块之间的依赖关联程序越低越好。

程序中高内聚的体现:
image.png
程序中耦合代码的体现:
把业务类变为EmpServiceB时,需要修改controller层中的代码
image.png
高内聚、低耦合的目的是使程序模块的可重用性、移植性大大增强。
image.png

解耦思路

之前我们在编写代码时,需要什么对象,就直接new一个就可以了。 这种做法呢,层与层之间代码就耦合了,当service层的实现变了之后, 我们还需要修改controller层的代码
image.png那应该怎么解耦呢?最简单的,就是将serveice层的数据放在一个容器中,contrller层直接调用这个数据就行。
image.png
再java中想实现这个操作,就需要了解Spring中的两个核心概念:

  • 控制反转: Inversion Of Control,简称IOC。对象的创建控制权由程序自身转移到外部(容器),这种思想称为控制反转
  • 依赖注入: Dependency Injection,简称DI。容器为应用程序提供运行时,所依赖的资源,称之为依赖注入

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

简单来说,就是把service的数据创建放在Bean容器中(IOC),容器为Controller进行数据注入(DI)

解耦的实现

  • 删除Controller层、Service层中new对象的代码
  • Service层及Dao层的实现类,交给IOC容器管理
  • 为Controller及Service注入运行时依赖的对象
    • Controller程序中注入依赖的Service层对象
    • Service程序中注入依赖的Dao层对象

第1步:删除Controller层、Service层中new对象的代码image.png
第2步:Service层及Dao层的实现类,交给IOC容器管理

  • 使用Spring提供的注解:@Component ,就可以实现类交给IOC容器管理
  • IOC容器类似一个内部服务,不用进行声明

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

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

image.png
完整的三层代码:
Dao层

@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;
    }
}

Service层

@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;
    }
}

Controller层

@RestController
public class EmpController {
   
   
    @Autowired //运行时,从IOC容器中获取该类型对象,赋值给该变量
    private EmpService empService ;
    @RequestMapping("/listEmp")
    public Result list(){
   
   
        // 调用service, 获取数据
        List<Emp> empList = empService.listEmp();
        // 响应数据
        return Result.success(empList);
    }
}

运行测试:
启动SpringBoot引导类,打开浏览器,输入:http://localhost:8080/emp.html
image.png

相关文章
|
13天前
|
Java API 数据库
2025 年最新 Java 实操学习路线,从入门到高级应用详细指南
2025年Java最新实操学习路线,涵盖从环境搭建到微服务、容器化部署的全流程实战内容,助你掌握Java 21核心特性、Spring Boot 3.2开发、云原生与微服务架构,提升企业级项目开发能力,适合从入门到高级应用的学习需求。
239 0
|
22天前
|
前端开发 Java 数据库连接
帮助新手快速上手的 JAVA 学习路线最详细版涵盖从入门到进阶的 JAVA 学习路线
本Java学习路线涵盖从基础语法、面向对象、异常处理到高级框架、微服务、JVM调优等内容,适合新手入门到进阶,助力掌握企业级开发技能,快速成为合格Java开发者。
272 3
|
23天前
|
监控 Java API
2025 年全新出炉的 Java 学习路线:从入门起步到实操精通的详细指南
2025年Java学习路线与实操指南,涵盖Java 21核心特性、虚拟线程、Spring Boot 3、微服务、Spring Security、容器化部署等前沿技术,助你从入门到企业级开发进阶。
196 0
|
2月前
|
NoSQL Java 关系型数据库
Java 从入门到进阶完整学习路线图规划与实战开发最佳实践指南
本文为Java开发者提供从入门到进阶的完整学习路线图,涵盖基础语法、面向对象、数据结构与算法、并发编程、JVM调优、主流框架(如Spring Boot)、数据库操作(MySQL、Redis)、微服务架构及云原生开发等内容,并结合实战案例与最佳实践,助力高效掌握Java核心技术。
218 1
|
2月前
|
Java 测试技术 API
Java IO流(二):文件操作与NIO入门
本文详解Java NIO与传统IO的区别与优势,涵盖Path、Files类、Channel、Buffer、Selector等核心概念,深入讲解文件操作、目录遍历、NIO实战及性能优化技巧,适合处理大文件与高并发场景,助力高效IO编程与面试准备。
|
2月前
|
Java 编译器 API
Java Lambda表达式与函数式编程入门
Lambda表达式是Java 8引入的重要特性,简化了函数式编程的实现方式。它通过简洁的语法替代传统的匿名内部类,使代码更清晰、易读。本文深入讲解Lambda表达式的基本语法、函数式接口、方法引用等核心概念,并结合集合操作、线程处理、事件回调等实战案例,帮助开发者掌握现代Java编程技巧。同时,还解析了面试中高频出现的相关问题,助你深入理解其原理与应用场景。
|
2月前
|
安全 Java 数据库连接
2025 年最新 Java 学习路线图含实操指南助你高效入门 Java 编程掌握核心技能
2025年最新Java学习路线图,涵盖基础环境搭建、核心特性(如密封类、虚拟线程)、模块化开发、响应式编程、主流框架(Spring Boot 3、Spring Security 6)、数据库操作(JPA + Hibernate 6)及微服务实战,助你掌握企业级开发技能。
254 3
|
2月前
|
算法 Java 测试技术
零基础学 Java: 从语法入门到企业级项目实战的详细学习路线解析
本文为零基础学习者提供完整的Java学习路线,涵盖语法基础、面向对象编程、数据结构与算法、多线程、JVM原理、Spring框架、Spring Boot及项目实战,助你从入门到进阶,系统掌握Java编程技能,提升实战开发能力。
113 0
|
2月前
|
前端开发 Java 数据库
Java 项目实战从入门到精通 :Java Web 在线商城项目开发指南
本文介绍了一个基于Java Web的在线商城项目,涵盖技术方案与应用实例。项目采用Spring、Spring MVC和MyBatis框架,结合MySQL数据库,实现商品展示、购物车、用户注册登录等核心功能。通过Spring Boot快速搭建项目结构,使用JPA进行数据持久化,并通过Thymeleaf模板展示页面。项目结构清晰,适合Java Web初学者学习与拓展。
181 1
|
3月前
|
存储 缓存 NoSQL
java 集合入门基础理论的核心概念与实用长尾知识
本文介绍了Java集合框架的基础理论知识,包括单列集合(List、Set、Queue)和双列集合(Map)的特点及常用实现类(如ArrayList、HashSet、HashMap等)。详细讲解了集合的遍历方式(迭代器、增强for循环、Lambda表达式)和典型应用场景(如数据去重、键值存储等)。通过具体代码示例,帮助初学者理解集合框架的核心概念和实际应用,为Java编程中的数据存储与管理提供基础指导。
94 0