💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
[TOC] ### Bean概述 一个Spring Ioc容器管理一个或多个Beans。Beans由你提供给容器的配置元数据创建的,如XML格式的\<bean/>定义。 在容器内部,bean定义由 *BeanDefinition* 对象表示,它包含以下信息(以及其他信息): - 包限定类名:通常是定义bean的实际实现类 - Bean行为配置元素,它们表明bean在容器内部的行为(作用域,生命周期回调等等) - Bean工作所需要的其他Bean的引用;这些引用也称为合作者或依赖 - 在创建新对象时设置的其他配置信息,如在一个Bean中可以使用的管理连接池的连接数量,或者连接池自身的数量限制。 元数据转换成构成每个Bean定义的属性集。 表一 bean定义 | 属性 | 解释模块 | | --- | --- | | class | 实例化Beans | | name | Beans命名 | | scope | Bean作用域 | | constructor arguments | 依赖注入 | | properties | 依赖注入 | | autowiring mode | 自动装配依赖 | | lazy-initialization mode | 懒初始化Beans | | initialization method | 初始化回调 | | destruction method | 销毁回调 | 除了包含如何创建特定bean的信息的bean定义之外,*ApplicationContext* 实现还允许用户注册在容器外部创建的现有对象。这是通过 *getBeanFactory()* 方法访问 *ApplicationContext* 的*BeanFactory*来完成的,该方法返回 *BeanFactoryd* 的实现 *DefaultListableBeanFactory* 。 *DefaultListableBeanFactory* 通过方法 *registerSingleton(..)* 和 *registerBeanDefinition(..)* 来支持这种注册。但是,通常的应用程序只能通过元数据bean定义来定义bean。 Bean元数据和手动提供的单实例需要尽早注册,以便容器在自动装配和其他自省步骤中正确推理它们。虽然重写现有的元数据和现有的单实例在某种程度上受到支持,但在运行时(与实时访问工厂同时)注册新的Bean并未得到正式支持,并且可能导致并发访问异常或bean容器中的状态不一致。 #### 1.3.1. Beans命名 每个bean都有一个或多个标识。这些标识在管理bean的容器内必须是唯一的。一个bean通常只有一个标识,但是如果它需要多个标识符,额外的标识可以被认为是别名。 在基于XML的配置元数据中,使用 *id* 和 *name* 属性来指定bean标识符。 *id* 属性允许你指定一个id。通常,这些名称是字母('myBean','fooService'等),但也可能包含特殊字符。如果要将其他别名引入到bean中,还可以在 *name* 属性中指定它们,并用逗号(,),分号(;)或空格分隔。作为历史记录,在Spring 3.1之前的版本中, *id* 属性被定义为 *xsd:ID* 类型,它限制了可能的字符。从3.1开始,它被定义为一个 *xsd:string* 类型。请注意,bean的 *id* 唯一性仍由容器强制执行,尽管不再由XML解析器执行。 为bean提供name或id不是必须的。如果没有显式提供name和id,容器为该bean生成一个唯一name。但是,如果您想通过name引用,或者通过使用 *ref* 元素或 *Service Locator* 样式查找该bean,您必须提供一个name。不提供name的动机与使用inner beans和自动装配依赖有关。 >:-: **Bean命名约定** > >约定是在命名bean时使用标准Java约定作为实例字段名称。也就是说,bean名称以小写字母开头,并且后面的字符以驼峰命名法为基础。例如这样的name(不带引号)'accountManager','accountService','userDao','loginController'等等。 >命名bean始终使您的配置更易于阅读和理解,如果您使用的是Spring AOP,则将建议应用于与名称相关的一组bean时会有很大帮助。 >[info] 通过在类路径中的组件扫描,Spring 遵循上述规则为未命名组件生成 bean 名称,: 基本上,使用简单的类名并将其初始字符转换为小写。 然而,在(不寻常的)特殊情况下,如果有一个以上的字符,而且第一个和第二个字符都是大写的,则使用原始的名称。 这些和 java.beans.inseartor.disapitalize(Spring 使用的) 具有相同的规则。 ##### 在bean定义之外创建别名 在对bean定义时,除了使用id属性指定一个唯一的名称外,为了提供多个名称,需要通过name属性加以指定。所有这个名称都指向同一个bean,在某些情况下提供别名非常有用,比如为了让应用每一个组件都能更容易的对公共组件进行引用。 然而,在定义bean时就指定所有的别名并不总是很恰当。有时我们期望能够在当前位置为那些在别处定义的bean引入别名。在XML配置文件中,可以通过 *\<alias/>* 元素来完成bean别名的定义,例如: ~~~xml <alias name="fromName" alias="toName"/> ~~~ 在这种情况下,如果同一容器中存在名为 *fromName* 的bean定义,在增加别名定义后,也可以用 *toName* 来引用。 例如,在子系统A中通过名字 *fsubsystemA-dataSource* 配置的数据源。在子系统B中可能通过名字 *fsubsystemB-dataSource*f 来引用。当两个子系统构成主应用的时候,主应用可能通过名字 *fmyApp-dataSource*f 引用数据源,将全部三个名字引用同一个对象,你可以将下面的别名定义添加到应用配置中: ~~~xml <alias name="subsystemA-dataSource" alias="subsystemB-dataSource"/> <alias name="subsystemA-dataSource" alias="myApp-dataSource" /> ~~~ 现在每个子系统和主应用都可以通过唯一的名称来引用相同的数据源,并且可以保证他们的定义不与任何其他的定义冲突。 >:-: **基于Java的配置** > >如果你想使用基于Java的配置,@Bean注解可以用来提供别名,详细信息请看 **使用@Bean注解** 。 #### 1.3.2. 实例化bean bean定义基本上就是用来创建一个或多个对象的配方(recipe),当需要一个bean的时候,容器查看配方并且根据bean定义封装的配置元数据创建(或获取)一个实际的对象。 如果你使用基于XML的配置,你可以在 *\<bean/>* 元素通过class属性来指定对象的类型。这个 *class* 属性,实际上是 *BeanDefinition* 实例中的一个Class属性。这个 *class* 属性通常是必须的(例外情况,查看 **使用实例工厂方法实例化** 和 **Bean定义的继承**),使用Class属性的两种方式: * 通常情况下,直接通过反射调用构造方法来创建bean,和在Java代码中使用new有点像。 * 通过静态工厂方法创建,类中包含静态方法。通过调用静态方法返回对象的类型可能和Class一样,也可能完全不一样。 >*内部类名* > >如果你想配置使用静态的内部类,你必须用内部类的二进制名称。 > >例如,在com.example包下有个Foo类,这里类里面有个静态的内部类Bar,这种情况下bean定义的class属性应该… > >com.example.Foo$Bar > >注意,使用$字符来分割外部类和内部类的名称。 ##### 通过构造函数实例化 当你使用构造方法来创建bean的时候,Spring对class没有特殊的要求。也就是说,正在开发的类不需要实现任何特定的接口或者以特定的方式进行编码。但是,根据你使用那种类型的IoC来指定bean,你可能需要一个默认(无参)的构造方法。 Spring IoC 容器可以管理几乎所有你想让它管理的类,它不限于管理POJO。大多数Spring用户更喜欢使用POJO(一个默认无参的构造方法和setter,getter方法)。但在容器中使用非bean形式(non-bean style)的类也是可以的。比如遗留系统中的连接池,很显然它与JavaBean规范不符,但Spring也能管理它。 当使用基于XML的元数据配置文件,可以这样来指定bean类: ~~~xml <bean id="exampleBean" class="examples.ExampleBean"/> <bean name="anotherExample" class="examples.ExampleBeanTwo"/> ~~~ 给构造方法指定参数以及为bean实例化设置属性将在后面的依赖注入中详细说明。 ##### 使用静态工厂方法实例化 当采用静态工厂方法创建bean时,除了需要指定 *class* 属性外,还需要通过 *factory-method* 属性来指定创建bean实例的工厂方法。Spring将调用此方法(其可选参数接下来介绍)返回实例对象,就此而言,跟通过普通构造器创建类实例没什么两样。 下面的bean定义展示了如何通过工厂方法来创建bean实例。注意,此定义并未指定返回对象的类型,仅指定该类包含的工厂方法。在此例中,*createInstance()* 必须是一个static方法。 ~~~xml <bean id="clientService" class="examples.ClientService" factory-method="createInstance"/> ~~~ ~~~Java public class ClientService { private static ClientService clientService = new ClientService(); private ClientService() {} public static ClientService createInstance() { return clientService; } } ~~~ 给工厂方法指定参数以及为bean实例设置属性的详细内容请查阅依赖和配置详解。 ##### 使用实例工厂方法实例化 与通过 *静态工厂方法* 实例化类似,通过调用工厂实例的非静态方法进行实例化。 使用这种方式时,*class*属性必须为空,而*factory-bean*属性必须指定为当前(或其祖先)容器中包含工厂方法的bean的名称,而该工厂bean的工厂方法本身必须通过factory-method属性来设定。 ~~~xml <!-- 工厂bean,包含createInstance()方法 --> <bean id="serviceLocator" class="examples.DefaultServiceLocator"> <!-- 其他需要注入的依赖项 --> </bean> <!-- 通过工厂bean创建的ben --> <bean id="clientService" factory-bean="serviceLocator" factory-method="createClientServiceInstance"/> ~~~ ~~~Java public class DefaultServiceLocator { private static ClientService clientService = new ClientServiceImpl(); private DefaultServiceLocator() {} public ClientService createClientServiceInstance() { return clientService; } } ~~~ 一个工厂类也可以有多个工厂方法,如下代码所示: ~~~xml <bean id="serviceLocator" class="examples.DefaultServiceLocator"> <!-- 其他需要注入的依赖项 --> </bean> <bean id="clientService" factory-bean="serviceLocator" factory-method="createClientServiceInstance"/> <bean id="accountService" factory-bean="serviceLocator" factory-method="createAccountServiceInstance"/> ~~~ ~~~Java public class DefaultServiceLocator { private static ClientService clientService = new ClientServiceImpl(); private static AccountService accountService = new AccountServiceImpl(); private DefaultServiceLocator() {} public ClientService createClientServiceInstance() { return clientService; } public AccountService createAccountServiceInstance() { return accountService; } } ~~~ 这种做法表明工厂bean本身也可以通过依赖注入(DI)进行管理配置。查看依赖和配置详解。 >[info] 在Spring文档中,*factory bean*是指在Spring容器中配置的工厂类通过 实例 或 静态 工厂方法来创建对象。相比而言, *FactoryBean* (注意大小写) 代表了Spring中特定的 FactoryBean.