Spring前置准备(七)——DefaultListableBeanFactory
终于到了我们的DefaultListableBeanFactory(默认列表Bean工厂)的解析,首先先回顾一下,通过前面的了解我们对于ConfigurableApplicationContext有了清晰的理解,ConfigurableApplicationContext主要作用是对Beean工厂对象进行了部分功能的扩展,例如:资源解析,生命周期管理等等,并且保证了BeanFacotry功能的单一职责,那么这章我们将深入解析DefaultListableBeanFactory,还是老样子,先从接口或者类的上级看起:
AliasRegistry
AliasRegistry是 Spring 框架中的一个接口,它提供了管理对象别名的功能。在 Spring 的 Bean 管理场景中,别名可以为 Bean 提供额外的标识符,增加了配置的灵活性和可维护性。以下是关于AliasRegistry的详细介绍:
public interface AliasRegistry {/*** 注册bean的别名* Given a name, register an alias for it.* @param name the canonical name* @param alias the alias to be registered* @throws IllegalStateException if the alias is already in use* and may not be overridden*/void registerAlias(String name, String alias);/*** 移除别名* Remove the specified alias from this registry.* @param alias the alias to remove* @throws IllegalStateException if no such alias was found*/void removeAlias(String alias);/*** Determine whether the given name is defined as an alias* (as opposed to the name of an actually registered component).* @param name the name to check* @return whether the given name is an alias*/boolean isAlias(String name);/*** Return the aliases for the given name, if defined.* @param name the name to check for aliases* @return the aliases, or an empty array if none*/String[] getAliases(String name);}
1. 核心功能
- 注册别名:通过
registerAlias(String name, String alias)
方法,可以为指定的对象(通常是Bean)注册一个别名。例如,在配置文件中,你可能有一个名为dataSource
的Bean,通过registerAlias("dataSource", "myDataSource")
,就为dataSource
这个Bean注册了一个别名myDataSource
。这样在后续获取Bean时,既可以使用dataSource
,也可以使用myDataSource
。 - 移除别名:
removeAlias(String alias)
方法用于移除指定的别名。当某个别名不再需要时,可以调用这个方法将其从别名注册表中删除。 - 检查别名:
isAlias(String name)
方法用于判断给定的名称是否是一个别名。这在需要动态处理Bean名称,并且要区分是真实名称还是别名的场景中很有用。 - 获取所有别名:
getAliases(String name)
方法返回指定对象的所有别名。这在需要获取某个Bean的所有别名列表,以便进行统一处理或展示时非常方便。
2. 应用场景
- 配置灵活性:在大型项目中,不同的模块可能习惯使用不同的名称来引用同一个Bean。通过别名机制,可以满足各个模块的需求,同时保持Bean的实际定义的一致性。例如,一个通用的日志记录Bean,在业务模块A中可能习惯使用别名
logService
,而在模块B中使用别名loggingBean
,但实际上它们都指向同一个真实的Bean名称。 - 简化配置与迁移:当需要对Bean进行重构或迁移时,别名可以减少对配置文件和代码的修改。比如,将一个Bean的名称从
oldBeanName
改为newBeanName
,只需要在别名注册表中注册registerAlias("newBeanName", "oldBeanName")
,而无需在所有引用该Bean的地方修改名称,降低了维护成本。 - 兼容旧有配置:在项目升级或整合不同的配置源时,可能会存在旧的配置使用了特定的Bean名称。通过为新的Bean名称注册旧的别名,可以保持与旧配置的兼容性,确保系统平稳过渡。
3. 接口实现
AliasRegistry
接口在Spring框架中有多个实现类,其中DefaultSingletonBeanRegistry
是一个重要的实现类,它是Spring单例Bean注册和管理的核心类之一。DefaultSingletonBeanRegistry
实现了AliasRegistry
接口,使得在单例Bean的管理过程中可以方便地使用别名功能。以下是一个简单的代码示例展示如何使用AliasRegistry
:
import org.springframework.beans.factory.support.DefaultSingletonBeanRegistry;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.core.io.ClassPathResource;public class AliasRegistryExample {public static void main(String[] args) {AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();DefaultSingletonBeanRegistry registry = (DefaultSingletonBeanRegistry) context.getDefaultListableBeanFactory();// 注册别名registry.registerAlias("dataSource", "myDataSource");// 加载Bean定义XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(context);reader.loadBeanDefinitions(new ClassPathResource("applicationContext.xml"));// 检查别名boolean isAlias = registry.isAlias("myDataSource");System.out.println("Is myDataSource an alias? " + isAlias);// 获取所有别名String[] aliases = registry.getAliases("dataSource");for (String alias : aliases) {System.out.println("Alias for dataSource: " + alias);}context.refresh();context.close();}
}
在上述示例中,首先获取DefaultSingletonBeanRegistry
实例,然后注册了一个别名,接着检查别名并获取所有别名,展示了AliasRegistry
接口的基本使用方式。通过AliasRegistry
,Spring框架为Bean的管理提供了更加灵活和便捷的方式。 DefaultSingletonBeanRegistry
的继承了SimpleAliasRegistry
,而SimpleAliasRegistry
实现了AliasRegistry
,在SimpleAliasRegistry
中通过aliasMap这个集合保存了所有的别名和Bean名称的映射:
public class SimpleAliasRegistry implements AliasRegistry {/** Logger available to subclasses. */protected final Log logger = LogFactory.getLog(getClass());/*** 别名和bean名称映射的Map集合*/private final Map<String, String> aliasMap = new ConcurrentHashMap<>(16);@Overridepublic void registerAlias(String name, String alias) {Assert.hasText(name, "'name' must not be empty");Assert.hasText(alias, "'alias' must not be empty");synchronized (this.aliasMap) {// 如果别名本省就是Bean名称,则不需要设置别名,因为直接通过bean名称就能获取到Bean,没必要多次一举if (alias.equals(name)) {this.aliasMap.remove(alias);if (logger.isDebugEnabled()) {logger.debug("Alias definition '" + alias + "' ignored since it points to same name");}}else {// 通过别名获取bean名称String registeredName = this.aliasMap.get(alias);// 如果已经存在,则不进行重复放入if (registeredName != null) {if (registeredName.equals(name)) {// An existing alias - no need to re-registerreturn;}if (!allowAliasOverriding()) {throw new IllegalStateException("Cannot define alias '" + alias + "' for name '" +name + "': It is already registered for name '" + registeredName + "'.");}if (logger.isDebugEnabled()) {logger.debug("Overriding alias '" + alias + "' definition for registered name '" +registeredName + "' with new target name '" + name + "'");}}checkForAliasCircle(name, alias);this.aliasMap.put(alias, name);if (logger.isTraceEnabled()) {logger.trace("Alias definition '" + alias + "' registered for name '" + name + "'");}}}}// 代码省略..........
}
其中通过registerAlias方法我们可以看到这段代码:
this.aliasMap.put(alias, name);
通过这段代码我们可以看出,别名map集合的key是别名,val 是Bean的名称,以此来完成映射
别名的使用
在Spring的XML配置文件中,使用别名有以下两种常见方式:
- 通过
bean
标签的name
属性:在bean
标签中,name
属性可以用来指定一个或多个别名,多个别名之间可以用逗号、分号或空格隔开。示例如下:
<?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"><!-- 这里bookService是bean的id,service、service4、bookEbi是别名 --><bean id="bookService" name="service service4 bookEbi" class="com.itheima.service.impl.BookServiceImpl"><property name="bookDao" ref="bookDao"/></bean><bean id="bookDao" name="dao" class="com.itheima.dao.impl.BookDaoImpl"/>
</beans>
在上述配置中,可以通过bookService
、service
、service4
或bookEbi
中的任意一个名称从Spring容器中获取BookServiceImpl
类型的bean。
- 通过
alias
标签:使用<alias>
标签也可以为bean指定别名,name
属性指定要设置别名的bean的名称,alias
属性指定别名。示例如下:
<?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="user" class="org.cjw.pojo.User" /><!-- 为user这个bean设置别名user1 --><alias name="user" alias="user1" />
</beans>
在这种情况下,既可以使用user
,也可以使用user1
从Spring容器中获取User
类型的bean。
本来还有一个Bean工厂没说,但是我觉得大家只要学习过Spring都清楚,这里省略了
SingletonBeanRegistry
从某种意义上来讲这个接口是一种规范,其实现类必须按照该规范实现单例Bean的注册类,而其实现类DefaultSingletonBeanRegistry是单例Bean工厂的核心类,也是Spring单例Bean的核心类,这个类中的Map集合保存了Spring 框架的所有单例对象,关于DefaultSingletonBeanRegistry我觉得有必要单独开一章来讲,因为太过于重要,后面有机会会详细讲解
HierarchicalBeanFactory
之前已经介绍过了,主要作用是代码分层,详情请看专栏里这篇文章: Spring前置准备(六)——Spring 的核心接口HierarchicalBeanFactory
AutowireCapableBeanFactory
这个类和Spring框架的自动装配有关,AutowireCapableBeanFactory
定义了自动注入的规范,它在Spring框架的依赖注入过程中起着关键作用,老办法,单开一章
ListableBeanFactory
- ListableBeanFactory让bean工厂有了批量处理bean的能力,是对整个bean工厂容器所在集合的整体批处理操作,
- 在文档注释中提到了一个词:“枚举(enumerate )”,在英文里这个单词的意思是 “逐个列出、遍历、清点” 的意思,我第一次看到这个词想到了枚举类,但是这里的枚举不是枚举类,
- 而是一次性列出所有 Bean,而不是像普通 BeanFactory 那样只能通过 getBean(“beanName”) 一个个查找,
- 它下面的方法包括:containsBeanDefinition,getBeanDefinitionCount等等从方法名就可以看出,需要遍历集合才能获取结果,单个getBean方法是没有办法处理的
ConfigurableBeanFactory
ConfigurableBeanFactory是 Spring 框架中一个非常重要的接口,它扩展了BeanFactory接口,提供了更多用于配置和管理 Bean 工厂的功能。主要扩展了例如:
- Bean定义的操作:允许对Bean定义进行操作,如注册、获取和移除Bean定义。通过这些操作,Spring容器能够灵活地管理Bean的创建规则。例如,
registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
方法可以将一个新的Bean定义注册到Bean工厂中,使得容器能够根据这个定义来创建Bean实例。 - 作用域管理:支持定义Bean的作用域。Spring框架中常见的Bean作用域包括单例(
SCOPE_SINGLETON
)和原型(SCOPE_PROTOTYPE
)等。ConfigurableBeanFactory
提供了设置和获取Bean作用域的方法,如void setScope(String name, String scope)
,可以动态地为指定的Bean设置作用域,满足不同的应用场景需求。 - 依赖关系处理:它参与了Bean依赖关系的管理。例如,在解析Bean的依赖时,
ConfigurableBeanFactory
会根据Bean定义和容器的状态来确定如何满足这些依赖,确保Bean在创建时能够正确地获取其所需的其他Bean实例。
2. 与其他接口的关系
- 继承结构:
ConfigurableBeanFactory
继承自HierarchicalBeanFactory
和SingletonBeanRegistry
接口。通过继承HierarchicalBeanFactory
,它获得了处理父子Bean工厂关系的能力,使得Spring容器可以构建分层的Bean工厂结构,这在一些复杂的应用场景中非常有用,例如在Spring的Web应用中,可能存在根应用上下文和特定Servlet的上下文,它们之间通过父子Bean工厂关系进行管理。继承SingletonBeanRegistry
则为其提供了管理单例Bean的基础功能,如注册、获取和移除单例Bean。 - 与
BeanFactory
的关系:BeanFactory
是Spring框架中Bean管理的基础接口,定义了获取Bean实例等基本操作。ConfigurableBeanFactory
在BeanFactory
的基础上进行了扩展,提供了更多配置和管理Bean工厂的高级功能,是Spring容器在实际实现中用于精细控制Bean创建和管理的重要接口。
3. 应用场景
- 自定义Bean创建逻辑:在开发自定义框架或对Spring容器进行扩展时,开发人员可能需要自定义Bean的创建逻辑。
ConfigurableBeanFactory
提供的方法允许在Bean创建前、创建过程中和创建后进行干预。例如,可以通过自定义的BeanPostProcessor
结合ConfigurableBeanFactory
来对Bean进行特殊的初始化或增强操作。 - 动态Bean注册与管理:在一些动态配置或插件化的应用场景中,可能需要在运行时动态注册、修改或移除Bean定义。
ConfigurableBeanFactory
提供的相关方法使得这种动态管理成为可能。比如,在一个支持插件扩展的应用中,新的插件可以在运行时向Spring容器注册新的Bean定义,ConfigurableBeanFactory
能够很好地支持这种操作。 - 资源管理与优化:通过对Bean作用域的管理,
ConfigurableBeanFactory
有助于优化应用的资源使用。例如,对于资源消耗较大的Bean,可以将其设置为单例作用域,确保在容器中只有一个实例,避免重复创建带来的资源浪费;而对于一些每次使用都需要全新状态的Bean,则可以设置为原型作用域。
4. 示例代码
以下是一个简单的示例,展示如何使用ConfigurableBeanFactory
的部分功能:
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;public class ConfigurableBeanFactoryExample {public static void main(String[] args) {AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();ConfigurableBeanFactory beanFactory = context.getBeanFactory();// 注册一个Bean定义BeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(MyBean.class).getBeanDefinition();beanFactory.registerBeanDefinition("myBean", beanDefinition);// 设置Bean的作用域为原型beanFactory.setScope("myBean", ConfigurableBeanFactory.SCOPE_PROTOTYPE);context.refresh();// 获取Bean实例MyBean myBean = (MyBean) beanFactory.getBean("myBean");System.out.println(myBean);context.close();}
}class MyBean {@Overridepublic String toString() {return "This is MyBean";}
}
在上述示例中,首先获取ConfigurableBeanFactory
实例,然后注册了一个MyBean
的Bean定义,并将其作用域设置为原型,最后从容器中获取MyBean
实例并打印。通过这个示例,可以直观地了解ConfigurableBeanFactory
在Bean管理中的基本使用方式。
总结
DefaultListableBeanFactory
是Spring框架中一个核心的类,它实现了 ConfigurableListableBeanFactory
接口,而 ConfigurableListableBeanFactory
又继承了多个重要接口,如 ConfigurableBeanFactory
、ListableBeanFactory
等。以下从多个方面来介绍 DefaultListableBeanFactory
:
1. 功能特性
- Bean定义的管理:它负责管理Bean定义,提供了注册、获取和查询Bean定义的功能。例如,可以使用
registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
方法向工厂中注册一个新的Bean定义。同时,通过getBeanDefinition(String beanName)
方法能够获取指定名称的Bean定义信息,包括Bean的类、作用域、依赖关系等。这使得Spring容器能够根据这些定义来创建和管理Bean实例。 - Bean实例的创建与获取:
DefaultListableBeanFactory
具备创建和获取Bean实例的能力。当调用getBean
方法时,它会根据Bean定义以及相关的依赖解析策略来创建Bean实例。对于单例Bean,它会在内部维护一个缓存,确保整个容器中只有一个实例;对于原型Bean,则每次都会创建一个新的实例。例如,在doGetBean
方法中,会根据Bean的作用域(单例或原型等)以及依赖关系来决定如何创建和返回Bean实例。 - 依赖注入处理:在创建Bean实例的过程中,
DefaultListableBeanFactory
会处理Bean之间的依赖关系,完成依赖注入。它通过解析Bean定义中的依赖信息,递归地获取和注入依赖的Bean。例如,如果一个BeanA
依赖于BeanB
,DefaultListableBeanFactory
会先创建或获取BeanB
的实例,然后将其注入到BeanA
中。这种依赖注入的处理机制是Spring框架实现解耦和控制反转(IoC)的关键。 - 作用域支持:支持多种Bean作用域,如单例(
SCOPE_SINGLETON
)、原型(SCOPE_PROTOTYPE
)等。通过setScope(String name, String scope)
方法可以为指定的Bean设置作用域,并且在创建和管理Bean实例时,会根据设置的作用域来采取相应的策略。例如,对于单例作用域的Bean,会在容器启动时或首次请求时创建,并缓存起来供后续使用;对于原型作用域的Bean,每次请求都会创建新的实例。
2. 在Spring容器中的角色
- 基础Bean工厂:
DefaultListableBeanFactory
是Spring容器的基础实现之一,为其他高级容器(如ApplicationContext
)提供了底层的Bean管理功能。ApplicationContext
通常会包含一个DefaultListableBeanFactory
实例,通过委托的方式,将大部分Bean管理的操作委托给DefaultListableBeanFactory
来完成。例如,ClassPathXmlApplicationContext
在初始化时,会创建一个DefaultListableBeanFactory
实例,并使用它来加载和解析XML配置文件中的Bean定义。 - 灵活的扩展点:由于
DefaultListableBeanFactory
实现了多个接口,它为开发人员提供了丰富的扩展点。开发人员可以通过继承DefaultListableBeanFactory
或实现相关接口,来定制Bean的创建、依赖注入等行为。例如,可以自定义一个BeanPostProcessor
,并注册到DefaultListableBeanFactory
中,在Bean初始化前后执行自定义的逻辑,从而对Bean进行增强或修改。
3. 示例代码
以下是一个简单的示例,展示如何使用 DefaultListableBeanFactory
来注册和获取Bean:
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;public class DefaultListableBeanFactoryExample {public static void main(String[] args) {// 创建DefaultListableBeanFactory实例DefaultListableBeanFactory factory = new DefaultListableBeanFactory();// 定义一个BeanDefinitionAbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(MyBean.class).getBeanDefinition();// 注册BeanDefinitionfactory.registerBeanDefinition("myBean", beanDefinition);// 获取Bean实例MyBean myBean = (MyBean) factory.getBean("myBean");myBean.doSomething();}
}class MyBean {public void doSomething() {System.out.println("MyBean is doing something.");}
}
在上述示例中,首先创建了一个 DefaultListableBeanFactory
实例,然后使用 BeanDefinitionBuilder
创建了一个 MyBean
的 BeanDefinition
,并将其注册到 DefaultListableBeanFactory
中。最后通过 getBean
方法获取 MyBean
的实例并调用其方法。这个示例简单演示了 DefaultListableBeanFactory
的基本使用流程。