长文干货! 一文搞懂IoC的依赖注入
有梦想,有情怀,有温度,干货满满; 微信搜索【 PoXing 】 关注一下我吧。
一、注解驱动IoC
xml驱动的IoC容器使用的是ClassPathXmlApplicationContext
读取xml内bean信息
注解驱动的IoC容器使用的是AnnotationConfigApplicationContext
读取Java类中的bean信息
1. AnnotationConfigApplicationContext 的注册使用
相比于xml文件作为驱动, 注解驱动需要指明配置类 一个配置类可以理解为"相当于"一个xml 配置类只需要在类上标注注解 @Configuration
@Configuration
public class DemoConfiguration {}
在xml中声明bean的方式
<bean id="person" class="com.huodd.bean.Person"></bean>
在配置类中使用的是@Bean
注解
@Bean
public Person person() {return new Person();
}
说明: 向IoC容器注册一个类型为Persion,id为Person的Bean
方法名表示的是bean的id 返回值表示的是注册的bean的类型
@Bean
注解也可以显示的声明bean的id 如 @Bean("person1")
2. 注解IoC容器的初始化
public class AnnotationConfigApplication {public static void main(String[] args) {ApplicationContext ctx = new AnnotationConfigApplicationContext(DemoConfiguration.class);Person person = ctx.getBean(Person.class);System.out.println(person);}
}
运行后Person控制台打印结果
com.huodd.bean.Person@55536d9e
3. 组件的注册和扫描
上述初始化时 我们在使用AnnotationConfigApplicationContext
时传递了参数 Class<?>... componentClasses
翻看AnnotationConfigApplicationContext
的构造方法可以发现还可以传递参数的参数类型还有 String... basePackages
这里就涉及到组件的注册和扫描
这里可以思考一个问题, 如果我们要注册的组件特别多, 那进行编写这些
@Bean
的时候代码工作量也会特别多,这时候该如何解决呢?
Spring 给我们提供了几个注解,可以帮助我们快速注册需要的组件, 这些注解被称为模式注解(stereotype annotations)
@Component
@Component可以说是所有组件注册的根源 在类上标注 @Component
代表该类被注册到IoC容器中作为一个Bean
@Component
public class Person {}
如果未指定 Bean 的名称 默认规则是 “类名称首字母小写” 上面的bean名称默认会是 person
如果要自定义bean的名称 可以在@Component
声明value的值即可 如
@Component("person1")
public class Person {}
在xml中相当于
<bean id="person1" class="com.huodd.bean.Person"/>
@ComponentScan
这个时候 如果我们直接运行启动类 获取Person
的bean对象,会报错NoSuchBeanDefinitionException
这是为什么呢?
因为我们只是声明了组件,而后直接启动了IoC容器,这样容器是感知不到有@Component
存在的,
解决方案1:
我们需要在写配置类时再额外标注一个新的注解@ComponentScan
目的是告诉IoC容器 我要扫描哪个包下面的带有@Component
注解的类
@Configuration
@ComponentScan("com.huodd.bean")
public class DemoComponentScanConfiguration {}
注: 如果不指定扫描路径, 则默认扫描本类所在包及所有子包下带有@Component
的组件
启动类代码如下:
public class AnnotationConfigApplication {public static void main(String[] args) {ApplicationContext ctx = new AnnotationConfigApplicationContext(DemoComponentScanConfiguration.class);Person person = ctx.getBean(Person.class);System.out.println(person);}
}
解决方案2:
这里也可以不写@ComponentScan
而直接在AnnotationConfigApplicationContext
方法参数内传入String类型的包扫描路径 代码如下
public class AnnotationConfigApplication {public static void main(String[] args) {ApplicationContext ctx = new AnnotationConfigApplicationContext("com.huodd.bean");Person person = ctx.getBean(Person.class);System.out.println(person);}
}
PS: 组件扫描并非是注解驱动IoC所特有的, 其实在xml驱动的IoC模式下 同样可以启用组件扫描, 只需要在xml中声明一个标签即可
<context:component-scan base-package="com.huodd.bean"/>
这里需要注意下: 如需要扫描多个路径,需要写多个标签 也就是 一个标签只能声明一个根包
组件注册的补充
SpringFramework 提供了在进行Web开发三层架构时的扩展注解: 分别为 @Controller
、 @Service
、@Repository
小伙伴有没有很熟悉?
分别代表 表现层、业务层、持久层 这三个注解的作用与 @Component
完全一样 扒开源码我们可以看到 底层在这三个注解类上又添加了 @Component
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Service {}
这样 我们在进行符合三层架构的开发时 对于相应的如 ServiceImpl等 就可以直接标注 @Service
等注解了
@Configuration
@Configuration
底层也有标注@Component
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration { ... }
由此可以说明,配置类不是向我们所想的那样,只是单纯的做一个配置而已, 它也会被视为 bean,也被注册到IoC容器里面
4. 注解驱动与xml驱动互相引用
4.1 xml引用注解
需开启注解配置 再注册相应配置类
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.springframework.org/schema/context"xsi:schemaLocation="http://www.springframework.org/schema/beanshttps://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd"><!-- 开启注解配置 --><context:annotation-config /><!-- 注册配置类 --><bean class="com.huodd.config.AnnotationConfigConfiguration"/>
</beans>
4.2 注解引用XMl
需在配置类上标注 @ImportResource
并声明配置文件的路径
@Configuration
@ImportResource("classpath:annotation/demo-beans.xml")
public class ImportXmlAnnotationConfiguration {
}
二、IoC的依赖注入
1.Setter属性注入
创建对象 将属性值set进去 之后返回对象
@Bean
public Person person() {Person person = new Person();person.setId(1);person.setName("PoXing");person.setAge(18);return person;
}
xml中的setter注入
<bean id="person" class="com.huodd.bean.Person"><property name="id" value="1"/><property name="name" value="PoXing"/><property name="age" value="18"/>
</bean>
2. 构造器注入
使用构造器注入,需要在bean本身添加有参构造方法, 如在Person中添加有参构造方法如下
public Person(Integer id, String name, Integer age) {this.id = id;this.name = name;this.age = age;
}
注解驱动中,我们创建bean的时候注入属性时 就需要同时指定参数值
@Bean
public Person person() {return new Person(1, "PoXing", 18);
}
xml驱动中如下
<bean id="person" class="com.huodd.bean.Person"><!-- index: 表示构造器的参数索引value: 表示对应的参数值--><constructor-arg index="0" value="1"/><constructor-arg index="1" value="PoXing"/><constructor-arg index="2" value="18"/>
</bean>
3. 注解式属性注入
这里先说明一下,为何会有注解式属性值注入. 细心的小伙伴可能会发现 上面我们谈到的 Setter属性注入、构造器注入 好像在只能是在使用 @Bean
注解的时候时候使用, 但是 如果是通过标注 @Component
注解的组件呢(像前面我们的Person类中标注了@Component
注解),怎么给它设定属性值, 该节主要就是说一下这部分
@Component 下的属性注入
这里我们使用Dog类做为演示(这里我悄悄的添加了@Component
注解 自己尝试的小伙伴要注意哦 否则会报错的)
@Component
public class Dog {private Integer id;private String name;private Integer age;... 省略 Getter、Setter... 省略 toString}
这里要实现注解式属性注入,可以直接在要注入的字段上标注 @Value
注解 如
@Value("1")
private Integer id;@Value("wangcai")
private String name;@Value("3")
private Integer age;
启动类代码如下
public class DiApplication {public static void main(String[] args) {ApplicationContext ctx = new AnnotationConfigApplicationContext("com.huodd.bean");Dog dog = ctx.getBean(Dog.class);System.out.println(dog);}
}
控制台打印结果
Dog{id=1, name='wangcai', age=3}
外部配置文件(@PropertySource)
这里主要是解决上面的@Value中注入 我们把属性值直接固定写死了,如果要修改 还要去Java代码中去修改,很不符合开发规范,
SpringFramework为我们扩展了新的注解@PropertySource
主要用来导入外部配置文件
- 这里我们创建一个
dog.properties
dog.id=1
dog.name=wangcai
dog.age=3
- 引入配置文件
@PropertySource("classpath:di/dog.properties")
@ComponentScan("com.huodd.bean")
@Configuration
public class DemoComponentScanConfiguration {}
- Dog类中属性注入 这里
@Value
需要配合占位符 来获取properties配置文件中的内容
@Value("${dog.id}")
private Integer id;@Value("${dog.name}")
private String name;@Value("${dog.age}")
private Integer age;
- 修改一下启动类
public class DiApplication {public static void main(String[] args) {ApplicationContext ctx = new AnnotationConfigApplicationContext(DemoComponentScanConfiguration.class);Dog dog = ctx.getBean(Dog.class);System.out.println(dog);}
}
控制台打印结果如下
Dog{id=1, name='wangcai', age=3}
此时配置文件的属性已经注入成功
4.自动注入
在xml模式中有ref
属性 可以将一个bean注入到另外一个bean中, 注解模式中也同样可以
@Autowired
给Dog的bean中注入 Person的Bean (即 给dog指定它的主人)
方法1 → 在属性上标注
@Component
public class Dog {// ......@Autowiredprivate Person person;
}
方法2 → 使用构造器注入方式
@Component
public class Dog {// ......private Person person;@Autowiredpublic Dog(Person person) {this.person = person;}
}
方法3 → 使用setter方法注入
@Component
public class Dog {// ......private Person person;@Autowiredpublic void setPerson(Person person) {this.person = person;}
}
JSR250规范下的@Resource
@Resource
也是用来属性注入的注解
它与@Autowired
的区别是:
@Autowired
是按照类型注入@Resource
是按照属性名(也就是bean的名称)注入
@Resource
注解相当于标注 @Autowired
和 @Qualifier
@Qualifier
这里简要说明下,为指定bean的名称而存在,如果存在多个相同的bean,而bean的名称不同,我们可以使用@Autowired
配置 @Qualifier
注解
如: 下面表示该Dog类注入的主人Bean是名称为 xiaowang的, 而当前容器内可能存在多个 主人bean对象 比如 xiaoli、xiaoming …
@Component
public class Dog {// ......@Autowired@Qualifier("xiaowang")private Person person;
}
下面如果使用@Resource
可以更方便些 代码如下
@Component
public class Dog {// ......@Resource(name="xiaowang")private Person person;
}
JSR330规范下的@Inject
@Inject
注解也是按照类型注入,与@Autowire
的策略一样, 不过如要使用@Inject
需要额外的导入依赖
<!-- jsr330 -->
<dependency><groupId>javax.inject</groupId><artifactId>javax.inject</artifactId><version>1</version>
</dependency>
后面的使用方法就与SpringFramework 原生的 @Autowire
+ @Qualifier
相同了
@Component
public class Dog {@Inject // 等同于@Autowired@Named("xiaowang") // 等同于@Qualifierprivate Person person;
它与@Autowired
的区别是:
@Autowired
所在的包为org.springframework.beans.factory.annotation.Autowired
即为 SpringFramework 提供的@Inject
所在的包为javax.inject.Inject
属于JSR的规范 也就是说如果不使用SpringFramework时可以使用该注解
5. 复杂类型注入
Array注入
<property name="names"><array><value>PoXing</value><value>LaoWang</value></array>
</property>
List注入
<property name="tels"><list><value>13000000000</value><value>13000000001</value></list>
</property>
Set注入-
<!-- 已经提前声明好的Dog -->
<bean id="wangcai" class="com.huodd.bean.ext.Dog"/>
---<property name="dogs"><set><bean class="com.huodd.bean.Dog"/><ref bean="wangcai"/></set>
</property>
Map注入
<property name="homesMap"><map><entry key="1" value="main"><ref bean="myHome1" /></entry><entry key="2" value="other"><ref bean="myHome2" /></entry></map>
</property>
Properties注入
<property name="props"><props><prop key="sex">男</prop><prop key="age">18</prop></props>
</property>
面试题
1.@Autowired注入原理是什么?
- 先拿属性对应的类型,去IoC容器中找相应的Bean
- 如果没有找到 直接抛出
NoUniqueBeanDefinitionException
异常 - 如果找到一个 直接返回
- 如果找到多个相同类型的bean 再拿属性名去与这多个bean的id进行对比
- 如果有多个或者没有 则会抛出
NoUniqueBeanDefinitionException
异常 - 如果只有一个 直接返回
2.依赖注入的方式有哪些,都有什么区别
注入方式 | 被注入对象是否可改变 | 是否依赖IOC框架的API | 使用场景 |
---|---|---|---|
构造器注入 | 不可变 | 否 | 不可变的固定注入 |
参数注入 | 不可变 | 否 |
注解配置类中 @Bean 方法注册 bean
|
属性注入(注解式属性注入) | 不可变 | 是(只能通过标注注解来侵入式注入) | 通常用于不可变的固定注入 |
setter注入 | 可变 | 否 | 可选属性的注入 |
3.自动注入的注解对比
注解 | 注入方式 | 是否支持@Primary | 来源 | Bean不存在时处理 |
---|---|---|---|---|
@Autowired | 根据类型注入 | 是 | SpringFramework原生注解 | 可指定required=false来避免注入失败 |
@Resource | 根据名称注入 | 是 | JSR250规范 | 容器中不存在指定Bean会抛出异常 |
@Inject | 根据类型注入 | 是 | JSR330规范 ( 需要导jar包 ) | 容器中不存在指定Bean会抛出异常 |
@Qualifier
:如果被标注的成员/方法在根据类型注入时发现有多个相同类型的 Bean ,则会根据该注解声明的 name 寻找特定的 bean
@Primary
:如果有多个相同类型的 Bean 同时注册到 IOC 容器中,使用 “根据类型注入” 的注解时会注入标注 @Primary
注解的 bean 即默认策略
4.使用依赖注入有什么优缺点
- 依赖注入作为 IOC 的实现方式之一,目的就是解耦,我们不需要直接去 new 那些依赖的类对象就可以直接从容器中去取来使用, 如果组件存在多级依赖,依赖注入可以将这些依赖的关系简化。
- 依赖对象的可配置:通过 xml 或者注解声明,可以指定和调整组件注入的对象,借助 Java 的多态特性,可以不需要大批量的修改就完成依赖注入的对象替换
型注入 | 是 | SpringFramework原生注解 | 可指定required=false来避免注入失败 |
| @Resource | 根据名称注入 | 是 | JSR250规范 | 容器中不存在指定Bean会抛出异常 |
| @Inject | 根据类型注入 | 是 | JSR330规范 ( 需要导jar包 ) | 容器中不存在指定Bean会抛出异常 |
@Qualifier
:如果被标注的成员/方法在根据类型注入时发现有多个相同类型的 Bean ,则会根据该注解声明的 name 寻找特定的 bean
@Primary
:如果有多个相同类型的 Bean 同时注册到 IOC 容器中,使用 “根据类型注入” 的注解时会注入标注 @Primary
注解的 bean 即默认策略
4.使用依赖注入有什么优缺点
- 依赖注入作为 IOC 的实现方式之一,目的就是解耦,我们不需要直接去 new 那些依赖的类对象就可以直接从容器中去取来使用, 如果组件存在多级依赖,依赖注入可以将这些依赖的关系简化。
- 依赖对象的可配置:通过 xml 或者注解声明,可以指定和调整组件注入的对象,借助 Java 的多态特性,可以不需要大批量的修改就完成依赖注入的对象替换
我是Baoxing,开始做一件事情最好的时机,要么是十年前,要么就是现在。 感谢各位的 点赞、收藏和评论
长文干货! 一文搞懂IoC的依赖注入相关推荐
- 长文干货丨一文搞懂IoC的依赖注入
一.注解驱动IoC xml驱动的IoC容器使用的是ClassPathXmlApplicationContext读取xml内bean信息 注解驱动的IoC容器使用的是AnnotationConfigAp ...
- 干货 | 一文搞懂极大似然估计
极大似然估计,通俗理解来说,就是在假定整体模型分布已知,利用已知的样本结果信息,反推最具有可能(最大概率)导致这些样本结果出现的模型参数值! 换句话说,极大似然估计提供了一种给定观察数据来评估模型参数 ...
- 2万长文,一文搞懂Kafka
作者:erainm 来源:https://blog.csdn.net/eraining/article/details/115860664 1.为什么有消息系统 解耦合 异步处理 例如电商平台,秒杀活 ...
- 万字长文,一文搞懂TCP/IP和HTTP、HTTPS
来自:非科班的科班 TCP/IP概念 TCP/IP(Transmission Control Protocol/Internet Protocol,传输控制协议/网际协议)是指能够在多个不同网络间实现 ...
- 一文搞懂AWS EC2, IGW, RT, NAT, SG 基础篇下
B站实操视频更新 跟着拉面学习AWS--EC2, IGW, RT, NAT, SG 简介 长文多图预警,看结论可以直接拖到"总结"部分 本文承接上一篇文章介绍以下 AWS 基础概念 ...
- 【UE·蓝图底层篇】一文搞懂NativeClass、GeneratedClass、BlueprintClass、ParentClass
本文将对蓝图类UBlueprint的几个UClass成员变量NativeClass.GeneratedClass.BlueprintClass.ParentClass进行比较深入的讲解,看完之后对蓝图 ...
- 一文搞懂k近邻(k-NN)算法(一)
原文链接 一文搞懂k近邻(k-NN)算法(一) 前几天和德川一起在学习会上讲解了k-NN算法,这里进行总结一下,力争用最 通俗的语言讲解以便更多同学的理解. 本文目录如下: 1.k近邻算法的基本概念, ...
- 都2021年了,再不学ES6你就out了 —— 一文搞懂ES6
JS干货分享 -- 一文搞懂ES6 导语:ES6是什么?用来做什么? 1. let 与 const 2. 解构赋值 3. 模板字符串 4. ES6 函数(升级后更爽) 5. Class类 6. Map ...
- 网络知识扫盲,一文搞懂 DNS
在找工作面试的过程中,面试官非常喜欢考察基础知识,除了数据结构与算法之外,网络知识也是一个非常重要的考察对象. 而网络知识,通常是很抽象,不容易理解的,有很多同学就在这里裁了跟头.为了更好地通过面试, ...
最新文章
- [DruidAbstractDataSource] maxIdle is deprecated
- dofilter 无效_“鹅厂”商标注册成功,腾讯异议无效
- 【Qt】Qt中QJsonParseError类
- WhateverOrigin –与Heroku和Play对抗相同的原产地政策! 构架
- android mkdirs 不起作用,Android mkdirs()创建一个零字节文件而不是文件夹
- Java+包裹类型_java中的包裹类型
- python线程池传入多个参数_python线程池问题
- Solr 原理、API 使用
- 河北科技大学校园网设计和实现
- 艾永亮:优衣库,究竟是怎么卖衣服的?
- 还在为美容护肤问题焦虑吗?不妨试试红光光浴#大健康#红光光浴#红光#种光光学
- 深圳大学大学计算机考试科目,深圳大学计算机考研科目有哪些
- centos无法识别NTFS格式的U盘解决办法
- bp 神经网络 优点 不足_【学术论文】基于灰度共生矩阵和BP神经网络的乳腺肿瘤识别...
- 计算机无法屏幕亮度,电脑更新windows 10 四月更新1803后,笔记本电脑亮度无法调节。...
- handler机制--handler概览
- 279. 完全平方数 (数学定理 四平方数之和定理)
- 判断是否为平衡二叉树(Java)
- 安卓 Installation via USB is disabled
- java命令--jstack 工具