后端Web实战(IOC+DI)

YangeIT大约 33 分钟JavaWebTomcatServletHTTPServletServlet参数传递

后端Web实战(IOC+DI)

今日目标

目标

Web开发的基础知识 ,包括 Tomcat、Servlet、HTTP协议、SpringBoot入门案例等,我们都已经学习完毕了,那接下来,我们就要进入Web开发的实战篇。在实战篇中,我们将通过一个案例,来讲解Web开发的核心技术。

我们先来看一下,在这个实战篇中,我们都要完成哪些功能。

  1. 部门管理open in new window
  2. 员工管理open in new window
  3. 员工信息统计open in new window
  4. 日志信息统计open in new window
  5. 班级管理
  6. 学员管理
  7. 学员信息统计
  8. 登录认证

上述需求,都是在这个案例中,我们需要完成的功能 。

今天主要完成如下功能:

  1. 开发规范
  2. 环境准备
  3. 查询部门
  4. 分层解耦(IOC+DI)

1. 开发规范

1.1 前后端分离开发

前言

现在的企业项目开发有2种开发模式:前后台混合开发前后台分离开发

1. 前后台混合开发,顾名思义就是前台后台代码混在一起开发,如下图所示:

image-20231124150054091

这种开发模式有如下缺点:

  • 沟通成本高:后台人员发现前端有问题,需要找前端人员修改,前端修改成功,再交给后台人员使用
  • 分工不明确:后台开发人员需要开发后台代码,也需要开发部分前端代码。很难培养专业人才
  • 不便管理:所有的代码都在一个工程中
  • 难以维护:前端代码更新,和后台无关,但是需要整个工程包括后台一起重新打包部署。

2. 我们目前基本都是采用的前后台分离开发方式,如下图所示:

image-20231124150213401

我们将原先的工程分为前端工程后端工程2个工程,然后前端工程交给专业的前端人员开发,后端工程交给专业的后端人员开发。

前端页面需要数据,可以通过发送异步请求,从后台工程获取。但是,我们前后台是分开来开发的,那么前端人员怎么知道后台返回数据的格式呢?后端人员开发,怎么知道前端人员需要的数据格式呢?

针对这个问题,我们前后台统一制定一套规范!我们前后台开发人员都需要遵循这套规范开发,这就是我们的接口文档。接口文档有离线版和在线版本,接口文档示可以查询今天提供资料/接口文档里面的资料。

那么接口文档的内容怎么来的呢?

是我们后台开发者根据产品经理提供的产品原型和需求文档所撰写出来的,产品原型示例可以参考今天提供资料/页面原型里面的资料。

那么基于前后台分离开发的模式下,我们后台开发者开发一个功能的具体流程如何呢? 如下图所示:

image-20231124150335609
  1. 需求分析:首先我们需要阅读需求文档,分析需求,理解需求。
  2. 接口定义:查询接口文档中关于需求的接口的定义,包括地址,参数,响应数据类型等等
  3. 前后台并行开发:各自按照接口文档进行开发,实现需求
  4. 测试:前后台开发完了,各自按照接口文档进行测试
  5. 前后段联调测试:前段工程请求后端工程,测试功能

总结

课堂作业

  1. 为什么项目开发主流是前后端分离?这样做有什么好处?
  2. 前后台分离开发的模式,后台开发者开发一个功能的具体流程分为哪些步骤?🎤

1.2 Restful

Restful

我们的案例是基于当前最为主流的前后端分离模式 进行开发。

image-20231124150634301

在前后端分离的开发模式中,前后端开发人员都需要根据提前定义好的接口文档,来进行前后端功能的开发。

后端开发人员:必须严格遵守提供的接口文档进行后端功能开发(保障开发的功能可以和前端对接)

而在前后端进行交互的时候,我们需要基于当前主流的REST风格的API接口进行交互。

什么是REST风格呢?

  • REST(Representational State Transfer),表述性状态转换,它是一种软件架构风格。

传统URL风格如下:

http://localhost:8080/user/getById?id=1     GET:查询id为1的用户
http://localhost:8080/user/saveUser         POST:新增用户
http://localhost:8080/user/updateUser       POST:修改用户
http://localhost:8080/user/deleteUser?id=1  GET:删除id为1的用户

我们看到,原始的传统URL呢,定义比较复杂,而且将资源的访问行为对外暴露出来了。

基于REST风格URL如下:

http://localhost:8080/users/1  GET:查询id为1的用户
http://localhost:8080/users    POST:新增用户
http://localhost:8080/users    PUT:修改用户
http://localhost:8080/users/1  DELETE:删除id为1的用户

其中总结起来,就一句话:通过URL定位要操作的资源,通过HTTP动词(请求方式)来描述具体的操作。

在REST风格的URL中,通过四种请求方式,来操作数据的增删改查。

  • GET : 查询
  • POST :新增
  • PUT :修改
  • DELETE :删除

我们看到如果是基于REST风格,定义URL,URL将会更加简洁、更加规范、更加优雅。

注意事项:

  1. REST是风格,是约定方式,约定不是规定,可以打破
  2. 描述模块的功能通常使用复数 ,也就是加s的格式来描述,表示此类资源,而非单个资源。如:users、emps、books…

代码操作

apifox发送4种类型的请求方式

准备代码

/**
 * 控制层:接收前端请求的
 */

@RestController
public class FormController {
    //前端访问什么地址,才能调用这个方法尼?
    @RequestMapping("/login")
    public String login(String name, String password) {
        System.out.println("用户名:" + name + " 密码:" + password);

        if ("yange".equals(name) && "123".equals(password)) {
            return "login ok!";
        }
        return "login error!";
    }
}

发起GET请求截图:image

发起POst请求截图:image

发起Put请求截图:

image
image

发起Delete请求截图:

image
image

总结

课堂作业

  1. 使用apifox发送4种类型的请求,如通上述案例🎤

2.2 工程搭建

工程搭建

今天一上来,就要搭建工作,这样流畅度会高一点。

  • 工程名字:edu_management
  • jdk:8或者11
  • springboot版本:2.7.4
  • 编码:utf-8
  • maven坐标:
    • 组织名字:cn.yangeit
    • 项目名字:edu_management
    • 版本:1.0
  • 依赖坐标:
<!-- 如果安装的是1.8  就改成1.8  -->
  <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.4</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

 <properties>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>

<dependencies>
<!--   web的开发的起步依赖,包含tomcat     -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

<!--   单元测试     -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
<!--可以免除getset方法的书写-->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.24</version>
    </dependency>
    <!--工具类-->
    <dependency>
        <groupId>cn.hutool</groupId>
        <artifactId>hutool-all</artifactId>
        <version>5.8.27</version>
    </dependency>
</dependencies>

<build>
    <plugins>
        <plugin>
<!--作用: 项目打包时,把需要的各种依赖包都打到jar包中,jar包可以独立运行,使用“java -jar”可以直接运行-->
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <version>2.7.4</version>
        </plugin>
    </plugins>
</build>

代码操作

1). 使用maven方式,创建SpringBoot工程,并且在resources下创建application.yml配置文件,并粘贴上述提供的依赖,

image
image

2). 准备基础的包结构

image
image

总结

课堂作业

  1. 根据上述的提示信息,完成工程项目的创建,和测试Controller🎤

3. 查询部门

3.1 查询部门基本实现

查询部门基本实现

需求

查询所有的部门数据:将 dept.txt 文件中存储的部门数据,查询出来展示在部门管理的页面中。

image-20231124152404419

代码操作

实现思路

  1. 加载并读取dept.txt文本中的数据
  2. 解析文本中的数据,并将其封装为集合
  3. 响应数据(json格式)
image-20231124152719998

1. 将dept.txt文件放到resources文件夹中

dept.txt文件的内容

1,教研部,2023-01-01 12:00:00
2,学工部,2023-02-01 12:00:00
3,研发部,2023-03-01 12:00:00
4,人事部,2023-04-01 12:00:00
5,行政部,2023-05-01 12:00:00

根据dept.txt,创建Dept实体类,并且提供id name updateTime等属性,并且提供get和set方法

2. DeptController控制类代码如下:

@RestController
public class DeptController {

    @RequestMapping("/depts")
    public List<Dept> list2(){
        //1. 加载文件 ,  获取原始数据
        InputStream in = this.getClass().getClassLoader().getResourceAsStream("dept.txt");
        List<String> lines = IoUtil.readLines(in, "UTF-8",new ArrayList<>());

        //2. 对原始数据进行处理 , 组装部门数据
        List<Dept> deptList = lines.stream().map(line -> {
            String[] parts = line.split(",");
            Integer id = Integer.parseInt(parts[0]);
            String name = parts[1];
            LocalDateTime updateTime = LocalDateTime.parse(parts[2],
                    DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
            return new Dept(id, name, updateTime);
        }).collect(Collectors.toList());

        //2. 响应数据
        return deptList;
    }
}




打开Apifox,来测试当前接口:

image-20231124153047358
image-20231124153047358

新建请求,请求 http://localhost:8080/depts

image-20231124155649710
image-20231124155649710

总结

课堂作业

  1. lombok依赖有何作用!!
  2. 为什么Controller中的方法,返回值是json格式的?🎤
  3. 参考上述代码,将controller代码进行书写,并使用apifox进行测试,观察结果是否为json格式。
  4. 上述案例是返回所有部门,如果要你写一个getDeptByID 通过Id获得某个部门,应该怎么构建代码!! 练习一下把!!!

3.2 统一响应结果

统一响应结果

1). 刚才我们执行查询部门操作 ,查询返回的结果是一个List<Dept> ,原始代码及响应给前端的结果如下:

image-20231124154223330

2). 如果我们还要实现一个需求,根据ID查询部门名称 ,原始代码及响应给前端的结果如下:

image-20231124154330119

3). 如果我们还要实现一个需求,根据ID查询部门数据 ,原始代码及响应给前端的结果如下:

image-20231124154444878

大家会发现:

  1. 上述的每一个需求,我们都实现了,但是呢,所有的Controller的方法的返回值是各式各样的,什么样的都有,响应的结果,也是各式各样。
  2. 如果做一个大型项目,要实现的需求、功能非常多,如果按照这种方案来,最终就会造成项目不便管理、难以维护

为了解决这个问题,我们就需要统一响应结果。 也就是说,无论什么实现什么功能,最终响应给前端的格式应该是统一的 。

统一响应结果代码操作

统一响应结果说明

image-20231124155025797

前端:只需要按照统一格式的返回结果进行解析(仅一种解析方案),就可以拿到数据。

统一的返回结果使用类来描述,在这个结果中包含:

  • 响应状态码 :当前请求是成功,还是失败
  • 状态码信息 :给页面的提示信息
  • 返回的数据 :给前端响应的数据(字符串、对象、集合)

1. 定义在一个实体类Result来包含以上信息。代码如下:

import lombok.Data;
import java.io.Serializable;

/**
 * 后端统一返回结果
 */
@Data//自动生成get set 方法
public class Result {

    private Integer code; //编码:1成功,0和其它数字为失败
    private String msg; //错误信息
    private Object data; //数据

    public static Result success() {
        Result result = new Result();
        result.code = 1;
        return result;
    }

    public static Result success(Object object) {
        Result result = new Result();
        result.data = object;
        result.code = 1;
        return result;
    }

    public static Result error(String msg) {
        Result result = new Result();
        result.msg = msg;
        result.code = 0;
        return result;
    }

}

总结

在测试时候发现,即使我们将请求方式设置为 POST PUT DELETE,也都是可以请求成功的。 如下所示:

image-20231124160042355
image-20231124160042355

这是因为,我们服务器端,也就是Controller程序中并没有限制 该接口的请求方式,那么此时任何请求方式都是可以的。 如果要设置请求方式,可以通过如下两种方式来设置:

  1. 在controller的方法上,声明 @RequestMapping 注解的method属性,通过method属性指定请求方式。 如下:
@RequestMapping(value = "/depts", method = RequestMethod.GET)
  1. 直接使用 @GetMapping 来替换 @RequestMapping 注解,@GetMapping其实就是对@RequestMapping的封装,并限定了请求方式为GET。
@GetMapping("/depts")

课堂作业

  1. 为什么要统一结果响应!🎤
  2. 结合主流的网站,一般统一结果响应的类主要有哪些变量?
  3. 根据上述提示,完成同一结果响应类的封装,以及代码的改装!

3.3 前后端联调测试

前后端联调测试

联调测试

完成了查询部门的功能,我们也通过 Apifox 工具测试通过了,下面我们再基于前后端分离的方式进行接口联调 。具体操作如下:

  1. 将资料中提供的 "前端环境" 文件夹中的压缩包,拷贝到一个没有中文不带空格 的目录下。

image-20231124162424878

2、拷贝到一个没有中文不带空格 的目录后,进行解压 (解压到当前目录)

image-20231124163038191

3、双击 nginx.exe 启动Nginx,一闪而过,就说明nginx以启动完成。

image-20231124163207404

如果在任务管理器中,能看到上述两个进程,就说明nginx已经启动成功。

4、打开浏览器,访问:http://localhost:90open in new window

image-20231124163350170

5、测试:系统信息管理 -> 查询部门列表

image-20231124163448039

总结

课堂作业

  1. 在非中文目录中运行nginx服务器,并访问http://localhost:90/api/deptsopen in new window 返回前端网页,观察是否正常显示🎤
  2. 为什么要使用Nginx服务器部署前端代码,而java代码是在tomcat服务器中,这样前后端分离部署有什么好处?
  3. 使用hutool工具包改造程序,depts.json新格式如下拓展题
[{
   "id": 1,
   "name": "教研部",
   "updateTime": 1672545600000
}, {
   "id": 2,
   "name": "学工部",
   "updateTime": 1675224000000
}, {
   "id": 3,
   "name": "研发部",
   "updateTime": 1677643200000
}, {
   "id": 4,
   "name": "人事部",
   "updateTime": 1680321600000
}, {
   "id": 5,
   "name": "行政部",
   "updateTime": 1682913600000
}]

提示:

//使用hutool工具包中的FileUtil工具类,将json文件读成json字符串
String strings = FileUtil.readUtf8String("D://depts.json");
//将json字符串 使用工具类变成集合
List<Dept> list = JSONUtil.toList(strings, Dept.class);
//使用随机工具类,随机生成整数
int id = RandomUtil.randomInt();
//将对象转化成json字符串
String jsonStr = JSONUtil.toJsonStr(list);
//将字符串以utf-8的格式写入到文件
FileUtil.writeUtf8String(jsonStr,"D://depts.json");

如果你能运用上述的提示,完成部门的增加删除修改和查询改造,你的能力将在完成的瞬间,得到巨大提升!!

4. 分层解耦

4.1 三层架构

三层架构

上述案例的功能,我们虽然已经实现,但是呢,我们会发现案例中:解析文本文件中的数据,处理数据的逻辑代码,给页面响应的代码全部都堆积在一起了,全部都写在controller方法中了。

image-20231124164752386

当前程序的这个业务逻辑还是比较简单的,如果业务逻辑再稍微复杂一点,我们会看到Controller方法的代码量就很大了。

  • 当我们要修改操作数据部分的代码,需要改动Controller
  • 当我们要完善逻辑处理部分的代码,需要改动Controller
  • 当我们需要修改数据响应的代码,还是需要改动Controller

这样呢,就会造成我们整个工程代码的复用性比较差,而且代码难以维护 。 那如何解决这个问题呢?其实在现在的开发中,有非常成熟的解决思路,那就是分层开发。

代码操作

分层的具体操作

我们使用三层架构思想,来改造下之前的程序:

  • 控制层包名:com.itheima.controller
  • 业务逻辑层包名:com.itheima.service.impl
  • 数据访问层包名:com.itheima.dao.impl
image-20231124170711727

1). 控制层:接收前端发送的请求,对请求进行处理,并响应数据

@RestController
public class DeptController {
    
    private DeptServiceImpl deptService = new DeptServiceImpl();
    
    @GetMapping("/depts")
    public Result list(){
        //1. 调用deptService
        List<Dept> deptList = deptService.queryDeptList();
        //2. 响应数据
        return Result.success(deptList);
    }
	
}

2). 业务逻辑层:处理具体的业务逻辑

public class DeptServiceImpl  {
    
    private DeptDaoImpl deptDao= new DeptDaoImpl();

    @Override
    public List<Dept> queryDeptList() {
        //1. 调用deptDao
        List<String> lines = deptDao.queryDeptList();
       //2. 对原始数据进行处理 , 组装部门数据
        List<Dept> deptList = lines.stream().map(line -> {
            String[] parts = line.split(",");
            Integer id = Integer.parseInt(parts[0]);
            String name = parts[1];
            LocalDateTime updateTime = LocalDateTime.parse(parts[2],
                    DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
            return new Dept(id, name, updateTime);
        }).collect(Collectors.toList());
        //......
        return deptList;
    }
}

3). 数据访问层:负责数据的访问操作,包含数据的增、删、改、查

public class DeptDaoImpl {
    @Override
    public List<String> queryDeptList(){
        //1. 加载文件 ,  获取原始数据
        InputStream in = this.getClass().getClassLoader().getResourceAsStream("dept.txt");
        List<String> lines = IoUtil.readLines(in, "UTF-8",new ArrayList<>());
        return lines;
    }
}

具体的请求调用流程:

image-20231124171204191

总结

三层架构的好处

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

课堂作业

  1. 三层架构的好处有哪些?🎤
  2. 参考三层设计,将案例代码进行拆分,并进行测试!

4.2 分层解耦

分层解耦

问题分析

由于我们现在在程序中,需要什么对象,直接new一个对象 new DeptServiceImpl()

image-20231124172514798

如果说我们需要更换实现类,比如由于业务的变更,DeptServiceImpl 不能满足现有的业务需求,我们需要切换为 DeptServiceImpl2 这套实现,就需要修改Contorller的代码,需要创建 DeptServiceImpl2 的实现new DeptServiceImpl2()

image-20231124172952826
image-20231124172952826

Service中调用Dao,也是类似的问题。这种呢,我们就称之为层与层之间 耦合 了。 那什么是耦合呢 ?

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

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

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

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

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

目前层与层之间是存在耦合的,Controller耦合了Service、Service耦合了Dao。而 高内聚、低耦合的目的是使程序模块的可重用性、移植性大大增强。

image-20231124173422622

那最终我们的目标呢,就是做到层与层之间,尽可能的降低耦合,甚至解除耦合。

image-20231124173821481

代码操作

解耦思路

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

那应该怎么解耦呢?

1). 首先不能在EmpController中使用new对象。代码如下:

image-20231124173929898

此时,就存在另一个问题了,不能new,就意味着没有业务层对象(程序运行就报错),怎么办呢?

我们的解决思路是:

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

2). 将要用到的对象交给一个容器管理。

image-20231124174106742

3). 应用程序中用到这个对象,就直接从容器中获取

image-20231124174825721

那问题来了,我们如何将对象交给容器管理呢?程序运行时,容器如何为程序提供依赖的对象呢?

我们想要实现上述解耦操作,就涉及到Spring中的两个核心概念:

  • 控制反转: Inversion Of Control,简称IOC

对象的创建控制权由程序自身转移到外部(容器),这种思想称为控制反转。 对象的创建权由程序员主动创建转移到容器(由容器创建、管理对象)。这个容器称为:IOC容器或Spring容器

  • 依赖注入: Dependency Injection,简称DI

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

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

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

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

总结

课堂作业

  1. 高内聚、低耦合的目的是什么?🎤
  2. Spring中的两个核心概念分别是什么?
  3. Spring中的对象,我们一般叫什么对象?

4.3 IOC&DI入门

IOC&DI入门

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

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

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

代码操作

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

在实现类加上 @Component 注解,就代表把当前类产生的对象交给IOC容器管理。

A. DeptDaoImpl

@Component
public class DeptDaoImpl implements DeptDao {
    @Override
    public List<String> queryDeptList(){
        //1. 加载文件 ,  获取原始数据
        InputStream in = this.getClass().getClassLoader().getResourceAsStream("dept.txt");
        List<String> lines = IoUtil.readLines(in, "UTF-8",new ArrayList<>());
        return lines;
    }
}
 









B. DeptServiceImpl

@Component
public class DeptServiceImpl implements DeptService {
    private DeptDao deptDao;

    @Override
    public List<Dept> queryDeptList() {
        //1. 调用deptDao
        List<String> lines = deptDao.queryDeptList();
        //2. 对原始数据进行处理 , 组装部门数据
        List<Dept> deptList = lines.stream().map(line -> {
            String[] parts = line.split(",");
            Integer id = Integer.parseInt(parts[0]);
            String name = parts[1];
            LocalDateTime updateTime = LocalDateTime.parse(parts[2],
                    DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
            return new Dept(id, name, updateTime);
        }).collect(Collectors.toList());
        //......
        return deptList;
    }
}
 




















4.4 IOC详解

通过IOC和DI的入门程序呢,我们已经基本了解了IOC和DI的基础操作。接下来呢,我们学习下IOC控制反转和DI依赖注入的细节。

IOC详解

Bean的声明

前面我们提到IOC控制反转 ,就是将对象的控制权交给Spring的IOC容器,由IOC容器创建及管理对象。IOC容器创建的对象称为bean对象

在之前的入门案例中,要把某个对象交给IOC容器管理,需要在类上添加一个注解:@Component

而Spring框架为了更好的标识web应用程序开发当中,bean对象到底归属于哪一层,又提供了@Component的衍生注解:

注解说明位置
@Component声明bean的基础注解不属于以下三类时,用此注解
@Controller@Component的衍生注解标注在控制层类上
@Service@Component的衍生注解标注在业务层类上
@Repository@Component的衍生注解标注在数据访问层类上(由于与mybatis整合,用的少)

可以使用 @Service 注解声明Service层的bean。 使用 @Repository 注解声明Dao层的bean。 代码实现如下:

代码操作

Service层

@Service
public class DeptServiceImpl implements DeptService {
    @Autowired
    private DeptDao deptDao= new DeptDaoImpl();

    @Override
    public List<Dept> queryDeptList() {
        //1. 调用deptDao
        List<String> lines = deptDao.queryDeptList();
        //2. 对原始数据进行处理 , 组装部门数据
        List<Dept> deptList = lines.stream().map(line -> {
            String[] parts = line.split(",");
            Integer id = Integer.parseInt(parts[0]);
            String name = parts[1];
            LocalDateTime updateTime = LocalDateTime.parse(parts[2],
                    DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
            return new Dept(id, name, updateTime);
        }).collect(Collectors.toList());
        //......
        return deptList;
    }
}
 

 



















Dao层

@Repository
public class DeptDaoImpl implements DeptDao {
    @Override
    public List<String> queryDeptList(){
        //1. 加载文件 ,  获取原始数据
        InputStream in = this.getClass().getClassLoader().getResourceAsStream("dept.txt");
        List<String> lines = IoUtil.readLines(in, "UTF-8",new ArrayList<>());
        return lines;
    }
}
 









注意1: 声明bean的时候,可以通过注解的value属性指定bean的名字,如果没有指定,默认为类名首字母小写

注意2:使用以上四个注解都可以声明bean,但是在springboot集成web开发中,声明控制器bean只能用@Controller。

总结

课堂作业

  1. 谈谈你对IOC的理解!!🎤
  2. @Controller@RestController有什么区别?

4.5 DI详解

DI详解

上一小节我们讲解了控制反转IOC的细节,接下来呢,我们学习依赖注解DI的细节。

依赖注入,是指IOC容器要为应用程序去提供运行时所依赖的资源,而资源指的就是对象。

在入门程序案例中,我们使用了@Autowired这个注解,完成了依赖注入的操作,而这个Autowired翻译过来叫:自动装配

@Autowired注解,默认是按照类型进行自动装配的(去IOC容器中找某个类型的对象,然后完成注入操作)

入门程序举例:在EmpController运行的时候,就要到IOC容器当中去查找EmpService这个类型的对象,而我们的IOC容器中刚好有一个EmpService这个类型的对象,所以就找到了这个类型的对象完成注入操作。

那如果在IOC容器中,存在多个相同类型的bean对象,会出现什么情况呢?

image-20231124184659272

此时,我们运行程序,看到控制台已经报错了。

image-20231124184734329
image-20231124184734329

如何解决上述问题呢? 👇

代码操作

Spring提供了以下几种解决方案:

  • @Primary
  • @Qualifier
  • @Resource

方案一:使用@Primary注解

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

@Primary
@Service
public class DeptServiceImpl implements DeptService {
}

总结

课堂作业

面试题 : @Autowird 与 @Resource的区别?

  • @Autowired 是spring框架提供的注解,而@Resource是JDK提供的注解
  • @Autowired 默认是按照类型注入,而@Resource是按照名称注入