【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,一经查实,立即删除!

相关文章

7-30 三天打鱼两天晒网

中国有句俗语叫“三天打鱼两天晒网”。假设某人从某天起,开始“三天打鱼两天晒网”,问这个人在以后的第N天中是“打鱼”还是“晒网”? 输入格式: 输入在一行中给出一个不超过1000的正整数N。 输出格式: 在一行中输…

视频中有无声音的检测

最近遇到一个烦心的事,晚上车停在路边车窗被砸了,行车记录仪正好没安装好,没有拍到,需要对视频声音进行分析确定被砸时间,但我的行车记录仪是每一分钟拍一个视频,一晚上的视频非常多,听起来非常…

树梅派Raspberry Pi OS(Debian)源码交叉编译升级内核参数PAGESIZE

树梅派Raspberry Pi OS(Debian)源码交叉编译升级内核参数PAGESIZE 环境: device: Raspberry Pi 3 Model B Rev 1.2 os: 2024-03-15-raspios-bookworm-arm64 (Debian GNU/Linux 12 bookworm aarch64) kernel: 6.6.20rpt-rpi-v8 arch: aarch64 编译机: ubuntu 22.0…

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

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

每天学习一个Linux命令之hostname

每天学习一个Linux命令之hostname 简介 hostname命令用于显示或设置系统的主机名。主机名是在网络环境中使用的标识符,可用于识别和定位服务器或设备。 基本语法 hostname [选项] [新主机名]可用选项 hostname命令有一些常用的选项,以下是它们的详细…

Itextpdf电子签章

印章 印章是我国特有的历史文化产物,古代主要用作身份凭证和行驶职权的工具。它的起源是由于社会生活的实际需要。早在商周时代,印章就已经产生。如今的印章已成为一种独特的,融实用性和艺术性为一体的艺术瑰宝。传统的印章容易被坏人、小人…

久菜盒子|毕业设计|金融|DCC-GARCH模型

在R语言中,提到“DCC(1,1)”通常是指使用Dynamic Conditional Correlation (DCC)模型对一组金融资产收益率之间的动态相关性进行建模。DCC模型是GARCH族模型的一个变种,特别适用于处理多元时间序列数据中的条件相关结构。它由Robert Engle等人提出&#…

深度学习Trick

Vscode查看文件目录 cmdshiftp选择->查看:将焦点置于辅助侧栏 View: Focus into Secondary Side Bar把主边栏的大纲拖入 快捷查看卷积过程,利用 torchinfo 在 model 下打断点F5 运行调试,F10 运行下一步在调试控制台输入from torchinfo…

Scrapy爬虫开发实验

什么是Scrapy? Scrapy是一个基于Python的强大的开源网络爬虫框架,用于从网站上抓取信息。它提供了广泛的功能,使得爬取和分析数据变得相对容易。Scrapy的特点包括: 强大的数据提取工具,支持XPath和CSS选择器。简化的…

vmware虚拟机下ubuntu扩大磁盘容量

1、扩容: 可以直接在ubuntu setting界面里直接扩容,也可通过vmware命令,如下: vmware提供一个命令行工具,vmware-vdiskmanager.exe,位于vmware的安装目录下,比如 C:/Program Files/VMware/VMwar…

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

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

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

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

02 React 组件使用

import React, { useState } from react;// 定义一个简单的函数式组件 function Counter() {// 使用 useState hook 来创建一个状态变量 count,并提供修改该状态的函数 setCountconst [count, setCount] useState(0);// 在点击按钮时增加计数器的值const increment…

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…

# 16 React 使用自定义Hook实现网络请求

自定义Hook可以帮助你在React应用中重用一些逻辑。以下是如何使用自定义Hook实现网络请求: import { useState, useEffect } from react; import axios from axios;const useApi (url) > {const [data, setData] useState([]);const [loading, setLoading] u…

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%时…