3.3 Bean的注入
在Bean的配置中介绍的是Bean的声明问题,即在哪声明、怎么声明的问题。本节将要讲解的Bean的注入是怎么实例化、怎么注入的问题。Bean注入的方式有两种:一种是在XML中配置,另一种是使用注解的方式注入。
3.3.1 XML方式注入
XML方式注入一般有三种方式:属性注入、构造函数注入和工厂方法注入。
1.属性注入
在传统的对象实例化时可以先使用new class(),然后通过setXXX()方法设置对象的属性值或依赖对象,属性注入也是采用这种方式,只是Spring框架会在内部完成这些操作,它会先调用Bean的默认构造函数实例化Bean对象,然后通过反射的方式调用Setter方法注入属性值。它会使用默认的构造函数(无参数构造函数),只需为注入的属性设置set方法,可选择性和灵活性比较高,所以也是比较常用的一种注入方式。这里还是在IOC章节人和空气的基础上稍作修改来演示。IAir接口和CleanAir、DirtyAir类不变,这里就不贴了。
(1)新建XMLInstance类
在XMLInstance类中并未声明构造函数,对于air属性只设置了set方法,get方法也没设置。
在XML中使用property类配置属性,name表示属性名,value用来设置基本数据类型的属性值。在Spring配置文件中,bean之间可以相互引用,引用时可以用<ref>标签配置bean的id属性。<ref>可以用在<property>属性中,也可以用在<construct-arg>构造函数的参数值中,还可以用在其他地方,通过引用能减少bean的声明。
2.构造函数注入
在属性注入时先使用默认的构造函数(无参数构造函数)实例化,然后通过set方法注入属性,在传统实例化对象时可以自定义构造函数进行实例化。构造函数注入就是通过自定义构造函数来进行对象的实例化。这里在XMLInstance类的基础上增加了一个构造函数,第一个参数是String类型的name,第二个参数是IAir类型的air。
在XML中,使用<construect-arg>来设置构造函数的参数,index属性设置参数的顺序,参数顺序应该与构造函数一致,ref设置引用bean的id,value设置构造函数参数的值。
3.工厂方法注入
工厂方法注入参考的是工厂设计模式,在工厂类中实现对象的实例化。工厂类负责创建一个或多个目标类实例,一般以接口或抽象类变量的形式返回目标类实例。工厂类对外屏蔽了目标类的实例化步骤,调用者甚至不用知道具体的目标类是什么。工厂方法也分静态工厂方法和非静态工厂方法。静态工厂方法不用实例化工厂类,直接通过类名调用。非静态工厂方法需要先实例化工厂类,然后通过工厂类对象调用获取对象。这里创建了一个工厂类XMLFactory,在类中定义了一个静态方法和一个实例方法(用来实例化bean对象)。
(1)静态工厂方法
只需设置工厂方法对应的类,以及对应的工厂方法。
<bean id="xmlfactorystaticinstance" class="com.demo.model.XMLFactory" factory-method="CreateStaticInstance"></bean>
(2)实例工厂方法
需要先实例化工厂类,再通过工厂类对象调用实例方法获取bean对象。
<bean id="xmlfactory" class="com.demo.model.XMLFactory"></bean> <bean id="xmlfactoryinstance" factory-bean="xmlfactory" factory-method="CreateInstance"></bean>
4.常见数据类型注入
(1)List属性注入
使用<list>配置java.util.List类型的属性。List属性中的元素可以是任何数据类型的值,如果是Java对象,可以使用ref指定,或使用<bean>定义新实例。如果是基础数据类型,可直接使用字符串。<list>中的元素会按配置的先后顺序排序。
(2)Set属性注入
使用<set>配置java.util.Set类型的属性。Set属性中的元素可以是任何数据类型的值,如果是Java对象,可以使用ref指定,或使用<bean>定义新实例。如果是基础数据类型,可直接使用字符串。<set>中的元素没有先后顺序。
(3)Map属性注入
使用<map>配置java.util.Map类型的属性。<entry>配置Map里的元素,key指定索引,value指定值。如果是Java对象,可以使用ref指定,或使用<bean>定义新实例。
(4)Properties属性注入
使用<props>配置java.util.Properties类型的属性。<props>配置一个Properties对象,<prop>配置一条属性,属性key配置索引。
(5)自定义属性编辑器
有一些属性是没法注入的,此时就需要自定义,比如日期类型。可以通过继承PropertyEditorSupport的类,重写setAsText方法来实现注入。这里定义了CustomerProperty继承PropertyEditorSupport,重写了setAsText方法,并将该bean配置到XML中。
配置之后就可以注入Date类型的属性了。
<property name="date" value="2018-8-20"/>
新建XmlCollectionsDemo类,配置上面几个类型的属性来演示。
下面是属性对应的XML配置文件。
通过运行main方法,打印出属性值:
[1, CleanAir, CleanAir] {prokey2=prokeyB, prokey1=prokeyA} [1, CleanAir, CleanAir] {key1=1, key2=CleanAir, key3=CleanAir} Mon Aug 20 00:00:00 CST 2018
5.初始化函数、销毁函数
通过上面几种注入方式的学习,对通过XML对bean实例化有了了解。有的对象在实例化之后还需要执行某些初始化代码,但这些初始化代码还不能写在构造函数中,此时可以将初始化代码写到某个方法中。将init-method属性值设置为该方法,Spring会强制执行该方法进行初始化。有的对象在使用完毕之后需要释放,可以使用destroy-method来进行销毁。
这里先在XMLInstance类中增加上面两个方法来模拟销毁和初始化方法,然后在xml配置bean时设置destroy-method、init-method属性值对应两个方法的方法名。注解中的@PostConstruct对应init-method、@PreDestory对应destroy-method。
<bean id="xmlfactoryinstance" factory-bean="xmlfactory" factory-method= "CreateInstance" destroy-method="DestoryMethod" init-method="InitMethod"> </bean>
3.3.2 注解方式注入
1.常用注解介绍
学习完XML注入之后再学习注解方式注入就容易得多了,注解方式注入主要涉及@Autowired、@Resource、@Required、@Qualifier、@Value这几个注解。在2.2.4节定义Person时就使用过@Autowired、@Qualifier。下面来了解一下它们的具体用法。
● @Autowired:默认按类型匹配注入bean,可以对类成员变量、方法及构造函数进行标注,完成自动装配的工作。在使用@Autowired时,首先在容器中查询对应类型的bean。如果查询结果刚好为一个,就将该bean装配给@Autowired指定的数据;如果查询的结果不止一个,那么@Autowired会根据名称来查找。如果查询的结果为空,那么会抛出异常。解决方法时,使用required=false。
● @Required:适用于bean属性setter方法,并表示受影响的bean属性必须在XML配置文件在配置时进行填充;否则,容器会抛出一个BeanInitializationException异常。
● @Qualifier:@Autowired默认是单实例的,但是在面向接口编程中,如果把一个属性设置为接口类型,一个接口就可能有多个实现,那么到底注入哪一个呢?为了解决这个问题,就有了@Qualifier。
● @Value:在xml配置属性时可以通过property的value设置默认值,@Value也可以为属性设置默认值。
● @Resource:默认按名称匹配注入bean。要求提供一个bean名称的属性,如果属性为空,就自动采用标注处的变量名或方法名作为bean的名称。如果我们没有在使用@Resource时指定bean的名字,同时Spring容器中又没有该名字的bean,这时@Resource就会退化为@Autowired,即按照类型注入。
在上面的代码中,使用@Value注解为name设置了默认值,使用@Resources设置bean的名称为IAir属性注入bean,也可以使用@Autowired+@Qualifier为IAir注入bean。
2.开启注解
配置完注解之后,还要告诉Spring开启注解,这样@Autowired、@Resources这些注解才起作用。开启注解有两种比较简单的方式。
(1)在xml配置文件中使用context:annotation-config:
<context:annotation-config />
(2)在xml配置文件中使用context:component-scan:
<context:component-scan base-package="com.demo.model"/>