如何理解IoC的依赖注入
这篇文章主要讲解了“如何理解IoC的依赖注入”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“如何理解IoC的依赖注入”吧!
一、注解驱动IoC
xml驱动的IoC容器使用的是ClassPathXmlApplicationContext读取xml内bean信息
注解驱动的IoC容器使用的是AnnotationConfigApplicationContext读取Java类中的bean信息
1. AnnotationConfigApplicationContext 的注册使用
相比于xml文件作为驱动, 注解驱动需要指明配置类 一个配置类可以理解为"相当于"一个xml 配置类只需要在类上标注注解 @Configuration
@Configuration public class DemoConfiguration { }
在xml中声明bean的方式
在配置类中使用的是@Bean注解
<bean id="person" class="com.huodd.bean.Person"></bean>
说明: 向IoC容器注册一个类型为Persion,id为Person的Bean
方法名表示的是bean的id 返回值表示的是注册的bean的类型
@Bean注解也可以显示的声明bean的id 如 @Bean("person1")
@Bean public Person person() { return new Person(); }
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
@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
2.引入配置文件
@PropertySource("classpath:di/dog.properties") @ComponentScan("com.huodd.bean") @Configuration public class DemoComponentScanConfiguration { }
3.Dog类中属性注入 这里@Value需要配合占位符 来获取properties配置文件中的内容
@Value("${dog.id}") private Integer id; @Value("${dog.name}") private String name; @Value("${dog.age}") private Integer age;
4.修改一下启动类
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注入原理是什么?
鸿蒙官方战略合作共建——HarmonyOS技术社区
先拿属性对应的类型,去IoC容器中找相应的Bean
如果没有找到 直接抛出NoUniqueBeanDefinitionException异常
如果找到一个 直接返回
如果找到多个相同类型的bean 再拿属性名去与这多个bean的id进行对比
如果有多个或者没有 则会抛出NoUniqueBeanDefinitionException异常
如果只有一个 直接返回
2.依赖注入的方式有哪些,都有什么区别
3.自动注入的注解对比
@Qualifier :如果被标注的成员/方法在根据类型注入时发现有多个相同类型的 Bean ,则会根据该注解声明的 name 寻找特定的 bean
@Primary :如果有多个相同类型的 Bean 同时注册到 IOC 容器中,使用 “根据类型注入” 的注解时会注入标注 @Primary 注解的 bean 即默认策略
4.使用依赖注入有什么优缺点
依赖注入作为 IOC 的实现方式之一,目的就是解耦,我们不需要直接去 new 那些依赖的类对象就可以直接从容器中去取来使用, 如果组件存在多级依赖,依赖注入可以将这些依赖的关系简化。
依赖对象的可配置:通过 xml 或者注解声明,可以指定和调整组件注入的对象,借助 Java 的多态特性,可以不需要大批量的修改就完成依赖注入的对象替换
感谢各位的阅读,以上就是“如何理解IoC的依赖注入”的内容了,经过本文的学习后,相信大家对如何理解IoC的依赖注入这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是编程网,小编将为大家推送更多相关知识点的文章,欢迎关注!
免责声明:
① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。
② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341