Spring(SpringBoot)--原理--@Component_component注解启动原理-程序员宅基地

技术标签: Spring/SpringBoot  Spring  Component  原理  

原文网址:Spring(SpringBoot)--原理--@Component_IT利刃出鞘的博客-程序员宅基地

简介

说明

        本文介绍Spring的@Component的原理。

        本文的Spring的版本为:5.x。

原理

概述

        我们经常使用Spring的@Component、@Service、@Repository以及 @Controller等注解来实现bean托管给Spring容器管理。Spring是怎么样实现的呢?我们一起跟着源码看看整个过程吧!

先看调用时序图:

概述@Component到Spring bean容器管理过程。

第一步:初始化时设置了Component类型过滤器;
第二步:根据指定扫描包扫描.class文件,生成Resource对象;
第三步:解析.class文件并注解归类,生成MetadataReader对象;
第四步:使用第一步的注解过滤器过滤出有@Component类;
第五步:生成BeanDefinition对象;
第六步:把BeanDefinition注册到Spring容器。
以上是@Component注解原理,@Service、@Controller和@Repository上都有@Component修饰,所以原理是一样的。

第1步:扫描指定包下的文件

        对应时序图方法1,ClassPathBeanDefinitionScanner#scan。交给ClassPathBeanDefinitionScanner处理。

public AnnotationConfigApplicationContext(String... basePackages) {
   this();
   scan(basePackages);
   refresh();
}

Spring启动时,会去扫描指定包下的文件。

public void scan(String... basePackages) {
   Assert.notEmpty(basePackages, "At least one base package must be specified");
   this.scanner.scan(basePackages);
}

第2步:把class注册到容器

对应时序图方法2,ClassPathBeanDefinitionScanner#doScan。该方法对包下class文件解析,若类符合Spring容器管理条件,则生成BeanDefinition,并注册到容器中。

ClassPathBeanDefinitionScanner 初始化时设置了注解过滤器

public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry, boolean useDefaultFilters,Environment environment, @Nullable ResourceLoader resourceLoader) {
	Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
	this.registry = registry;
	if (useDefaultFilters) {
    // 注册注解过滤器
		registerDefaultFilters();
	}
	setEnvironment(environment);
	setResourceLoader(resourceLoader);
}
protected void registerDefaultFilters() {
   // 添加Component类型
   this.includeFilters.add(new AnnotationTypeFilter(Component.class));
   ClassLoader cl = ClassPathScanningCandidateComponentProvider.class.getClassLoader();
   try {
      this.includeFilters.add(new AnnotationTypeFilter(
            ((Class<? extends Annotation>) ClassUtils.forName("javax.annotation.ManagedBean", cl)), false));
   }
   catch (ClassNotFoundException ex) {
   }
   try {
      this.includeFilters.add(new AnnotationTypeFilter(
            ((Class<? extends Annotation>) ClassUtils.forName("javax.inject.Named", cl)), false));
   }
   catch (ClassNotFoundException ex) {
   }
}

在includeFilters添加了Component,ManagedBean两种注解类型。后面用来过滤加载到的class文件是否需要交给Spring容器管理。

protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
   Assert.notEmpty(basePackages, "At least one base package must be specified");
   Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
   for (String basePackage : basePackages) {
      // 扫描包下有Spring Component注解,并且生成BeanDefinition
      Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
      for (BeanDefinition candidate : candidates) {
         // 设置scope,默认是singleton
         ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
         candidate.setScope(scopeMetadata.getScopeName());
         String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
         if (candidate instanceof AbstractBeanDefinition) {
            postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
         }
         if (candidate instanceof AnnotatedBeanDefinition) {
            AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
         }
         if (checkCandidate(beanName, candidate)) {
            BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
            // 生成代理类信息
            definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
            beanDefinitions.add(definitionHolder);
            // 注册到Spring容器
            registerBeanDefinition(definitionHolder, this.registry);
         }
      }
   }
   return beanDefinitions;
}

第3步:返回有@Component封装BeanDefinition列表

对应时序图方法3:ClassPathScanningCandidateComponentProvider#findCandidateComponents。 

扫描包下的class文件,把有Component注解的封装BeanDefinition列表返回。

public Set<BeanDefinition> findCandidateComponents(String basePackage) {
   if (this.componentsIndex != null && indexSupportsIncludeFilters()) {
      return addCandidateComponentsFromIndex(this.componentsIndex, basePackage);
   }
   else {
      return scanCandidateComponents(basePackage);
   }
}

对应时序图方法4:ClassPathScanningCandidateComponentProvider#scanCandidateComponents。

private Set<BeanDefinition> scanCandidateComponents(String basePackage) {
   Set<BeanDefinition> candidates = new LinkedHashSet<>();
   try {
      // classpath*:basePackage/**/*.class
      String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
            resolveBasePackage(basePackage) + '/' + this.resourcePattern;
      // 获取 basePackage 包下的 .class 文件资源
      Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);
      for (Resource resource : resources) {
         // 判断是否可读
         if (resource.isReadable()) {
            try {
               // 获取.class文件类信息
               MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);
               if (isCandidateComponent(metadataReader)) {
                  ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
                  sbd.setResource(resource);
                  sbd.setSource(resource);
                  if (isCandidateComponent(sbd)) {
                     candidates.add(sbd);
                  }
               }
            } catch (Throwable ex) {
               throw new BeanDefinitionStoreException("Failed to read candidate component class: " + resource, ex);
            }
         }
      }
   }
   catch (IOException ex) {
      throw new BeanDefinitionStoreException("I/O failure during classpath scanning", ex);
   }
   return candidates;
}

第4步:获得元数据读取器

对应时序图方法5:CachingMetadataReaderFactory#getMetadataReader。 super.getMetadataReader(resource) 调用的是 SimpleMetadataReaderFactory#getMetadataReader。

public MetadataReader getMetadataReader(Resource resource) throws IOException {
   if (this.metadataReaderCache instanceof ConcurrentMap) {
      // No synchronization necessary...
      MetadataReader metadataReader = this.metadataReaderCache.get(resource);
      if (metadataReader == null) {
         // 获取.class类元信息
         metadataReader = super.getMetadataReader(resource);
         this.metadataReaderCache.put(resource, metadataReader);
      }
      return metadataReader;
   }
   else if (this.metadataReaderCache != null) {
      synchronized (this.metadataReaderCache) {
         MetadataReader metadataReader = this.metadataReaderCache.get(resource);
         if (metadataReader == null) {
            metadataReader = super.getMetadataReader(resource);
            this.metadataReaderCache.put(resource, metadataReader);
         }
         return metadataReader;
      }
   }
   else {
      return super.getMetadataReader(resource);
   }
}

对应时序图方法6,SimpleMetadataReader#SimpleMetadataReader。 组装SimpleMetadataReader。

public MetadataReader getMetadataReader(Resource resource) throws IOException {
   // 默认是SimpleMetadataReader实例
   return new SimpleMetadataReader(resource, this.resourceLoader.getClassLoader());
}
SimpleMetadataReader(Resource resource, @Nullable ClassLoader classLoader) throws IOException {
   // 加载.class文件
   InputStream is = new BufferedInputStream(resource.getInputStream());
   ClassReader classReader;
   try {
      classReader = new ClassReader(is);
   }
   catch (IllegalArgumentException ex) {
      throw new NestedIOException("ASM ClassReader failed to parse class file - " +
            "probably due to a new Java class file version that isn't supported yet: " + resource, ex);
   }
   finally {
      is.close();
   }
   AnnotationMetadataReadingVisitor visitor = new AnnotationMetadataReadingVisitor(classLoader);
   // 解析.class元信息
   classReader.accept(visitor, ClassReader.SKIP_DEBUG);
   this.annotationMetadata = visitor;
   this.classMetadata = visitor;
   this.resource = resource;
}

第5步:把.class解析并组装出来

对应时序图方法7,ClassReader#accept。该方法把二进制的.class文件解析组装到AnnotationMetadataReadingVisitor

public void accept(
    final ClassVisitor classVisitor,
    final Attribute[] attributePrototypes,
    final int parsingOptions) {
  Context context = new Context();
  context.attributePrototypes = attributePrototypes;
  context.parsingOptions = parsingOptions;
  context.charBuffer = new char[maxStringLength];

  ... 省略代码
    
  // Visit the RuntimeVisibleAnnotations attribute.
  if (runtimeVisibleAnnotationsOffset != 0) {
    int numAnnotations = readUnsignedShort(runtimeVisibleAnnotationsOffset);
    int currentAnnotationOffset = runtimeVisibleAnnotationsOffset + 2;
    while (numAnnotations-- > 0) {
      // Parse the type_index field.
      String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer);
      currentAnnotationOffset += 2;
      // 这里面封装Spring Component注解
      currentAnnotationOffset =
          readElementValues(classVisitor.visitAnnotation(annotationDescriptor,true),
              currentAnnotationOffset,true,charBuffer);
    }
  }

 ... 省略代码
}

第6步:读取元素的值

对应时序图方法8:ClassReader#readElementValues。

private int readElementValues(
    final AnnotationVisitor annotationVisitor,
    final int annotationOffset,
    final boolean named,
    final char[] charBuffer) {
  ... 省略代码
  if (annotationVisitor != null) {
    // 主要逻辑在这里面
    annotationVisitor.visitEnd();
  }
  return currentOffset;
}

第7步:去掉非Spring的注解

对应时序图方法9,AnnotationAttributesReadingVisitor#visitEnd。

过滤掉 java.lang.annotation 包下的注解,然后把剩下的注解放到metaAnnotationMap。

public void visitEnd() {
   super.visitEnd();

   Class<? extends Annotation> annotationClass = this.attributes.annotationType();
   if (annotationClass != null) {
      ... 省略代码
      // 过滤java.lang.annotation包下的注解,及保留Spring注解
      if (!AnnotationUtils.isInJavaLangAnnotationPackage(annotationClass.getName())) {
         try {
            // 获取该类上的所有注解
            Annotation[] metaAnnotations = annotationClass.getAnnotations();
            if (!ObjectUtils.isEmpty(metaAnnotations)) {
               Set<Annotation> visited = new LinkedHashSet<>();
               for (Annotation metaAnnotation : metaAnnotations) {
                  // 过滤java.lang.annotation包下的注解,及保留Spring注解
                  recursivelyCollectMetaAnnotations(visited, metaAnnotation);
               }
               // 封装需要的注解
               if (!visited.isEmpty()) {
                  Set<String> metaAnnotationTypeNames = new LinkedHashSet<>(visited.size());
                  for (Annotation ann : visited) {
                     metaAnnotationTypeNames.add(ann.annotationType().getName());
                  }
                  this.metaAnnotationMap.put(annotationClass.getName(), metaAnnotationTypeNames);
               }
            }
         }
         catch (Throwable ex) {
         }
      }
   }
}

对应时序图方法10:ClassPathScanningCandidateComponentProvider#isCandidateComponent。使用前面提过的ClassPathBeanDefinitionScanner初始化时设置的注解类型过滤器,includeFilters 包含ManagedBean和Component类型。

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;
}

对应时序图方法11,AbstractTypeHierarchyTraversingFilter#match。

public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory)
      throws IOException {

   if (matchSelf(metadataReader)) {
      return true;
   }

  ... 省略代码

   return false;
}

对应时序图方法12,AnnotationTypeFilter#matchSelf。判断类的metadata中是否包含Component。

protected boolean matchSelf(MetadataReader metadataReader) {
   AnnotationMetadata metadata = metadataReader.getAnnotationMetadata();
   return metadata.hasAnnotation(this.annotationType.getName()) ||
         (this.considerMetaAnnotations && metadata.hasMetaAnnotation(this.annotationType.getName()));
}

其他网址

Spring注解Component原理源码解析 - Griez - 博客园

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/feiying0canglang/article/details/106484664

智能推荐

Zotero6.0来了,内容丰富,适合初学者(插件安装,翻译器更新)_v1.4.2 latest updated for zotero 6-程序员宅基地

文章浏览阅读3.1w次,点赞52次,收藏120次。Zotero,一款非常好的文献管理软件,丰富的插件库,以及更新后的PDF阅读器,给正在做科研的你带来无限的可能!!!这篇文章可以解决你在使用时候的所有问题,插件安装,翻译器更新,抓取学位论文等等_v1.4.2 latest updated for zotero 6

float拆分_float 详细使用说明,高度塌陷等问题-程序员宅基地

文章浏览阅读146次。float在网页布局作用很大, 但也不需要如此泛用。 最后结果只有是清除浮动都不知道在什么地方下手。 这几天也自己查找了各个关于float资料, 加深对其认识,免得出现不必要的麻烦!浮动介绍(2篇)float 是 css 的定位属性。在传统的印刷布局中,文本可以按照需要围绕图片。一般把这种方式称为“文本环绕”。在网页设计中,应用了CSS的float属性的页面元素就像在印刷布局里面的被文字包围的图片..._按钮样式 float 怎么分开

洪涝有源淹没算法及淹没结果分析【转】-程序员宅基地

文章浏览阅读1.4k次。http://blog.csdn.net/giser_whu/article/details/41288761洪涝模拟仿真的实现方法主要有两种:一种是基于水动力学的洪水演进模型;另一种是基于DEM的洪水淹没分析。具体分析如下:我是GIS从业者,从我们的专业角度出发,选择基于DEM的洪水淹没分析来做洪涝的模拟仿真。而基于DEM的洪水淹没分析方法主要分为有源淹没和无源淹没。本篇...

Java Selenium Actions模拟鼠标拖动dragAndDrop总结_selenium actions.draganddropby-程序员宅基地

文章浏览阅读1.2w次。鼠标拖动APIActions action = new Actions(webdriver);##source-要拖动的元素A,target-拖动元素A到达的目标元素action.dragAndDrop(source, target);##source-要拖动的元素A,拖动元素移动多少,标准以元素A左上角为准,拖动元素相对元素A移到右边是x是正值,左边是负值,拖动元素相对元素A移到上_selenium actions.draganddropby

amd显卡RX560控制功耗问题_a卡560功耗-程序员宅基地

文章浏览阅读4.2w次。前言现在显卡市场可以说就是 amd 和 英伟达 的双雄会。经过漫长的发展,英伟达显然技高一筹,在核心技术上牢牢压了AMD一头。价格就是最有力的证明。比如说今天我要说的RX560D。都说rx560可以媲美1050ti,但是却比1050ti便宜了一半,为什么?是amd为了消费者,所以他们有商业道德,不屑于赚取多余的金钱?我想应该不会有这样的商家存在吧。遗憾的是rx560只要不是xt,或..._a卡560功耗

springboot停车场车辆定位管理可视化分析系统的设计与实现 毕业设计-附源码101702-程序员宅基地

文章浏览阅读111次。该系统采用Springboot框架、JSP技术、Ajax技术进行业务系统的编码及其开发,实现了本系统的全部功能。本系统采取组件化的方式对系统进行拆分,并对数据库中各个表的增删查改、表与表之间的约束关系进行分析与设计,最终实现符合用户需求功能的商业级应用。

随便推点

wps生成正态分布的随机数_如何用excel批量生成正态分布的随机数?-程序员宅基地

文章浏览阅读3.4k次。昨天的文章提到《如何不留痕迹做批量 "假"数据?》提到用rand函数批量生成随机数,这是最简单最快速的方法,但同时也有一个缺陷,就是rand函数生成的是均匀分布的随机数,在区间范围内会分布得比较均匀,不符合日常生活中数据的按照正态分布的特点,这样也就留下了明显的痕迹。比如我用rand函数生成0-100的100个随机数,用Minitab自动生成直方图,频数是下面这样的:大家可以看到,大致上每个区间分..._wps生成正态分布的随机数

java开发命名规范总结_java命名规范-程序员宅基地

文章浏览阅读1.7k次。驼峰命名法(Camel-Case): 当变量名或方法名是由一个或多个单词连结在一起,而构成的命名时;首字母以小写开头,每个单词首字母大写(第一个单词除外)。_java命名规范

当前线程不在单线程单元中,因此无法实例化 ActiveX 控件解决办法_当前线程不在单线程单元中,因此无法实例化 activex 控件-程序员宅基地

文章浏览阅读3.3w次,点赞2次,收藏6次。(一)引经据典(MSDN):1.单元是进程内部具有相同线程访问要求的对象的逻辑容器。同一单元中的所有对象都可以接收从该单元中的任何线程发出的调用。.NET Framework 不使用单元,托管对象自己负责以线程安全的方式使用所有共享资源。由于 COM 类使用单元,因此公共语言运行库需要在 COM interop 的情况下调用 COM 对象时创建并初始化一个单元。托管线程可以创建并进入只允许有一个线程的单线程单元 (STA) 或者包含一个或多个线程的多线程单元 _当前线程不在单线程单元中,因此无法实例化 activex 控件

Python 获取多维数组数据_robotframework 获取 多维数组-程序员宅基地

文章浏览阅读533次。几种常用获取数据的方法。_robotframework 获取 多维数组

C++11的一些新特性|右值引用|STL中的一些变化_stl中有移动赋值-程序员宅基地

文章浏览阅读340次,点赞2次,收藏2次。如果没有自己实现移动构造,且没有实现析构,拷贝构造,拷贝赋值重载中的任意一个,那么编译器会自己生成一个默认移动构造。在类中增加移动构造,移动构造本质上是将参数右值的资源窃取,占为己有,那么就不用做深拷贝,所以叫它移动构造,就是窃取别人的资源来构造自己。原来c++类中,有6个默认成员函数:构造,析构,拷贝构造,拷贝赋值,取地址重载,const取地址重载。传统的c++语法中就有引用的语法,而c++11中新增了右值引用的语法特性,无论是左值引用还是右值引用,都是给对象取别名。可以让你更好的控制要使用的默认函数。_stl中有移动赋值

Playground v2.5最新的文本到图像生成模型,官方宣称V2.5的模型优于 SDXL、Playground v2、PixArt-α、DALL-E 3 和 Midjourney_playgrouind v2.5-程序员宅基地

文章浏览阅读724次。Playground在去年发布Playground v2.0之后再次开源新的文生图模型Playground v2.5。新版本提升了图像的美学质量,增强了颜色和对比度、改进了多纵横比图像生成,可以生成各种比例图像以及人像细节的提升。官方宣称:根据用户研究表明,V2.5的模型优于 SDXL、Playground v2、PixArt-α、DALL-E 3 和 Midjourney 5.2。_playgrouind v2.5