Spring前置准备(七)——DefaultListableBeanFactory

终于到了我们的DefaultListableBeanFactory(默认列表Bean工厂)的解析,首先先回顾一下,通过前面的了解我们对于ConfigurableApplicationContext有了清晰的理解,ConfigurableApplicationContext主要作用是对Beean工厂对象进行了部分功能的扩展,例如:资源解析,生命周期管理等等,并且保证了BeanFacotry功能的单一职责,那么这章我们将深入解析DefaultListableBeanFactory,还是老样子,先从接口或者类的上级看起:

AliasRegistry

AliasRegistry是 Spring 框架中的一个接口,它提供了管理对象别名的功能。在 Spring 的 Bean 管理场景中,别名可以为 Bean 提供额外的标识符,增加了配置的灵活性和可维护性。以下是关于AliasRegistry的详细介绍:

java 复制代码
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

java 复制代码
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名称的映射:

java 复制代码
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);


    @Override
    public 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-register
                        return;
                    }
                    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方法我们可以看到这段代码:

java 复制代码
this.aliasMap.put(alias, name);

通过这段代码我们可以看出,别名map集合的key是别名,val 是Bean的名称,以此来完成映射

别名的使用

在Spring的XML配置文件中,使用别名有以下两种常见方式:

  • 通过bean标签的name属性 :在bean标签中,name属性可以用来指定一个或多个别名,多个别名之间可以用逗号、分号或空格隔开。示例如下:
xml 复制代码
<?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>

在上述配置中,可以通过bookServiceserviceservice4bookEbi中的任意一个名称从Spring容器中获取BookServiceImpl类型的bean。

  • 通过alias标签 :使用<alias>标签也可以为bean指定别名,name属性指定要设置别名的bean的名称,alias属性指定别名。示例如下:
xml 复制代码
<?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

  1. ListableBeanFactory让bean工厂有了批量处理bean的能力,是对整个bean工厂容器所在集合的整体批处理操作,
  2. 在文档注释中提到了一个词:"枚举(enumerate )",在英文里这个单词的意思是 "逐个列出、遍历、清点" 的意思,我第一次看到这个词想到了枚举类,但是这里的枚举不是枚举类,
  3. 而是一次性列出所有 Bean,而不是像普通 BeanFactory 那样只能通过 getBean("beanName") 一个个查找
  4. 它下面的方法包括: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继承自HierarchicalBeanFactorySingletonBeanRegistry接口。通过继承HierarchicalBeanFactory,它获得了处理父子Bean工厂关系的能力,使得Spring容器可以构建分层的Bean工厂结构,这在一些复杂的应用场景中非常有用,例如在Spring的Web应用中,可能存在根应用上下文和特定Servlet的上下文,它们之间通过父子Bean工厂关系进行管理。继承SingletonBeanRegistry则为其提供了管理单例Bean的基础功能,如注册、获取和移除单例Bean。
  • BeanFactory的关系BeanFactory是Spring框架中Bean管理的基础接口,定义了获取Bean实例等基本操作。ConfigurableBeanFactoryBeanFactory的基础上进行了扩展,提供了更多配置和管理Bean工厂的高级功能,是Spring容器在实际实现中用于精细控制Bean创建和管理的重要接口。

3. 应用场景

  • 自定义Bean创建逻辑 :在开发自定义框架或对Spring容器进行扩展时,开发人员可能需要自定义Bean的创建逻辑。ConfigurableBeanFactory提供的方法允许在Bean创建前、创建过程中和创建后进行干预。例如,可以通过自定义的BeanPostProcessor结合ConfigurableBeanFactory来对Bean进行特殊的初始化或增强操作。
  • 动态Bean注册与管理 :在一些动态配置或插件化的应用场景中,可能需要在运行时动态注册、修改或移除Bean定义。ConfigurableBeanFactory提供的相关方法使得这种动态管理成为可能。比如,在一个支持插件扩展的应用中,新的插件可以在运行时向Spring容器注册新的Bean定义,ConfigurableBeanFactory能够很好地支持这种操作。
  • 资源管理与优化 :通过对Bean作用域的管理,ConfigurableBeanFactory有助于优化应用的资源使用。例如,对于资源消耗较大的Bean,可以将其设置为单例作用域,确保在容器中只有一个实例,避免重复创建带来的资源浪费;而对于一些每次使用都需要全新状态的Bean,则可以设置为原型作用域。

4. 示例代码

以下是一个简单的示例,展示如何使用ConfigurableBeanFactory的部分功能:

java 复制代码
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 {
    @Override
    public String toString() {
        return "This is MyBean";
    }
}

在上述示例中,首先获取ConfigurableBeanFactory实例,然后注册了一个MyBean的Bean定义,并将其作用域设置为原型,最后从容器中获取MyBean实例并打印。通过这个示例,可以直观地了解ConfigurableBeanFactory在Bean管理中的基本使用方式。

总结

DefaultListableBeanFactory 是Spring框架中一个核心的类,它实现了 ConfigurableListableBeanFactory 接口,而 ConfigurableListableBeanFactory 又继承了多个重要接口,如 ConfigurableBeanFactoryListableBeanFactory 等。以下从多个方面来介绍 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。例如,如果一个Bean A 依赖于Bean BDefaultListableBeanFactory 会先创建或获取Bean B 的实例,然后将其注入到Bean A 中。这种依赖注入的处理机制是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:

java 复制代码
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();

        // 定义一个BeanDefinition
        AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(MyBean.class).getBeanDefinition();

        // 注册BeanDefinition
        factory.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 创建了一个 MyBeanBeanDefinition,并将其注册到 DefaultListableBeanFactory 中。最后通过 getBean 方法获取 MyBean 的实例并调用其方法。这个示例简单演示了 DefaultListableBeanFactory 的基本使用流程。

相关推荐
Wang15302 小时前
jdk内存配置优化
java·计算机网络
0和1的舞者2 小时前
Spring AOP详解(一)
java·开发语言·前端·spring·aop·面向切面
Wang15302 小时前
Java多线程死锁排查
java·计算机网络
嘟嘟MD3 小时前
程序员副业 | 2025年12月复盘
后端·创业
小小星球之旅3 小时前
CompletableFuture学习
java·开发语言·学习
利刃大大3 小时前
【SpringBoot】Spring事务 && @Transactional详解 && Spring事务失效问题
spring boot·spring·事务
jiayong233 小时前
知识库概念与核心价值01
java·人工智能·spring·知识库
皮皮林5514 小时前
告别 OOM:EasyExcel 百万数据导出最佳实践(附开箱即用增强工具类)
java
..过云雨4 小时前
17-2.【Linux系统编程】线程同步详解 - 条件变量的理解及应用
linux·c++·人工智能·后端
Da Da 泓4 小时前
多线程(七)【线程池】
java·开发语言·线程池·多线程