写在前面
小伙伴儿们,Spring 的依赖注入以及 Bean 的装配是面试常问的知识点,今天我们来学习一下Spring中的依赖注入方式,以及如何将自己开发的Bean装配到Spring IoC容器中。
思维导图
setter注入是Spring中最主流的注入方式,它利用Java Bean规范所定义的setter方法来完成注入,灵活且可读性高。
比方说我刚开始输出一个对象的话,看代码:
package com.java.entity;
public class People {
private int id;
private String name;
private int age;
public People() {
//调用默认的构造方法
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="people" class="com.java.entity.People"></bean>
</beans>
package com.java.test;
import com.java.entity.People;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Test2 {
public static void main(String[] args) {
//加载beans.xml文件,调用Spring接口
ApplicationContext ac=new ClassPathXmlApplicationContext("beans.xml");
//通过id获取bean,返回一个对象
People people=(People)ac.getBean("people");
//调用方法
System.out.println(people);
}
}
我们看运行结果:
那么,setter注入又是怎么回事呢?
setter注入就是可以在beans配置文件中主动加入属性,以此来改变输出对象的特点;
public People() {
//调用默认的构造方法
}
@Override
public String toString() {
return "People{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
'}';
}
//属性注入
<bean id="people2" class="com.java.entity.People">
<property name="id" value="1"></property>
<property name="age" value="18"></property>
<property name="name" value="张三"></property>
</bean>
//属性注入
People people2=(People)ac.getBean("people2");
System.out.println(people2);
我们看运行效果:
构造器注入依赖于构造方法实现,而构造方法可以是有参数或者无参数。在大部分的情况下,我们都是通过类的构造方法来创建对象,Spring也可以采用反射的方式,通过使用构造方法来完成注入,这就是构造器注入的原理。
我们来看看实例:
public People(int id, String name, int age) {
this.id = id;
this.name = name;
this.age = age;
}
<!--类型注入-->
<bean id="people3" class="com.java.entity.People">
<constructor-arg type="int" value="2"></constructor-arg>
<constructor-arg type="String" value="李四"></constructor-arg>
<constructor-arg type="int" value="19"></constructor-arg>
</bean>
<!--索引注入-->
<bean id="people4" class="com.java.entity.People">
<constructor-arg index="0" value="3"></constructor-arg>
<constructor-arg index="1" value="王五"></constructor-arg>
<constructor-arg index="2" value="20"></constructor-arg>
</bean>
<!--联合使用-->
<bean id="people5" class="com.java.entity.People">
<constructor-arg type="int" index="0" value="4"></constructor-arg>
<constructor-arg type="String" index="1" value="赵六"></constructor-arg>
<constructor-arg type="int" index="2" value="21"></constructor-arg>
</bean>
//类型注入
People people3=(People)ac.getBean("people3");
System.out.println(people3);
//索引注入
People people4=(People)ac.getBean("people4");
System.out.println(people4);
//联合使用
People people5=(People)ac.getBean("people5");
System.out.println(people5);
运行结果:
前面已经介绍了Spring IoC的理念和设计,现在我们来学习一下如何将自己开发的Bean装配到Spring IoC容器中;
大部分场景下,我们都会使用 ApplicationContext 的具体实现类,因为对应的 Spring IoC 容器功能相对强大。
而在 Spring 中提供了 3 种方法进行配置:
在现实的工作中,这 3 种方式都会被用到,并且在学习和工作之中常常混合使用,所以这里给出一些关于这 3 种优先级的建议:
(1)最优先:通过隐式 Bean 的发现机制和自动装配的原则。
(2)其次:Java 接口和类中配置实现配置
(3)最后:XML 方式配置
使用 XML 装配 Bean 需要定义对应的 XML,这里需要引入对应的 XML 模式(XSD)文件,这些文件会定义配置 Spring Bean 的一些元素,比方说当我们在 IDEA 中创建 XML 文件时,就会有对应的配置文件(Spring Config)提示,也就是上面所说的XSD文件:
<?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="work" class="com.java.service.Work">
<property name="jiekou" ref="b"></property>
</bean>
</beans>
我们在这里面需要引入 beans
的定义,引入xsd
文件,它是一个根元素,这样它所定义的元素将可以定义对应的 Spring Bean;
id
:对象的唯一标识;class
:bean的完全限定名称,从包名称到类名称;property
:给属性赋值,name
的名称取决于set()
方法后面的参数,ref
引用具体的值;这里的详细在我上一篇文章中有详解,这里就不再赘述了。
有些时候需要做一些复杂的装配工作,比如 Set、Map、List、Array 和 Properties 等,为此我们新建一个 ComplexAssembly 类:
package com.java.ssm;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
public class ComplexAssembly {
private Long id;
private List<String> list;
private Map<String, String> map;
private Properties properties;
private Set<String> set;
private String[] array;
}
这个 Bean 没有任何的实际意义,只是为了介绍如何装配这些常用的集合类:
<bean id="complexAssembly" class="com.java.ssm.ComplexAssembly">
<property name="id" value="1"/>
<property name="list">
<list>
<value>value-list-1</value>
<value>value-list-2</value>
<value>value-list-3</value>
</list>
</property>
<property name="map">
<map>
<entry key="key1" value="value-key-1"/>
<entry key="key2" value="value-key-2"/>
<entry key="key3" value="value-key-2"/>
</map>
</property>
<property name="properties">
<props>
<prop key="prop1">value-prop-1</prop>
<prop key="prop2">value-prop-2</prop>
<prop key="prop3">value-prop-3</prop>
</props>
</property>
<property name="set">
<set>
<value>value-set-1</value>
<value>value-set-2</value>
<value>value-set-3</value>
</set>
</property>
<property name="array">
<array>
<value>value-array-1</value>
<value>value-array-2</value>
<value>value-array-3</value>
</array>
</property>
</bean>
总结:
<list>
元素进行装配,然后通过多个 <value>
元素设值。<map>
元素进行装配,然后通过多个 <entry>
元素设值,只是 entry
包含一个键值对(key-value)的设置<properties>
元素进行装配,通过多个 <property>
元素设值,只是 properties
元素有一个必填属性 key
,然后可以设置值<set>
元素进行装配,然后通过多个 <value>
元素设值<array>
设置值,然后通过多个 <value>
元素设值。集合注入总结:
<list>
元素定义注入,使用多个 <ref>
元素的 Bean 属性去引用之前定义好的 Bean<map>
元素定义注入,使用多个 <entry>
元素的 key-ref
属性去引用之前定义好的 Bean 作为键,而用 value-ref
属性引用之前定义好的 Bean 作为值<set>
元素定义注入,使用多个 <ref>
元素的 bean
去引用之前定义好的 Bean通过上面的学习,我们已经了解了如何使用 XML 的方式去装配 Bean,但是更多的时候已经不再推荐使用 XML 的方式去装配 Bean,更多的时候会考虑使用注解(annotation) 的方式去装配 Bean。
优点:
减少XML配置,注解功能更为强大,既能实现 XML 的功能,也提供了自动装配的功能,采用了自动装配后,程序员所需要做的决断就少了,更加有利于对程序的开发,这就是“约定优于配置
”的开发原则。
在 Spring 中,它提供了两种方式来让 Spring IoC 容器发现 Bean;
@Component
装配Bean举个栗子,我们创建一个People类:
package com.java.test;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component(value="people")
public class People {
@Value("1")
private int id;
@Value("jack")
private String name;
}
注解的相关解释:
value
属性代表这个类在 Spring 中的 id
,这就相当于在 XML 中定义的 Bean 的 id:<bean id="people" class="com.java.test.People" />
,也可以简写成 @Component("people")
,甚至直接写成 @Component
,对于不写的,Spring IoC 容器就默认以类名来命名作为 id
,只不过首字母小写,配置到容器中。value
属性是一样的。这样我们就创建好了一个Bean,相当于XML中类似的配置:
<bean id="people" class="com.java.test.People">
<property name="id" value="1" />
<property name="name" value="jack"/>
</bean>
现在有了这个类,但是还不能进行测试,因为Spring IoC并不知道需要去哪里扫描对象,这个时候我们可以使用一个 PeopleConfig 类去告诉 Spring IoC :
package com.java.test;
import org.springframework.context.annotation.ComponentScan;
@ComponentScan
public class PeopleConfig {
}
这个类十分简单,没有任何逻辑,但是需要说明两点:
@Component
注解的类。这样一来,我们就可以通过 Spring 定义好的 Spring IoC 容器的实现类——AnnotationConfigApplicationContext 去生成 IoC 容器了:
ApplicationContext context = new AnnotationConfigApplicationContext(PeopleConfig.class);
People people = (People) context.getBean("people", People.class);
people.printInformation();
这里可以看到使用了 AnnotationConfigApplicationContext 类去初始化 Spring IoC 容器,它的配置项是 PeopleConfig 类,这样 Spring IoC 就会根据注解的配置去解析对应的资源,来生成 IoC 容器了。
所谓自动装配技术是一种由 Spring 自己发现对应的 Bean,自动完成装配工作的方式,它会应用到一个十分常用的注解 @Autowired
上,这个时候 Spring 会根据类型去寻找定义的 Bean 然后将其注入,举个实例看看:
1,先创建一个PeopleService接口:
package com.java.service;
public interface PeopleService {
public void printPeopleInfo();
}
2,为上面接口创建一个PeopleServiceImp实现类:
package com.java.service;
import com.java.test.People;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component("peopleService")
public class PeopleServiceImp implements PeopleService{
@Autowired
private People people=null;
public void printPeopleInfo(){
System.out.println("人的id为:"+people.getId());
System.out.println("人的name为:"+people.getName());
}
}
该实现类实现了接口的 printPeopleInfo() 方法,打印出成员对象 people的相关信息,这里的 @Autowired
注解,表示在 Spring IoC 定位所有的 Bean 后,这个字段需要按类型注入,这样 IoC 容器就会寻找资源,然后将其注入。
3,测试类:
修改PeopleConfig类,告诉Spring IoC的扫描路径:
package com.java.test;
import org.springframework.context.annotation.ComponentScan;
@ComponentScan(basePackages = {"com.java.test","com.java.service"})
public class PeopleConfig {
}
然后是测试函数:
package com.java.service;
import com.java.test.People;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component("peopleService")
public class PeopleServiceImp implements PeopleService{
@Autowired
private People people=null;
public void printPeopleInfo(){
System.out.println("人的id为:"+people.getId());
System.out.println("人的name为:"+people.getName());
}
}
运行结果:
@Autowired
注解表示在 Spring IoC 定位所有的 Bean 后,再根据类型寻找资源,然后将其注入。required
来改变,比如 @Autowired(required = false)
@Autowired
注解不仅仅能配置在属性之上,还允许方法配置,常见的 Bean 的 setter 方法也可以使用它来完成注入,总之一切需要 Spring IoC
去寻找 Bean 资源的地方都可以用到,例如:
public class Spring {
......
@Autowired
public void setSource(Source source) {
this.source = source;
}
}
在大部分的配置中都推荐使用@Autowired
自动注入来完成,这是 Spring IoC
帮助我们自动装配完成的,这样使得配置大幅度减少,满足约定优于配置的原则,增强程序的健壮性。
本文我们学会了装配Bean的常用方式,常用的是通过注解装配Bean,也就是基于约定优于配置的原则。后面我们继续来探讨一下Bean的作用域与生命周期等流程。