Spring IoC容器管理一个或多个bean。这些bean是使用我们提供给容器的配置元数据创建的。
在容器本身中,这些bean定义被表示为BeanDefinition
对象,其中包含(以及其他信息)以下元数据:
这些元数据都在下表中:
Property | Explained in… |
---|---|
Class | Instantiating Beans |
Name | Naming Beans |
Scope | Bean Scopes |
Constructor arguments | Dependency Injection |
Properties | Dependency Injection |
Autowiring mode | Autowiring Collaborators |
Lazy initialization mode | Lazy-initialized Beans |
Initialization method | Initialization Callbacks |
Destruction method | Destruction Callbacks |
除了配置元数据来实例化对象之外,还可以注册接管那些未在配置中配置的对象,将它们也变成bean,具体实现在源代码中有所涉及,这里也有两篇博文供参考:
Spring 概念模型 -- SingletonBeanRegistry 单例bean对象注册表
如何注册Spring Bean(外部单体对象)
这些被注册的外部对象被称为外部单体对象,注册的行为主要由接口SingletonBeanRegistry
规定。
下面这段话是对手动提供单例和元数据配置的要求和建议:
Bean metadata and manually supplied singleton instances need to be registered as early as possible, in order for the container to properly reason about them during autowiring and other introspection steps. While overriding existing metadata and existing singleton instances is supported to some degree, the registration of new beans at runtime (concurrently with live access to the factory) is not officially supported and may lead to concurrent access exceptions, inconsistent state in the bean container, or both.
大意就是,越早越好!
每个bean都有一个或多个标识符。这些标识符在承载bean的容器中必须是唯一的。bean通常只有一个标识符。但是,如果需要多个,则可以考虑使用别名。
在XML文件中,id
就是唯一标识,可以通过name
属性来取别名,别名可以有多个,由逗号、分号、空格分割。
可以不为bean提供name
或id
。如果不显式提供name
或id
,容器将为该bean生成唯一名称。但是,如果您想通过名称引用该bean,则必须通过使用ref
元素或Service Locator
样式查找来提供名称。不提供名称是因为后续可以使用内部bean和自动装配。
bean的命名规则和java变量命名规则相同。
通过在类路径中扫描组件,并由Spring为未命名组件生成bean名遵循如下规则,在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); }
可以看到,当类名第一个和第二个字母都为大写时,会直接将类名返回;如果只有一个大写,会把第一个大小字母变成小写后返回。
除了在配置元数据的时候通过name
属性取别名之外,还可以通过<alias/>
来取别名。这中方法有如下的应用场景:
子系统A中对对象的引用名为subsystemA-dataSource
,子系统B中对对象的引用名为subsystemB-dataSource
,在总系统中为了让着两个系统引用同一个数据源,可以使用:
<alias name="myApp-dataSource" alias="subsystemA-dataSource"/> <alias name="myApp-dataSource" alias="subsystemB-dataSource"/>
这样一来,两个子系统就会同时引用一个数据源。
一个bean的定义本质上就像一个食谱,用于创建一个或者多个对象的食谱。容器查看食谱,当发现需要某个食材的的时候就通过食材的名字()找到它,然后根据配置的元数据填充并创造出实际对象,返回给你的应用。
如果使用XML配置,那就需要制定class
属性,它会通过反射的方法调用类的构造器来填充数据以实例化bean,我们通常也都是这么做的,这样做就类似于new
操作符。
还有一种方法暂时不太能理解,摆在这里(TODO):
To specify the actual class containing the
static
factory method that is invoked to create the object, in the less common case where the container invokes astatic
factory method on a class to create the bean. The object type returned from the invocation of thestatic
factory method may be the same class or another class entirely.
如果要实例化嵌套类(内部类),在指定class属性时可以使用$
或者.
来取到,源码中有实例
普通的实例化类只需要通过指定class参数就可以进行。However, depending on what type of IoC you use for that specific bean, you may need a default (empty) constructor.
我们一般将需要被 IoC 托管的类设置成JavaBean格式,当然,如果它们不是JavaBean格式,IoC 容器也能托管,方法同一般的配置方法(指定id
/name
和class
)
<bean id="clientService" class="examples.ClientService" factory-method="createInstance"/>
public class ClientService { private static ClientService clientService = new ClientService(); private ClientService() {} public static ClientService createInstance() { return clientService; } }
与通过静态工厂方法进行实例化类似,使用实例工厂方法进行实例化将从容器调用现有bean的非静态方法来创建新bean。
要使用这种机制,要将class属性设置为空,并在factory-bean属性中指定当前容器(或父容器或祖先容器)中bean的名称,该容器包含要调用的实例方法来创建对象。
使用factory-method属性设置工厂方法本身的名称。
<!-- the factory bean, which contains a method called createInstance() --> <bean id="serviceLocator" class="examples.DefaultServiceLocator"> <!-- inject any dependencies required by this locator bean --> </bean> <!-- the bean to be created via the factory bean --> <bean id="clientService" factory-bean="serviceLocator" factory-method="createClientServiceInstance"/>
public class DefaultServiceLocator { private static ClientService clientService = new ClientServiceImpl(); public ClientService createClientServiceInstance() { return clientService; } }
一个工厂类可以持有多个工厂方法:
<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"/>
public class DefaultServiceLocator { private static ClientService clientService = new ClientServiceImpl(); private static AccountService accountService = new AccountServiceImpl(); public ClientService createClientServiceInstance() { return clientService; } public AccountService createAccountServiceInstance() { return accountService; } }
The runtime type of a specific bean is non-trivial to determine. A specified class in the bean metadata definition is just an initial class reference, potentially combined with a declared factory method or being a FactoryBean
class which may lead to a different runtime type of the bean, or not being set at all in case of an instance-level factory method (which is resolved via the specified factory-bean
name instead). Additionally, AOP proxying may wrap a bean instance with an interface-based proxy with limited exposure of the target bean’s actual type (just its implemented interfaces).
The recommended way to find out about the actual runtime type of a particular bean is a BeanFactory.getType
call for the specified bean name. This takes all of the above cases into account and returns the type of object that a BeanFactory.getBean
call is going to return for the same bean name.