Java EE 进阶:5. Spring IoCDI(第一弹):Spring IoC

发布时间:2026/10/28 18:51:44
Java EE 进阶:5. Spring IoCDI(第一弹):Spring IoC 目录本节目标1.IoC DI 入门1.1 Spring 是什么1.1.1 什么是容器1.1.2 什么是 IoC?1.2 IoC 介绍1.2.1 传统程序开发1.2.2 问题分析1.2.3 解决方案1.2.4 IoC 程序开发1.2.5 IoC 优势课件内容1.3 DI 介绍2. IoC DI 使用本节目标1. 了解Spring,Spring MVC, Spring Boot 之间的联系及区别2. 掌握IoCDI的概念以及写法1.IoC DI 入门在前面的章节中, 我们学习了Spring Boot和Spring MVC的开发, 可以完成一些基本功能的开发了, 但是什么是Spring呢? Spring, Spring Boot 和SpringMVC又有什么关系呢? 咱们还是带着问题去学习.我们先看什么是SpringSpring 涉及到的面试题是非常少的EE 进阶涉及到的内容非常少其中 Spring 有两个重点IoC 和 AOP为啥 Spring 好用Spring(Spring Core) 两大核心思想IoC AOP现有 Spring Core 再有 Spring BootSpring Boot 已经给我们引进来了这都是原生的Spring 核心我们发现官方文档里也是包含了这些东西之前的 Spring 都是指 Spring 家族但是现在说的都是指 Spring Core1.1 Spring 是什么通过前面的学习, 我们知道了Spring是一个开源框架, 他让我们的开发更加简单. 他支持广泛的应用场景, 有着活跃而庞大的社区, 这也是Spring能够长久不衰的原因但是这个概念相对来说, 还是比较抽象.我们用一句更具体的话来概括Spring, 那就是:Spring 是包含了众多工具方法的 IoC 容器那问题来了什么是容器什么是 IoC 容器接下来我们一起来看1.1.1 什么是容器容器是用来容纳某种物品的基本装置。 ——来自百度百科生活中的水杯, 垃圾桶, 冰箱等等这些都是容器.我们想想之前课程我们接触的容器有哪些• List/Map - 数据存储容器• Tomcat - Web 容器1.1.2 什么是 IoC?IoC 是Spring的核心思想, 也是常见的面试题, 那什么是IoC呢?其实IoC我们在前面已经使用了, 我们在前面讲到, 在类上面添加 RestController 和Controller 注解, 就是把这个对象交给Spring管理, Spring 框架启动时就会加载该类. 把对象交给Spring管理, 就是IoC思想.IoC: Inversion of Control (控制反转), 也就是说 Spring 是一个控制反转的容器.什么是控制反转呢? 也就是控制权反转. 什么的控制权发生了反转? 获得依赖对象的控制权被反转了之前谁使用控制权就在谁手上现在Spring 来控制比如下面这段代码public class BookService { public ListBookInfo getList(){ BookDao bookDaonew BookDao(); //理应从数据库中查询图书信息但是还未学习数据库相关操作暂且mock数据 //mock:造假数据用于自行测试 ListBookInfo bookInfosbookDao.mockData(); //注意实际开发中可借阅这个东西应该交给前端做(有时候领导觉得需要改个名字啥的后端不好处理) //对结果进行二次处理 for(BookInfo bookInfo:bookInfos){ if(bookInfo.getStatus()1){ bookInfo.setStatusCN(可借阅); }else{ bookInfo.setStatusCN(不可借阅); } } return bookInfos; } }随着调用 BookService 这个类或者说调用 getList 这个方法bookDao 就 出生 了这个类或者这个方法不用了bookDao 就 消亡 了等咱们后续把这里改成 Spring 的写法就是 Spring 控制bookDao 的生死了~~也就是说, 当需要某个对象时, 传统开发模式中需要自己通过 new 创建对象, 现在不需要再进行创建, 把创建对象的任务交给容器, 程序中只需要依赖注入 (Dependency Injection,DI)就可以了.这个容器称为IoC容器. Spring是一个IoC容器, 所以有时Spring 也称为Spring 容器.控制反转是一种思想, 在生活中也是处处体现.比如自动驾驶, 传统驾驶方式, 车辆的横向和纵向驾驶控制权由驾驶员来控制, 现在交给了驾驶自动化系统来控制, 这也是控制反转思想在生活中的实现.比如招聘, 企业的员工招聘,入职, 解雇等控制权, 由老板转交给给HR(人力资源)来处理1.2 IoC 介绍接下来我们通过案例来了解一下什么是IoC需求: 造一辆车1.2.1 传统程序开发我们的实现思路是这样的先设计轮子(Tire)然后根据轮子的大小设计底盘(Bottom)接着根据底盘设计车身(Framework)最后根据车身设计好整个汽车(Car)。这里就出现了一个依赖关系汽车依赖车身车身依赖底盘底盘依赖轮子.最终程序的实现代码如下public class NewCarExample { public static void main(String[] args) { Car car new Car(); car.run(); } /** * 汽车对象 */ static class Car { private Framework framework; public Car() { framework new Framework(); System.out.println(Car init....); } public void run(){ System.out.println(Car run...); } } /** * 车身类 */ static class Framework { private Bottom bottom; public Framework() { bottom new Bottom(); System.out.println(Framework init...); } } /** * 底盘类 */ static class Bottom { private Tire tire; public Bottom() { this.tire new Tire(); System.out.println(Bottom init...); } } /** * 轮胎类 */ static class Tire { // 尺寸 private int size; public Tire(){ this.size 17; System.out.println(Tire init,size:size); } } }运行结果你需要啥就 new 一个啥控制权都在使用方手上1.2.2 问题分析这样的设计看起来没问题但是可维护性却很低.接下来需求有了变更: 那这个轮胎有的人喜欢 17寸的有的人喜欢 21寸的那我们干脆提供个参数你需要啥就自己传啥那这个时候就要对上面的程序进行修改了修改后的代码如下所示:修改之后, 其他调用程序也会报错, 我们需要继续修改完整代码如下:public class Main { public static void main(String[] args) { Car carnew Car(21); car.run(); } } public class Car { private Framework framework; public Car(Integer size){ this.frameworknew Framework(size); //有了车身汽车才完成初始化 System.out.println(Car init...); } public void run(){ System.out.println(Car run...); } } public class Framework {//车身 public Framework(Integer size){ this.bottomnew Bottom(size); System.out.println(Framework init...); } private Bottom bottom; } public class Bottom {//底盘 private Tire tire; public Bottom(Integer size){ this.tirenew Tire(size); System.out.println(Bottom init...); } } public class Tire {//轮胎 int size; public Tire(Integer size){ this.sizesize; System.out.println(Tire init,size:size); } }运行结果那这时候用户又说了我想要全身黑色的还要轮胎带花纹的那我们还得一层一层给改回去~~太TM烦了现在我们想到了解决方法这车我不造了我外包出去包给别人干你爱要啥样的我就从外包公司拿你要啥样的就给我传过来我都不自己 new 了全从外包公司拿然后自己组装一下就行了~~从以上代码可以看出以上程序的问题是当最底层代码改动之后整个调用链上的所有代码都需要修改.程序的耦合度非常高(修改一处代码, 影响其他处的代码修改)1.2.3 解决方案在上面的程序中, 我们是根据轮子的尺寸设计的底盘轮子的尺寸一改底盘的设计就得修改. 同样因为我们是根据底盘设计的车身那么车身也得改同理汽车设计也得改, 也就是整个设计几乎都得改我们尝试换一种思路, 我们先设计汽车的大概样子然后根据汽车的样子来设计车身根据车身来设计底盘最后根据底盘来设计轮子. 这时候依赖关系就倒置过来了轮子依赖底盘 底盘依赖车身车身依赖汽车这就类似我们打造一辆完整的汽车, 如果所有的配件都是自己造那么当客户需求发生改变的时候比如轮胎的尺寸不再是原来的尺寸了那我们要自己动手来改了但如果我们是把轮胎外包出去那么即使是轮胎的尺寸发生变变了我们只需要向代理工厂下订单就行了我们自身是不需要出力的如何来实现呢:我们可以尝试不在每个类中自己创建下级类如果自己创建下级类就会出现当下级类发生改变操作自己也要跟着修改.此时我们只需要将原来由自己创建的下级类改为传递的方式也就是注入的方式因为我们不需要在当前类中创建下级类了所以下级类即使发生变化创建或减少参数当前类本身也无需修改任何代码这样就完成了程序的解耦.1.2.4 IoC 程序开发基于以上思路我们把调用汽车的程序示例改造一下把创建子类的方式改为注入传递的方式.具体实现代码如下public class Main { public static void main(String[] args) { Tire tirenew Tire(17); Bottom bottomnew Bottom(tire); Framework frameworknew Framework(bottom); Car carnew Car(framework); car.run(); } } public class Car { private Framework framework; public Car(Framework framework){ this.frameworkframework; //有了车身汽车才完成初始化 System.out.println(Car init...); } public void run(){ System.out.println(Car run...); } } public class Framework {//车身 public Framework(Bottom bottom){ this.bottombottom; System.out.println(Framework init...); } private Bottom bottom; } public class Bottom {//底盘 private Tire tire; public Bottom(Tire tire){ this.tiretire; System.out.println(Bottom init...); } } public class Tire {//轮胎 int size; String color; public Tire(Integer size){ this.sizesize; this.colorcolor; System.out.println(Tire init,size:size); } }运行效果这种做法就好比我有什么要求我就交给外包公司就行了我什么都不用改了比如说用户想加个颜色那么此时我们这里依然会有报错但是这次报错的地方只有这一个了我们就没必要再一层一层改了代码经过以上调整无论底层类如何变化整个调用链是不用做任何改变的这样就完成了代码之间的解耦从而实现了更加灵活、通用的程序设计了~~那有人就说了那这些方法不还是要写public class Main { public static void main(String[] args) { Tire tirenew Tire(17,red); Bottom bottomnew Bottom(tire); Framework frameworknew Framework(bottom); Car carnew Car(framework); car.run(); } }当然不用写了这些类我们都交给外包公司了而 Spring 就是我们的外包公司我们需要什么直接去 Spring 去取就好了让 Spring 去帮我们管理这些对象~~之前对象的控制权是这一大堆谁用谁来管现在的对象控制权就在 Spring让 Spring 帮我们做这些事情了这就是 IoC~~1.2.5 IoC 优势课件内容在传统的代码中对象创建顺序是Car - Framework - Bottom - Tire改进之后解耦的代码的对象创建顺序是Tire - Bottom - Framework - Car我们发现了一个规律通用程序的实现代码类的创建顺序是反的传统代码是 Car 控制并创建了 FrameworkFramework 创建并创建了 Bottom依次往下而改进之后的控制权发生的反转不再是使用方对象创建并控制依赖对象了而是把依赖对象注入将当前对象中依赖对象的控制权不再由当前类控制了.这样的话, 即使依赖类发生任何改变当前类都是不受影响的这就是典型的控制反转也就是 IoC 的实现思想。学到这里, 我们大概就知道了什么是控制反转了, 那什么是控制反转容器呢, 也就是IoC容器这部分代码, 就是IoC容器做的工作.从上面也可以看出来, IoC容器具备以下优点:资源不由使用资源的双方管理而由不使用资源的第三方管理这可以带来很多好处。第一资源集中管理实现资源的可配置和易管理。第二降低了使用资源双方的依赖程度也就是我们说的耦合度。1. 资源集中管理: IoC容器会帮我们管理一些资源(对象等), 我们需要使用时, 只需要从IoC容器中去取就可以了2. 我们在创建实例的时候不需要了解其中的细节, 降低了使用资源双方的依赖程度, 也就是耦合度.Spring 就是一种IoC容器, 帮助我们来做了这些资源管理.1.3 DI 介绍上面学习了IoC, 什么是DI呢?我们去创建对象的这个过程叫 IoC但是对象创建完之后总要去用的我拿来赋值给它这个过程叫做“注入”也就是 DIDI: Dependency Injection(依赖注入)容器在运行期间, 动态的为应用程序提供运行时所依赖的资源称之为依赖注入。程序运行时需要某个资源此时容器就为其提供这个资源.从这点来看, 依赖注入DI和控制反转IoC是从不同的角度的描述的同一件事情依赖注入是从应用程序的角度来描述, 就是指通过引入 IoC 容器利用依赖关系注入的方式实现对象之间的解耦.上述代码中, 是通过构造函数的方式, 把依赖对象注入到需要使用的对象中的IoC 是一种思想也是目标, 而思想只是一种指导原则最终还是要有可行的落地方案而 DI 就属于具体的实现。所以也可以说, DI 是IoC的一种实现.比如说我今天心情比较好吃一顿好的犒劳犒劳自己那么吃一顿好的是思想和目标是IoC但最后我是吃海底捞还是杨国福这就是具体的实现就是 DI。2. IoC DI 使用对IoC和DI有了初步的了解, 我们接下来具体学习Spring 如何帮助我们实现 IoC 和 DI 的~~闲聊其实大家都猜出来了就是使用“注解”一个注解搞不定我就再来一个注解还搞不定就再来一个反正是能搞定的我们的 Spring 就是各种注解~~依然是先使用, 再学习在之前的代码中我们知道 bookDao 这个对象是我们自己创建的并不是 Spring 创建的那我们如何让 Spring 帮我们创建呢我们不知道但我们已经在用了这个 Controller 就是告诉 Spring 帮我们创建一个对象~~我们之前学习的你要是想调用一个类里的方法就要先有一个对象才能调用它的方法除非它是 static 方法静态方法无需 new 对象即可调用普通方法要想被调用必须要有对象那么这个对象咋来的我们从头到尾都没有 new UserController 这个过程就是 Spring 帮我们创建了一个 UserController 这样的对象就是通过 Controller 这个注解创建的~~如果我还想让它帮我创建 bookservice还想让它帮我创建 bookDao怎么创建呢我们来学习一个新的注解Component我们的 Controller 表示 控制层表示层我们继续看 Controller 里面的实现Controller 的实现依靠 Component所以说真正起作用的其实是 Component既然 Spring 是一个 IoC控制反转容器作为容器, 那么它就具备两个最基础的功能• 存• 取Spring 容器 管理的主要是对象, 这些对象, 我们称之为Bean. 我们把这些对象交由Spring管理, 由 Spring来负责对象的创建和销毁. 我们程序只需要告诉Spring, 哪些需要存, 以及如何从Spring中取出对象目标:把BookDao, BookService 交给Spring管理, 完成Controller层, Service层, Dao层的解耦步骤:1. Service层及Dao层的实现类交给Spring管理: 使用注解: Component2. 在Controller层 和Service层 注入运行时依赖的对象: 使用注解 Autowired我们只需要在需要创建对象的上面加上 Controller比如就可以从 Spring 的容器中去拿出 BookService 这样的对象了如果说我还想拿出 BookService 里的 BookDao 对象我不再去 new 了也是相同的方法此时 Spring 帮我们管理了这两个对象BookService 和 BookDao接下来我们学习怎么用~~RequestMapping(/book) RestController public class BookController { //在此声明这个对象 private BookService bookService;//此时还未初始化还是none RequestMapping(/getList) public ListBookInfo getList(){ //此时这个代码就可以不要了 // BookService bookServicenew BookService(); return bookService.getList(); } }接下来我们在上面加上 Autowired 这个注解这个注解就表示从 Spring 中获得对象并且把值赋给它此时 bookservice 就有值了当然 BookDao 也是相同的操作RequestMapping(/book) RestController public class BookController { Autowired private BookService bookService;//此时还未初始化还是none RequestMapping(/getList) public ListBookInfo getList(){ //此时这个代码就可以不要了 // BookService bookServicenew BookService(); return bookService.getList(); } }Component public class BookService { Autowired private BookDao bookDao; public ListBookInfo getList(){ BookDao bookDaonew BookDao();//此时这里就可以不要了 //理应从数据库中查询图书信息但是还未学习数据库相关操作暂且mock数据 //mock:造假数据用于自行测试 ListBookInfo bookInfosbookDao.mockData(); //注意实际开发中可借阅这个东西应该交给前端做(有时候领导觉得需要改个名字啥的后端不好处理) //对结果进行二次处理 for(BookInfo bookInfo:bookInfos){ if(bookInfo.getStatus()1){ bookInfo.setStatusCN(可借阅); }else{ bookInfo.setStatusCN(不可借阅); } } return bookInfos; } }接下来我们来运行一下看看对不对输入错误的输入正确的发现确实没问题那么如果此时我们把 BookDao 的 Component 去掉看看是什么效果我们发现服务启动都启动不起来那可能是 Autowired 出问题了那就不加了呗此时再来运行看一下发现服务确实能启动起来但是登录进去后发现什么数据都没有观察控制台发现此时服务响应的状态码是 500500 是服务端错误我们观察服务端的日志发现发现 BookDao 值为 null所以报了个空指针异常为啥没有值因为没有取出来没有从 Spring 中取值这就说明 Autowired 是从 Spring 中拿东西去找类型为 BookDao 这样的对象找到之后赋值给它当然这一切的大前提都是你要先交给 Spring 管理这样 Spring 才能帮我们 new 对象Component把 对象 交给 Spring 管理→存Autowired从 Spring 中获取对象并给属性赋值不能加在类上→取答疑Q1没有前端请求怎么赋值这都是跟后端有关跟前端没关系的~~Q2Spring 是不是使用无参构造函数创建的目前咱们涉及到的是这样的但是它不一定当然也会有有参的与我们的写法有关咱们在后面会慢慢涉及到~~实现:课件内容跟上面一样1. 把BookDao 交给Spring管理, 由Spring来管理对象import org.springframework.stereotype.Component; import java.math.BigDecimal; import java.util.ArrayList; import java.util.List; import java.util.Random; Component public class BookDao { /** * 数据Mock 获取图书信息 * * return */ public ListBookInfo mockData() { ListBookInfo books new ArrayList(); for (int i 0; i 5; i) { BookInfo book new BookInfo(); book.setId(i); book.setBookName(书籍 i); book.setAuthor(作者 i); book.setCount(i * 5 3); book.setPrice(new BigDecimal(new Random().nextInt(100))); book.setPublish(出版社 i); book.setStatus(1); books.add(book); } return books; } }2. 把BookService 交给Spring管理, 由Spring来管理对象import org.springframework.stereotype.Component; import java.util.List; Component public class BookService { private BookDao bookDao new BookDao(); public ListBookInfo getBookList() { ListBookInfo books bookDao.mockData(); for (BookInfo book : books) { if (book.getStatus() 1) { book.setStatusCN(可借阅); } else { book.setStatusCN(不可借阅); } } return books; } }3. 删除创建BookDao的代码, 从Spring中获取对象import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.util.List; Component public class BookService { Autowired private BookDao bookDao; public ListBookInfo getBookList() { ListBookInfo books bookDao.mockData(); for (BookInfo book : books) { if (book.getStatus() 1) { book.setStatusCN(可借阅); } else { book.setStatusCN(不可借阅); } } return books; } }4. 删除创建BookService的代码, 从Spring中获取对象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; RequestMapping(/book) RestController public class BookController { Autowired private BookService bookService; RequestMapping(/getList) public ListBookInfo getList(){ //获取数据 ListBookInfo books bookService.getBookList(); return books; } }5. 重新运行程序, http://127.0.0.1:8080/book_list.html

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询