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 的基本使用流程。

相关推荐
java水泥工5 小时前
酒店客房管理系统|基于SpringBoot和Vue的酒店客房管理系统(源码+数据库+文档)
spring boot·vue·酒店管理系统·酒店客房管理系统
Moonbit5 小时前
MoonBit高校行 | 中大、深技大、深大、港科广回顾
后端·开源·编程语言
纸照片5 小时前
【邪修玩法】如何在WPF中开放 RESTful API 服务
后端·wpf·restful
心态特好5 小时前
详解WebSocket及其妙用
java·python·websocket·网络协议
Terio_my6 小时前
Spring Boot 虚拟 MVC 调用
spring boot
Haooog6 小时前
98.验证二叉搜索树(二叉树算法题)
java·数据结构·算法·leetcode·二叉树
武子康6 小时前
Java-143 深入浅出 MongoDB NoSQL:MongoDB、Redis、HBase、Neo4j应用场景与对比
java·数据库·redis·mongodb·性能优化·nosql·hbase
njsgcs6 小时前
sse mcp flask 开放mcp服务到内网
后端·python·flask·sse·mcp
jackaroo20207 小时前
后端_基于注解实现的请求限流
java