在上篇文章中我着重介绍了Spring的控制反转和依赖注入的概念,那么依赖注入有那几种方式呢?他们的优缺点分别是什么,我将在本章中详细讲解。
Spring的依赖注入根据对象类型注入可以分为属性注入和对象注入,根据注入的方式可以分成xml配置文件注入和注解注入,其中xml有三种注入方式:
注意:P名称空间为特殊的setter方法注入,因此也有人说xml有两种注入方式,本文为了详细介绍,所以分开讲解。
注解注入的方式分成三种:
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);
}
}
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=王五
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);
}
}
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
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);
}
}
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=李四
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(), "李四");
}
}
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=李四
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);
}
}
@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=李四
自动注入最具代表性的就是@Resource和@Autowired两个注解,在容器中只存在单一对象的时候,两个注解功能都相同,可以相互的替换,不影响使用。
但是如果存在多个对象的时候,@Autowired只根据type进行注入,不会去匹配name。如果涉及到type无法辨别注入对象时,那需要依赖@Qualifier或@Primary注解一起来修饰。
@Resource 是JDK1.6支持的注解, 默认按照name进行装配,名称可以通过name属性进行指定,如果没有指定name属性,当注解写在字段上时,默认取字段名,按照名称查找。如果注解写在setter方法上默认取属性名进行装配。当找不到与名称匹配的bean时才按照类型进行装配。但是需要注意的是,如果name属性一旦指定,就只会按照名称进行装配。
上面我们列举的注解注入的方法是通过在配置文件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);
}
}
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
@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
@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不仅可以自己配置属性还可以从配置文件或者配置中心中获取对应的属性
优点:基于构造器注入,会固定依赖注入的顺序,不允许我们创建的bean对象之间存在循环依赖关系,这样Spring能解决循环依赖的问题。
缺点:使用构造器注入的缺点是,当我们构造器需要注入的对象比较多时,会显得我们的构造器,冗余,不美观,可读性差,也不易维护。
优点:基于setter注入,只有对象是需要被注入的时候,才会注入依赖,而不是在初始化的时候就注入。
缺点:当我们选择setter方法来注入的时候,我们不能将对象设为final的;
优点:在成员变量上写上注解来注入,这种方式,精短,可读性高,不需要多余的代码,也方便维护。
缺点:
1.这样不符合JavaBean的规范,而且很有可能引起空指针;
2.同时也不能将对象标为final的;
3.类与DI容器高度耦合,我们不能在外部使用它;
4.类不通过反射不能被实例化(例如单元测试中),你需要用DI容器去实例化它,这更像集成测试;