Spring 核心类 ConfigurationClassPostProcessor 流程讲解及源码全面分析(二)

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: Spring 核心类 ConfigurationClassPostProcessor 流程讲解及源码全面分析(二)

processPropertySource

// 如果配置类上加了 @PropertySource 注解,那么就解析加载 properties 文件,并将属性添加到 spring 上下文中
for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(
    sourceClass.getMetadata(), PropertySources.class,
    org.springframework.context.annotation.PropertySource.class)) {
  if (this.environment instanceof ConfigurableEnvironment) {
    processPropertySource(propertySource);
  }
  else {
    logger.info("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() +
        "]. Reason: Environment must implement ConfigurableEnvironment");
  }
}

默认创建的环境对象实例是 StandardEnvironment,它实现了 ConfigurableEnvironment 接口,处理使用 @PropertySource 注解修饰的类

private void processPropertySource(AnnotationAttributes propertySource) throws IOException {
  // 获取name属性
  String name = propertySource.getString("name");
  if (!StringUtils.hasLength(name)) {
    name = null;
  }
  // 获取encoding属性
  String encoding = propertySource.getString("encoding");
  if (!StringUtils.hasLength(encoding)) {
    encoding = null;
  }
  // 获取value属性
  String[] locations = propertySource.getStringArray("value");
  Assert.isTrue(locations.length > 0, "At least one @PropertySource(value) location is required");
  boolean ignoreResourceNotFound = propertySource.getBoolean("ignoreResourceNotFound");
  Class<? extends PropertySourceFactory> factoryClass = propertySource.getClass("factory");
  PropertySourceFactory factory = (factoryClass == PropertySourceFactory.class ?
      DEFAULT_PROPERTY_SOURCE_FACTORY : BeanUtils.instantiateClass(factoryClass));
  for (String location : locations) {
    try {
      // 处理属性值的占位符
      String resolvedLocation = this.environment.resolveRequiredPlaceholders(location);
      // 讲指定位置的资源转换成 resource 对象
      Resource resource = this.resourceLoader.getResource(resolvedLocation);
      // 添加 resource 对象为属性资源
      addPropertySource(factory.createPropertySource(name, new EncodedResource(resource, encoding)));
    }
    catch (IllegalArgumentException | FileNotFoundException | UnknownHostException | SocketException ex) {
      // Placeholders not resolvable or resource not found when trying to open it
      if (ignoreResourceNotFound) {
        if (logger.isInfoEnabled()) {
          logger.info("Properties location [" + location + "] not resolvable: " + ex.getMessage());
        }
      }
      else {
        throw ex;
      }
    }
  }
}

获取 name、value、encoding 属性,将 value 属性转换为 String[] locations;

对 locations 数组进行遍历,先进行占位符解析工作,如:classpath:myconfig${name}.properties 再转换为 Resource 源对象,最后将其添加至 propertySources 集合中【创建标准化环境对象(StandardEnvironment)时有用到】

处理 @ComponentScan、@ComponentScans 注解

// 处理 @ComponentScan 或 @ComponentScans 注解,并将扫描包下的所有 bean 转换成填充后的 ConfigurationClass
Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
    sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
if (!componentScans.isEmpty() &&
    !this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
  for (AnnotationAttributes componentScan : componentScans) {
    // 比如 basePackages = com.vnjohn, 那么在这一步会扫描出这个包及子包下的 class,然后将其解析成 BeanDefinition(BeanDefinition 可以理解为等价于 BeanDefinitionHolder)
    Set<BeanDefinitionHolder> scannedBeanDefinitions =
        this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
    // 通过上一步扫描包 com.vnjohn,有可能扫描出来的 bean 中可能也添加了 ComponentScan 或 ComponentScans 注解.
    // 所以这里需要循环遍历一次,进行递归(parse),继续解析,直到解析出的类上没有 ComponentScan、ComponentScans
    for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
      BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
      if (bdCand == null) {
        bdCand = holder.getBeanDefinition();
      }
      // 判断是否是一个配置类,并设置 full 或 lite 属性
      if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
        // 通过递归方法进行解析
        parse(bdCand.getBeanClassName(), holder.getBeanName());
      }
    }
  }
}

通过注解工具类解析当前配置类中是否包含了 @ComponentScan、@ComponentScans 注解,存在就进行遍历挨个调用 ComponentScanAnnotationParser#parse 方法进行包的扫描工作

扫描工作完成后,将满足条件的 BeanDefinitions 进行再次解析,对 BD 集合进行遍历,判别集合中的元素是否依然是配置类,是的话就继续往回走,调用 ConfigurationClassParser#parse 方法 方法

ComponentScanAnnotationParser#parse 扫描包的解析方法前置工作进行详细分析,如下:

public Set<BeanDefinitionHolder> parse(AnnotationAttributes componentScan, final String declaringClass) {
  // 创建对应的扫描类
  ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(this.registry,
      componentScan.getBoolean("useDefaultFilters"), this.environment, this.resourceLoader);
  // 获取 @ComponentScan 参数,并进行参数的设置工作
  Class<? extends BeanNameGenerator> generatorClass = componentScan.getClass("nameGenerator");
  boolean useInheritedGenerator = (BeanNameGenerator.class == generatorClass);
  scanner.setBeanNameGenerator(useInheritedGenerator ? this.beanNameGenerator :
      BeanUtils.instantiateClass(generatorClass));
  // 获取 scopedProxy 属性
  ScopedProxyMode scopedProxyMode = componentScan.getEnum("scopedProxy");
  if (scopedProxyMode != ScopedProxyMode.DEFAULT) {
    scanner.setScopedProxyMode(scopedProxyMode);
  }
  else {
    Class<? extends ScopeMetadataResolver> resolverClass = componentScan.getClass("scopeResolver");
    scanner.setScopeMetadataResolver(BeanUtils.instantiateClass(resolverClass));
  }
  // 获取 resourcePattern 属性
  scanner.setResourcePattern(componentScan.getString("resourcePattern"));
  // 获取 includeFilters 属性
  for (AnnotationAttributes filter : componentScan.getAnnotationArray("includeFilters")) {
    for (TypeFilter typeFilter : typeFiltersFor(filter)) {
      scanner.addIncludeFilter(typeFilter);
    }
  }
  // 获取 excludeFilters 属性
  for (AnnotationAttributes filter : componentScan.getAnnotationArray("excludeFilters")) {
    for (TypeFilter typeFilter : typeFiltersFor(filter)) {
      scanner.addExcludeFilter(typeFilter);
    }
  }
  // 获取 lazyInit 属性
  boolean lazyInit = componentScan.getBoolean("lazyInit");
  if (lazyInit) {
    scanner.getBeanDefinitionDefaults().setLazyInit(true);
  }
  Set<String> basePackages = new LinkedHashSet<>();
  // 获取 basePackages 属性
  String[] basePackagesArray = componentScan.getStringArray("basePackages");
  for (String pkg : basePackagesArray) {
    String[] tokenized = StringUtils.tokenizeToStringArray(this.environment.resolvePlaceholders(pkg),
        ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
    Collections.addAll(basePackages, tokenized);
  }
  // 获取 basePackageClasses 属性,对全限定类进行包名截取
  for (Class<?> clazz : componentScan.getClassArray("basePackageClasses")) {
    basePackages.add(ClassUtils.getPackageName(clazz));
  }
  if (basePackages.isEmpty()) {
    basePackages.add(ClassUtils.getPackageName(declaringClass));
  }
  scanner.addExcludeFilter(new AbstractTypeHierarchyTraversingFilter(false, false) {
    @Override
    protected boolean matchClassName(String className) {
      return declaringClass.equals(className);
    }
  });
  // 开始执行扫描,最终的扫描器是 ClassPathBeanDefinitionScanner
  return scanner.doScan(StringUtils.toStringArray(basePackages));
}
  1. 创建实际进行扫描工作的类:ClassPathBeanDefinitionScanner,填充该类型所需用到的一些属性「scopedProxyMode、resourcePattern、includeFilters、excludeFilters、basePackages」
  2. 调用 ClassPathBeanDefinitionScanner#parse 方法,扫描出 basePackages 属性包及子包下的 class,然后将其解析成 BeanDefinition 信息

ClassPathBeanDefinitionScanner#parse 实际扫描工作的方法进行详细解析,开始执行扫描工作,如下:

protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
    Assert.notEmpty(basePackages, "At least one base package must be specified");
    Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
    // 遍历basePackages
    for (String basePackage : basePackages) {
        // 扫描 basePackage,将符合要求 bean 定义全部找出来
        Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
        // 遍历所有候选的 bean 定义
        for (BeanDefinition candidate : candidates) {
            // 解析 @Scope 注解,包括 scopeName 和 proxyMode
            ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
            candidate.setScope(scopeMetadata.getScopeName());
            // 使用 beanName 生成器来生成 beanName
            String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
            if (candidate instanceof AbstractBeanDefinition) {
                // 处理 beanDefinition 对象,例如:此 bean 是否可以自动装配到其他 bean 中
                postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
            }
            if (candidate instanceof AnnotatedBeanDefinition) {
                // 处理定义在目标类上的通用注解,包括 @Lazy,@Primary,@DependsOn,@Role,@Description
                AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
            }
            // 检查 beanName 是否已经注册过,如果注册过,检查是否兼容
            if (checkCandidate(beanName, candidate)) {
                // 将当前遍历的 bean 定义、beanName 封装成 BeanDefinitionHolder
                BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
                // 根据 proxyMode 值,选择是否创建作用域代理
                definitionHolder =
                    AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
                beanDefinitions.add(definitionHolder);
                // 注册 beanDefinition
                registerBeanDefinition(definitionHolder, this.registry);
            }
        }
    }
    return beanDefinitions;
}
  1. 先调用 findCandidateComponents(backPackage) 将符合条件的 BeanDefinition(实际是 ScannedGenericBeanDefinition) 找出来

条件1:excludeFilters 不包含

条件2:includeFilters 至少匹配一个

条件3:该类不能是接口或抽象类

findCandidateComponents->scanCandidateComponents->isCandidateComponent

  1. 处理 BeanDefinition 对象的属性信息,解析 @Scope 注解,设置 scopeName、proxyMode,通过 beanNameGenerator 生成 beanName
  2. 设置自动装配属性,例如:该 bean 是否可以自动装配到其他 bean 中
  3. AnnotationConfigUtils#processCommonDefinitionAnnotations:处理定义在目标类上的通用注解,包括「@Lazy、@Primary、@DependsOn、@Role、@Description」填充对应的属性进去
  4. checkCandidate(beanName, candidate):检查 beanName 是否已经被注册过,如果被注册过考虑其是否与当前类兼容,不兼容就抛出异常,兼容则跳过当前 BeanDefinition 操作;如果未注册过,进行如下操作:

把当前遍历的 bean 定义信息和 beanName 封装成 BeanDefinitionHolder

调用 AnnotationConfigUtils.applyScopedProxyMode,根据 proxyMode 值来选择是否要创建代理,接口基于 JDK 动态代理,类基于 CGLIB 动态代理

主动 beanDefinition,放入到 BeanDefinitionMap、BeanDefinitionNames 中

第一点描述的符合条件方法 isCandidateComponent 源码判断部分,如下:

protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {
  for (TypeFilter tf : this.excludeFilters) {
    if (tf.match(metadataReader, getMetadataReaderFactory())) {
      return false;
    }
  }
  for (TypeFilter tf : this.includeFilters) {
    if (tf.match(metadataReader, getMetadataReaderFactory())) {
      return isConditionMatch(metadataReader);
    }
  }
  return false;
}
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
  AnnotationMetadata metadata = beanDefinition.getMetadata();
  /**
   * isIndependent:确定底层类是否是独立的,即它是否是顶级类或者嵌套类,他可以独立于封闭类构造
   * isConcrete:判断底层类是否表示具体类,即:既不是接口也不是抽象类
   * isAbstract && hasAnnotatedMethods:是否被标记为抽象类 && 确定基础类是否具有使用给定注解 @Lookup 标注的方法
   */
  return (metadata.isIndependent() && (metadata.isConcrete() ||
      (metadata.isAbstract() && metadata.hasAnnotatedMethods(Lookup.class.getName()))));
}

至此,扫描工作完成以后就到了 ComponentScanAnnotationParser#parse 方法处理的最后一步了,拿到所扫描到的 Set<BeanDefinitionHolder> 集合,遍历每一个 BeanDefinitionHolder ,判断是否是一个配置类,并设置 full 或 lite 属性,再次往回走调用 (parse—>processConfigurationClass) 方法进行后续解析工作

processImports

处理 @Import 注解,导入额外的配置类,同时完成具体类的实例化工作,该类型处理也涉及到自动装配的工作

processImports(configClass, sourceClass, getImports(sourceClass), filter, true);

getImports—>collectImports:递归获取被 @Import 注解标注的类,被它标注的类无须加 @Component、@Configuration 等配置注解,否则该 Bean 会被添加两次,但 Spring 会进行合并的工作

private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
      Collection<SourceClass> importCandidates, Predicate<String> exclusionFilter,
      boolean checkForCircularImports) {
   // 如果使用 @Import 注解修饰的类集合为空,那么直接返回
   if (importCandidates.isEmpty()) {
      return;
   }
   // 通过一个栈结构解决循环引入,栈中存在该配置类则抛出异常
   if (checkForCircularImports && isChainedImportOnStack(configClass)) {
      this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
   }
   else {
      // 添加到栈中,用于处理循环引入的问题
      this.importStack.push(configClass);
      try {
         // 遍历每一个 @Import 注解的类
         for (SourceClass candidate : importCandidates) {
            // 检验配置类 Import 引入的类是否是 ImportSelector 子类:
            if (candidate.isAssignable(ImportSelector.class)) {
               // 候选类是一个导入选择器->委托来确定是否进行导入
               Class<?> candidateClass = candidate.loadClass();
               // 通过反射生成一个 ImportSelect 对象
               ImportSelector selector = ParserStrategyUtils.instantiateClass(candidateClass, ImportSelector.class,
                     this.environment, this.resourceLoader, this.registry);
               // 获取选择器的额外过滤器
               Predicate<String> selectorFilter = selector.getExclusionFilter();
               if (selectorFilter != null) {
                  exclusionFilter = exclusionFilter.or(selectorFilter);
               }
               // 判断引用选择器是否是 DeferredImportSelector 接口的实例
               // 如果是则应用选择器将会在所有的配置类都加载完毕后加载
               if (selector instanceof DeferredImportSelector) {
                  // 将选择器添加到 deferredImportSelectorHandler 实例中,预留到所有的配置类加载完成后统一处理自动化配置类
                  this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);
               }
               else {
                  // 获取引入的类,然后使用递归方式将这些类中同样添加了 @Import 注解引用的类
                  String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
                   Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames, exclusionFilter);
                   // 递归处理,被 Import 进来的类也有可能 @Import 注解
                   processImports(configClass, currentSourceClass, importSourceClasses, exclusionFilter, false);
               }
            }
             // 如果是实现了 ImportBeanDefinitionRegistrar 接口的 bd:SpringBoot 中的 AutoConfigurationPackages$Registrar
             else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
                 // 候选类是ImportBeanDefinitionRegistrar  -> 委托给当前注册器注册其他bean
                 Class<?> candidateClass = candidate.loadClass();
                 ImportBeanDefinitionRegistrar registrar =
                     ParserStrategyUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class,
                                                          this.environment, this.resourceLoader, this.registry);
                 // 放到当前 configClass 的 importBeanDefinitionRegistrars 中
                 // 在 ConfigurationClassPostProcessor 处理 configClass 时会随之一起处理
                 configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
             }
             else {
                 // 候选类既不是 ImportSelector 也不是 ImportBeanDefinitionRegistrar-->将其作为 @Configuration 配置类处理
                 this.importStack.registerImport(
                     currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
                 // 如果 Import 类型是普通类,则将其当作带有 @Configuration 类一样处理
                 // 把 candidate 构造为 ConfigurationClass,标注为 importedBy,意味着它是通过被@Import 进来的
                 // 后面处理会用到这个判断将这个普通类注册进 DefaultListableBeanFactory
                 processConfigurationClass(candidate.asConfigClass(configClass), exclusionFilter);
             }
         }
      }
       catch (BeanDefinitionStoreException ex) {
           throw ex;
       }
       catch (Throwable ex) {
           throw new BeanDefinitionStoreException(
               "Failed to process import candidates for configuration class [" +
               configClass.getMetadata().getClassName() + "]", ex);
       }
       finally {
           this.importStack.pop();
       }
   }
}
  1. 判断被 @Import 注解标注的类集合是否为空,不为空才进行后续的处理工作,循环遍历每一个配置类,判断它所匹配的类型
  2. 如果是 ImportSelector 接口的子类但非 DeferredImportSelector 接口的子类,就对其配置类进行递归处理,因为当前类可能还有使用 @Import 注解导入其他的配置类,递归调用的是 processImports 方法
  3. 如果是 ImportSelector & DeferredImportSelector 接口的子类,将其先暂时添加到 deferredImportSelectorHandler 集合中,待所有的配置类都加载完成以后:也就是当所有的类都调用 parse 方法结束后,再统一处理这些类型的配置类.
    回忆一下,在 ConfigurationClassParser#parse 方法中会作这步操作 > this.deferredImportSelectorHandler.process();
  4. 如果是 ImportBeanDefinitionRegistrar 接口子类,会将其进行实例化后存入集合中,待所有配置类处理完后,调用其类下的 registerBeanDefinitions 设置具体的 BeanDefinition 类型,如 SpringBoot 中设置的就是专门由 Spring 内部使用的 BD
  5. 如果以上都不是的话,则将其当作带有 @Configuration 类一样处理,将 candidate 构造为 ConfigurationClass,标注为 importedBy,意味着它是通过被 @Import 进来的

处理 @ImportResource 注解

AnnotationAttributes importResource =
    AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class);
if (importResource != null) {
  String[] resources = importResource.getStringArray("locations");
  Class<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader");
  for (String resource : resources) {
    String resolvedResource = this.environment.resolveRequiredPlaceholders(resource);
    configClass.addImportedResource(resolvedResource, readerClass);
  }
}

处理 @ImportResource 注解,导入 spring 配置文件,通过此方式引入的 xml 文件来通过 IOC 容器注入 Bean 实例对象

处理 @Bean 注解

检索配置类中加了 @Bean 注解的方法,将 @Bean 方法转化为 BeanMethod 对象,保存再集合中

Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
for (MethodMetadata methodMetadata : beanMethods) {
  configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
}

processInterfaces

private void processInterfaces(ConfigurationClass configClass, SourceClass sourceClass) throws IOException {
    // 找到配置类的所有接口,遍历接口
    for (SourceClass ifc : sourceClass.getInterfaces()) {
        // 找到含有 @Bean 注解的默认方法
        Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(ifc);
        for (MethodMetadata methodMetadata : beanMethods) {
            if (!methodMetadata.isAbstract()) {
                // A default method or other concrete method on a Java 8+ interface...
                // 添加到集合中
                configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
            }
        }
        // 递归处理,因为接口可能存在父接口
        processInterfaces(configClass, ifc);
    }
}

处理接口的默认方法实现,从 JDK8 开始,接口中的方法可以有自己的默认实现,若这个接口的方法加了 @Bean 注解,也需要被解析;然后检索配置类中加了 @Bean 注解的方法,将 @Bean 方法转化为 BeanMethod 对象,保存再集合中


目录
相关文章
|
6天前
|
缓存 Java 开发工具
Spring是如何解决循环依赖的?从底层源码入手,详细解读Spring框架的三级缓存
三级缓存是Spring框架里,一个经典的技术点,它很好地解决了循环依赖的问题,也是很多面试中会被问到的问题,本文从源码入手,详细剖析Spring三级缓存的来龙去脉。
Spring是如何解决循环依赖的?从底层源码入手,详细解读Spring框架的三级缓存
|
6天前
|
XML 缓存 Java
手写Spring源码(简化版)
Spring包下的类、手写@ComponentScan注解、@Component注解、@Autowired注解、@Scope注解、手写BeanDefinition、BeanNameAware、InitializingBean、BeanPostProcessor 、手写AnnotationConfigApplicationContext
手写Spring源码(简化版)
|
6天前
|
缓存 安全 Java
Spring框架中Bean是如何加载的?从底层源码入手,详细解读Bean的创建流程
从底层源码入手,通过代码示例,追踪AnnotationConfigApplicationContext加载配置类、启动Spring容器的整个流程,并对IOC、BeanDefinition、PostProcesser等相关概念进行解释
Spring框架中Bean是如何加载的?从底层源码入手,详细解读Bean的创建流程
|
23天前
|
人工智能 前端开发 Java
【实操】Spring Cloud Alibaba AI,阿里AI这不得玩一下(含前后端源码)
本文介绍了如何使用 **Spring Cloud Alibaba AI** 构建基于 Spring Boot 和 uni-app 的聊天机器人应用。主要内容包括:Spring Cloud Alibaba AI 的概念与功能,使用前的准备工作(如 JDK 17+、Spring Boot 3.0+ 及通义 API-KEY),详细实操步骤(涵盖前后端开发工具、组件选择、功能分析及关键代码示例)。最终展示了如何成功实现具备基本聊天功能的 AI 应用,帮助读者快速搭建智能聊天系统并探索更多高级功能。
168 2
【实操】Spring Cloud Alibaba AI,阿里AI这不得玩一下(含前后端源码)
|
20天前
|
缓存 Java 开发者
Spring高手之路22——AOP切面类的封装与解析
本篇文章深入解析了Spring AOP的工作机制,包括Advisor和TargetSource的构建与作用。通过详尽的源码分析和实际案例,帮助开发者全面理解AOP的核心技术,提升在实际项目中的应用能力。
18 0
Spring高手之路22——AOP切面类的封装与解析
|
29天前
|
设计模式 Java 程序员
学习 Spring 源码的意义是什么呢?
研究Spring源码能深化框架理解,提升代码分析与设计能力,助您掌握设计模式及最佳实践,增强解决问题的效率,促进职业生涯发展,并激发技术热情。选择稳定版本,从核心模块开始,结合实际项目并参与社区,让学习之旅既充实又具乐趣。
|
30天前
|
缓存 Java Maven
Java本地高性能缓存实践问题之SpringBoot中引入Caffeine作为缓存库的问题如何解决
Java本地高性能缓存实践问题之SpringBoot中引入Caffeine作为缓存库的问题如何解决
|
2月前
|
Java 测试技术 数据库
Spring Boot中的项目属性配置
本节课主要讲解了 Spring Boot 中如何在业务代码中读取相关配置,包括单一配置和多个配置项,在微服务中,这种情况非常常见,往往会有很多其他微服务需要调用,所以封装一个配置类来接收这些配置是个很好的处理方式。除此之外,例如数据库相关的连接参数等等,也可以放到一个配置类中,其他遇到类似的场景,都可以这么处理。最后介绍了开发环境和生产环境配置的快速切换方式,省去了项目部署时,诸多配置信息的修改。
|
2月前
|
Java 应用服务中间件 开发者
Java面试题:解释Spring Boot的优势及其自动配置原理
Java面试题:解释Spring Boot的优势及其自动配置原理
96 0
|
1天前
|
Java 应用服务中间件 开发者
深入探索并实践Spring Boot框架
深入探索并实践Spring Boot框架
13 2