全是干货的技术号:本文已收录在github,欢迎 star/fork:java
Spring管理的这些bean藉由配置元数据建立,例如被@Bean
注解。那么在 Spring 内部又是如何存储这些信息的呢?github
在容器内,这些bean定义被表示为BeanDefinition对象,它包含但不限于以下元数据:api
这些元数据会转换为构成每一个bean定义内的一组属性。
被定义bean的实际实现类数组
这些状态指示bean在容器中的行为(做用域、生命周期回调等)。以下即为做用域:缓存
singleton
这些引用也就是常见的协做或依赖对象。bash
除了包含有关如何建立特定bean信息的bean定义外,ApplicationContext
实现还容许注册在容器外部(用户自定义的)建立的现有对象。并发
这是经过getBeanFactory()
方法访问ApplicationContext
的BeanFactory
完成的,该方法返回其DefaultListableBeanFactory
实现。编码
DefaultListableBeanFactory
经过registerSingleton(..)
和registerBeanDefinition(..)
方法支持此注册。固然了,咱们开发的应用程序通常只使用经过常规的bean定义内的元数据定义的bean。spa
DefaultListableBeanFactory支持经过以下两种方式进行注册:
bean实例就是传递给registerSingleton方法的singletonObject对象
容器根据BeanDefinition实例化bean
固然了,通常的应用程序仍是仅经过元数据定义的bean来定义bean。
Bean元数据和显式编码提供的单例实例需尽早地注册,方便容器在自动装配和其余自省(指在运行时来判断一个对象的类型的能力)过程能正确推理它们。虽然在某种程度上支持覆盖现有的元数据或单例实例,但在运行时(与对工厂的实时访问并发)对新bean的注册并不被正式支持,而且可能致使并发访问异常,好比bean容器中的状态不一致。
每一个bean都有一或多个标识符,这些标识符在其所在容器中必须惟一。一个bean一般只有一个标识符。但若它就是须要有一个以上的,那么多余标识符被视为别名。
在bean定义中,可组合使用id、name 属性指定bean的标识符。
id
属性。通常来讲,这些名字由字母数字组成(如myBean,fooService),但也可能包含特殊字符。name
属性指定任意数量的其余名称。用逗号,
、分号;
或空格分隔。在Spring 3.1前,id
属性定义为xsd:ID
类型,该类型限制了可能的字符。从3.1开始,它被定义为xsd:string
类型。注意,Bean的id
惟一性仍由容器强制执行,而再也不是XML解析器。
开发者无需提供bean的name
或id
。若是未明确提供,容器将为该bean生成一个惟一name
。但若是想经过使用ref
元素或服务定位器模式查找来按名称引用该bean,则必须提供一个name
。不提供名称的缘由和内部beans和自动装配有关。
能够为bean提供多个名称。这些名称视做同一bean的别名,例如容许应用中的每一个组件经过使用特定于组件自己的bean名称来引用公共依赖。
与对实例字段名称的命名规范相同。即小写字母开头,后跟驼峰式大小写。
示例:userService
,roleController
。
扫描类路径下的组件,Spring就会按照该习惯为未命名的组件生成bean名称:将类名初始字符转换为小写。其实这个规范便是JDK 里的Introspector#decapitalize方法,Spring正使用了它:
java.beans.Introspector.decapitalize
public static String decapitalize(String name) { if (name == null || name.length() == 0) { return name; } // 若是有多个字符且第一和第二个字符均为大写字母 // 则会保留原始大小写 if (name.length() > 1 && Character.isUpperCase(name.charAt(1)) && Character.isUpperCase(name.charAt(0))){ return name; } // 使用简单的类名,并将其初始字符转换为小写 char chars[] = name.toCharArray(); chars[0] = Character.toLowerCase(chars[0]); return new String(chars); }
有时但愿为单个Bean提供多个名称,尤为是在多系统环境。
可以使用<alias/>
标签:
<alias name="srcName" alias="extName"/>
定义别名后,可将同一容器
中名为srcName
的bean称为extName
。
环境示例:
subA-ds
引用数据源subB-ds
引用数据源main-ds
引用数据源。要使全部三个名称都引用相同的对象,可将如下别名定义添加到配置元数据:
<alias name="subA-ds" alias="subB-ds"/> <alias name="subA-ds" alias="main-ds" />
如今,每一个组件和主应用程序均可以经过惟一名称引用数据源,而且可保证不与任何其它定义冲突(等于高效建立了名称空间),并且引用的是同一bean。
使用@Bean
注解的name
属性接收一个String数组。示例以下:
@Configuration public class AppConfig { @Bean({"dataSource", "subA-ds", "subB-ds"}) public DataSource dataSource() { // ... } }
BeanDefinition可看作是建立对象的配方。容器在被询问时,会查看被命名过的bean的BeanDefinition,并使用该BeanDefinition中的配置元数据建立(或直接从缓存池获取)对应的对象实例。
好比在XML方式下,在<bean/>
标签的class
属性指定要实例化的对象的类型。这个class
属性,其实就是BeanDefinition实例的Class
属性,所以该属性通常强制必须指定。
可经过以下方式使用Class
属性来实例化 bean:
在容器自身经过反射调用其构造器直接建立bean时,指定要构造的bean类,相似new运算符。该方式下,类基本上都能被Spring兼容。即bean类无需实现任何特定接口或以特定方式编码。 指定bean类便可。注意,根据所用的IoC类型,有时须要一个默认的无参构造器。
指定包含将要建立对象的静态工厂方法的实际类,容器将在类上调用静态工厂方法以建立bean。
定义使用静态工厂方法建立的bean时,可以使用class
属性来指定包含静态工厂方法的类,并使用factory-method
属性指定工厂方法自己的名称。开发者应该可以调用此方法并返回一个存活对象,该对象随后将被视为经过构造器建立的。
这种BeanDefinition的一种用法是在老代码中调用static工厂。
看个例子,以下BeanDefinition指定将经过调用工厂方法来建立bean。该定义不指定返回对象的类型,而仅指定包含工厂方法的类。该示例中的initInstance()
方法须是静态方法。
<bean id="serverService" class="examples.ServerService" factory-method="initInstance"/>
可与上面的BeanDefinition协同的类:
public class ServerService { private static ServerService serverService = new ServerService(); private ServerService() {} public static ServerService createInstance() { return serverService; } }
使用该方式实例化会从容器中调用现有bean的非静态方法来建立新bean。要使用此机制,需将class
属性置空,并在factory-bean
属性中,在当前(或父/祖先)容器中指定包含要建立该对象的实例方法的bean的名称。factory-method
设置工厂方法自己的名称。
示例以下,来看看如何配置这样的bean:
这种方式还代表,即便是工厂bean也能够经过依赖注入进行管理和配置。
“factory bean”是指在Spring容器中配置并经过实例或静态工厂方法建立对象的bean。相比之下,FactoryBean
是指特定于Spring的FactoryBean实现类。
bean元数据定义中的指定类只是初始类引用,可能结合使用的以下方式之一:
所以,看起来肯定bean运行时类型绝非易事,该如何准确获取呢?
推荐调用 BeanFactory.getType
肯定bean的运行时类型。
该方法可肯定给定名称bean的类型。 更确切地,返回针对相同bean名称的BeanFactory.getBean
调用将返回的对象的类型。
且该方法的实现考虑了前面穷举的全部状况,并针对于FactoryBean ,返回FactoryBean所建立的对象类型,和FactoryBean.getObjectType()
返回一致。