Bean的初始化
bean的定义(通过xml、注解、java配置)会被封装成BeanDefinition对象,该对象包括如下元数据:
1、类的全限定名
2、Bean行为配置元素,例如(scope、lifecycle callbacks等)
3、对于其他bean的依赖
4、其他配置信息,例如数据库连接池中连接的个数
BeanDefinition包括的这些元数据可用于描述一个Bean,容器在构建bean的时候,描述一个Bean所需要的信息如下:
- class:类的全限定名,通过类的全限定名,容器可以反射类的构造函数,总而创建实例,如果这个类是一个工厂类,并且工厂方法是静态的,我们也可以指定容器通过类的全限定名来调用工厂静态方法创建实例,如果我们想创建静态实名内部类的bean,class的值为外部类的全限定名$内部类类名,例如com.example.Foo$Bar
- name:除了id以外,可以使用name来为bean定义别名,不同于id,用name定义的别名可以有多个,别名与别名之间使用空格、逗号、分号来分割,可以通过id和name来引用bean,如果两者都不指定,则IOC容器会分配一个名字(类的全限定名+‘#’+一个数字(从0开始))给这个bean,定义别名格式有两种:
1、直接使用name:
<bean id="SystemA" class="..." name="SystemB">
</bean>
2、使用alias:
<bean id="Myapp" class="...">
<alias name="Myapp" alias="SystemA"/>
<alias name="Myapp", alias="SystemB"/>
</bean>
注意alias中的name必须与id同名
- scope:Bean的范围
- constructor argument:构造函数参数
- properties:所含有的属性
- autowiring mode:自动装配的方式(根据类型、名字等进行自动装配)
- lazy—initialization mode:延迟实例化,即在需要使用的时候实例化,而不是容器初始化时初始化
- initialization method:初始化方法(不是构造函数)
- destruction method:销毁方法
Bean的初始化有两种方式:
1、通过构造函数:
spring可以管理任何形式的bean,不局限于javaBean(即提供一个默认构造函数,给每个属性一个get和set方法)
2、通过静态的工厂方法:
配置如下:
<bean id="clientService"
class="examples.ClientService"
factory-method="createInstance"/>
factory-method指定静态工厂方法名,本例中为createInstance:
public class ClientService {
private static ClientService clientService = new ClientService();
private ClientService() {}
public static ClientService createInstance() {
return clientService;
}
}
3、通过非静态工厂方法
首先在容器中声明一个工厂bean:
<!-- the factory bean, which contains a method called createInstance() -->
<bean id="serviceLocator" class="examples.DefaultServiceLocator">
<!-- inject any dependencies required by this locator bean -->
</bean>
接着声明一个工厂方法bean,设置factory-bean为工厂bean的id,设置factory-method为工厂bean的工厂方法
<!-- the bean to be created via the factory bean -->
<bean id="clientService"
factory-bean="serviceLocator"
factory-method="createClientServiceInstance"/>
一个工厂bean可以有多个工厂方法bean:
<bean id="serviceLocator" class="examples.DefaultServiceLocator">
<!-- inject any dependencies required by this locator bean -->
</bean>
<bean id="clientService"
factory-bean="serviceLocator"
factory-method="createClientServiceInstance"/>
<bean id="accountService"
factory-bean="serviceLocator"
factory-method="createAccountServiceInstance"/>
Bean的依赖注入
什么是依赖注入:一般情况下,为了完成某件事情,需要多个对象进行协作,例如交女朋友,需要查阅追女孩子攻略的电脑对象,需要约会场所对象,例如电影院对象,需要有线上聊天的手机对象,而单身狗对象需要电脑对象、电影院对象、手机对象才能完成追女孩子这个任务,实例化的电脑对象、电影院对象、手机对象即为依赖,IOC容器分配这些依赖给单身狗对象,这就是依赖注入
依赖注入的方式有两种——构造函数、setter方法
一、构造函数
实现原理:通过配置信息和构造函数参数类型的比对,如果没有歧义,就调用依赖的构造函数创建对象
package x.y;
public class Foo {
public Foo(Bar bar, Baz baz) {
// ...
}
}
对于上面的例子来说,Bar、Baz实例化的对象就是Foo的依赖,我们可以在配置文件中说明Foo的依赖:
<beans>
<bean id="foo" class="x.y.Foo">
<constructor-arg ref="bar"/>
<constructor-arg ref="baz"/>
</bean>
<bean id="bar" class="x.y.Bar"/>
<bean id="baz" class="x.y.Baz"/>
</beans>
ref的值为bean的id名或是name名,通过解析ref,就可以知道依赖的类型,构造函数参数的类型与依赖类型比对,如果没有歧义(即不存在多个ref均可匹配一个构造函数参数类型的情况)就创建对象进行依赖注入,需要注意,如果Bar和Baz通过继承同一个类(接口)而联系在一起,则根据自己写的代码形式,需要在<constructor-arg>中使用index或是type,指明构造函数的参数顺序或是参数类型(否则,可能会出现歧义)。
对于基本数据类型或是String类型,可以直接通过value属性给出值,进行实测以后,我发现Spring会根据配置的constructor-arg的顺序依次赋值给String和基本数据类型,我们也可以不遵循这种规则,文档给出的例子如下:
package examples;
public class ExampleBean {
// Number of years to calculate the Ultimate Answer
private int years;
// The Answer to Life, the Universe, and Everything
private String ultimateAnswer;
public ExampleBean(int years, String ultimateAnswer) {
this.years = years;
this.ultimateAnswer = ultimateAnswer;
}
}
1、通过type属性
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg type="int" value="7500000"/>
<constructor-arg type="java.lang.String" value="42"/>
</bean>
2、通过index属性
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg index="0" value="7500000"/>
<constructor-arg index="1" value="42"/>
</bean>
构造函数有多个相同类型的参数时,可以通过index属性来消除歧义
3、通过name属性,其值为构造函数参数名
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg name="years" value="7500000"/>
<constructor-arg name="ultimateAnswer" value="42"/>
</bean>
二、Setter方法
上述方式是通过构造函数完成依赖注入,如果我们通过无参构造函数或是静态的无参工厂方法来实例化一个bean,在bean实例化后,容器调用setter方法完成依赖注入(即javaBean中的setxxx方法),如下:
public class SimpleMovieLister {
// the SimpleMovieLister has a dependency on the MovieFinder
private MovieFinder movieFinder;
// a setter method so that the Spring container can inject a MovieFinder
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// business logic that actually uses the injected MovieFinder is omitted...
}
需要在配置文件中使用property标签指明设置的属性,例如:
<?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="simplemovielistener" class="com.example.SimpleMovieListener">
<property name="movieFinder" ref="movieFinder"/>
</bean>
<bean id="movieFinder" class="com.example.MovieFinder"/>
</beans>
我们可以混合使用构造函数和setter方法进行依赖注入,即一部分属性通过构造函数进行依赖注入,另一部分在bean实例化后通过setter方法依赖注入,
(e.g 我们可以使用PropertyEditor实例实现属性格式的转换,常和BeanDefinition一起结合使用)
什么时候使用构造函数什么时候使用setter方法?
官方是这么建议的——不可变的依赖项使用构造函数,可变依赖项使用setter方法,以setter方法注入依赖要时刻判断依赖是否为空(由于可以手动更改依赖的值),需要注意,使用构造函数注入依赖可能会出现循环依赖,即class A的构造函数需要class B,class B的构造函数需要class A,可以使用setter注入来解决(由于setter是实例化一个类后在调用)
依赖解析过程
通过描述bean的配置元数据(xml、java、注解)创建并初始化ApplicationContext,容器初始化时会验证这些元数据,在创建bean时,容器将会进行检查(依赖是否不存在,是否存在循环依赖,依赖解析是否存在歧义等),如果检查到错误,将会抛出BeanCurrentlyInCreationException异常,当容器创建时,配置为单例和优先初始化(默认为优先初始化)的bean将会被创建,其余的bean将会在第一次使用的时候创建