Spring之IOC的注入方式
在java中,要使用一个对象,必须先创建一个实例,但是有了IOC之后,对象的创建与销毁都交给了IOC容器,不用我们手动创建,而是直接从IOC容器中获取,达到了解耦的效果。IOC是一种思想,在Spring中,实现IOC的方式是DI(依赖注入),本文会介绍Spring依赖注入的几种方式。
Spring的依赖注入
对象,在Spring中叫做bean,即使是最简单的应用,也需要多个bean共同协作。依赖注入是指对象之间的依赖关系,一起协作的其他对象,通过构造器的参数、工厂方法的参数创建的对象,或者构造函数、工厂方法创建的对象来设置属性。所以容器的工作实际上就是创建bean并注入依赖关系。Spring中的DI方式主要有两种,构造器注入和Setter注入。
项目准备
新建一个maven项目,JDK版本1.8,引入Spring的核心依赖
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>${spring.version}</version>
</dependency>
</dependencies>
构造器注入
新建一个User类作为注入的例子,添加get,set方法,重写toString方法,添加两个参数不同的构造器。
public class User { private String id; private String name; private Integer age; public User(String id, String name) { this.id = id; this.name = name; } public User(String name, Integer age) { this.name = name; this.age = age; } public String getId() { return id; } public void setId(String id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } @Override public String toString() { return "User{" + "id='" + id + '\'' + ", name='" + name + '\'' + ", age=" + age + '}'; } }
xml配置如下,有三种不同的写法
在resource下面新建application-constructor.xml<?xml version="1.0" encoding="UTF-8"?> <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.springframework.org/schema/beans" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!--构造方法注入--> <!--使用index属性来显式指定构造参数的索引--> <bean id="user1" class="org.spring.ioc.entity.User"> <constructor-arg index="0" value="1234"/> <constructor-arg index="1" value="spring"/> </bean> <!--使用type属性显式指定简单类型的构造器参数类型,这里对应的是User类中传入name,age的构造器--> <bean id="user2" class="org.spring.ioc.entity.User"> <constructor-arg type="java.lang.String" value="spring"/> <constructor-arg type="java.lang.Integer" value="20"/> </bean> <!--也可以使用构造器参数命名来指定值的类型--> <bean id="user3" class="org.spring.ioc.entity.User"> <constructor-arg name="id" value="1234"/> <constructor-arg name="name" value="spring"/> </bean> </beans>
在bean的constructor-arg元素下进行指定,constructor-arg顾名思义就是构造器参数的意思,其中包括了三个属性配置
- index 是一个索引顺序,对应构造器参数的索引,根据索引进行注入
- type 构造器的参数类型,可以通过类型进行匹配注入
- name 构造器参数名,根据名称进行匹配注入
验证
现在可以启动Spring容器来验证bean是否注入成功public class Main { private final static String APPLICATION = "classpath:application-*.xml"; public static void main(String[] args) { //加载xml配置文件 ApplicationContext context = new ClassPathXmlApplicationContext(APPLICATION); constructorInject(context); } private static void constructorInject(ApplicationContext context) { //获取bean实例,传入的参数值为xml中配置的id User user1 = (User) context.getBean("user1"); System.out.println(user1.toString()); User user2 = (User) context.getBean("user2"); System.out.println(user2.toString()); User user3 = (User) context.getBean("user3"); System.out.println(user3.toString()); } }
输出结果如下:
User{id='1234', name='spring', age=null}
User{id='null', name='spring', age=20}
User{id='1234', name='spring', age=null}
可以看出我们定义的User对象已经成功交给Spring容器管理
Setter注入
Setter注入也需要在xml中进行配置,在调用了无参的构造方法或者无参的静态工厂方法实例化bean之后,容器通过回调bean的setter方法来完成setter注入。
接下来新建Blog,Author两个类,添加get,set方法,重写toString方法:
Blog类:public class Blog { private String name; private String content; private Long date; private Author author; public String getName() { return name; } public void setName(String name) { this.name = name; } public String getContent() { return content; } public void setContent(String content) { this.content = content; } public Long getDate() { return date; } public void setDate(Long date) { this.date = date; } public Author getAuthor() { return author; } public void setAuthor(Author author) { this.author = author; } @Override public String toString() { return "Blog{" + "name='" + name + '\'' + ", content='" + content + '\'' + ", date=" + date + ", author=" + author + '}'; } }
Author类:
public class Author { private String name; private Integer age; private String url; public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } public String getUrl() { return url; } public void setUrl(String url) { this.url = url; } @Override public String toString() { return "Author{" + "name='" + name + '\'' + ", age=" + age + ", url='" + url + '\'' + '}'; } }
xml配置
setter注入是通过在bean下面配置property元素来完成的,在resource下面新建application-setter.xml<?xml version="1.0" encoding="UTF-8"?> <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.springframework.org/schema/beans" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!--setter注入--> <bean id="blog" class="org.spring.ioc.entity.Blog"> <property name="name" value="spring-ioc"/> <property name="content" value="spring"/> <property name="date" value="1520232449944"/> <property name="author" ref="author"/> </bean> <bean id="author" class="org.spring.ioc.entity.Author"> <property name="name" value="luoliang"/> <property name="age" value="18"/> <property name="url" value="https://luoliangdsga.github.io"/> </bean> </beans>
配置很简单,通过制定property元素的name和value属性,设置变量名对应的值,其中author属性引用的另一个bean,所以使用了ref属性。
验证
public class Main { private final static String APPLICATION = "classpath:application-*.xml"; public static void main(String[] args) { //加载xml配置文件 ApplicationContext context = new ClassPathXmlApplicationContext(APPLICATION); setterInject(context); } private static void setterInject(ApplicationContext context) { Blog blog = (Blog) context.getBean("blog"); System.out.println(blog.toString()); } }
结果如下:
Blog{name='spring-ioc', content='spring', date=1520232449944, author=Author{name='luoliang', age=18, url='https://luoliangdsga.github.io'}}
所有我们配置的属性都注入成功。
总结
我们可以混合使用构造器注入和Setter注入,最佳实践是强制性依赖关系时使用构造器注入,可选的依赖关系时使用Setter注入,在setter注入中可以使用@Required注解让属性成为必须的依赖项。
有很多小伙伴会觉得很奇怪,明明使用注解进行配置依赖更加的简单。不否认,SpringBoot推出之后,Spring已经不再推荐xml配置,而是提倡java配置和注解,但是xml配置是Spring的基础。正是因为传统的Spring应用xml配置太过于复杂,才会出现SpringBoot这门技术来解决这些问题,一个技术的兴起是有各种原因的。SpringBoot虽然解决了配置复杂的问题,但是对于刚入门的人来说,不知道其中的细节,这可能并不是一个好的开始。
上面用到的ApplicationContext,它所管理的beans支持构造函数注入和setter注入,在一些依赖已经使用构造器注入之后它还支持setter注入。我们也可以用BeanDefinition的形式配置依赖,它能根据指定的PropertyEditor实现将属性从一种格式转化为另外一种格式。但是,在日常的开发中我们不会直接以编程的方式去创建bean,而是采用上面所讲的xml配置创建bean,或者是通过注解(即@Component,@Service等注解类),或者基于@Configuration类的@Bean方法。本质上这些资源会转换成BeanDefinition的实例并且用于加载整个Spring IoC容器实例。所以,不管我们在传统Spring应用还是SpringBoot中,使用Spring的IOC,它的原理都是不会变的。
源码
上面所用到的代码我已放在我的github上,欢迎star,一起学习,共同进步,如有不对之处,欢迎指出。