Spring Boot 核心编程思想-第二部分-读书笔记

太过爱你忘了你带给我的痛 2022-09-04 14:52 265阅读 0赞

怕什么真理无穷

进一步有近一步的欢喜

2fc131bf07e7d86ef268f586c5fefc0b.png

说明

本文是Spring Boot核心编程思想记录的笔记,书籍地址:Spring Boot编程思想(核心篇):

这篇文档会记录这本我的一些读书的思考,内容可能比较多,我也会根据理解去扩展一些自己的东西,加强自己对技术理解以及应用。

在开头在叨叨一下,技术书籍的评论或评分有时候也就是简单参考下,因为不同的人对书籍内容的理解是不同的。莎士比亚也说过:”一千个观众眼中有一千个哈姆雷特”。我是觉得一本书如果你能从中有些许的收获和思考,那都是有价值的,有时候可以忽略网上的评论或者评价。

PS:本文有大部分内容摘抄自书籍内容,如果内容前后不是很通顺,请阅读书籍原文,谢谢。

第一部分读书笔记:Spring Boot 核心编程思想-第一部分-读书笔记

二、走向自动装配

Spring Boot的自动装配,很大程度上是基于Spring Framework 的努力,Spring Framework的注解驱动开发使得Spring Boot 能够兼容并包,继往开来。

第7 章 走向注解驱动编程(Annotation-Driver)

技术发展是不断演进的,什么都不是一蹴而就的。所以回顾技术的发展路线和轨迹也是和有必要的。就如 :以史为镜,可以知兴替。
Spring的两个核心特性:IOC DI。看技术大神对Spring IOC是怎么理解的

推崇:应用不应该以Java代码的方式直接控制依赖关系,而是通过容器去管理。(容器的原理就是Map,依赖关系在配置文件,如xml中管理),早期Spring 因为Java5没发布,不支持Annotation,Bean之间的依赖关系还是通过XML管理。
随着技术的不段发展,xml方式显得繁琐和笨重,则慢慢发展为注解驱动的方式。

注解驱动的发展历史和注解的使用场景

主要的发展轨迹

SF:Spring Framework

  • SF1.x : 启蒙 时期,随着Java5的发布,支持Annotation特性,Spring也不甘落后,框架层面支持了一些注解:如@Transaction 等
  • SF2.x :过渡阶段,出现了相对较多的注解,依赖注入(@AutoWired),依赖查询(@Qualifer)、组件申明(@Component),Spring MVC 的注解@Controller等;在此之还支持了可拓展的xml编写(Dubbo xml配置);支持了JSR-250(@Resource 、@PostConstruct、@PerDestroy)
  • 此时注解的激活和扫描还是需要使用xml配置
  • SF3.x :黄金时代,出现 @Configuration 对应 @Bean 相关注解 @ComponentScan ,这些基本上以及可以取代xml配置 了,也引入了 AnnotationConfigApplicationContext(**@since **3.0),以及 @Import @ImportResource。还有其他的如:抽象全新属性API Environment 、 PropertySource ;缓存 @Cache ;异步 @Ansys 等
  • SF4.x :完善阶段,如@Conditional ,@EventListener ,@ RestController 等
  • SF5.x :当下阶段, @Indexed,提升加载速度,也有需要的注意点,可以看:SpringFramework5.0 @Indexed注解 简单解析

核心注解的使用场景:
Spring 模式注解:

image.png
装配注解:
image.png

依赖注入:
image.png
Bean定义注解:
image.png
Spring 条件装配(@ConditionOnXXX等):
image.png

属性配置注解:
image.png

生命周期回调:

  • PostConstruct
  • PreDestroy

注解属性的注解:

image.png

注解编程模式和原理分析

元注解:注解注解的注解。也就是指一个能声明在其他注解上的注解。
组合注解:多个注解 注解的注解。也就是一个注解上声明了一个或者多个注解。

Spring 模式注解 :说白了就是 @Component “派生性”注解。

@Component “派生性”:被@Component注解后,能够被Spring 加入到容器中。
主要注意的是不同版本的层次性:

  • Spring2.x :单层次
  • Spring3.x :两层次
  • Spring4.x :多层次

@Component “派生性” 原理

1、 @Component 需要通过扫描的方式将其加入Spring容器。Spring的方式,xml的时候配置;注解使用 @ComponentScan 。
2、那么 xml方式或者注解的方式,Component-Scan 是如何被Spring处理的呢?
在Spring 中有两个类:分别是 用来处理 xml 和注解。

image.png image.png
以xml方式进行分析(注解同理)。

ComponentScanBeanDefinitionParser 的分析过程
(1)、首先是通过ContextNamespaceHandler 注册。

  1. @Override
  2. public void init() {
  3. // ...
  4. registerBeanDefinitionParser("component-scan", new ComponentScanBeanDefinitionParser());
  5. // ...
  6. }

(2)、ComponentScanBeanDefinitionParser implements BeanDefinitionParser ,则在运行的时候会执行 org.springframework.beans.factory.xml.BeanDefinitionParser#parse 接口方法
(3)、通过源码可以看到 ComponentScanBeanDefinitionParser 中定义了属性常量。如

  1. private static final String BASE_PACKAGE_ATTRIBUTE = "base-package";

我们 核心还是看 parse_(Element element, ParserContext parserContext) 方法。_

  1. @Override
  2. @Nullable
  3. public BeanDefinition parse(Element element, ParserContext parserContext) {
  4. // 获取 base-package 的值
  5. String basePackage = element.getAttribute(BASE_PACKAGE_ATTRIBUTE);
  6. // 解决占位符的问题
  7. basePackage = parserContext.getReaderContext().getEnvironment().resolvePlaceholders(basePackage);
  8. String[] basePackages = StringUtils.tokenizeToStringArray(basePackage,
  9. ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
  10. // Actually scan for bean definitions and register them.
  11. // 这里开始才真正的扫描 Bean ,首先创建扫描器,然后执行扫描
  12. ClassPathBeanDefinitionScanner scanner = configureScanner(parserContext, element);
  13. Set<BeanDefinitionHolder> beanDefinitions = scanner.doScan(basePackages);
  14. // 注册组件
  15. registerComponents(parserContext.getReaderContext(), beanDefinitions, element);
  16. return null;
  17. }

(4)在看 scanner.doScan(basePackages) 执行扫描,核心是 findCandidateComponents ,查找候选组件。

  1. protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
  2. Assert.notEmpty(basePackages, "At least one base package must be specified");
  3. Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
  4. for (String basePackage : basePackages) {
  5. Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
  6. // ....
  7. }
  8. return beanDefinitions;
  9. }

5、、org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider#scanCandidateComponents 方法

  1. String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
  2. resolveBasePackage(basePackage) + '/' + this.resourcePattern;
  3. Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);
  • 将 backagePage 转换为 ClassLoader 类资源 搜索路径。得到类的资源集合。

    MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);

  • 获取该资源的 MetadataReader对象(包含了类和注解的元信息)

    **

    • Simple facade for accessing class metadata,
    • as read by an ASM {@link org.springframework.asm.ClassReader}.
      *
    • @author Juergen Hoeller
    • @since 2.5
      */
      public interface MetadataReader {

      /**

      • Return the resource reference for the class file.
        org.springframework.core.io.Resource
        */
        Resource getResource();

        /**

      • Read basic class metadata for the underlying class.
        */
        ClassMetadata getClassMetadata();

        /**

      • Read full annotation metadata for the underlying class,
      • including metadata for annotated methods.
        */
        AnnotationMetadata getAnnotationMetadata();

    }

(6)根据 MetadataReader 进行判断 isCandidateComponent_(MetadataReader metadataReader)_

  1. protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {
  2. for (TypeFilter tf : this.excludeFilters) {
  3. if (tf.match(metadataReader, getMetadataReaderFactory())) {
  4. return false;
  5. }
  6. }
  7. for (TypeFilter tf : this.includeFilters) {
  8. if (tf.match(metadataReQader, getMetadataReaderFactory())) {
  9. return isConditionMatch(metadataReader);
  10. }
  11. }
  12. return false;
  13. }

注:match 是含义

image.png
注:includeFilters  在 createScanner创建 ClassPathBeanDefinitionScanner对象的时候,构建函数中有一个 registerDefaultFilters 方法。

  1. // 注册为默认过滤器@Component 。
  2. 这将隐式寄存器,有所有注释@Component元注解包括@Repository @Service@Controller典型化注解。
  3. 还支持Java EE 6javax.annotation.ManagedBeanJSR-330javax.inject.Named注释,如果有的话.
  4. protected void registerDefaultFilters() {
  5. this.includeFilters.add(new AnnotationTypeFilter(Component.class));
  6. }

(7)match返回true ,则封装为 ScannedGenericBeanDefinition 对象,并加入到 candidates 中。

  1. Set<BeanDefinition> candidates = new LinkedHashSet<>();

tips:
ClassPathBeanDefinitionScanner 运行自定义类型过滤规则,通过 scanner.addIncludeFilter_(typeFilter)_

Spring 5.x 之后,多层次 派生 的原理和 Sping 4.x 实现不同了。
org.springframework.core.type.classreading.AnnotationAttributesReadingVisitor#visitEndorg.springframework.core.type.classreading.SimpleAnnotationMetadataReadingVisitor#visitEnd

总结:通过扫描 @Component 以及其派生注解,然后加入 候选组件集合中,在Sping 启动的时候将后续组件 加入Spring 容器。其中 加入到候选组件集合中的时候,不同的Spring 版本可能存在实现上的差异。

附:org.springframework.context.annotation.ComponentScanAnnotationParser#parse Spring Boot启动执行时序图
然后 scanner.doScan扫描,获取候选的注解。

时序图0.jpg 时序图0.jpg

Spring 注解属性覆盖和别名

  • 较低层次注解属性覆盖较高层次。
  • 属性之间相互 @AliasFor ,他们的默认值就必须相等。多层次注解属性之间的 @AliasFor 关系 只能由 较低层次向较高层次建立。
  • Spring Framework 为Spring 元注解和@AliasFor 提供了属性覆盖和别名的特性,最终 由 AnnotationAttributes 对象来表达语义。

第8章 Spring 注解驱动设计模式

Spring @Enable 模块驱动

format_png @Import 注解
@Import : 提供的功能和 Spring XML中 标签元素一样。它能允许 引入[ ]() @Configuration classes, [ ]() ImportSelector 和 [ ]() ImportBeanDefinitionRegistrar的 实现类或者普通的 组件类。

  1. @Target(ElementType.TYPE)
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Documented
  4. public @interface Import {
  5. /**
  6. * {@link Configuration @Configuration}, {@link ImportSelector},
  7. * {@link ImportBeanDefinitionRegistrar}, or regular component classes to import.
  8. */
  9. Class<?>[] value();
  10. }

理解@Enable 模块驱动(SF3.1 发行)

模块:具备相同领域的功能组件集合,形成一个独立的单元。
Enable : 激活、启动
在Spring 中,如 Web MVC 模块、 AspectJ模块、Cachinig模块、Async模块等

使用了@Enablexx ,就能激活xxx领域相应的组件。

@Enable 在 SF 、 SB 、SC 中 一以贯之,命令模块化 的注解 均以 @Enable 作为前缀。

image.png

优点: 简化装配步骤,实现“ 按需装配”,屏蔽组件集合装配的细节。
缺点:该模块必须手动触发,即需要标注在某个配置Bean中;实现该模块成本相对较高,尤其是 理解其中的原理和加载机制及单元测试方面。

自定义@Enable模块驱动(三种方式)

自定义分为(本质)两种:

  • 注解驱动 :使用@Import 导入 @Configuration 标注的 类(直接导入配置类)
  • 接口编程 :使用@Import 导入 ImportSelector(依据条件选择配置类) 或 [ ]() ImportBeanDefinitionRegistrar(动态注册Bean) 实现类

两种方式的演练代码:
注解驱动 :
1、写配置类

  1. @Configuration
  2. public class HelloWorldConfiguration {
  3. @Bean
  4. public List helloWorld(){
  5. return new LinkedList();
  6. }
  7. }

2、Enable的注解, 使用 @Import(HelloWorldConfiguration.class)

  1. @Documented
  2. @Retention(value = RetentionPolicy.RUNTIME)
  3. @Import(HelloWorldConfiguration.class)
  4. public @interface EnableHelloWorld {
  5. String value() default "";
  6. }

3、在组件bean上使用 @ EnableHelloWorld 注解

  1. @Configuration
  2. @EnableHelloWorld
  3. public class EnableXxxBootStrap {
  4. public static void main(String[] args) {
  5. AnnotationConfigApplicationContext applicationContext =
  6. new AnnotationConfigApplicationContext(EnableXxxBootStrap.class);
  7. String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
  8. Arrays.stream(beanDefinitionNames).forEach(System.out::println);
  9. }
  10. }
  11. // 打印的 beab 中有 helloWorld

接口编程:实现接口,具体就不演示了。如果不会写,看一下 Spring框架 内部实现的类,可参考。

@Enable模块驱动原理

核心还是理解 @Import 注解,因为不管是Spring内建的Enable还是 自定义的Enable,均使用@Import实现。

@Import 的职责 在于装载导入类 ,将其定义为 Spring Bean。
这里肯定还是需要理解 Spring中的 BeanPostProcesser

@Import 注解是如何解析的,这个解析就包括了相关的原理。

第一步:注册 ConfigurationClassPostProcessor

三种情况注册

  • _

    --> _org.springframework.context.annotation.AnnotationConfigBeanDefinitionParser

    @Override

    1. @Nullable
    2. public BeanDefinition parse(Element element, ParserContext parserContext) {
    3. Object source = parserContext.extractSource(element);
    4. // Obtain bean definitions for all relevant BeanPostProcessors.
    5. Set<BeanDefinitionHolder> processorDefinitions =
    6. AnnotationConfigUtils.registerAnnotationConfigProcessors(parserContext.getRegistry(), source);
    7. // ....
    8. return null;
    9. }
  • _

    --> _org.springframework.context.annotation.ComponentScanBeanDefinitionParser#registerComponents

    protected void registerComponents(

    1. XmlReaderContext readerContext, Set<BeanDefinitionHolder> beanDefinitions, Element element) {
    2. // ....
    3. // Register annotation config processors, if necessary.
    4. boolean annotationConfig = true;
    5. if (element.hasAttribute(ANNOTATION_CONFIG_ATTRIBUTE)) {
    6. annotationConfig = Boolean.parseBoolean(element.getAttribute(ANNOTATION_CONFIG_ATTRIBUTE));
    7. }
    8. if (annotationConfig) {
    9. Set<BeanDefinitionHolder> processorDefinitions =
    10. AnnotationConfigUtils.registerAnnotationConfigProcessors(readerContext.getRegistry(), source);
    11. for (BeanDefinitionHolder processorDefinition : processorDefinitions) {
    12. compositeDef.addNestedComponent(new BeanComponentDefinition(processorDefinition));
    13. }
    14. }
    15. readerContext.fireComponentRegistered(compositeDef);
    16. }
  • org.springframework.context.annotation.AnnotationConfigUtils#registerAnnotationConfigProcessors(org.springframework.beans.factory.support.BeanDefinitionRegistry)

这个方法 找到ClassPathBeanDefinitionScanner相关的调用 :

image.png image.png
AnnotatedBeanDefinitionReader 注册方法,在构建 AnnotationConfigApplicationContext 的时候。

so , ComponentScanAnnotationParser 解析 ComponentScan 这个就不需要在执行的时候在注册一次了

image.png
org.springframework.context.annotation.AnnotationConfigUtils#registerAnnotationConfigProcessor

  1. public static Set<BeanDefinitionHolder> registerAnnotationConfigProcessors(
  2. BeanDefinitionRegistry registry, @Nullable Object source) {
  3. DefaultListableBeanFactory beanFactory = unwrapDefaultListableBeanFactory(registry);
  4. // 省略 ....
  5. Set<BeanDefinitionHolder> beanDefs = new LinkedHashSet<>(8);
  6. if (!registry.containsBeanDefinition(CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME)) {
  7. RootBeanDefinition def = new RootBeanDefinition(ConfigurationClassPostProcessor.class);
  8. def.setSource(source);
  9. // CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME = org.springframework.context.annotation.internalConfigurationAnnotationProcessor
  10. beanDefs.add(registerPostProcessor(registry, def, CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME));
  11. }
  12. // 省略 ....
  13. return beanDefs;
  14. }

在 beanFactory 注册一个 名称为 org.springframework.context.annotation.internalConfigurationAnnotationProcessor 的 ConfigurationClassPostProcessor,并且 这个Processor 在第一位,因为使用的是 LinkedHashSet 有序的(底层实现是LinkHashMap)。

总结:千方百计 要先将 ConfigurationClassPostProcessor 进行注册。

第二步:回调 BeanPostProcessor#postProcessBeanFactory 方法

image.png
ConfigurationClassPostProcessor 的优先级是最低的 。

_
执行回调顺序分析调用链路,执行main方法后:

  1. // 1.0
  2. org.springframework.boot.SpringApplication#run(java.lang.String...)
  3. org.springframework.boot.SpringApplication#refreshContext
  4. org.springframework.boot.SpringApplication#refresh
  5. org.springframework.context.support.AbstractApplicationContext#refresh
  6. org.springframework.context.support.AbstractApplicationContext#invokeBeanFactoryPostProcessors
  7. org.springframework.context.support.PostProcessorRegistrationDelegate#invokeBeanDefinitionRegistryPostProcessors
  8. private static void invokeBeanDefinitionRegistryPostProcessors(
  9. Collection<? extends BeanDefinitionRegistryPostProcessor> postProcessors, BeanDefinitionRegistry registry) {
  10. for (BeanDefinitionRegistryPostProcessor postProcessor : postProcessors) {
  11. postProcessor.postProcessBeanDefinitionRegistry(registry);
  12. }
  13. }
  14. // 1.1 先执行 postProcessBeanDefinitionRegistry
  15. org.springframework.context.annotation.ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry
  16. org.springframework.context.support.PostProcessorRegistrationDelegate#invokeBeanFactoryPostProcessors
  17. private static void invokeBeanFactoryPostProcessors(
  18. Collection<? extends BeanFactoryPostProcessor> postProcessors, ConfigurableListableBeanFactory beanFactory) {
  19. for (BeanFactoryPostProcessor postProcessor : postProcessors) {
  20. postProcessor.postProcessBeanFactory(beanFactory);
  21. }
  22. }
  23. // 1.2 然后执行
  24. org.springframework.context.annotation.ConfigurationClassPostProcessor#postProcessBeanFactory
第三步:处理 processConfigBeanDefinitions

Spring 之前的版本处理的处理是在 postProcessBeanFactory 方法中(具体哪个版本后进行修改了,没有去追踪)。在本次分析的5.2.1 版本中,首先执行 postProcessBeanDefinitionRegistry —> postProcessBeanFactory
方法。
在 postProcessBeanDefinitionRegistry 中会先处理 进行 processConfigBeanDefinitions_(registry) 的处理。_
_并且保存一个注册的处理ID。在执行 _postProcessBeanFactory 进行判断,已处理则不在处理。

image.png

入参都是 DefaultListableBeanFactory 对象,则获取 hashcode 值是一样的。即 registryId == factoryId 。

  1. /**
  2. * Build and validate a configuration model based on the registry of
  3. * {@link Configuration} classes.
  4. */
  5. public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
  6. List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
  7. // 从 DefaultListableBeanFactory 中获取 所有 BeanDefinitionName
  8. String[] candidateNames = registry.getBeanDefinitionNames();
  9. // 循环
  10. for (String beanName : candidateNames) {
  11. // 通过Bean的名字获取 BeanDefinition
  12. BeanDefinition beanDef = registry.getBeanDefinition(beanName);
  13. // 判断beanDef是否有被处理过,处理过则不进行 BeanDefinitionHolder 封装
  14. if (beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE) != null) {
  15. if (logger.isDebugEnabled()) {
  16. logger.debug("Bean definition has already been processed as a configuration class: " + beanDef);
  17. }
  18. }
  19. // checkConfigurationClassCandidate 检查和筛选
  20. //(检查给定bean定义是否为配置类的候选(或配置/部件类中声明的一个嵌套组件类,以自动注册为好),并相应地标记它)
  21. else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
  22. configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
  23. }
  24. }
  25. // Return immediately if no @Configuration classes were found
  26. if (configCandidates.isEmpty()) {
  27. return;
  28. }
  29. // Sort by previously determined @Order value, if applicable
  30. // 根据order 对候选的Config类进行排序
  31. configCandidates.sort((bd1, bd2) -> {
  32. int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());
  33. int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());
  34. return Integer.compare(i1, i2);
  35. });
  36. // Detect any custom bean name generation strategy supplied through the enclosing application context
  37. SingletonBeanRegistry sbr = null;
  38. if (registry instanceof SingletonBeanRegistry) {
  39. sbr = (SingletonBeanRegistry) registry;
  40. if (!this.localBeanNameGeneratorSet) {
  41. BeanNameGenerator generator = (BeanNameGenerator) sbr.getSingleton(
  42. AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR);
  43. if (generator != null) {
  44. this.componentScanBeanNameGenerator = generator;
  45. this.importBeanNameGenerator = generator;
  46. }
  47. }
  48. }
  49. if (this.environment == null) {
  50. this.environment = new StandardEnvironment();
  51. }
  52. // Parse each @Configuration class
  53. // 解析 Configuration 类
  54. ConfigurationClassParser parser = new ConfigurationClassParser(
  55. this.metadataReaderFactory, this.problemReporter, this.environment,
  56. this.resourceLoader, this.componentScanBeanNameGenerator, registry);
  57. Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
  58. Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
  59. do {
  60. // 解析 candidates
  61. parser.parse(candidates);
  62. parser.validate();
  63. // 解析 后 通过 getConfigurationClasses 获取 ConfigurationClass 集合
  64. Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
  65. configClasses.removeAll(alreadyParsed);
  66. // Read the model and create bean definitions based on its content
  67. if (this.reader == null) {
  68. this.reader = new ConfigurationClassBeanDefinitionReader(
  69. registry, this.sourceExtractor, this.resourceLoader, this.environment,
  70. this.importBeanNameGenerator, parser.getImportRegistry());
  71. }
  72. this.reader.loadBeanDefinitions(configClasses);
  73. alreadyParsed.addAll(configClasses);
  74. candidates.clear();
  75. if (registry.getBeanDefinitionCount() > candidateNames.length) {
  76. String[] newCandidateNames = registry.getBeanDefinitionNames();
  77. Set<String> oldCandidateNames = new HashSet<>(Arrays.asList(candidateNames));
  78. Set<String> alreadyParsedClasses = new HashSet<>();
  79. for (ConfigurationClass configurationClass : alreadyParsed) {
  80. alreadyParsedClasses.add(configurationClass.getMetadata().getClassName());
  81. }
  82. for (String candidateName : newCandidateNames) {
  83. if (!oldCandidateNames.contains(candidateName)) {
  84. BeanDefinition bd = registry.getBeanDefinition(candidateName);
  85. if (ConfigurationClassUtils.checkConfigurationClassCandidate(bd, this.metadataReaderFactory) &&
  86. !alreadyParsedClasses.contains(bd.getBeanClassName())) {
  87. candidates.add(new BeanDefinitionHolder(bd, candidateName));
  88. }
  89. }
  90. }
  91. candidateNames = newCandidateNames;
  92. }
  93. }
  94. while (!candidates.isEmpty());
  95. // Register the ImportRegistry as a bean in order to support ImportAware @Configuration classes
  96. if (sbr != null && !sbr.containsSingleton(IMPORT_REGISTRY_BEAN_NAME)) {
  97. sbr.registerSingleton(IMPORT_REGISTRY_BEAN_NAME, parser.getImportRegistry());
  98. }
  99. if (this.metadataReaderFactory instanceof CachingMetadataReaderFactory) {
  100. // Clear cache in externally provided MetadataReaderFactory; this is a no-op
  101. // for a shared cache since it'll be cleared by the ApplicationContext.
  102. ((CachingMetadataReaderFactory) this.metadataReaderFactory).clearCache();
  103. }
  104. }

重点看下 parser.parse_(candidates)_; -> processConfigurationClass ->doProcessConfigurationClass

  1. protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass)
  2. throws IOException {
  3. if (configClass.getMetadata().isAnnotated(Component.class.getName())) {
  4. // Recursively process any member (nested) classes first
  5. processMemberClasses(configClass, sourceClass);
  6. }
  7. // Process any @PropertySource annotations
  8. for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(
  9. sourceClass.getMetadata(), PropertySources.class,
  10. org.springframework.context.annotation.PropertySource.class)) {
  11. if (this.environment instanceof ConfigurableEnvironment) {
  12. processPropertySource(propertySource);
  13. }
  14. else {
  15. logger.info("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() +
  16. "]. Reason: Environment must implement ConfigurableEnvironment");
  17. }
  18. }
  19. // Process any @ComponentScan annotations
  20. Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
  21. sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
  22. if (!componentScans.isEmpty() &&
  23. !this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
  24. for (AnnotationAttributes componentScan : componentScans) {
  25. // The config class is annotated with @ComponentScan -> perform the scan immediately
  26. Set<BeanDefinitionHolder> scannedBeanDefinitions =
  27. this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
  28. // Check the set of scanned definitions for any further config classes and parse recursively if needed
  29. for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
  30. BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
  31. if (bdCand == null) {
  32. bdCand = holder.getBeanDefinition();
  33. }
  34. if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
  35. parse(bdCand.getBeanClassName(), holder.getBeanName());
  36. }
  37. }
  38. }
  39. }
  40. // Process any @Import annotations
  41. processImports(configClass, sourceClass, getImports(sourceClass), true);
  42. // Process any @ImportResource annotations
  43. AnnotationAttributes importResource =
  44. AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class);
  45. if (importResource != null) {
  46. String[] resources = importResource.getStringArray("locations");
  47. Class<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader");
  48. for (String resource : resources) {
  49. String resolvedResource = this.environment.resolveRequiredPlaceholders(resource);
  50. configClass.addImportedResource(resolvedResource, readerClass);
  51. }
  52. }
  53. // Process individual @Bean methods
  54. Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
  55. for (MethodMetadata methodMetadata : beanMethods) {
  56. configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
  57. }
  58. // Process default methods on interfaces
  59. processInterfaces(configClass, sourceClass);
  60. // Process superclass, if any
  61. if (sourceClass.getMetadata().hasSuperClass()) {
  62. String superclass = sourceClass.getMetadata().getSuperClassName();
  63. if (superclass != null && !superclass.startsWith("java") &&
  64. !this.knownSuperclasses.containsKey(superclass)) {
  65. this.knownSuperclasses.put(superclass, configClass);
  66. // Superclass found, return its annotation metadata and recurse
  67. return sourceClass.getSuperClass();
  68. }
  69. }
  70. // No superclass -> processing is complete
  71. return null;
  72. }

终于找打了Import 处理

  1. // Process any @Import annotations
  2. processImports(configClass, sourceClass, getImports(sourceClass), true);
  3. // getImports(sourceClass) 递归获取 imports
  4. private Set<SourceClass> getImports(SourceClass sourceClass) throws IOException {
  5. Set<SourceClass> imports = new LinkedHashSet<>();
  6. Set<SourceClass> visited = new LinkedHashSet<>();
  7. collectImports(sourceClass, imports, visited);
  8. return imports;
  9. }
  10. private void collectImports(SourceClass sourceClass, Set<SourceClass> imports, Set<SourceClass> visited)
  11. throws IOException {
  12. if (visited.add(sourceClass)) {
  13. for (SourceClass annotation : sourceClass.getAnnotations()) {
  14. String annName = annotation.getMetadata().getClassName();
  15. if (!annName.equals(Import.class.getName())) {
  16. collectImports(annotation, imports, visited);
  17. }
  18. }
  19. imports.addAll(sourceClass.getAnnotationAttributes(Import.class.getName(), "value"));
  20. }
  21. }
  22. // 然后是处理 processImports,递归调用,实现多层次的@Import 元标注ConfigurationClass 的解析。
  23. // ImportSelector.class 和 ImportBeanDefinitionRegistrar.class 处理也在此逻辑中

很多细节没有去写。可以看源码的时候,通过debug 方式 跟踪。解析最后,注册成 BeanDefiniton。

第四步:增强 enhanceConfigurationClasses

先判断是ConfigurationClassPostProcessor.getName + “configurationClass”
Object configClassAttr = beanDef.getAttribute_(ConfigurationClassUtils._CONFIGURATION_CLASS_ATTRIBUTE);

然后判断是 ConfigurationClassUtils.CONFIGURATION_CLASS_FULL 完全模式。可进行增强、
_

  • org.springframework.context.annotation.ConfigurationClassUtils#checkConfigurationClassCandidate

    Map config = metadata.getAnnotationAttributes(Configuration.class.getName());
    // Configuration proxyBeanMethods 默认是 true,默认就是完全模式。
    if (config != null && !Boolean.FALSE.equals(config.get(“proxyBeanMethods”))) {

    1. beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_FULL);

    }
    else if (config != null || isConfigurationCandidate(metadata)) {

    1. // 轻量模式,
    2. beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_LITE);

    }
    else {

    1. return false;

    }

Spring web 自动装配

前面 掌握了 @Enable 模块驱动, 这种方式 是需要手动触发的, Spring Boot 提供的是自动 装配的能力, 首先看一下 Spring web的自动装配,对后续SB的自动装配会更得心应手。
SB自动装配:

  • Web应用
  • 非Web应用

Spring Framework 3.1+ 自动装配:

  • 仅支持 web应用,并且依赖的容器 必须是Servlet 3.0 +

理解 WebApplicationInitializer

Spring web自动装配依托于 Servlet3.0+ ,Spring自己也做了一些工作来适应Servlet3.0+的改变。在SpringFramework 3.1.0中 新增了一个类:WebApplicationInitializer 。构建在Servlet3.0 的 ServletContainerIniitializer 之上,WebApplicationInitializer 的自定义实现,能够被任何Servlet3.0容器侦测并自动初始化。初始化调用的是 onStartup 方法。

  1. public interface WebApplicationInitializer {
  2. /**
  3. * Configure the given {@link ServletContext} with any servlets, filters, listeners
  4. * context-params and attributes necessary for initializing this web application. See
  5. * examples {@linkplain WebApplicationInitializer above}.
  6. * @param servletContext the {@code ServletContext} to initialize
  7. * @throws ServletException if any call against the given {@code ServletContext}
  8. * throws a {@code ServletException}
  9. */
  10. void onStartup(ServletContext servletContext) throws ServletException;
  11. }
  • 用编程的方式支持替换传统的 web.xml

image.png
image.png

相关代码可 Spring WebMVC 官网介绍:https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html\#spring-web

image.png

  • AbstractAnnotationConfigDispatcherServletInitializer :SpringJava代码配置驱动

    public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

    1. @Override
    2. protected Class<?>[] getRootConfigClasses() {
    3. return new Class<?>[] { RootConfig.class };
    4. }
    5. @Override
    6. protected Class<?>[] getServletConfigClasses() {
    7. return new Class<?>[] { App1Config.class };
    8. }
    9. @Override
    10. protected String[] getServletMappings() {
    11. return new String[] { "/app1/*" };
    12. }

    }

上面的代码等同于下面的xml配置。

  1. <web-app>
  2. <listener>
  3. <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  4. </listener>
  5. <context-param>
  6. <param-name>contextConfigLocation</param-name>
  7. <param-value>/WEB-INF/root-context.xml</param-value>
  8. </context-param>
  9. <servlet>
  10. <servlet-name>app1</servlet-name>
  11. <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
  12. <init-param>
  13. <param-name>contextConfigLocation</param-name>
  14. <param-value>/WEB-INF/app1-context.xml</param-value>
  15. </init-param>
  16. <load-on-startup>1</load-on-startup>
  17. </servlet>
  18. <servlet-mapping>
  19. <servlet-name>app1</servlet-name>
  20. <url-pattern>/app1/*</url-pattern>
  21. </servlet-mapping>
  22. </web-app>
  • AbstractDispatcherServletInitializer :Spring XML配置驱动

    public class MyWebAppInitializer extends AbstractDispatcherServletInitializer {

    1. @Override
    2. protected WebApplicationContext createRootApplicationContext() {
    3. return null;
    4. }
    5. @Override
    6. protected WebApplicationContext createServletApplicationContext() {
    7. XmlWebApplicationContext cxt = new XmlWebApplicationContext();
    8. cxt.setConfigLocation("/WEB-INF/spring/dispatcher-config.xml");
    9. return cxt;
    10. }
    11. @Override
    12. protected String[] getServletMappings() {
    13. return new String[] { "/" };
    14. }

    }

自定义Web自动装配

1、 实现 AbstractAnnotationConfigDispatcherServletInitializer
2、写配置 类- ConfigClasses
3、将配置类加入 getServletConfigClasses

  1. @Override
  2. protected Class<?>[] getServletConfigClasses() {
  3. return new Class<?>[] { App1Config.class };
  4. }

4、打包,启动
5、测试

具体示例可自行实现下。

理解 Servlet 3.0 - ServletContainerInitializer

1、首先 Servlet3.0开始提供 ServletContext 可以 通过编程的方式动态的装配Servlet、Filter和各种Listener 。(SpringBoot中使用较多)

image.png

2、ServletContext 仅能在 如下两个方法被调用。

  • javax.servlet.ServletContainerInitializer#onStartup:当容器启动的时候,onStartup 方法执行,ServletContext当作参数传入
  • javax.servlet.ServletContextListener#contextInitialized (监听 ServletContext 的生命周期事件- 初始化 - 销毁)

关于ServletContainerInitializer 可以参考 Servlet 3.0规范。需要关注的两个点:

  • 第一:当容器或应用启动的时候,onStartup 方法回调,onStartup 有两个参数
  • Set

    <class@HandlersTypes 来进行过滤。(Spring web mvc 自动装配就是利用了这一特性。)</class

  • ServletContext ctx:

    /**

    1. * 应用启动的时候,会运行onStartup方法;
    2. *
    3. * Set<Class<?>> arg0:感兴趣的类型的所有子类型;
    4. * ServletContext arg1:代表当前Web应用的ServletContext;一个Web应用一个ServletContext
    5. *
    6. * 1)、使用ServletContext注册Web组件(ServletFilterListener
    7. * 2)、使用编码的方式,在项目启动的时候给ServletContext里面添加组件;
    8. * 必须在项目启动的时候来添加;
    9. * 1)、ServletContainerInitializer得到的ServletContext
    10. * 2)、ServletContextListener得到的ServletContext
    11. */

    public void onStartup(Set> c, ServletContext ctx)

    1. throws ServletException;
  • 第二:ServletContainerInitializer 的实现类必须放到 javax.servlet.ServletContainerInitializer 文本文件中,该文件存在在独立JAR包中的 METE-INF/services 目录。

META-INF/services

Spring web自动装配原理-SpringServletContainerInitializer

  1. @HandlesTypes(WebApplicationInitializer.class)
  2. public class SpringServletContainerInitializer implements ServletContainerInitializer {
  3. }

侦测 WebApplicationInitializer 以及其所有的子类,调用 onStartup ,它的子类在上文已经贴出。具体源码实现可以参考SpringFramework 框架实现。
总结:

image.png

推荐阅读:

Java SPI (Service Provider Interface) and ServiceLoader:

https://www.journaldev.com/31602/java-spi-service-provider-interface-and-serviceloader

可以看看,讲解的比较清晰,还有对应的示例。

Spring 条件装配

条件装配 @Profile 和 @Conditional
Profile:侧面,通过某个角度去观察。Maven 中类似语义。静态激活和配置,Spring中存在两种类型:Active 、 Default,当 Active 不存在,采用默认 Profile。

image.png

在Spring中的原理: 解析@Profile 注解,然后根据当前的环境配置 进行验证是否匹配。

@Conditional :相较于 @Profile 更关注 运行时 动态选择。Spring Boot中内建了不少条件注解:

  • ConditionalOnClass
  • ConditionalOnBean
  • ConditionalOnProperty
  • ….

自定义@Conditional 条件装配
1、写一个类实现 Condition 接口,实现 matches 方法

  1. public class SystemCondition implements Condition {
  2. @Override
  3. public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
  4. Map<String, Object> annotationAttributes = metadata.getAnnotationAttributes(SystemOnCondition.class.getName());
  5. String name = (String)annotationAttributes.get("name");
  6. if("aflyun".equals(name)){
  7. return true;
  8. }
  9. return false;
  10. }
  11. }

2、写一个条件注解,使用 @Conditional 注解,@Conditional 的 value 加入自定义的实现

  1. @Target({ElementType.TYPE, ElementType.METHOD})
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Documented
  4. @Conditional(SystemCondition.class)
  5. public @interface SystemOnCondition {
  6. String name() default "";
  7. }

3、在需要进行条件判断的地方使用此注解

  1. @Bean
  2. @SystemOnCondition(name = "aflyun")
  3. public String dufy(){
  4. return "hello Java编程技术乐园";
  5. }

image.png

第9 章 Spring Boot 自动装配

掌握@SpringBootApplication#@EnableAutoConfiguration

@EnableAutoConfiguration 适合用 Import导入 Selector。

@Import_(AutoConfigurationImportSelector.class)_

_

  1. protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
  2. AnnotationMetadata annotationMetadata) {
  3. if (!isEnabled(annotationMetadata)) {
  4. return EMPTY_ENTRY;
  5. }
  6. AnnotationAttributes attributes = getAttributes(annotationMetadata);
  7. List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
  8. configurations = removeDuplicates(configurations);
  9. Set<String> exclusions = getExclusions(annotationMetadata, attributes);
  10. checkExcludedClasses(configurations, exclusions);
  11. configurations.removeAll(exclusions);
  12. configurations = filter(configurations, autoConfigurationMetadata);
  13. fireAutoConfigurationImportEvents(configurations, exclusions);
  14. return new AutoConfigurationEntry(configurations, exclusions);
  15. }

_
调用流程简单分析:

  1. org.springframework.context.annotation.ConfigurationClassParser#processImports
  2. -->org.springframework.context.annotation.ConfigurationClassParser.DeferredImportSelectorHandler#handle
  3. --> org.springframework.context.annotation.ConfigurationClassParser.DeferredImportSelectorGroupingHandler#processGroupImports
  4. -->org.springframework.context.annotation.ConfigurationClassParser.DeferredImportSelectorGrouping#getImports
  5. -->org.springframework.context.annotation.DeferredImportSelector.Group#process
  6. -->org.springframework.boot.autoconfigure.AutoConfigurationImportSelector.AutoConfigurationGroup#process
  7. -->org.springframework.boot.autoconfigure.AutoConfigurationImportSelector#getAutoConfigurationEntry

记录Spring Boot启动SPI 加载机制

1、如果是内嵌的tomcat容器,则 不走 SPI机制(注:SPI机制可以往上翻看推荐阅读。),直接 EnableAutoXX 进行配置。如DispatcherServlet bean 配置。

2、如果使用外部Tomcat 启动的时候,则 需要 配置 SpringBootServletInitializer 。如下:

  • 1.必须创建war项目,需要创建好web项目的目录。
  • 2.嵌入式Tomcat依赖scope指定provided。
  • 3.编写SpringBootServletInitializer类子类,并重写configure方法。

    public class MySpringBootServletInitializer extends SpringBootServletInitializer {

    1. private static Logger logger = LoggerFactory.getLogger(MySpringBootServletInitializer.class);
    2. @Override
    3. protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
    4. logger.info("MySpringBootServletInitializer--->Springboot2CoreCh02Application引导");
    5. return createSpringApplicationBuilder().sources(Springboot2CoreCh02Application.class);
    6. }

    }

这的一套流程,原理是Spring Framework web的自动装配的原理。使用了Servlet3.0+ ,具体可以看上面 :Spring web 自动装配

Tomcat 加载 ServletContainerInitializer 文件。

  1. import java.util.Set;
  2. import javax.servlet.ServletContainerInitializer;
  3. // ************** ServletContainerInitializer接口 的使用 **************
  4. // 1、在jar包中创建META-INF/services/javax.servlet.ServletContainerInitializer文件
  5. // 2、在文件中写入实现的类路径,如:org.apache.jasper.servlet.JasperInitializer
  6. // ************** Tomcat中对ServletContainerInitializer接口的实现类的检测和自动调用 **************
  7. // 检测实现ServletContainerInitializer接口的类---------------------------1
  8. class org.apache.catalina.core.StandardContext{
  9. protected synchronized void startInternal() throws LifecycleException {
  10. // 读取并解析“d:/a/c/d/tomcat/conf/web.xml”,读取并解析"d:/a/b/tomcat/conf/Catalina/localhost/web.xml.default"
  11. // 取得输入流 "d:/a/b/c/tomcat/webapps/dir1/WEB-INF/web.xml"
  12. // 合并配置文件内容
  13. // 合并全局配置
  14. // 使用 org.apache.jasper.servlet.JspServlet 包装 <jsp-file>/a/b/c/file.jsp</jsp-file>的文件
  15. // 把解析处理的内容设置到 context 中 ,如:context.addFilterMap(filterMap);
  16. // 在org.apache.catalina.core.NamingContextListener事件处理器中,内部调用了createNamingContext()创建 envCtx
  17. // Notify our interested LifecycleListeners 触发事件监听器 org.apache.catalina.startup.ContextConfig
  18. fireLifecycleEvent(Lifecycle.CONFIGURE_START_EVENT, null); // 配置启动事件 "configure_start"----------------------
  19. }
  20. // 添加初始化器
  21. public void addServletContainerInitializer(
  22. ServletContainerInitializer sci, Set<Class<?>> classes) {
  23. initializers.put(sci, classes);
  24. }
  25. }
  26. class org.apache.catalina.startup.ContextConfig{
  27. public void lifecycleEvent(LifecycleEvent event) {
  28. context = (Context) event.getLifecycle(); // 取得触发者 org.apache.catalina.core.StandardContext
  29. configureStart();
  30. }
  31. protected synchronized void configureStart() {
  32. // 读取并解析“d:/a/c/d/tomcat/conf/web.xml”,读取并解析"d:/a/b/tomcat/conf/Catalina/localhost/web.xml.default"
  33. // 取得输入流 "d:/a/b/c/tomcat/webapps/dir1/WEB-INF/web.xml"
  34. // 合并配置文件内容
  35. // 合并全局配置
  36. // 使用 org.apache.jasper.servlet.JspServlet 包装 <jsp-file>/a/b/c/file.jsp</jsp-file>的文件
  37. // 把解析处理的内容设置到 context 中 ,如:context.addFilterMap(filterMap);
  38. webConfig();//!!!! 核心
  39. }
  40. protected void webConfig() {
  41. // org.apache.jasper.servlet.JasperInitializer jasper.jar
  42. // org.apache.tomcat.websocket.server.WsSci tomcat-websocket.jar
  43. // 解析类路径中"META-INF/services/javax.servlet.ServletContainerInitializer"文件内容
  44. // 创建文件中声明的类型对象,并把创建对象转成ServletContainerInitializer类型的引用
  45. // initializerClassMap{
  46. // 'MyServletContainerInitializer1_Obj'=>[],
  47. // 'MyServletContainerInitializer2_Obj'=>[],
  48. // }
  49. // typeInitializerMap{
  50. // 'MyAnnotation1.class'=>[MyServletContainerInitializer1_Obj ],
  51. // 'MyAnnotation2.class'=>[MyServletContainerInitializer2_Obj ]
  52. // }
  53. processServletContainerInitializers(); // 查看实现ServletContainerInitializer的初始化器
  54. if (ok) {
  55. // org.apache.jasper.servlet.JasperInitializer jasper.jar
  56. // org.apache.tomcat.websocket.server.WsSci tomcat-websocket.jar
  57. // 解析类路径中"META-INF/services/javax.servlet.ServletContainerInitializer"文件内容
  58. // 创建文件中声明的类型对象,并把创建对象转成ServletContainerInitializer类型的引用
  59. // initializerClassMap{
  60. // 'MyServletContainerInitializer1_Obj'=>[],
  61. // 'MyServletContainerInitializer2_Obj'=>[],
  62. // }
  63. // typeInitializerMap{
  64. // 'MyAnnotation1.class'=>[MyServletContainerInitializer1_Obj ],
  65. // 'MyAnnotation2.class'=>[MyServletContainerInitializer2_Obj ]
  66. // }
  67. for (Map.Entry<ServletContainerInitializer,Set<Class<?>>> entry : initializerClassMap.entrySet()) {
  68. if (entry.getValue().isEmpty()) { // 添加Servlet容器初始化器到StandardContext
  69. // 添加初始化器
  70. // context === org.apache.catalina.core.StandardContext
  71. // StandardContext.initializers.put(entry.getKey(), null);
  72. context.addServletContainerInitializer(
  73. entry.getKey(), null);
  74. } else {
  75. context.addServletContainerInitializer(
  76. entry.getKey(), entry.getValue());
  77. }
  78. }
  79. }
  80. protected void processServletContainerInitializers() {
  81. // 容器初始化器
  82. WebappServiceLoader<ServletContainerInitializer> loader = new WebappServiceLoader<>(context);
  83. // org.apache.jasper.servlet.JasperInitializer jasper.jar
  84. // org.apache.tomcat.websocket.server.WsSci tomcat-websocket.jar
  85. // 解析类路径中"META-INF/services/javax.servlet.ServletContainerInitializer"文件内容
  86. // 创建文件中声明的类型对象,并把创建对象转成ServletContainerInitializer类型的引用
  87. detectedScis = loader.load(ServletContainerInitializer.class); // 检测到的 ServletContainerInitializer
  88. for (ServletContainerInitializer sci : detectedScis) {
  89. initializerClassMap.put(sci, new HashSet<Class<?>>()); // 要调用的初始化器
  90. }
  91. }
  92. }
  93. public List<T> load(Class<T> serviceType) throws IOException {
  94. String configFile = "META-INF/services/" + serviceType.getName();
  95. LinkedHashSet<String> applicationServicesFound = new LinkedHashSet();
  96. LinkedHashSet<String> containerServicesFound = new LinkedHashSet();
  97. ClassLoader loader = this.servletContext.getClassLoader();
  98. List<String> orderedLibs = (List)this.servletContext.getAttribute("javax.servlet.context.orderedLibs");
  99. return containerServicesFound.isEmpty() ? Collections.emptyList() : this.loadServices(serviceType, containerServicesFound);
  100. }
  101. // 调用实现ServletContainerInitializer接口的类的方法,进行初始化---------------------------2
  102. class org.apache.catalina.core.StandardContext{
  103. protected synchronized void startInternal() throws LifecycleException {
  104. // fireLifecycleEvent(Lifecycle.CONFIGURE_START_EVENT, null);
  105. // ....
  106. // org.apache.jasper.servlet.JasperInitializer jasper.jar
  107. // org.apache.tomcat.websocket.server.WsSci tomcat-websocket.jar
  108. // Call ServletContainerInitializers
  109. for (Map.Entry<ServletContainerInitializer, Set<Class<?>>> entry :
  110. initializers.entrySet()) { // 调用容器初始化器 ,
  111. // 如:org.apache.jasper.servlet.JasperInitializer.onStartup();
  112. try {
  113. // 执行初始化器
  114. entry.getKey().onStartup(entry.getValue(),getServletContext());
  115. } catch (ServletException e) {
  116. log.error(sm.getString("standardContext.sciFail"), e);
  117. ok = false;
  118. break;
  119. }
  120. }
  121. }
  122. }
  123. }

@EnableAutoConfiguration扫描BasePackage

本质是理解:@AutoConfigurationPackage 注解。

  1. /**
  2. * Indicates that the package containing the annotated class should be registered with
  3. * {@link AutoConfigurationPackages}.
  4. *
  5. * @author Phillip Webb
  6. * @since 1.3.0
  7. * @see AutoConfigurationPackages
  8. */
  9. @Target(ElementType.TYPE)
  10. @Retention(RetentionPolicy.RUNTIME)
  11. @Documented
  12. @Inherited
  13. @Import(AutoConfigurationPackages.Registrar.class)
  14. public @interface AutoConfigurationPackage {
  15. }
  16. static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
  17. // new PackageImport(metadata).getPackageName() 获取 当前 metadata 对应的包名
  18. @Override
  19. public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
  20. register(registry, new PackageImport(metadata).getPackageName());
  21. }
  22. @Override
  23. public Set<Object> determineImports(AnnotationMetadata metadata) {
  24. return Collections.singleton(new PackageImport(metadata));
  25. }
  26. }

ConstructorArgumentValues 应该如何理解?

第9 章 Spring Boot 自动装配

9.3 自定义 Spring Boot 自动装配

1、如何命名自动装配Class和package

官网并没有给出命名规则。

从官方目前实现的源码中窥探一二。

  • Class 命名:xxxAutoConfiguration
  • package命名:

2bdb1c5b5927ac2f623fad8fb59218f6.png

{module-package}
|- AutoConfiguration
|- ${sub-module-package}
|- …

例子 :org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration

2、如何命名自动装配 Starter

Spring boot starter 包含的组件

  • autofigure 模块
  • starter 模块

官方建议 :将自动装配的代码存放在 autoconfigure 模块, starter模块依赖该模块。

上述建议并非强制,也可以将两个模块合在一起,当Starter部署结构确定后,取一个佳名即可。

Spring boot starter 命名规则
官方推荐 ${module-name}-spring-boot-starter .
Spring 官方 Starter 通常命名为 spring-boot-starter-{module-name}如:spring-boot-starter-web,
比如:spring-cloud-starter-openfeign
Spring 官方建议非官方的 Starter 命名应遵守 {module-name}-spring-boot-starter 的格式。
比如:mybatis-spring-boot-starte

注意:starter 的配置文件 命名空间不要 使用 server 、management、Spring 等作为配置key 命名空间。

3、实现一个 Spring boot starter

①:新建Maven工程,在pom中添加依赖配置

  1. <!-- 添加 Spring boot starter 基础依赖 -->
  2. <dependency>
  3. <groupId>org.springframework.boot</groupId>
  4. <artifactId>spring-boot-starter</artifactId>
  5. <!-- 注意:设置为true ,不传递这个依赖-->
  6. <optional>true</optional>
  7. </dependency>

②:写功能代码
③:自动装配类 xxxAutoConfiguration
④:在 META-INF/spring.factories 资源申明 xxxAutoConfiguration

  1. org.springframework.boot.autoconfigure.EnableAutoConfiguration=
  2. com.test.configure.xxxAutoConfiguration

⑤:在其他的工程中依赖 此 Starter

实现的套路比较简单,真正要思考的是实际场景下如何更好的使用。

9.4 Spring Boot 条件化自动装配-@Conditional

所有 Spring 条件注解 @Conditional 均采用 元标注 @Conditional(OnClassCondition.class) 的方式实现。

Spring boot 条件注解学习成本不高,但是合理的运用需要较高的专业化程度。
1、Class 条件注解
2、Bean 条件注解
3、Property 条件注解
4、Resource 条件注解
5、Web Application 条件注解
6、SpEL 表达式 条件注解

总结

Spring boot 自动装配 所依赖的

  • 注解驱动
  • @Enable模块驱动
  • 条件装配
  • Spring 工厂加载机制等(从spring.factories中加载)

这些特性 均来自 Spring Framework。

Sprng Framework 时代,Spring应用上下文通常 由容器启动,如 ContextLoaderListener 或 WebApplicationInitializer 的实现类由Servlet 容器装载并驱动。

Spring boot 时代,只用一个SpringApplication#run 结合 @SpringBootApplication 或 @EnableAutoConfiguration注解方式完成,启动方式发生了逆转?(和之前WAR启动对比,不需要发布到容器就能启动)

需知后续,请看下回分解,或者你自己直接去看书吧。

tips:最近很多伙伴后台留言说准备换新地方体验【拧螺丝】的工作了,

但是没有好的【造火箭】的资料,这不,特意整理了一份,内容非常丰富,包括大厂Java面试资料和经验总结!

2033b4961b93b83bf3500659d147d968.png

See you next good day~

a232fbe6423d1ec95c9b019a8199347e.gif

b13fb9cd1afcd627e2997ee99ddc266d.png

不定期分享干货技术/ 秘籍】 ,每天进步一点点 小的积累,能带来大的改变

714c2c3206784ef6da3f73c6530dd6dd.gif

发表评论

表情:
评论列表 (有 0 条评论,265人围观)

还没有评论,来说两句吧...

相关阅读

    相关 java编程思想读书笔记2

    十三、内部类 内部类,就是定义在类中的类。这种机制老实说还没在实际开发中用到~~ 所谓嵌套类,就是static的内部类。当然它就不能访问外围对象了。这一系列遇到了再总结到一

    相关 《Java编程思想读书笔记(12)

    以前学c语言时,总是在自己写的函数里为程序的各种运行情况设置一个返回标志值,返回值可以是1,0等标志值,来根据这些标志值来判断程序是否正常运行,但代码多了就总是搞不清楚这些标志