spring framework 4 学习之路 3 -- bean
Spring Ioc容器管理了一个或多个beans。这些beans通过配置元数据被创建。这些元数据通过XML中<bean/>定义提供给容器。
在容器内部,这些bean定义被表达为BeanDefinition对象,该对象包含了如下信息:
1.包完全限定类名:所定义的bean实际的实现类;
2.Bean行为配置元素,表达了bean在容器中如何工作(scope, lifecycle callbacks等等);
3.指向其他beans,当前bean需要依赖这些beans工作;这些引用也被称为协作者或者依赖;
4.其他配置用来设置在新创建的对象中,比如,bean中连接数用来管理连接池,或者限制连接池大小。
这些元数据翻译成一系列属性,构成了每一个bean定义。
除了bean定义包含了如何去创建一个特定的bean信息外,ApplicationContext实现同样允许注册由容器外创建的对象。实现方式是通过getBeanFactory()方法获取ApplicationContext的BeanFactory。getBeanFactory()方法返回BeanFactory实现DefaultListableBeanFactory。DefaultListableBeanFactory支持通过方法registerSingleton(..)和registerBeanDefinition(..)进行注册。但是,一般情况下应用都是通过元数据bean定义来处理bean的。
Bean元数据和单例应当尽可能早的被注册到容器中,使得容器能够在autowiring和其他内建步骤里能够被合适加载。虽然一定程度上支持已经存在的元数据和单例重载,但是运行时新的beans注册并不被官方支持,而且有可能导致并发获取异常或者容器不一致状态异常。
1.beans命名
每个bean都有一个或多个标识。这些标识必须在包含bean的容器中是唯一的。一个bean一般情况下只有一个标识,但是如果需要多个的话,其他的标识可以考虑通过别名(aliases)来实现。
在基于XML的配置元数据中,你可以使用id或者name属性来确定bean的标识。id属性允许你精确定义一个id。id名称都是字符数字的,但是也有可能会包含特殊字符。如果你希望对bean引入其他别名,你也可以在name属性定义,通过用,或者; 或者空格符来进行分隔。在早于Spring3.1的版本中,id属性被定义为xsd:ID类型,可能会限制一些字符。在3.1中,它被定义为xsd:string类型。注意bean id的唯一性是由容器强制要求的,不是XML解析器要求的。
对于每个bean,并不要求都提供id或者name。如果一个bean的id或者name没有被显性提供,那么容器会为这个bean生成一个唯一的名称。但是,如果你想通过名称引用bean,通过ref标签元素或者服务定位方式查询,你就必须提供一个名称。不需要提供bean名称一般都是针对内部beans或者autowiring协作者。
在类路径下扫描组件时,Spring会为未命名的组件生成bean名称,基于以下规则:一般的,取简单类名,同时初始字符小写。但是在一些特殊情况下,当类名多于一个字符,同时第一个字符和第二个字符都是大写情况下,最初的形式会被保留下来。这些规则定义在java.beans.Introspector.decapitalize。
在一个bean定义中,你可以为bean提供不止一个名字,一个由id属性精确定义的名称,其他任意多个由name属性定义的名称。这些名称都是对同一个bean的别名,在一些情况下会比较有用,比如允许一个应用中每个组件通过bean名称指向同一个公共依赖。
但是,在bean定义的地方声明所有的别名并不总是合适的。有些需要在其他地方为一个bean定义别名。假如在一个大型系统里,配置分散在各个子系统,每个子系统有自己的对象定义集。在基于XML的配置元数据中,你可以使用<alias/>元素来完成。
<alias name="fromName" alias="toName"/>
在上述例子中,同一个容器中的名称为fromName的bean,同样的,在使用了别名定义后,也指向名称为toName的bean。
比如,子系统A配置元数据指向数据源使用的名称是subsystemA-dataSource。子系统B的配置元数据指向数据源使用的名称是subsystemB-dataSource。使用这些子系统组装的主应用使用名称myApp-dataSource指向数据源。为了使这三个名称指向相同的对象,你可以在MyApp配置元数据下使用如下的别名定义:
<alias name="subsystemA-dataSource" alias="subsystemB-dataSource"/> <alias name="subsystemA-dataSource" alias="myApp-dataSource" />
现在每一个子系统和主应用都能通过一个唯一的名称指向数据源,并且保证不会与其他定义产生冲突,同时指向的是同一个bean。
2.beans初始化
bean定义对于创建一个或多个对象来说就是菜谱。当需要初始化一个bean时,容器就会去菜谱来查找对应名称的bean定义,然后通过bean定义里封装的配置元数据创建一个实际的对象。
在基于XML的配置元数据中,你会在<bean/>元素的class属性定义用来初始化该bean的类。这个class属性,相当于BeanDefinition实例中的Class成员变量,是必填的。你可以通过以下两种方式之一使用Class属性:
1.声明bean的class,容器通过调用该类的构造器来直接创建bean,类似于java代码里的new方法;
2.声明包含静态工厂方法的类,容器在创建bean时会调用该静态工厂方法。调用静态工厂方法返回的对象类型可以是相同的类,也可以完全是另一个类。
如果你想通过静态内部类来定义bean的class属性,那么你必须为内部类使用二进制名称。比如,你在com.example包下有一个类Foo,然后类Foo有一个静态的内部类Bar,那么该bean定义class属性就应该是com.example.Foo$Bar.
2.1 通过构造器初始化
当通过构造器的方式来初始化bean时,所有的普通类都是适用的,也就是说,类的实现不需要实现任何特定的接口或者以一种特定的方式编码。简单的声明bean的类就足够了。但是依赖于你为bean声明使用的是何种Ioc类型,你可能需要一个默认的构造器。
Spring Ioc容器可以管理任何你希望它管理的类,不限制于管理真正的JavaBeans。大多数Spring使用者更喜欢带有一个默认构造器和适当数量的setters和getters方法的JavaBeans。你也可以在容器中定义一些不一样形式的bean。比如,应用中需要用到连接池,你不必完全按照JavaBean的方式定义,容器也可以很好的管理。在基于XML的配置元数据中你可以使用如下方式定义:
<bean id="exampleBean" class="examples.ExampleBean"/> <bean name="anotherExample" class="examples.ExampleBeanTwo"/>
2.2 通过静态工厂方法初始化
当你通过静态工厂方法定义bean时,你可以在class属性声明包含静态工厂方法的类以及属性factory-method来定义工厂方法的名称。你应当能够调用这个方法,并且返回有效的对象,类似于通过构造器创建的一样。
如下的bean定义声明了bean会通过调用工厂方法来创建对象。bean定义并不会声明返回的对象的具体类型,只声明包含工厂方法的类。在这个例子中,createInstance()方法必须是一个静态方法。
<bean id="clientService" class="examples.ClientService" factory-method="createInstance"/>
2.3 使用实例工厂方法初始化
类似于通过静态工厂方法初始化,一个实例工厂方法初始化会调用一个已经存在于容器中的bean的非静态方法来创建一个bean。使用这种机制,class属性可以为空,在factory-bean属性中,声明包含了用来创建对象的实例方法的bean名称,factory-method属性可以用来设置实例工厂方法。
一个工厂类也可以包含多个工厂方法,如下所示: