Spring源码 05 IOC 注解方式

Spring IOC 主要有两种实现方式:XML注解

Spring 3.0 推出了注解注入,成为了现在的主流,也是官方推荐的。

这里分析注解方式。

AnnotationConfigApplicationContext(AppConfig.class)

AppConfig

package cn.sail.ioc;

import org.springframework.context.annotation.ComponentScan;

@ComponentScan
public class AppConfig {
    
}

UserService

package cn.sail.ioc.service;

import cn.sail.ioc.dao.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class UserService {
    
    @Autowired
    UserDao userDao;
    
    public void test() {
        System.out.println(userDao);
    }
    
}

UserDao

package cn.sail.ioc.dao;

import org.springframework.stereotype.Component;

@Component
public class UserDao {

}

使用

ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
UserService userService = context.getBean(UserService.class);
userService.test();

执行结果:cn.sail.ioc.dao.UserDao@1bd39d3c

接下来开始分析 AnnotationConfigApplicationContext(AppConfig.class)

由于 Spring 源码层级十分复杂,约定如下规则

  • 数字 类名:数字代表该类出现的顺序。
  • 类数字-数字 方法注释:数字代表该方法在类中执行的层级。

1 AnnotationConfigApplicationContext

由于其父类 AbstractApplicationContext 存在静态代码块,先进入父类的静态代码块。

image

2 AbstractApplicationContext

2-1 静态代码块

进入 ClassPathXmlApplicationContext 的构造方法,会先进入 AbstractApplicationContext 的静态代码块。

static {
    /**
     * 优先加载上下文关闭事件来防止奇怪的类加载问题
     * WebLogic 8.1 在应用程序关闭的时候出现的 BUG
     */
    ContextClosedEvent.class.getName();
}

这里是针对 WebLogic 8.1 的特殊处理,与主体逻辑不关,不用过于关注。

1 AnnotationConfigApplicationContext

由于继承了GenericApplicationContext,会先执行父类的构造方法。

3 GenericApplicationContext

public GenericApplicationContext() {
   this.beanFactory = new DefaultListableBeanFactory();
}

初始化了内部的beanFactoryDefaultListableBeanFactory

1 AnnotationConfigApplicationContext

public AnnotationConfigApplicationContext(Class<?>... componentClasses) {
   // 构造
   this();
   // 注册
   register(componentClasses);
   // 刷新
   refresh();
}

1-1 构造

public AnnotationConfigApplicationContext() {
   StartupStep createAnnotatedBeanDefReader = this.getApplicationStartup().start("spring.context.annotated-bean-reader.create");
   // 读取被注解了的 Bean
   this.reader = new AnnotatedBeanDefinitionReader(this);
   createAnnotatedBeanDefReader.end();
	/*
    定义扫描器
    可以用来扫描包或者类,继而转换成 Bean 定义信息
    但实际上我们扫描包不是 scanner 这个对象,是 Spring 自己 new 的一个 ClassPathBeanDefinitionScanner
    这里的 scanner 仅仅是为了程序员能够在外部调用 AnnotationConfigApplicationContext 对象 scan 方法
    */
   this.scanner = new ClassPathBeanDefinitionScanner(this);
}

1-2 读取被注解了的 Bean

AnnotatedBeanDefinitionReader(this)

4 AnnotatedBeanDefinitionReader

public AnnotatedBeanDefinitionReader(BeanDefinitionRegistry registry) {
   this(registry, getOrCreateEnvironment(registry));
}
public AnnotatedBeanDefinitionReader(BeanDefinitionRegistry registry, Environment environment) {
   Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
   Assert.notNull(environment, "Environment must not be null");
   this.registry = registry;
   this.conditionEvaluator = new ConditionEvaluator(registry, environment, null);
   // 将处理注解的基础设施类放入
   AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
}

4-1 将处理注解的基础设施类放入

AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry)

5 AnnotationConfigUtils

public static void registerAnnotationConfigProcessors(BeanDefinitionRegistry registry) {
    // 将处理注解的基础设施类放入
    registerAnnotationConfigProcessors(registry, null);
}
public static Set<BeanDefinitionHolder> registerAnnotationConfigProcessors(BeanDefinitionRegistry registry, @Nullable Object source) {

   DefaultListableBeanFactory beanFactory = unwrapDefaultListableBeanFactory(registry);
   if (beanFactory != null) {
      if (!(beanFactory.getDependencyComparator() instanceof AnnotationAwareOrderComparator)) {
         beanFactory.setDependencyComparator(AnnotationAwareOrderComparator.INSTANCE);
      }
      if (!(beanFactory.getAutowireCandidateResolver() instanceof ContextAnnotationAutowireCandidateResolver)) {
         beanFactory.setAutowireCandidateResolver(new ContextAnnotationAutowireCandidateResolver());
      }
   }

   Set<BeanDefinitionHolder> beanDefs = new LinkedHashSet<>(8);

   if (!registry.containsBeanDefinition(CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME)) {
      RootBeanDefinition def = new RootBeanDefinition(ConfigurationClassPostProcessor.class);
      def.setSource(source);
      beanDefs.add(registerPostProcessor(registry, def, CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME));
   }

   if (!registry.containsBeanDefinition(AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME)) {
      RootBeanDefinition def = new RootBeanDefinition(AutowiredAnnotationBeanPostProcessor.class);
      def.setSource(source);
      beanDefs.add(registerPostProcessor(registry, def, AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME));
   }

   // Check for JSR-250 support, and if present add the CommonAnnotationBeanPostProcessor.
   if (jsr250Present && !registry.containsBeanDefinition(COMMON_ANNOTATION_PROCESSOR_BEAN_NAME)) {
      RootBeanDefinition def = new RootBeanDefinition(CommonAnnotationBeanPostProcessor.class);
      def.setSource(source);
      beanDefs.add(registerPostProcessor(registry, def, COMMON_ANNOTATION_PROCESSOR_BEAN_NAME));
   }

   // Check for JPA support, and if present add the PersistenceAnnotationBeanPostProcessor.
   if (jpaPresent && !registry.containsBeanDefinition(PERSISTENCE_ANNOTATION_PROCESSOR_BEAN_NAME)) {
      RootBeanDefinition def = new RootBeanDefinition();
      try {
         def.setBeanClass(ClassUtils.forName(PERSISTENCE_ANNOTATION_PROCESSOR_CLASS_NAME,
               AnnotationConfigUtils.class.getClassLoader()));
      }
      catch (ClassNotFoundException ex) {
         throw new IllegalStateException(
               "Cannot load optional framework class: " + PERSISTENCE_ANNOTATION_PROCESSOR_CLASS_NAME, ex);
      }
      def.setSource(source);
      beanDefs.add(registerPostProcessor(registry, def, PERSISTENCE_ANNOTATION_PROCESSOR_BEAN_NAME));
   }

   if (!registry.containsBeanDefinition(EVENT_LISTENER_PROCESSOR_BEAN_NAME)) {
      RootBeanDefinition def = new RootBeanDefinition(EventListenerMethodProcessor.class);
      def.setSource(source);
      beanDefs.add(registerPostProcessor(registry, def, EVENT_LISTENER_PROCESSOR_BEAN_NAME));
   }

   if (!registry.containsBeanDefinition(EVENT_LISTENER_FACTORY_BEAN_NAME)) {
      RootBeanDefinition def = new RootBeanDefinition(DefaultEventListenerFactory.class);
      def.setSource(source);
      beanDefs.add(registerPostProcessor(registry, def, EVENT_LISTENER_FACTORY_BEAN_NAME));
   }

   return beanDefs;
}

1 AnnotationConfigApplicationContext

1-2 定义扫描器

ClassPathBeanDefinitionScanner(this)

6 ClassPathBeanDefinitionScanner

public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry) {
   this(registry, true);
}
public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry, boolean useDefaultFilters) {
   this(registry, useDefaultFilters, getOrCreateEnvironment(registry));
}
public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry, boolean useDefaultFilters, Environment environment) {
   this(registry, useDefaultFilters, environment, (registry instanceof ResourceLoader ? (ResourceLoader) registry : null));
}

1 AnnotationConfigApplicationContext

1-1 注册

register(componentClasses)
public void register(Class<?>... componentClasses) {
   Assert.notEmpty(componentClasses, "At least one component class must be specified");
   StartupStep registerComponentClass = this.getApplicationStartup().start("spring.context.component-classes.register").tag("classes", () -> Arrays.toString(componentClasses));
   // 注册
   this.reader.register(componentClasses);
   registerComponentClass.end();
}

7 AnnotatedBeanDefinitionReader

public void register(Class<?>... componentClasses) {
   for (Class<?> componentClass : componentClasses) {
      // 注册 Bean
      registerBean(componentClass);
   }
}

7-1 注册 Bean

registerBean(componentClass)
public void registerBean(Class<?> beanClass) {
   // 进一步注册 Bean
   doRegisterBean(beanClass, null, null, null, null);
}

7-2 进一步注册 Bean

doRegisterBean(beanClass, null, null, null, null)
private <T> void doRegisterBean(Class<T> beanClass, @Nullable String name,
      @Nullable Class<? extends Annotation>[] qualifiers, @Nullable Supplier<T> supplier,
      @Nullable BeanDefinitionCustomizer[] customizers) {
   /*
   得到 bean 的描述信息
   比如 bean 的注解,作用范围,是否懒加载,注入方式等
   */
   AnnotatedGenericBeanDefinition abd = new AnnotatedGenericBeanDefinition(beanClass);
   // 被条件注解 @Conditional 注释的 bean 跳过注册
   if (this.conditionEvaluator.shouldSkip(abd.getMetadata())) {
      return;
   }

   abd.setInstanceSupplier(supplier);
   /*
   解析 bean 的 Scope
   比如是否单例 singleton 还是其他
   */
   ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(abd);
   abd.setScope(scopeMetadata.getScopeName());
   // 生成beanName,默认就是类名小写
   String beanName = (name != null ? name : this.beanNameGenerator.generateBeanName(abd, this.registry));
   // 通过判断注解内容,设置一些公共属性,比如是否懒加载,优先级等
   AnnotationConfigUtils.processCommonDefinitionAnnotations(abd);
   if (qualifiers != null) {
      for (Class<? extends Annotation> qualifier : qualifiers) {
         if (Primary.class == qualifier) {
            abd.setPrimary(true);
         }
         else if (Lazy.class == qualifier) {
            abd.setLazyInit(true);
         }
         else {
            abd.addQualifier(new AutowireCandidateQualifier(qualifier));
         }
      }
   }
   if (customizers != null) {
      for (BeanDefinitionCustomizer customizer : customizers) {
         customizer.customize(abd);
      }
   }

   BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(abd, beanName);
   definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
   BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, this.registry);
}

7-3 解析 bean 的 Scope

private ScopeMetadataResolver scopeMetadataResolver = new AnnotationScopeMetadataResolver();

this.scopeMetadataResolver.resolveScopeMetadata(abd)

8 AnnotationScopeMetadataResolver

public ScopeMetadata resolveScopeMetadata(BeanDefinition definition) {
   ScopeMetadata metadata = new ScopeMetadata();
   if (definition instanceof AnnotatedBeanDefinition) {
      AnnotatedBeanDefinition annDef = (AnnotatedBeanDefinition) definition;
      AnnotationAttributes attributes = AnnotationConfigUtils.attributesFor(
            annDef.getMetadata(), this.scopeAnnotationType);
      if (attributes != null) {
         // 获取 @Scope 注解的值,没有默认为 singleton
         metadata.setScopeName(attributes.getString("value"));
         ScopedProxyMode proxyMode = attributes.getEnum("proxyMode");
         if (proxyMode == ScopedProxyMode.DEFAULT) {
            proxyMode = this.defaultProxyMode;
         }
         metadata.setScopedProxyMode(proxyMode);
      }
   }
   return metadata;
}

8-1 获取 @Scope 注解的值

getString("value")

9 AnnotationAttributes

public String getString(String attributeName) {
    // 获取必需属性
    return getRequiredAttribute(attributeName, String.class);
}

9-1 获取必需属性

getRequiredAttribute(attributeName, String.class)
private <T> T getRequiredAttribute(String attributeName, Class<T> expectedType) {
    Assert.hasText(attributeName, "'attributeName' must not be null or empty");
    Object value = get(attributeName);
    // 断言属性存在
    assertAttributePresence(attributeName, value);
    // 断言没有异常
    assertNotException(attributeName, value);
    if (!expectedType.isInstance(value) && expectedType.isArray() &&
        expectedType.getComponentType().isInstance(value)) {
        Object array = Array.newInstance(expectedType.getComponentType(), 1);
        Array.set(array, 0, value);
        value = array;
    }
    // 断言属性类型
    assertAttributeType(attributeName, value, expectedType);
    return (T) value;
}

7 AnnotatedBeanDefinitionReader

7-3 设置公共属性

AnnotationConfigUtils.processCommonDefinitionAnnotations(abd)

10 AnnotationConfigUtils

public static void processCommonDefinitionAnnotations(AnnotatedBeanDefinition abd) {
   processCommonDefinitionAnnotations(abd, abd.getMetadata());
}
static void processCommonDefinitionAnnotations(AnnotatedBeanDefinition abd, AnnotatedTypeMetadata metadata) {
   AnnotationAttributes lazy = attributesFor(metadata, Lazy.class);
   if (lazy != null) {
      abd.setLazyInit(lazy.getBoolean("value"));
   }
   else if (abd.getMetadata() != metadata) {
      lazy = attributesFor(abd.getMetadata(), Lazy.class);
      if (lazy != null) {
         abd.setLazyInit(lazy.getBoolean("value"));
      }
   }

   if (metadata.isAnnotated(Primary.class.getName())) {
      abd.setPrimary(true);
   }
   AnnotationAttributes dependsOn = attributesFor(metadata, DependsOn.class);
   if (dependsOn != null) {
      abd.setDependsOn(dependsOn.getStringArray("value"));
   }

   AnnotationAttributes role = attributesFor(metadata, Role.class);
   if (role != null) {
      abd.setRole(role.getNumber("value").intValue());
   }
   AnnotationAttributes description = attributesFor(metadata, Description.class);
   if (description != null) {
      abd.setDescription(description.getString("value"));
   }
}

7 AnnotatedBeanDefinitionReader

7-3 注册 Bean 定义信息

BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, this.registry)
public static void registerBeanDefinition(BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry) throws BeanDefinitionStoreException {
   // 使用 beanName 做唯一标识注册
   String beanName = definitionHolder.getBeanName();
   // 注册 Bean 定义信息
   registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());
   // 注册所有的别名
   String[] aliases = definitionHolder.getAliases();
   if (aliases != null) {
      for (String alias : aliases) {
         registry.registerAlias(beanName, alias);
      }
   }
}

11 GenericApplicationContext

public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
      throws BeanDefinitionStoreException {
   // 注册 Bean 定义信息
   this.beanFactory.registerBeanDefinition(beanName, beanDefinition);
}

12 DefaultListableBeanFactory

public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
      throws BeanDefinitionStoreException {

   Assert.hasText(beanName, "Bean name must not be empty");
   Assert.notNull(beanDefinition, "BeanDefinition must not be null");

   if (beanDefinition instanceof AbstractBeanDefinition) {
      try {
         /*
         注册前的最后一次校验
         这里的校验不同于之前的 XML 文件校验,主要是对于 AbstractBeanDefinition 属性中的 methodOverrides 校验
         校验 methodOverrides 是否与工厂方法并存或者 methodOverrides 对应的方法根本不存在
         */
         ((AbstractBeanDefinition) beanDefinition).validate();
      }
      catch (BeanDefinitionValidationException ex) {
         throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
               "Validation of bean definition failed", ex);
      }
   }
   /*
   注册BeanDefinition,就是将BeanDefinition放入一个map中,key是beanName
   注册之前,先查下是否被注册过
    */
   BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName);
   if (existingDefinition != null) {
      // 如果对应的 BeanName 已经注册且在配置中配置了 bean 不允许被覆盖,则抛出异常(默认允许覆盖)
      if (!isAllowBeanDefinitionOverriding()) {
         throw new BeanDefinitionOverrideException(beanName, beanDefinition, existingDefinition);
      }
      else if (existingDefinition.getRole() < beanDefinition.getRole()) {
         // e.g. was ROLE_APPLICATION, now overriding with ROLE_SUPPORT or ROLE_INFRASTRUCTURE
         if (logger.isInfoEnabled()) {
            logger.info("Overriding user-defined bean definition for bean '" + beanName +
                  "' with a framework-generated bean definition: replacing [" +
                  existingDefinition + "] with [" + beanDefinition + "]");
         }
      }
      else if (!beanDefinition.equals(existingDefinition)) {
         if (logger.isDebugEnabled()) {
            logger.debug("Overriding bean definition for bean '" + beanName +
                  "' with a different definition: replacing [" + existingDefinition +
                  "] with [" + beanDefinition + "]");
         }
      }
      else {
         if (logger.isTraceEnabled()) {
            logger.trace("Overriding bean definition for bean '" + beanName +
                  "' with an equivalent definition: replacing [" + existingDefinition +
                  "] with [" + beanDefinition + "]");
         }
      }
      // 如果允许 BeanDefinition 的覆盖,那就向 beanDefinitionMap 中再次存一次值,覆盖之前的值
      this.beanDefinitionMap.put(beanName, beanDefinition);
   }
   else {
      /*
      检查 bean 的创建过程是否已经开始了
      通过判断一个 set 集合是否为空,因为创建过的 bean 都会放到那个 set 中保存
       */
      if (hasBeanCreationStarted()) {
         // Cannot modify startup-time collection elements anymore (for stable iteration)
         /*
         如果项目已经运行了
         由于 beanDefinitionMap 是一个全局变量,可能存在并发问题,所以要加锁处理
          */
         synchronized (this.beanDefinitionMap) {
            this.beanDefinitionMap.put(beanName, beanDefinition);
            // 更新 beanDefinitionNames 的 list
            List<String> updatedDefinitions = new ArrayList<>(this.beanDefinitionNames.size() + 1);
            updatedDefinitions.addAll(this.beanDefinitionNames);
            updatedDefinitions.add(beanName);
            this.beanDefinitionNames = updatedDefinitions;
            // 更新人工注册的单例集合
            removeManualSingletonName(beanName);
         }
      }
      else {
         // Still in startup registration phase
         /*
          仍然在启动注册阶段
          注册 beanDefinition
          */
         this.beanDefinitionMap.put(beanName, beanDefinition);
         // 记录 beanName
         this.beanDefinitionNames.add(beanName);
         // 更新人工注册的单例集合
         removeManualSingletonName(beanName);
      }
      this.frozenBeanDefinitionNames = null;
   }

   if (existingDefinition != null || containsSingleton(beanName)) {
      // 重置所有 beanName 对应的缓存
      resetBeanDefinition(beanName);
   }
   else if (isConfigurationFrozen()) {
      clearByTypeCache();
   }
}

12-1 更新人工注册的单例集合

removeManualSingletonName(beanName)
private void removeManualSingletonName(String beanName) {
   updateManualSingletonNames(set -> set.remove(beanName), set -> set.contains(beanName));
}
private void updateManualSingletonNames(Consumer<Set<String>> action, Predicate<Set<String>> condition) {
   // 判断是否已开始创建 Bean
   if (hasBeanCreationStarted()) {
      synchronized (this.beanDefinitionMap) {
         if (condition.test(this.manualSingletonNames)) {
            Set<String> updatedSingletons = new LinkedHashSet<>(this.manualSingletonNames);
            action.accept(updatedSingletons);
            this.manualSingletonNames = updatedSingletons;
         }
      }
   }
   else {
      if (condition.test(this.manualSingletonNames)) {
         action.accept(this.manualSingletonNames);
      }
   }
}

1 AnnotationConfigApplicationContext

1-1 刷新

refresh()

2 AbstractApplicationContext

public void refresh() throws BeansException, IllegalStateException {
   // 同步监视器
   synchronized (this.startupShutdownMonitor) {
      StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");

      /*
      1 准备刷新的上下文环境。例如对系统属性或者环境变量进行准备及验证
      设置容器的启动时间
      设置关闭状态为 false
      设置活跃状态为 true
      获取 Environment 对象,并加载当前系统的属性值到 Environment 对象中并进行验证
      准备监听器和事件的集合对象,默认为空的集合
       */
      prepareRefresh();

      /*
      2 初始化 BeanFactory,并进行 XML 文件读取
      创建容器对象:DefaultListableBeanFactory
      加载 XML 配置文件的属性值到当前工厂中,最重要的就是 BeanDefinition
       */
      ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

      /*
      3 对 BeanFactory 进行各种功能填充
      比如 @Qualifier 与 @Autowired 就是在这一步骤中增加的支持
       */
      prepareBeanFactory(beanFactory);

      try {
         /*
         4 定义 Bean 工厂的增强器,子类覆盖方法做额外的处理(此处我们自己一般不做任何扩展工作,但是可以查看 web 中的代码是有具体实现的)
          */
         postProcessBeanFactory(beanFactory);

         StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");

         /*
         5 执行 Bean 工厂的增强器,激活各种 beanFactory 处理器
          */
         invokeBeanFactoryPostProcessors(beanFactory);

         /*
         6 注册 Bean 增强器。注册拦截 Bean 创建的 Bean 处理器,这里只是注册,真正的调用是在 getBean 时候
          */
         registerBeanPostProcessors(beanFactory);
         beanPostProcess.end();

         /*
         7 为上下文初始化 message 源,即不同语言的消息体,国际化处理
          */
         initMessageSource();

         /*
         8 初始化应用消息广播器,并放入 "applicationEventMulticaster" bean 中
          */
         initApplicationEventMulticaster();

         /*
         9 特定刷新。初始化其他的 bean,留给子类扩展
          */
         onRefresh();

         /*
         10 注册监听器。在所有注册的 bean 中查找 listen bean,注册到消息广播器中
          */
         registerListeners();

         /*
         11 初始化剩下的单实例(非懒加载的)
          */
         finishBeanFactoryInitialization(beanFactory);

         /*
         12 完成刷新过程,通知生命周期处理器 lifecycleProcessor 刷新过程,同时发出 ContextRefreshEvent 通知别人
          */
         finishRefresh();
      } catch (BeansException ex) {
         if (logger.isWarnEnabled()) {
            logger.warn("Exception encountered during context initialization - " + "cancelling refresh attempt: " + ex);
         }
         // 为防止bean资源占用,在异常处理中,销毁已经在前面过程中生成的单件bean
         destroyBeans();
         // 重置active标志
         cancelRefresh(ex);
         throw ex;
      } finally {
         /*
         13 清空缓存
          */
         resetCommonCaches();
         contextRefresh.end();
      }
   }
}

AbstractApplicationContext 中的 refresh() 是整个 IOC 的核心。
后续会对其中的 13 个主要方法做详细解析。


参考

https://www.bilibili.com/video/BV1tR4y1F75R?spm_id_from=333.337.search-card.all.click

https://www.bilibili.com/video/BV12Z4y197MU?spm_id_from=333.999.0.0

《Spring源码深度解析(第2版)》

版本

Spring 5.3.15

posted @ 2022-03-31 18:03  天航星  阅读(43)  评论(0编辑  收藏  举报