【小家Spring】Spring IOC容器启动流程 AbstractApplicationContext#refresh()方法源码分析(二),Spring容器启动/刷新的完整总结(中)

本文涉及的产品
容器服务 Serverless 版 ACK Serverless,952元额度 多规格
容器服务 Serverless 版 ACK Serverless,317元额度 多规格
容器镜像服务 ACR,镜像仓库100个 不限时长
简介: 【小家Spring】Spring IOC容器启动流程 AbstractApplicationContext#refresh()方法源码分析(二),Spring容器启动/刷新的完整总结(中)

refresh() 第十步:registerListeners();


我们知道,上面我们已经把事件源、多播器都注册好了,这里就是注册监听器了:


  protected void registerListeners() {
    // 这一步和手动注册BeanDefinitionRegistryPostProcessor一样,可以自己通过set手动注册监听器  然后是最新执行的(显然此处我们无自己set)
    for (ApplicationListener<?> listener : getApplicationListeners()) {
      // 把手动注册的监听器绑定到广播器
      getApplicationEventMulticaster().addApplicationListener(listener);
    }
    // Do not initialize FactoryBeans here: We need to leave all regular beans
    // uninitialized to let post-processors apply to them!
    // 取到容器里面的所有的监听器的名称,绑定到广播器  后面会广播出去这些事件的
    // 同时提醒大伙注意:此处并没有说到ApplicationListenerDetector这个东东,下文会分解
    String[] listenerBeanNames = getBeanNamesForType(ApplicationListener.class, true, false);
    for (String listenerBeanName : listenerBeanNames) {
      getApplicationEventMulticaster().addApplicationListenerBean(listenerBeanName);
    }
    // Publish early application events now that we finally have a multicaster...
    // 这一步需要注意了:如果存在早期应用事件,这里就直接发布了(同时就把earlyApplicationEvents该字段置为null)
    // 
    Set<ApplicationEvent> earlyEventsToProcess = this.earlyApplicationEvents;
    this.earlyApplicationEvents = null;
    if (earlyEventsToProcess != null) {
      for (ApplicationEvent earlyEvent : earlyEventsToProcess) {
        getApplicationEventMulticaster().multicastEvent(earlyEvent);
      }
    }
  }


refresh() 第十一步:finishBeanFactoryInitialization(beanFactory)


这进行这一步之前,我这里截图看看,当前工厂里的Bean的的一个情况:

容器内所有的单例Bean们: 有的是提前经历过getBean()被提前实例化了,有的是直接addSingleton()方法直接添加的


image.png


容器内所有的Bean定义信息: 我们能够发现,我们自己@Bean进去的目前都仅仅存在于Bean定义信息内,还并没有真正的实例化。这就是我们这一步需要做的事~~~~


image.png


这里先建立一个快照,等执行完成这一步之后,再截图对比。


创建所有非懒加载的单例类(并invoke BeanPostProcessors)。这一步可谓和我们开发者打交道最多的,我们自定义的Bean绝大都是在这一步被初始化的,包括依赖注入等等~


因此了解这一步,能让我们更深入的了解Spring是怎么管理我们的Bean的声明周期,以及依赖关系的。

  protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {
    // Initialize conversion service for this context.
    // 初始化上下文的转换服务,ConversionService是一个类型转换接口
    if (beanFactory.containsBean(CONVERSION_SERVICE_BEAN_NAME) && beanFactory.isTypeMatch(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class)) {
      beanFactory.setConversionService( beanFactory.getBean(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class));
    }
    // Register a default embedded value resolver if no bean post-processor
    // (such as a PropertyPlaceholderConfigurer bean) registered any before:
    // at this point, primarily for resolution in annotation attribute values.
    // 设置一个内置的值处理器(若没有的话),该处理器作用有点像一个PropertyPlaceholderConfigurer bean
    if (!beanFactory.hasEmbeddedValueResolver()) {
      beanFactory.addEmbeddedValueResolver(strVal -> getEnvironment().resolvePlaceholders(strVal));
    }
    // Initialize LoadTimeWeaverAware beans early to allow for registering their transformers early.
    // 注意此处已经调用了getBean方法,初始化LoadTimeWeaverAware Bean
    // getBean()方法的详细,下面会详细分解
    // LoadTimeWeaverAware是类加载时织入的意思
    String[] weaverAwareNames = beanFactory.getBeanNamesForType(LoadTimeWeaverAware.class, false, false);
    for (String weaverAwareName : weaverAwareNames) {
      getBean(weaverAwareName);
    }
    // Stop using the temporary ClassLoader for type matching.
    // 停止使用临时的类加载器
    beanFactory.setTempClassLoader(null);
    // Allow for caching all bean definition metadata, not expecting further changes.
    // 缓存(冻结)所有的bean definition数据,不期望以后会改变
    beanFactory.freezeConfiguration();
    // Instantiate all remaining (non-lazy-init) singletons.
    // 这个就是最重要的方法:会把留下来的Bean们  不是lazy懒加载的bean都实例化掉
    //  bean真正实例化的时刻到了
    beanFactory.preInstantiateSingletons();
  }
  @Override
  public void freezeConfiguration() {
    this.configurationFrozen = true;
    this.frozenBeanDefinitionNames = StringUtils.toStringArray(this.beanDefinitionNames);
  }


接下来重点看看DefaultListableBeanFactory#preInstantiateSingletons:实例化所有剩余的单例Bean


  @Override
  public void preInstantiateSingletons() throws BeansException {
    // Iterate over a copy to allow for init methods which in turn register new bean definitions.
    // While this may not be part of the regular factory bootstrap, it does otherwise work fine.
    // 此处目的,把所有的bean定义信息名称,赋值到一个新的集合中
    List<String> beanNames = new ArrayList<>(this.beanDefinitionNames);
    // Trigger initialization of all non-lazy singleton beans...
    for (String beanName : beanNames) {
      //getMergedLocalBeanDefinition:见下~
      RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
      // 不是抽象类&&是单例&&不是懒加载
      if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
        // 这是Spring提供的对工程bean模式的支持:比如第三方框架的继承经常采用这种方式
        // 如果是工厂Bean,那就会此工厂Bean放进去
        if (isFactoryBean(beanName)) {
          // 拿到工厂Bean本省,注意有前缀为:FACTORY_BEAN_PREFIX 
          Object bean = getBean(FACTORY_BEAN_PREFIX + beanName);
          if (bean instanceof FactoryBean) {
            final FactoryBean<?> factory = (FactoryBean<?>) bean;
            boolean isEagerInit;
            if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {
              isEagerInit = AccessController.doPrivileged((PrivilegedAction<Boolean>)
                      ((SmartFactoryBean<?>) factory)::isEagerInit,
                  getAccessControlContext());
            } else {
              isEagerInit = (factory instanceof SmartFactoryBean &&
                  ((SmartFactoryBean<?>) factory).isEagerInit());
            }
            // true:表示渴望马上被初始化的,那就拿上执行初始化~
            if (isEagerInit) {
              getBean(beanName);
            }
          }
        } else { // 这里,就是普通单例Bean正式初始化了~  核心逻辑在方法:doGetBean 
          // 关于doGetBean方法的详解:下面有贴出博文,专文讲解
          getBean(beanName);
        }
      }
    }
    // Trigger post-initialization callback for all applicable beans...
    // SmartInitializingSingleton:所有非lazy单例Bean实例化完成后的回调方法 Spring4.1才提供
    //SmartInitializingSingleton的afterSingletonsInstantiated方法是在所有单例bean都已经被创建后执行的
    //InitializingBean#afterPropertiesSet 是在仅仅自己被创建好了执行的
    // 比如EventListenerMethodProcessor它在afterSingletonsInstantiated方法里就去处理所有的Bean的方法
    // 看看哪些被标注了@EventListener注解,提取处理也作为一个Listener放到容器addApplicationListener里面去
    for (String beanName : beanNames) {
      Object singletonInstance = getSingleton(beanName);
      if (singletonInstance instanceof SmartInitializingSingleton) {
        final SmartInitializingSingleton smartSingleton = (SmartInitializingSingleton) singletonInstance;
        if (System.getSecurityManager() != null) {
          AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
            smartSingleton.afterSingletonsInstantiated();
            return null;
          }, getAccessControlContext());
        }
        else {
          // 比如:ScheduledAnnotationBeanPostProcessor CacheAspectSupport  MBeanExporter等等
          smartSingleton.afterSingletonsInstantiated();
        }
      }
    }
  }


此处必须说明:此处绝大部分的单例Bean定义信息都会被实例化,但是如果是通过FactoryBean定义的,它是懒加载的(如果没人使用,就先不会实例化。只会到使用的时候才实例化~)。如下例子:


@Configuration
public class RootConfig {
    @Bean
    public Person person() {
        System.out.println("this is from @Bean person");
        return new Person();
    }
    @Bean("personFactoryBean")
    public FactoryBean<Person> personFactoryBean() {
        return new FactoryBean<Person>() {
            @Override
            public Person getObject() throws Exception {
                System.out.println("this is from personFactoryBean");
                return new Person();
            }
            @Override
            public Class<?> getObjectType() {
                return Person.class;
            }
        };
    }
}


默认情况下,person()会被启动时候,实例化,但是personFactoryBean()不会。


此时通过applicationContext.getBeanDefinitionNames()能找到personFactoryBean这个Bean定义。并且通过.beanFactory.getSingletonNames()也能找到personFactoryBean这个单例Bean,所以其实此时容器内的Bean是FactoryBean而不是真正的Bean,只有在真正使用的时候,才会create一个真正的Bean出来~


比如我只需要在某个组件内注入一下:

    @Autowired
    @Qualifier("personFactoryBean")
    private Person person;


这个FactoryBean#getObject就会立马执行了~


getMergedLocalBeanDefinition方法分解:

这里先解释一下getMergedLocalBeanDefinition方法的含义,因为这个方法会常常看到。

Bean定义公共的抽象类是AbstractBeanDefinition,普通的Bean在Spring加载Bean定义的时候,实例化出来的是GenericBeanDefinition,而Spring上下文包括实例化所有Bean用的AbstractBeanDefinition是RootBeanDefinition,这时候就使用getMergedLocalBeanDefinition方法做了一次转化,将非RootBeanDefinition转换为RootBeanDefinition以供后续操作。

  // 该方法功能说明:在map缓存中把Bean的定义拿出来。交给getMergedLocalBeanDefinition处理。最终转换成了RootBeanDefinition类型
  //在转换的过程中如果BeanDefinition的父类不为空,则把父类的属性也合并到RootBeanDefinition中,
  //所以getMergedLocalBeanDefinition方法的作用就是获取缓存的BeanDefinition对象并合并其父类和本身的属性
  //注意如果当前BeanDefinition存在父BeanDefinition,会基于父BeanDefinition生成一个RootBeanDefinition,然后再将调用OverrideFrom子BeanDefinition的相关属性覆写进去
  protected RootBeanDefinition getMergedLocalBeanDefinition(String beanName) throws BeansException {
    // Quick check on the concurrent map first, with minimal locking.
    RootBeanDefinition mbd = this.mergedBeanDefinitions.get(beanName);
    if (mbd != null) {
      return mbd;
    }
    return getMergedBeanDefinition(beanName, getBeanDefinition(beanName));
  }
getBeanDefinition(beanName):方法如下
  // 这一步说白了:就是把前面已经保存在IOC容器里的BeanDefinition定义信息
  @Override
  public BeanDefinition getBeanDefinition(String beanName) throws NoSuchBeanDefinitionException {
    BeanDefinition bd = this.beanDefinitionMap.get(beanName);
    if (bd == null) {
      if (this.logger.isTraceEnabled()) {
        this.logger.trace("No bean named '" + beanName + "' found in " + this);
      }
      throw new NoSuchBeanDefinitionException(beanName);
    }
    return bd;
  }


getBean():创建Bean的核心方法


getBean()方法为创建Bean,包括初始化剩余的单实例Bean时候的核心方法,专门写了一篇博文来介绍它,请移步此处:【小家Spring】AbstractBeanFactory#getBean()、doGetBean完成Bean的初始化、实例化,以及BeanPostProcessor后置处理器源码级详细分析


至此,finishBeanFactoryInitialization这一步完成,所有的单例Bean已经创建完成并放置容器里。


refresh() 第十二步:finishRefresh()


refresh做完之后需要做的其他事情。

  protected void finishRefresh() {
    // Clear context-level resource caches (such as ASM metadata from scanning).
    // 这个是Spring5.0之后才有的方法
    // 表示清除一些resourceCaches,如doc说的  清楚context级别的资源缓存,比如ASM的元数据
    clearResourceCaches();
    // Initialize lifecycle processor for this context.
    // 初始化所有的LifecycleProcessor  详见下面
    initLifecycleProcessor();
    // Propagate refresh to lifecycle processor first.
    // 上面注册好的处理器,这里就拿出来,调用它的onRefresh方法了
    getLifecycleProcessor().onRefresh();
    // Publish the final event.
    // 发布容器刷新的事件:
    publishEvent(new ContextRefreshedEvent(this));
    // Participate in LiveBeansView MBean, if active.
    // 和MBeanServer和MBean有关的。相当于把当前容器上下文,注册到MBeanServer里面去。
    // 这样子,MBeanServer持久了容器的引用,就可以拿到容器的所有内容了,也就让Spring支持到了MBean的相关功能
    LiveBeansView.registerApplicationContext(this);
  }


initLifecycleProcessor():


在 Spring 中还提供了 Lifecycle 接口, Lifecycle 中包含start/stop方法,实现此接口后Spring会保证在启动的时候调用其start方法开始生命周期,并在Spring关闭的时候调用 stop方法来结束生命周期,通常用来配置后台程序,在启动后一直运行(如对 MQ 进行轮询等)。而ApplicationContext的初始化最后正是保证了这一功能的实现。


当ApplicationContext启动或停止时,它会通过LifecycleProcessor来与所有声明的bean的周期做状态更新,而在LifecycleProcessor的使用前首先需要初始化。

  // 这个初始化逻辑比较简单
  protected void initLifecycleProcessor() {
    ConfigurableListableBeanFactory beanFactory = getBeanFactory();
    // 如果工厂里已经存在LifecycleProcessor,那就拿出来,把值放上去this.lifecycleProcessor
    if (beanFactory.containsLocalBean(LIFECYCLE_PROCESSOR_BEAN_NAME)) {
      this.lifecycleProcessor = beanFactory.getBean(LIFECYCLE_PROCESSOR_BEAN_NAME, LifecycleProcessor.class);
    }
    // 一般情况下,都会注册上这个默认的处理器DefaultLifecycleProcessor
    else {
      DefaultLifecycleProcessor defaultProcessor = new DefaultLifecycleProcessor();
      defaultProcessor.setBeanFactory(beanFactory);
      this.lifecycleProcessor = defaultProcessor;
      // 直接注册成单例Bean进去容器里
      beanFactory.registerSingleton(LIFECYCLE_PROCESSOR_BEAN_NAME, this.lifecycleProcessor);
    }
  }

初始化生命周期处理器,并设置到Spring容器中(LifecycleProcessor)

调用生命周期处理器的onRefresh方法,这个方法会找出Spring容器中实现了SmartLifecycle接口的类并进行start方法的调用

发布ContextRefreshedEvent事件告知对应的ApplicationListener进行响应的操作

调用LiveBeansView的registerApplicationContext方法:如果设置了JMX相关的属性,则就调用该方法

发布EmbeddedServletContainerInitializedEvent事件告知对应的ApplicationListener进行响应的操作


getLifecycleProcessor().onRefresh():DefaultLifecycleProcessor讲解


备注:LifecycleProcessor作为一个Bean,它自己也实现了Lifecycle这个接口。LifecycleProcessor和DefaultLifecycleProcessor都是Spring3.0提供的,Lifecycle在Spring2.0就有了,但子接口SmartLifecycle是Spring3.0后提供的


public interface LifecycleProcessor extends Lifecycle {}
public interface SmartLifecycle extends Lifecycle, Phased {
  // 是否伴随这容器的启动而启动  true表示容器refreshed它就会启动了
  // false:必须显示的执行了它的start()才行
  boolean isAutoStartup();
  // 相比于Lifecycle 的stop,增加了回调函数
  void stop(Runnable callback);
}
public interface Phased {
  // 权重值
  int getPhase();
}

这里的处理器(只能有一个),就是我们上面注册好了的DefaultLifecycleProcessor,我们看看它的主要方法:


  • 启动和关闭调用的顺序是很重要的。如果两个对象之间存在依赖关系,依赖类要在其依赖类后启动,依赖类也要在其依赖类前停止。


// Lifecycle的方法
  @Override
  public void start() {
    // 传false,表示Bean一定会启动
    startBeans(false);
    this.running = true;
  }
  @Override
  public void stop() {
    stopBeans();
    this.running = false;
  }
  @Override
  public boolean isRunning() {
    return this.running;
  }
// LifecycleProcessor的方法
  //getLifecycleProcessor().onRefresh(); 这个方法才是容器启动时候自动会调用的,其余都不是
  // 显然它默认只会执行实现了SmartLifecycle接口并且isAutoStartup = true的Bean的start方法
  @Override
  public void onRefresh() {
    startBeans(true);
    this.running = true;
  }
  // 容器关闭的时候自动会调的
  @Override
  public void onClose() {
    stopBeans();
    this.running = false;
  }
// 而到底bean实现的Lifecyle的start()、stop()方法是否会调用呢? 请看下文
//======================startBeans和stopBeans  LifecycleGroup#start======================
  // autoStartupOnly:是否仅支持自动启动   
  // true:只支持伴随容器启动 (bean必须实现了`SmartLifecycle`接口且isAutoStartup为true才行)
  // false:表示无所谓。都会执行bean的start方法=======
  private void startBeans(boolean autoStartupOnly) {
    //拿到所有的实现了Lifecycle/SmartLifecycle的  已经在IOC容器里面的单例Bean们(备注:不包括自己this,也就是说处理器自己不包含进去)
    // 这里若我们自己没有定义过实现Lifecycle的Bean,这里就是空的
    Map<String, Lifecycle> lifecycleBeans = getLifecycleBeans();
    // phases 这个Map,表示按照phase 值,吧这个Bean进行分组,最后分组执行
    Map<Integer, LifecycleGroup> phases = new HashMap<>();
    lifecycleBeans.forEach((beanName, bean) -> {
      // 若Bean实现了SmartLifecycle 接口并且标注是AutoStartup  或者  强制要求自动自行的autoStartupOnly = true
      if (!autoStartupOnly || (bean instanceof SmartLifecycle && ((SmartLifecycle) bean).isAutoStartup())) {
        int phase = getPhase(bean);
        LifecycleGroup group = phases.get(phase);
        if (group == null) {
          group = new LifecycleGroup(phase, this.timeoutPerShutdownPhase, lifecycleBeans, autoStartupOnly);
          phases.put(phase, group);
        }
        // 添加到phase 值相同的组  分组嘛
        group.add(beanName, bean);
      }
    });
    if (!phases.isEmpty()) {
      List<Integer> keys = new ArrayList<>(phases.keySet());
      // 此处有个根据key从小到大的排序,然后一个个的调用他们的start方法
      Collections.sort(keys);
      for (Integer key : keys) {
        // 这里调用LifecycleGroup#start() 如下
        phases.get(key).start();
      }
    }
  }
//LifecycleGroup#start()
    public void start() {
      if (this.members.isEmpty()) {
        return;
      }
      if (logger.isInfoEnabled()) {
        logger.info("Starting beans in phase " + this.phase);
      }
      // 按照权重值进行排序  若没有实现Smart接口的  权重值都为0
      Collections.sort(this.members);
      for (LifecycleGroupMember member : this.members) {
        if (this.lifecycleBeans.containsKey(member.name)) {
          // 一次执行这些Bean的start方法(这里面逻辑就没啥好看的,只有一个考虑到getBeanFactory().dependenciesForBean控制Bean的依赖关系的)
          doStart(this.lifecycleBeans, member.name, this.autoStartupOnly);
        }
      }
    }
//stopBeans原理基本同startBeans,只是顺序是倒序的,此处省略


就这样,实现了Lifecycle接口的Bean start方法什么时候调用就有门路了。


从上面的源码中,我们能够读出什么异常的地方呢?我们发现Lifecycle这个接口并不能直接使用。

因为DefaultLifecycleProcessor的onRefresh方法传值为autoStartupOnly=true:表示只有实现了SmartLifecycle的Bean才会调用start方法,因为实现了SmartLifecycle接口会有一个phase值,根据上面源码会根据此值分组执行。

autoStartupOnly=false则只要是Lifecycle 的实现既可以被调用,我们会给其默认的phase。


所以,我们要想要这个功能,请实现SmartLifecycle,而不是Lifecycle接口


结论:


  • Spring的IoC容器启动过程中,默认只会执行实现了SmartLifecycle接口且isAutoStartup()=true的Bean的start()方法的。(所以你要想容器启动后就执行,请实现SmartLifecycle吧)
  • AbstractApplicationContext#start() 手动调用触发。常见的一般这么做的:
 context = new ClassPathXmlApplicationContext(configPath.split("[,\\s]+"));
 context.start();
相关文章
|
7天前
|
XML Java 测试技术
spring复习01,IOC的思想和第一个spring程序helloWorld
Spring框架中IOC(控制反转)的思想和实现,通过一个简单的例子展示了如何通过IOC容器管理对象依赖,从而提高代码的灵活性和可维护性。
spring复习01,IOC的思想和第一个spring程序helloWorld
|
4天前
|
缓存 Java Spring
手写Spring Ioc 循环依赖底层源码剖析
在Spring框架中,IoC(控制反转)是一个核心特性,它通过依赖注入(DI)实现了对象间的解耦。然而,在实际开发中,循环依赖是一个常见的问题。
13 4
|
7天前
|
XML Java 开发者
经典面试---spring IOC容器的核心实现原理
作为一名拥有十年研发经验的工程师,对Spring框架尤其是其IOC(Inversion of Control,控制反转)容器的核心实现原理有着深入的理解。
29 3
|
2月前
|
XML Java 数据格式
Spring5入门到实战------7、IOC容器-Bean管理XML方式(外部属性文件)
这篇文章是Spring5框架的实战教程,主要介绍了如何在Spring的IOC容器中通过XML配置方式使用外部属性文件来管理Bean,特别是数据库连接池的配置。文章详细讲解了创建属性文件、引入属性文件到Spring配置、以及如何使用属性占位符来引用属性文件中的值。
Spring5入门到实战------7、IOC容器-Bean管理XML方式(外部属性文件)
|
4月前
|
XML Java 数据格式
Spring5系列学习文章分享---第一篇(概述+特点+IOC原理+IOC并操作之bean的XML管理操作)
Spring5系列学习文章分享---第一篇(概述+特点+IOC原理+IOC并操作之bean的XML管理操作)
41 1
|
19天前
|
XML Java 数据格式
Spring IOC—基于XML配置Bean的更多内容和细节(通俗易懂)
Spring 第二节内容补充 关于Bean配置的更多内容和细节 万字详解!
116 18
Spring IOC—基于XML配置Bean的更多内容和细节(通俗易懂)
|
4月前
|
XML druid Java
Spring5系列学习文章分享---第二篇(IOC的bean管理factory+Bean作用域与生命周期+自动装配+基于注解管理+外部属性管理之druid)
Spring5系列学习文章分享---第二篇(IOC的bean管理factory+Bean作用域与生命周期+自动装配+基于注解管理+外部属性管理之druid)
45 0
|
2月前
|
XML Java 数据格式
Spring5入门到实战------3、IOC容器-Bean管理XML方式(一)
这篇文章详细介绍了Spring框架中IOC容器的Bean管理,特别是基于XML配置方式的实现。文章涵盖了Bean的定义、属性注入、使用set方法和构造函数注入,以及如何注入不同类型的属性,包括null值、特殊字符和外部bean。此外,还探讨了内部bean的概念及其与外部bean的比较,并提供了相应的示例代码和测试结果。
Spring5入门到实战------3、IOC容器-Bean管理XML方式(一)
|
2月前
|
XML Java 数据格式
Spring5入门到实战------5、IOC容器-Bean管理(三)
这篇文章深入探讨了Spring5框架中IOC容器的高级Bean管理,包括FactoryBean的使用、Bean作用域的设置、Bean生命周期的详细解释以及Bean后置处理器的实现和应用。
Spring5入门到实战------5、IOC容器-Bean管理(三)
|
2月前
|
XML Java 数据格式
Spring5入门到实战------4、IOC容器-Bean管理XML方式、集合的注入(二)
这篇文章是Spring5框架的实战教程,主题是IOC容器中Bean的集合属性注入,通过XML配置方式。文章详细讲解了如何在Spring中注入数组、List、Map和Set类型的集合属性,并提供了相应的XML配置示例和Java类定义。此外,还介绍了如何在集合中注入对象类型值,以及如何使用Spring的util命名空间来实现集合的复用。最后,通过测试代码和结果展示了注入效果。
Spring5入门到实战------4、IOC容器-Bean管理XML方式、集合的注入(二)
下一篇
无影云桌面