长文干货! 一文搞懂IoC的依赖注入

bd2star 2021-01-22 10:59:16
Ioc 干货 一文 搞懂 长文


有梦想,有情怀,有温度,干货满满; 关注一下我吧。

一、注解驱动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/beans
https://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 主要用来导入外部配置文件

  1. 这里我们创建一个 dog.properties
dog.id=1
dog.name=wangcai
dog.age=3
  1. 引入配置文件
@PropertySource("classpath:di/dog.properties")
@ComponentScan("com.huodd.bean")
@Configuration
public class DemoComponentScanConfiguration {
}
  1. Dog类中属性注入 这里@Value需要配合占位符 来获取properties配置文件中的内容
@Value("${dog.id}")
private Integer id;
@Value("${dog.name}")
private String name;
@Value("${dog.age}")
private Integer age;
  1. 修改一下启动类
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 {
// ......
@Autowired
private Person person;
}

方法2 → 使用构造器注入方式

@Component
public class Dog {
// ......
private Person person;
@Autowired
public Dog(Person person) {
this.person = person;
}
}

方法3 → 使用setter方法注入

@Component
public class Dog {
// ......
private Person person;
@Autowired
public 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") // 等同于@Qualifier
private 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注入原理是什么?

  1. 先拿属性对应的类型,去IoC容器中找相应的Bean
  2. 如果没有找到 直接抛出NoUniqueBeanDefinitionException异常
  3. 如果找到一个 直接返回
  4. 如果找到多个相同类型的bean 再拿属性名去与这多个bean的id进行对比
  5. 如果有多个或者没有 则会抛出NoUniqueBeanDefinitionException异常
  6. 如果只有一个 直接返回

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 的多态特性,可以不需要大批量的修改就完成依赖注入的对象替换

我是Baoxing,开始做一件事情最好的时机,要么是十年前,要么就是现在。 感谢各位的 点赞收藏评论

版权声明
本文为[bd2star]所创,转载请带上原文链接,感谢
https://segmentfault.com/a/1190000039057950

  1. 【计算机网络 12(1),尚学堂马士兵Java视频教程
  2. 【程序猿历程,史上最全的Java面试题集锦在这里
  3. 【程序猿历程(1),Javaweb视频教程百度云
  4. Notes on MySQL 45 lectures (1-7)
  5. [computer network 12 (1), Shang Xuetang Ma soldier java video tutorial
  6. The most complete collection of Java interview questions in history is here
  7. [process of program ape (1), JavaWeb video tutorial, baidu cloud
  8. Notes on MySQL 45 lectures (1-7)
  9. 精进 Spring Boot 03:Spring Boot 的配置文件和配置管理,以及用三种方式读取配置文件
  10. Refined spring boot 03: spring boot configuration files and configuration management, and reading configuration files in three ways
  11. 精进 Spring Boot 03:Spring Boot 的配置文件和配置管理,以及用三种方式读取配置文件
  12. Refined spring boot 03: spring boot configuration files and configuration management, and reading configuration files in three ways
  13. 【递归,Java传智播客笔记
  14. [recursion, Java intelligence podcast notes
  15. [adhere to painting for 386 days] the beginning of spring of 24 solar terms
  16. K8S系列第八篇(Service、EndPoints以及高可用kubeadm部署)
  17. K8s Series Part 8 (service, endpoints and high availability kubeadm deployment)
  18. 【重识 HTML (3),350道Java面试真题分享
  19. 【重识 HTML (2),Java并发编程必会的多线程你竟然还不会
  20. 【重识 HTML (1),二本Java小菜鸟4面字节跳动被秒成渣渣
  21. [re recognize HTML (3) and share 350 real Java interview questions
  22. [re recognize HTML (2). Multithreading is a must for Java Concurrent Programming. How dare you not
  23. [re recognize HTML (1), two Java rookies' 4-sided bytes beat and become slag in seconds
  24. 造轮子系列之RPC 1:如何从零开始开发RPC框架
  25. RPC 1: how to develop RPC framework from scratch
  26. 造轮子系列之RPC 1:如何从零开始开发RPC框架
  27. RPC 1: how to develop RPC framework from scratch
  28. 一次性捋清楚吧,对乱糟糟的,Spring事务扩展机制
  29. 一文彻底弄懂如何选择抽象类还是接口,连续四年百度Java岗必问面试题
  30. Redis常用命令
  31. 一双拖鞋引发的血案,狂神说Java系列笔记
  32. 一、mysql基础安装
  33. 一位程序员的独白:尽管我一生坎坷,Java框架面试基础
  34. Clear it all at once. For the messy, spring transaction extension mechanism
  35. A thorough understanding of how to choose abstract classes or interfaces, baidu Java post must ask interview questions for four consecutive years
  36. Redis common commands
  37. A pair of slippers triggered the murder, crazy God said java series notes
  38. 1、 MySQL basic installation
  39. Monologue of a programmer: despite my ups and downs in my life, Java framework is the foundation of interview
  40. 【大厂面试】三面三问Spring循环依赖,请一定要把这篇看完(建议收藏)
  41. 一线互联网企业中,springboot入门项目
  42. 一篇文带你入门SSM框架Spring开发,帮你快速拿Offer
  43. 【面试资料】Java全集、微服务、大数据、数据结构与算法、机器学习知识最全总结,283页pdf
  44. 【leetcode刷题】24.数组中重复的数字——Java版
  45. 【leetcode刷题】23.对称二叉树——Java版
  46. 【leetcode刷题】22.二叉树的中序遍历——Java版
  47. 【leetcode刷题】21.三数之和——Java版
  48. 【leetcode刷题】20.最长回文子串——Java版
  49. 【leetcode刷题】19.回文链表——Java版
  50. 【leetcode刷题】18.反转链表——Java版
  51. 【leetcode刷题】17.相交链表——Java&python版
  52. 【leetcode刷题】16.环形链表——Java版
  53. 【leetcode刷题】15.汉明距离——Java版
  54. 【leetcode刷题】14.找到所有数组中消失的数字——Java版
  55. 【leetcode刷题】13.比特位计数——Java版
  56. oracle控制用户权限命令
  57. 三年Java开发,继阿里,鲁班二期Java架构师
  58. Oracle必须要启动的服务
  59. 万字长文!深入剖析HashMap,Java基础笔试题大全带答案
  60. 一问Kafka就心慌?我却凭着这份,图灵学院vip课程百度云