1、依赖注入简介
DI—Dependency Injection,即“依赖注入”:组件之间依赖关系由容器在运行期决定,形象的说,即由容器动态的将某个依赖关系注入到组件之中。**依赖注入的目的并非为软件系统带来更多功能,而是为了提升组件重用的频率,并为系统搭建一个灵活、可扩展的平台。**通过依赖注入机制,我们只需要通过简单的配置,而无需任何代码就可指定目标需要的资源,完成自身的业务逻辑,而不需要关心具体的资源来自何处,由谁实现。
理解DI的关键是:“谁依赖谁,为什么需要依赖,谁注入谁,注入了什么”,那我们来深入分析一下:
●谁依赖于谁:当然是应用程序依赖于IoC容器;
●为什么需要依赖:应用程序需要IoC容器来提供对象需要的外部资源;
●谁注入谁:很明显是IoC容器注入应用程序某个对象,应用程序依赖的对象;
●注入了什么:就是注入某个对象所需要的外部资源(包括对象、资源、常量数据)。
IoC和DI由什么关系呢?其实它们是同一个概念的不同角度描述,由于控制反转概念比较含糊(可能只是理解为容器控制对象这一个层面,很难让人想到谁来维护对象关系),所以2004年大师级人物Martin Fowler又给出了一个新的名字:“依赖注入”,相对IoC 而言,“依赖注入”明确描述了“被注入对象依赖IoC容器配置依赖对象”。
IoC的一个重点是在系统运行中,动态的向某个对象提供它所需要的其他对象。这一点是通过DI(Dependency Injection,依赖注入)来实现的
。比如对象A需要操作数据库,以前我们总是要在A中自己编写代码来获得一个Connection对象,有了 spring我们就只需要告诉spring,A中需要一个Connection,至于这个Connection怎么构造,何时构造,A不需要知道。在系统运行时,spring会在适当的时候制造一个Connection,然后像打针一样,注射到A当中,这样就完成了对各个对象之间关系的控制。A需要依赖 Connection才能正常运行,而这个Connection是由spring注入到A中的,依赖注入的名字就这么来的。那么DI是如何实现的呢? Java 1.3之后一个重要特征是反射(reflection),它允许程序在运行的时候动态的生成对象、执行对象的方法、改变对象的属性,spring就是通过反射来实现注入的。
2、Spring依赖注入
上面讲解的只是DI依赖注入的设计思想,接下来我们看看Spring是如何实现依赖注入的
2.1、构造器注入
上篇文章中通过
2.2、Set方式注入
- 依赖:bean对象的创建依赖于容器
- 注入:bean对象中的所有属性,由容器来注入
Spring提供了多种类型的注入,以下将测试用例全部集合到一个类中方便演示
beans.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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="zhangsanWife" class="cc.leeleo.entity.Wife">
<property name="name" value="翠花"/>
<property name="age" value="18"/>
</bean>
<bean id="zhangsan" class="cc.leeleo.entity.User">
<!--普通注入-->
<property name="name" value="张三"/>
<!--bean注入-->
<property name="wife" ref="zhangsanWife"/>
<!--数组注入-->
<property name="girlFriends">
<array>
<value>翠花</value>
<value>小妮</value>
<value>大曼</value>
</array>
</property>
<!--List注入-->
<property name="hobbies">
<list>
<value>抽烟</value>
<value>喝酒</value>
<value>看美女</value>
</list>
</property>
<!--Map注入-->
<property name="certificates">
<map>
<entry key="身份证" value="511602199901234567"/>
<entry key="手机号" value="18888888888"/>
<entry key="QQ号" value="666666"/>
<entry key="微信号" value="18888888888"/>
</map>
</property>
<!--Set注入-->
<property name="cars">
<set>
<value>兰博基尼</value>
<value>布加迪威龙</value>
<value>法拉利</value>
</set>
</property>
<!--空值注入-->
<property name="house" value="null"/>
<!--properties注入-->
<property name="resume">
<props>
<prop key="单位">哔哩哔哩</prop>
<prop key="职位">java工程师</prop>
<prop key="工龄">1年</prop>
</props>
</property>
</bean>
</beans>
User类和Wife类
@Data
public class User {
private String name;
private Wife wife;
private String[] girlFriends;
private List<String> hobbies;
private Map<String,String> certificates;
private Set<String> cars;
private String house;
private Properties resume;
}
@Data
public class Wife {
private String name;
private Integer age;
}
测试:
public class SpringApplicationStarter {
@Test
public void test(){
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
User bean = context.getBean(User.class);
System.out.println(bean.toString());
}
}

2.3、扩展方式注入
除了以上的基础的及复杂的注入方式意外,Spring官方还提供了两种扩展的属性注入方式
-
添加命名空间
在
标签中加入以下两个属性 xmlns:pxmlns:c<?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:p="http://www.springframework.org/schema/p" xmlns:c="http://www.springframework.org/schema/c" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> </beans> -
使用
p:属性,等同于<**property>** 标签<?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:p="http://www.springframework.org/schema/p" xmlns:c="http://www.springframework.org/schema/c" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!--p:顾名思义我猜是property的缩写--> <bean id="xiaoni" class="cc.leeleo.entity.Wife" p:name="小妮" p:age="18" /> -
使用
c:属性,等同于<constructor-arg>标签<?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:p="http://www.springframework.org/schema/p" xmlns:c="http://www.springframework.org/schema/c" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!--c:顾名思义我猜是constructor的缩写--> <bean id="daman" class="cc.leeleo.entity.Wife" c:name="大曼" c:age="18"/> </beans> -
也可以同时使用
c和p属性<?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:p="http://www.springframework.org/schema/p" xmlns:c="http://www.springframework.org/schema/c" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!--p:顾名思义我猜是property的缩写--> <bean id="xiaoni" class="cc.leeleo.entity.Wife" c:name="afa" c:age="123" p:name2="小妮" p:age2="18"></bean> </beans>Wife类:
@Data public class Wife { private String name; private Integer age; private String name2; private Integer age2; public Wife(String name, Integer age) { this.name = name; this.age = age; } }测试结果:

2.4、Bean的作用域

以上是Bean的几个作用域,目前我们涉及到的bean暂时只有prototype和singleton这俩作用域,其他的几个都是在引入SpringMVC后可能会使用到的作用域。
-
单例模式(spring默认机制)
<bean id="xiaoni" class="cc.leeleo.entity.Wife" p:name="小妮" p:age="18" scope="singleton"></bean>测试结果:

-
原型模式:每次从容器中get时,都会产生一个新的对象
<bean id="xiaoni" class="cc.leeleo.entity.Wife" p:name="小妮" p:age="18" scope="prototype"></bean>测试结果:

3、自动装配
以上演示的这些例子全都是手动来实现一个bean的装配的,需要手动一个个去写
Spring中提供了三种装配的方式:
- 在xml中显式的配置
- 在java中显式的配置
- 隐式的自动装配
3.1、byName的装配方式
这里先列出示例代码:
beans.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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="son" class="cc.leeleo.entity.Son">
<property name="name" value="erzi"/>
</bean>
<bean id="father" class="cc.leeleo.entity.Father">
<property name="name" value="baba" />
<property name="son" ref="son" />
</bean>
</beans>
一个Father类和一个Son类
public class Father {
private String name;
private Son son;
public void saySon(){
System.out.println(this.name+"说:erzi");
}
public void setName(String name) {
this.name = name;
}
public void setSon(Son son) {
this.son = son;
}
public Son getSon() {
return son;
}
}
public class Son {
private String name;
public void sayDaddy(){
System.out.println(this.name+"说:baba");
}
public void setName(String name) {
this.name = name;
}
}
好了 重点来了,在beans.xml中我们可以看到在注入son这个对象的时候,我们需要去指定id为son的bean对应到father的son属性上,这一步可以通过Spring提供的auto wired属性来省去这一步。

我们看到bean标签的autowire的属性中包含五个值,顾名思义,可以是通过名称(bean的id)、通过类型(bean的clas)以及构造器等方式来实现自动装配。
首先我们在bean中加入该属性**autowire="byName"** ,并删除以下值
~~<**property name~="son" ref="son"** />~~
完整代码如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="son" class="cc.leeleo.entity.Son">
<property name="name" value="erzi"/>
</bean>
<bean id="father" class="cc.leeleo.entity.Father" autowire="byName">
<property name="name" value="baba" />
</bean>
</beans>
接下来我们测试一下

可以看到由于我们定义的Father类中的Son的set方法名称setSon和beans中定义的bean id = “son”一致,所以装配成功,我们再试下更改bean的id

此时我们发现,由于spring找不到装配时所需的set方法,此时为了验证其到底是不是根据set方法装配,我们将setSon方法更改为setSon1再来看下

此时便验证了我们之前所说,使用byName方式Spring会
根据需要装配的bean的set方法后的id值,去查找上下文中包含该id的bean进行装配
需要注意的是 使用byName的方式需要保证装配bean的id是唯一的,并且这个bean需要和注入属性的set方法名的值一致
3.2、byType的装配方式
这里我们更改**autowire="byType" ,**再试一下,使用此种方式可以将bean中的id属性干掉不用

由此得知,使用byType方式
Spring会在上下文中,根据需要装配的对象类型,在上下文中查找对应类型的bean进行装配
需要注意的是 使用byType的方式需要保证装配的bean的类上下文中是唯一的
3.3、使用注解的装配方式
在使用了上面两种方式进行装配以后,虽然是便利了些,但是还是差点意思,Spring 还提供了一种更爽的配置方式,注解!
jdk1.5支持的注解,Spring2.5就支持注解了使用注解须知:
- 导入约束:context约束
- 配置注解的支持:context:annotation-config/

完整示例:
beans.xml 这里我们添加上面的三个步骤,并且添加两个bean,这里细心的小伙伴们可能就发现了,咱们bean里面只有一个class属性,id和autowire都干掉了,代码又干净了不少。
<?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
http://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="cc.leeleo.entity.Son">
<property name="name" value="erzi"/>
</bean>
<bean class="cc.leeleo.entity.Father">
<property name="name" value="baba"/>
</bean>
</beans>
Father,这里我们把setSon的方法干掉!并且在son属性上加上@Autowired注解
public class Father {
private String name;
@Autowired
private Son son;
public void saySon(){
System.out.println(this.name+"说:erzi");
}
public void setName(String name) {
this.name = name;
}
public Son getSon() {
return son;
}
}
上面的代码是不是又简洁了不少?接下来我们再测试下

成功执行~
@Autowired 可以在属性上使用 也可以在set方法上使用,使用该方式可以省略set方法
3.3.1、Qualifier 注解
跟@Autowired一起使用的注解还有@Qualifier 注解,它的使用场景是,当一个spring的容器中存在多个相同类型的bean,这里就可以指定你使用哪个bean作为注入的对象
还是上面的代码,这里我们更改一下Fater类,增加@Qualifier注解,指定注入的bean为son2
public class Father {
private String name;
@Autowired
@Qualifier(value = "son2")
private Son son;
public void saySon(){
System.out.println(this.name+"说:erzi");
}
public void setName(String name) {
this.name = name;
}
public Son getSon() {
return son;
}
}
还有beans.xml,新增一个bean
<?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
http://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="cc.leeleo.entity.Son">
<property name="name" value="erzi"/>
</bean>
<bean id="son2" class="cc.leeleo.entity.Son">
<property name="name" value="xiaoerzi"/>
</bean>
<bean class="cc.leeleo.entity.Father">
<property name="name" value="baba"/>
</bean>
</beans>
测试:

需要注意的是,
@Qualifier的value值指定的是Spring上下文中存在的bean的id
3.3.2、@Resource注解
除了Spring提供的@Autowired注解以外,java也提供了一个@Resource注解能实现相同的功能,这里我们废话不多说 上代码:
beans.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
http://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="cc.leeleo.entity.Son">
<property name="name" value="erzi"/>
</bean>
<bean class="cc.leeleo.entity.Father">
<property name="name" value="baba"/>
</bean>
</beans>
Father.java 这里我们使用@Resource注解
public class Father {
private String name;
@Resource
private Son son;
public void saySon(){
System.out.println(this.name+"说:erzi");
}
public void setName(String name) {
this.name = name;
}
public Son getSon() {
return son;
}
}
测试:

如果有多个相同类型的bean,我们也可以通过@Resource(name = “xxx”)来指定所需的bean,
上代码:
Father.java 修改注解,指定name值为xml中bean的id
@Resource(name = "son2")
private Son son;
beans.xml 新增一个bean
<bean id="son2" class="cc.leeleo.entity.Son">
<property name="name" value="xiaoerzi"/>
</bean>
测试:

依旧没有问题~
- @Autowired和@Resource区别:
- 都是用来自动装配的,都可以放在属性字段上
- @Autowired通过byType实现,必须要求这个对象存在
- @Resoutce默认通过byName方式实现,如果找不到名字,则通过byType实现
- 执行顺序不同:@Autowired通过byType实现,@Resoutce通过byName实现