在上篇文章中我着重介绍了Spring的控制反转和依赖注入的概念,那么依赖注入有那几种方式呢?他们的优缺点分别是什么,我将在本章中详细讲解。
Spring的依赖注入根据对象类型注入可以分为属性注入和对象注入,根据注入的方式可以分成xml配置文件注入和注解注入,其中xml有三种注入方式:
- 构造方法注入;
- setter方法注入;
- p名称空间注入。
注意:P名称空间为特殊的setter方法注入,因此也有人说xml有两种注入方式,本文为了详细介绍,所以分开讲解。
注解注入的方式分成三种:
- 构造方法注入;
- setter方法注入;
- 自动注入。
一、对象注入
1.1、xml文件注入
1.1.1、构造方法注入
public class A {
private B b;
private String name;
public void a(){
b.b();
System.out.println("A中a方法执行了,name=" + name);
}
public A(B b, String name) {
this.b = b;
this.name = name;
}
}
public class B {
private String name;
private Double age;
public B(String name, Double age) {
this.name = name;
this.age = age;
}
public void b(){
System.out.println("B中b方法执行了,name=" + name + ",age=" + age);
}
}
<!--创建B的Bean对象-->
<!--
constructor-arg:构造函数赋值,
注意:需要提供一个有参的构造函数
有以下取值:
name:构造函数所需要的参数名称
value:参数名称对应的值
ref:参数为其他bean
type:指定参数在构造函数中的数据类型
index:指定参数在构造函数中的索引位置 -->
<bean id="b" class="com.sxx.service.entity.B">
<constructor-arg name="name" value="张三"/>
<constructor-arg name="age" value="12.58"/>
</bean>
<bean name="a" class="com.sxx.service.entity.A">
<constructor-arg name="b" ref="b"/>
<constructor-arg name="name" value="王五"/>
</bean>
public class IocTest {
public static void main(String[] args) {
ApplicationContext applicationContext =
new ClassPathXmlApplicationContext("bean.xml");
A a = (A) applicationContext.getBean("a");
a.a();
}
}
B中b方法执行了,name=张三,age=12.58
A中a方法执行了,name=王五
1.1.2、set方法注入
public class A {
private B b;
private String name;
public void a(){
b.b();
System.out.println("A中a方法执行了,name=" + name);
}
}
public class B {
private String name;
private String age;
public void b(){
System.out.println("B中b方法执行了,name=" + name + ",age=" + age);
}
}
<!--创建B的Bean对象-->
<!--
property:赋值属性,有以下取值:
name:类中的属性值,实际找的是set方法对应的属性,比如setName -> name才能在此赋值,没有set方法会报错;
value:给属性赋值,基本数据和String类型的用此种方法;
ref:属性赋值是其他bean类型的
-->
<bean id="b" class="com.sxx.service.entity.B">
<property name="name" value="张三"/>
<property name="age" value="12"/>
</bean>
<bean name="a" class="com.sxx.service.entity.A">
<property name="name" value="李四"/>
<!--ref指向的是上面id="b"-->
<property name="b" ref="b"/>
</bean>
public class IocTest {
public static void main(String[] args) {
ApplicationContext applicationContext =
new ClassPathXmlApplicationContext("bean.xml");
A a = (A) applicationContext.getBean("a");
a.a();
B b = (B) applicationContext.getBean("b");
b.b();
}
}
结果如下:
B中b方法执行了,name=张三,age=12.58
A中a方法执行了,name=李四
B中b方法执行了,name=张三,age=12.58
1.1.3、P名称空间注入
public class A {
private B b;
private String name;
public void a(){
b.b();
System.out.println("A中a方法执行了,name=" + name);
}
}
public class B {
private String name;
private Double age;
public void b(){
System.out.println("B中b方法执行了,name=" + name + ",age=" + age);
}
}
<?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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
<bean id="a" class="com.sxx.service.entity.A"
p:name="李四" p:b-ref="b">
</bean>
<bean id="b" class="com.sxx.service.entity.B">
<property name="name" value="张三"/>
<property name="age" value="12.58"/>
</bean>
</beans>
public class IocTest {
public static void main(String[] args) {
ApplicationContext applicationContext =
new ClassPathXmlApplicationContext("bean.xml");
A a = (A) applicationContext.getBean("a");
a.a();
}
}
B中b方法执行了,name=张三,age=12.58
A中a方法执行了,name=李四
1.2、注解注入
1.2.1、构造方法注入
public class A {
private B b;
private String name;
public void a(){
b.b();
System.out.println("A中a方法执行了,name=" + name);
}
public A(B b, String name) {
this.b = b;
this.name = name;
}
}
public class B {
private String name;
private Double age;
public void b(){
System.out.println("B中b方法执行了,name=" + name + ",age=" + age);
}
public B(String name, Double age) {
this.name = name;
this.age = age;
}
}
@Configuration
public class BeanConfig {
@Bean("b")//指定name=b,而非默认B
public B B(){
return new B("张三", 12.4D);
}
@Bean
public A a(){
return new A(B(), "李四");
}
}
<?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
http://www.springframework.org/schema/context/spring-context.xsd">
<!--添加spring扫描,范围是com.sxx下所有包及其子包-->
<context:component-scan base-package="com.sxx"/>
</beans>
public class IocTest {
public static void main(String[] args) {
ApplicationContext applicationContext =
new ClassPathXmlApplicationContext("bean.xml");
A a = (A) applicationContext.getBean("a");
a.a();
}
}
B中b方法执行了,name=张三,age=12.4
A中a方法执行了,name=李四
1.2.2、setter方法注入
public class A {
private B b;
private String name;
public void a(){
b.b();
System.out.println("A中a方法执行了,name=" + name);
}
}
public class B {
private String name;
private Double age;
public void b(){
System.out.println("B中b方法执行了,name=" + name + ",age=" + age);
}
}
<!--添加spring扫描,范围是com.sxx下所有包及其子包-->
<context:component-scan base-package="com.sxx"/>
@Configuration
public class BeanConfig
@Bean
//如果没有指定A在Ioc容器中的key,默认为方法名
public B b(){
B b = new B();
b.setName("张三");
b.setAge(20D);
return b;
}
@Bean
//如果没有指定A在Ioc容器中的key,默认为方法名
public A a(){
A a = new A();
//引入的是上面的b()方法
a.setB(b());
a.setName("李四");
return a;
}
}
public class IocTest {
public static void main(String[] args) {
ApplicationContext applicationContext =
new ClassPathXmlApplicationContext("bean.xml");
A a = (A) applicationContext.getBean("a");
a.a();
}
}
B中b方法执行了,name=张三,age=20.0
A中a方法执行了,name=李四
1.2.3、自动注入
自动注入最具代表性的就是@Resource和@Autowired两个注解,在容器中只存在单一对象的时候,两个注解功能都相同,可以相互的替换,不影响使用。
但是如果存在多个对象的时候,@Autowired只根据type进行注入,不会去匹配name。如果涉及到type无法辨别注入对象时,那需要依赖@Qualifier或@Primary注解一起来修饰。
@Resource 是JDK1.6支持的注解, 默认按照name进行装配,名称可以通过name属性进行指定,如果没有指定name属性,当注解写在字段上时,默认取字段名,按照名称查找。如果注解写在setter方法上默认取属性名进行装配。当找不到与名称匹配的bean时才按照类型进行装配。但是需要注意的是,如果name属性一旦指定,就只会按照名称进行装配。
1.3、注解注入的另外一种形式
上面我们列举的注解注入的方法是通过在配置文件BeanConfig实现依赖注入的方式,还有另外一种方式可以直接在class文件中直接用@Resource或@Autowired直接引入。
1.3.1、构造方法注入
@Component
public class A {
private B b;
private String name;
public void a(){
b.b();
System.out.println("A中a方法执行了,name=" + name);
}
@Autowired
public A(B b) {
this.b = b;
}
}
@Component
public class B {
private String name;
private Double age;
public void b(){
System.out.println("B中b方法执行了,name=" + name + ",age=" + age);
}
}
<!--添加spring扫描,范围是com.sxx下所有包及其子包-->
<context:component-scan base-package="com.sxx"/>
public class IocTest {
public static void main(String[] args) {
ApplicationContext applicationContext =
new ClassPathXmlApplicationContext("bean.xml");
A a = (A) applicationContext.getBean("a");
a.a();
}
}
B中b方法执行了,name=null,age=null
A中a方法执行了,name=null
1.3.2、setter方法注入
@Component
public class A {
private B b;
private String name;
public void a(){
b.b();
System.out.println("A中a方法执行了,name=" + name);
}
@Autowired
public void setB(B b) {
this.b = b;
}
}
其他方法及结果参照1.3.1
1.3.3、直接注入
@Component
public class A {
@Autowired
private B b;
private String name;
public void a(){
b.b();
System.out.println("A中a方法执行了,name=" + name);
}
}
其他方法及结果参照1.3.1
二、参数注入
参数注入主要是通过@Value注入
@Component
public class A {
@Autowired
private B b;
@Value("李四")
private String name;
public void a(){
b.b();
System.out.println("A中a方法执行了,name=" + name);
}
}
@Component
public class B {
@Value("张三")
private String name;
@Value("20.4")
private Double age;
public void b(){
System.out.println("B中b方法执行了,name=" + name + ",age=" + age);
}
}
其他配置可以参照其他依赖注入的方式
结果为:
B中b方法执行了,name=张三,age=20.4
A中a方法执行了,name=李四
注意:@Value不仅可以自己配置属性还可以从配置文件或者配置中心中获取对应的属性
三、几种依赖注入的优缺点对比
3.1、构造器注入
优点:基于构造器注入,会固定依赖注入的顺序,不允许我们创建的bean对象之间存在循环依赖关系,这样Spring能解决循环依赖的问题。
缺点:使用构造器注入的缺点是,当我们构造器需要注入的对象比较多时,会显得我们的构造器,冗余,不美观,可读性差,也不易维护。
3.2、基于setter方法注入
优点:基于setter注入,只有对象是需要被注入的时候,才会注入依赖,而不是在初始化的时候就注入。
缺点:当我们选择setter方法来注入的时候,我们不能将对象设为final的;
3.3、基于自动注入
优点:在成员变量上写上注解来注入,这种方式,精短,可读性高,不需要多余的代码,也方便维护。
缺点:
1.这样不符合JavaBean的规范,而且很有可能引起空指针;
2.同时也不能将对象标为final的;
3.类与DI容器高度耦合,我们不能在外部使用它;
4.类不通过反射不能被实例化(例如单元测试中),你需要用DI容器去实例化它,这更像集成测试;