【Spring】IoCDI详解

1. IoC详解

前面提到过IoC就是将对象的控制权交由Spring的IoC容器进行管理,由Spring的IoC容器创建和销毁bean,那么既然涉及到容器,就一定包含以下两方面功能:

  1. bean的存储
  2. bean的获取

1.1 类注解

Spring框架为了更好地服务应用程序,提供了非常丰富的注解用于存储bean,大致可以分为如下两类:

  1. 类注解:例如@Controller@Service@Repository@Configuration@Component
  2. 方法注解:例如:@Bean

接下来我们分别来看各类注解的使用:

1.1.1 @Controller(控制器存储)

1.1.1.1 bean的存储

使用@Controller存储bean的测试代码如下:

@Controller // 将bean存储到Spring容器中
public class TestController {public void testController() {System.out.println("test controller");}
}

那么我们如何才能够获取到Spring容器中的bean呢?

1.1.1.2 **bean的获取 **

我们可以在使用@SpringBootApplication的启动类中获取到ApplicationContext对象,该上下文对象就保存着程序启动时的一些信息,可以看做是IoC容器实例

@SpringBootApplication
public class IocDiDemoApplication {public static void main(String[] args) {// 1. 获取IoC容器ApplicationContext context = SpringApplication.run(IocDiDemoApplication.class, args);// 2. 获取beanTestController controllerBean = context.getBean(TestController.class);// 3. 测试bean的testController方法controllerBean.testController();}
}

此时运行结果如下:
image.png
如果再把@Controller注解去掉,观察结果:
image.png
此时就会抛出这样一个异常:No qualifying bean of type 'com.example.ioc_di_demo.controller.TestController' available表明没有找到类型为TestController的bean实例

1.1.1.3 获取bean对象的其他方式

上述方式我们使用了通过类型的方式获取Bean,但是如果Spring容器中有多个同一类型的bean呢,我们应该如何进行获取?ApplicationContext也提供了其他的方式获取bean,这些方式都是其父接口BeanFactory提供的
image.png
其中第一种、第二种、第四种方式是比较常用的!其中1、2两种方式都涉及到name即根据名称来获取,那么bean的名称是什么呢?实质上,Spring容器在管理bean的时候会为每一个bean分配不同的唯一的名称来标识该bean实例,bean的命令规则如下(可观察源码java.beans.Introspector的decapitalize方法求证):

一般来说,如果程序员没有手动指定bean的名称,那么就会自动按照类名首字母小写,小驼峰形式,例如UserController => userController、AccountService => accountService

但是也会有特殊情况,当有多个字符,并且第一个和第二个字符都是大写字符是就保留原始的类名称

例如IService的bean名称就是IService

示例代码

@SpringBootApplication
public class IocDiDemoApplication {public static void main(String[] args) {// 1. 获取IoC容器ApplicationContext context = SpringApplication.run(IocDiDemoApplication.class, args);// 2.1 获取bean(通过类型)TestController controllerBean1 = context.getBean(TestController.class);// 2.2 获取bean(通过名称)TestController controllerBean2 = (TestController) context.getBean("testController");// 2.3 获取bean(通过名称 + 类型)TestController controllerBean3 = context.getBean("testController", TestController.class);// 3.1 测试controllerBean1的testController方法controllerBean1.testController();// 3.2 测试controllerBean2的testController方法controllerBean2.testController();// 3.3 测试controllerBean3的testController方法controllerBean3.testController();}
}

1.1.2 @Service(业务逻辑存储)

1.1.2.1 bean的存储

我们也可以使用@Service注解将bean交由Spring容器进行管理

@Service // 将TestService作为bean交由Spring容器管理
public class TestService {public void testService() {System.out.println("test service");}
}
1.1.2.2 bean的获取

示例代码如下:

@SpringBootApplication
public class IocDiDemoApplication {public static void main(String[] args) {// 1. 获取IoC容器ApplicationContext context = SpringApplication.run(IocDiDemoApplication.class, args);// 2. 获取TestService的bean对象TestService beanService = (TestService) context.getBean("testService");// 3. 调用bean对象的testService方法beanService.testService();}
}

运行结果
image.png

1.1.3 @Repository(仓库存储)

1.1.3.1 bean的存储
@Repository // 将bean存储到Spring容器
public class TestMapper {public void testRepository() {System.out.println("test repository");}
}
1.1.3.2 bean的获取
@SpringBootApplication
public class IocDiDemoApplication {public static void main(String[] args) {// 1. 获取IoC容器ApplicationContext context = SpringApplication.run(IocDiDemoApplication.class, args);// 2. 获取TestMapper的bean对象TestMapper beanRepository = (TestMapper) context.getBean("testMapper");// 3. 调用bean对象的testRepository方法beanRepository.testRepository();}
}

1.1.4 @Component(组件存储)

1.1.4.1 bean的存储
@Component
public class TestComponent {public void testComponent() {System.out.println("test component");}
}
1.1.4.2 bean的获取
@SpringBootApplication
public class IocDiDemoApplication {public static void main(String[] args) {// 1. 获取IoC容器ApplicationContext context = SpringApplication.run(IocDiDemoApplication.class, args);// 2. 获取TestComponent的bean对象TestComponent beanComponent = (TestComponent) context.getBean("testComponent");// 3. 调用bean对象的testComponent方法beanComponent.testComponent();}
}

1.1.5 @Configuration(配置存储)

1.1.5.1 存储bean
@Configuration
public class TestConfiguration {public void testConfiguration() {System.out.println("test configuration");}
}
1.1.5.2 获取bean
@SpringBootApplication
public class IocDiDemoApplication {public static void main(String[] args) {// 1. 获取IoC容器ApplicationContext context = SpringApplication.run(IocDiDemoApplication.class, args);// 2. 获取TestConfiguration的bean对象TestConfiguration beanConfiguration = (TestConfiguration) context.getBean("testConfiguration");// 3. 调用bean对象的testConfiguration方法beanConfiguration.testConfiguration();}
}

1.1.6 为什么要使用那么多注解?

Spring为我们提供了各式各样的注解,其实与三层架构这样的应用分层思想不谋而合,可以让程序员方便的看到类的注解就明白该类的含义与作用:

  • @Controller:用户标识控制层(表现层),用于接收请求,返回处理完毕的响应结果
  • @Service:用于标识业务逻辑层,用于处理具体的业务逻辑
  • @Repository:用于标识数据层(持久层),负责数据访问操作即与数据库交互
  • @Configuration:用于标识配置层,管理程序中一些配置信息

各个注解之间的关系
观察五大注解的详细信息如下:
image.pngimage.png
image.pngimage.png
可以发现他们都是@Component的衍生注解,而@Component是一个元注解,可以被其他注解所使用

常见面试题:五大注解是否可以相互替换?

  1. 从功能上来说,大部分注解可以相互替换,但是唯独@Controller注解不可以使用别的注解替换,因为该注解用于表现层进行路由映射,使用其他注解代替有极小概率发生意想不到的错误
  2. 从语义性来说,不可以相互替换,@Controller用于标识控制层,@Service注解用于标识业务逻辑层,@Repository用于标识这是一个持久层

1.2 方法注解

上述我们介绍了五大类注解的使用,但是如果面对以下情况就无法使用类注解:

  1. 使用第JDK原生类/第三方包中的类,无法在类上使用注解
  2. 一个类需要多个对象,比如需要多个数据源对象

1.2.1 方法注解的使用

1.2.1.1 bean的配置
public class BeanConfig {@Beanpublic LocalDateTime localDateTime() {return LocalDateTime.now();}
}
1.2.1.2 bean的获取

上述代码我们使用@Bean方法注解配置了一个对象,交由Spring容器进行管理,下面我们尝试获取该实例

@SpringBootApplication
public class IocDiDemoApplication {public static void main(String[] args) {// 1. 获取IoC容器ApplicationContext context = SpringApplication.run(IocDiDemoApplication.class, args);// 2. 获取LocalDateTime的bean对象LocalDateTime bean = context.getBean(LocalDateTime.class);// 3. 打印获取到的beanSystem.out.println(bean.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));}
}

image.png
但是此时却抛出了异常!这是因为 方法注解需要配合类注解使用!

1.2.1.3 方法注解需要配合类注解使用

将上述配置bean代码更改如下:

@Configuration
public class BeanConfig {@Beanpublic LocalDateTime localDateTime() {return LocalDateTime.now();}
}

此时程序正常运行:
image.png

1.2.1.4 定义多个对象

比如在多数据源的场景下,我们需要使用多个@Bean注解,如果依旧按照之前通过类型的方式获取,会出现什么问题呢?

@Configuration
public class BeanConfig {@Beanpublic LocalDateTime localDateTime() {return LocalDateTime.now();}@Bean LocalDateTime localDateTime2() {return LocalDateTime.of(2024, 4, 1, 12, 0, 0);}@Bean LocalDateTime localDateTime3() {return LocalDateTime.of(2024, 5, 1, 8, 0, 0);}
}

image.png
此时出现该异常,表示如果按照类型匹配,期望只得到一个实例,但是却匹配到了三个!这个时候我们就需要使用名称来获取bean了!

使用@Bean方法注解声明的bean的名称默认就是方法名称!

@SpringBootApplication
public class IocDiDemoApplication {public static void main(String[] args) {// 1. 获取IoC容器ApplicationContext context = SpringApplication.run(IocDiDemoApplication.class, args);// 2. 获取TestConfiguration的bean对象LocalDateTime bean = (LocalDateTime) context.getBean("localDateTime");LocalDateTime bean2 = (LocalDateTime) context.getBean("localDateTime2");LocalDateTime bean3 = (LocalDateTime) context.getBean("localDateTime3");// 3. 打印获取到的beanSystem.out.println(bean.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));System.out.println(bean2.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));System.out.println(bean3.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));}
}

此时就可以正确获取结果!
image.png

1.3 配置扫描路径

Q:假设我们按照规则正确地使用注解配置bean,那么一定会被Spring容器进行管理吗?
A:不一定,我们还需要配置bean的扫描路径
image.pngimage.png
我们修改启动类的路径,再次运行程序!
image.png
此时程序运行错误了!其实原因就在于启动类中包含了注解@ComponentScan默认会扫描启动类所在的包及其子包,只有在指定路径下的bean才会被Spring容器管理,解决方式就是使用注解@ComponentScan配置扫描路径!

@ComponentScan(basePackages = {"com.example.ioc_di_demo"})
@SpringBootApplication
public class IocDiDemoApplication {public static void main(String[] args) {// 1. 获取IoC容器ApplicationContext context = SpringApplication.run(IocDiDemoApplication.class, args);// 2. 获取TestConfiguration的bean对象LocalDateTime bean = (LocalDateTime) context.getBean("localDateTime");LocalDateTime bean2 = (LocalDateTime) context.getBean("localDateTime2");LocalDateTime bean3 = (LocalDateTime) context.getBean("localDateTime3");// 3. 打印获取到的beanSystem.out.println(bean.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));System.out.println(bean2.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));System.out.println(bean3.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));}
}

2. DI详解

下面我们就来详细介绍下DI(Dependency Injection),中文翻译就是依赖注入,它是一种设计模式,用来实现IoC编程思想,它的核心思想就是将对象之间的依赖关系进行解耦合。通过依赖注入,可以为类的属性、构造方法、setter方法中提供所依赖的对象和值,是一种可重用、低耦合的思想
关于依赖注入,Spring提供了三种方式:

  1. 属性注入(Field Injection)
  2. 构造方法注入(Constructor Injection)
  3. setter注入(Setter Injection)

2.1 属性注入

Controller层代码如下:

@Controller // 将bean存储到Spring容器中
public class TestController {@Autowiredprivate TestService testService; // 属性注入public void testDI() {// 调用属性方法testService.testService();}
}

此时在启动类中获取bean实例并调用testDI方法

@SpringBootApplication
public class IocDiDemoApplication {public static void main(String[] args) {// 1. 获取IoC容器ApplicationContext context = SpringApplication.run(IocDiDemoApplication.class, args);// 2. 获取TestController的bean对象TestController bean = (TestController) context.getBean("testController");// 3. 调用testDI方法bean.testDI();}
}

image.png
此时程序正常运行!说明Spring容器通过@Autowired注解已经自动使用 属性注入 的方式给TestService进行赋值了

2.2 构造方法注入

2.2.1 构造注入示例

此时如果我们不使用属性注入的方式,而是使用构造方法进行注入!代码示例如下:

@Controller // 将bean存储到Spring容器中
public class TestController {private TestService testService;public TestController(TestService testService) {this.testService = testService;}public void testDI() {// 调用属性方法testService.testService();}public void testController() {System.out.println("test controller");}
}

再次执行启动类代码,发现程序正常运行!

2.2.2 多个构造方法

此时我们将代码进行修改,提供多个构造方法:

@Controller // 将bean存储到Spring容器中
public class TestController {private TestService testService;private TestMapper testMapper;public TestController(TestService testService) {this.testService = testService;}public TestController(TestService testService, TestMapper testMapper) {this.testService = testService;}public void testDI() {// 调用属性方法testService.testService();}
}

image.png
此时就会发现程序报错了!

这是因为如果提供了多个构造器,那么Spring容器无法确定使用哪个构造器,因此默认会注入到无参构造器中,如果想要自己指定构造器,可以在构造方法上加上@Autowired注解

解决方式如下:

@Controller // 将bean存储到Spring容器中
public class TestController {private TestService testService;private TestMapper testMapper;@Autowiredpublic TestController(TestService testService) {this.testService = testService;}public TestController(TestService testService, TestMapper testMapper) {this.testService = testService;}public void testDI() {// 调用属性方法testService.testService();}
}

2.3 setter注入

我们还可以提供setter方法注入类所需要的依赖对象

@Controller // 将bean存储到Spring容器中
public class TestController {private TestService testService;@Autowiredpublic void setTestService(TestService testService) {this.testService = testService;}public void testDI() {// 调用属性方法testService.testService();}
}

启动程序后,正常打印结果,说明setter方法同样可以进行依赖注入

2.4 三种注入方式优缺点分析

  • 属性注入:
    • 优点:比较简单,使用方便
    • 缺点:只能在IoC容器中进行使用;有可能出现NPE(空指针异常);而且不能注入Final修饰的属性
  • 构造注入:
    • 优点:可以注入Final修饰的属性;注入的对象不会被改变较为安全;依赖对象在使用之前一定会被完全初始化;通用性好(是JDK支持的)
    • 缺点:如果需要注入多个对象,代码比较冗长
  • setter注入:
    • 优点:方便在类实例创建完后,重新赋值或者注入
    • 缺点:不能注入Final修饰的属性;对象有可能被外部类访问调用,不安全

2.5 @Autowired存在的问题

下面来演示@Autowired注解存在的问题!
Config/BeanConfig.java

@Configuration
public class BeanConfig {@Beanpublic LocalDateTime localDateTime() {return LocalDateTime.now();}@BeanLocalDateTime localDateTime2() {return LocalDateTime.of(2024, 4, 1, 12, 0, 0);}@BeanLocalDateTime localDateTime3() {return LocalDateTime.of(2024, 5, 1, 8, 0, 0);}
}

我们将三个相同类型的LocalDateTime对象交由Spring容器进行管理!此时在TestConrtoller中尝试使用@Autowired注解就会发现编译失败!
image.png
这是因为@Autowired注解按照bean的类型进行自动装配,此时Spring容器内部有三个同一类型的bean,因此无法进行注入,解决方式有如下三种:

  1. 使用@Primary注解,告知Spring容器在获取该类型bean时优先选择该对象

image.png

  1. 使用@Autowired + @Qualifier搭配使用,配置value属性为bean的名称

image.png

  1. 使用@Resource注解声明bean的名称

image.png

常见面试题:@Autowired和@Resource注解的区别

  • @Autowired是Spring框架提供的注解,而@Resource是JDK提供的注解
  • @Autowired默认按照类型进行依赖注入,而@Resource是按照名称进行注入,相比较于@Autowired,@Resource提供了更多的注解参数设置

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/770839.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

高效物联网连接技术创新:ECWAN边缘协同自组网的未来——基于ChirpLAN窄带扩频技术的无线混合组网

物联网是指将各种物理设备通过互联网进行连接和通信的技术。它是一个庞大的网络,由传感器、设备、网络和云服务组成,旨在实现对物体的远程监测、控制和数据采集。 基于ChirpLAN窄带扩频技术的无线混合组网协议ChirpLAN,ChirpLAN是基于其自有的…

http模块 获取http请求报文中的路径 与 查询字符串

虽然request.url已包含属性和查询字符串,但使用不便,若只需其中一个不好提取,于是用到了如下路径和字符串的单独查询方法: 一、获取路径 例如:我在启动谷歌端口时输入http://127.0.0.1:9000 后接了 "/search?k…

编译安装飞桨fastdeploy@FreeBSD(失败)

FastDeploy是一款全场景、易用灵活、极致高效的AI推理部署工具, 支持云边端部署。提供超过 🔥160 Text,Vision, Speech和跨模态模型📦开箱即用的部署体验,并实现🔚端到端的推理性能优化。包括 物…

Gemma开源AI指南

近几个月来,谷歌推出了 Gemini 模型,在人工智能领域掀起了波澜。 现在,谷歌推出了 Gemma,再次引领创新潮流,这是向开源人工智能世界的一次变革性飞跃。 与前代产品不同,Gemma 是一款轻量级、小型模型&…

1升级powershell后才能安装WSL2--最后安装linux--Ubuntu 22.04.3 LTS

视频 https://www.bilibili.com/video/BV1uH4y1W7UX特殊开启–Hyper-V虚拟机 把一下代码保存到【a.bat】的执行文件中,进行Hyper-V虚拟机的安装开启【Windows 批处理文件 (.bat)】 pushd "%~dp0" dir /b %SystemRoot%\servicing\Packages\*Hyper-V*.mu…

鸿蒙Harmony跨模块交互

1. 模块分类介绍 鸿蒙系统的模块一共分为四种,包括HAP两种和共享包两种 HAP(Harmony Ability Package) Entry:项目的入口模块,每个项目都有且只有一个。feature:项目的功能模块,内部模式和En…

(已解决)vue3使用富文本出现样式乱码

我在copy代码到项目里面时候发现我的富文本乱码了 找了一圈不知道是哪里vue3不适配还是怎么,后来发现main.js还需要引入 import VueQuillEditor from vue-quill-editor // require styles 引入样式 import quill/dist/quill.core.css import quill/dist/quill.snow…

YOLOv9代码解读[01] readme解读

文章目录 YOLOv9COCO数据集上指标:环境安装训练验证重参数化 Re-parameterization推断相关链接 YOLOv9 paper: YOLOv9: Learning What You Want to Learn Using Programmable Gradient Information github: https://github.com/WongKinYiu/yolov9 COCO数据集上指…

网络安全笔记-day8,DHCP部署

DHCP部署与安全 全称(Dynamic Host Configura Protocol)动态主机配置协议 DHCP原理 DHCP协议_科来测试dhcp网络包-CSDN博客🔍 注意的是利用广播地址发送包 ACK(确认) 如果DHCP服务器损坏,则在87.5%时…

Open WebUI大模型对话平台-适配Ollama

什么是Open WebUI Open WebUI是一种可扩展、功能丰富、用户友好的大模型对话平台,旨在完全离线运行。它支持各种LLM运行程序,包括与Ollama和Openai兼容的API。 功能 直观的界面:我们的聊天界面灵感来自ChatGPT,确保了用户友好的体验。响应…

线性代数 - 应该学啥 以及哪些可以交给计算机

AI很热,所以小伙伴们不免要温故知新旧时噩梦 - 线代。 (十几年前,还有一个逼着大家梦回课堂的风口,图形学。) 这个真的不是什么美好的回忆,且不说老师的口音,也不说教材的云山雾绕,单…

【考研数学二】线性代数重点笔记

目录 第一章 行列式 1.1 行列式的几何意义 1.2 什么是线性相关,线性无关 1.3 行列式几何意义 1.4 行列式求和 1.5 行列式其他性质 1.6 余子式 1.7 对角线行列式 1.8 分块行列式 1.9 范德蒙德行列式 1.10 爪形行列式的计算 第二章 矩阵 2.1 初识矩阵 2…

查看VMWare ESXi 6.5/6.7服务器上 GPU直通的状态

VMWare ESXi 6.5/6.7服务器状态 查看配置参数

生物信息学 GO、KEGG

文章目录 北大基因本体论分子通路KEGGGO注释分子通路鉴定 关于同源 相似性 b站链接:北大课程 概述了当前生物信息学领域中几个重要的概念和工具,介绍基因本体论(Gene Ontology, GO)、分子通路知识库KEGG(Kyoto Encyclo…

纯前端调用本机原生Office实现Web在线编辑Word/Excel/PPT,支持私有化部署

在日常协同办公过程中,一份文件可能需要多次重复修改才能确定,如果你发送给多个人修改后再汇总,这样既效率低又容易出错,这就用到网页版协同办公软件了,不仅方便文件流转还保证不会出错。 但是目前一些在线协同Office…

go的for循环应该这么用

目录 目录 一:介绍 1: for流程控制 2:for-range流程控制 二:实例展示 1://按照一定次数循环 2://无限循环 3: //循环遍历整数、各种容器和通道 4:遍历通道 5://指针数组循环 6&…

Pillow教程05:NumPy数组和PIL图像的相互转化

---------------Pillow教程集合--------------- Python项目18:使用Pillow模块,随机生成4位数的图片验证码 Python教程93:初识Pillow模块(创建Image对象查看属性图片的保存与缩放) Pillow教程02:图片的裁…

SpringBoot 文件上传(三)

之前讲解了如何接收文件以及如何保存到服务端的本地磁盘中: SpringBoot 文件上传(一)-CSDN博客 SpringBoot 文件上传(二)-CSDN博客 这节讲解如何利用阿里云提供的OSS(Object Storage Service)对象存储服务保存文件。…

vite5+vue3+ import.meta.glob动态导入vue组件

import.meta.glob 是 Vite 提供的一个特殊功能,它允许你在模块范围内动态地导入多个模块。这在处理大量的文件,如组件、页面或其他模块时特别有用,特别是当你需要根据某些条件或模式来动态加载它们时。 1.创建需要动态导入的组件目录 假设你…

设计模式—观察者模式与发布订阅

观察者设计模式 观察者设计模式(Observer Design Pattern)是一种常用的软件设计模式,它是一种行为型模式。该模式用于定义对象之间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知…