五.Spring源码剖析-基于注解的IOC启动流程

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
简介: 在上一篇文章我们分析了一下基于注解的IOC启动流程的第一种方式,根据指定的BeanClass启动,这篇文章我们分析另外一种方式,扫描一个包路径来启动。

前言

在上一篇文章我们分析了一下基于注解的IOC启动流程的第一种方式,根据指定的BeanClass启动,这篇文章我们分析另外一种方式,扫描一个包路径来启动。

IOC案例

我这里还是使用 AnnotationConfigApplicationContext 写一个简单的IOC案例

第一步:创建一个类

package cn.xx
//通过扫描方式注册Bean
@Component
public class OtherBean {
   
}

第二步:使用 AnnotationConfigApplicationContext 扫描一个包

public class MyBeanTest {
   

    @Test
    public void testMyBean(){
   
        //通过容器工厂:AnnotationConfigApplicationContext 加载一个包中的Bean
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext("cn.xxx");
        //通过容器工厂获取bean
        OtherBean bean= applicationContext.getBean(OtherBean .class);
        //打印bean
        System.out.println(bean);
    }
}

与之前不一样的是,这次的OtherBean这个类上面贴了 @Component注解,对于AnnotationConfigApplicationContext我们传入了该所在的包,我们希望它可以自动扫描到该注解所在的类,然后自动注册到Spring容器中。

AnnotationConfigApplicationContext

我们直接定位到这个构造器中,

    /**
     * Create a new AnnotationConfigApplicationContext, scanning for bean definitions
     * in the given packages and automatically refreshing the context.
     * @param basePackages the packages to check for annotated classes
     */
     //创建一个新的 AnnotationConfigApplicationContext,扫描给定包中的 bean 定义并自动刷新上下文。
    public AnnotationConfigApplicationContext(String... basePackages) {
   
        //创建 AnnotatedBeanDefinitionReader Bean注册器 和   ClassPathBeanDefinitionScanner Bean扫描器
        this();
        //扫描给定的包
        scan(basePackages);
        //刷新容器
        refresh();
    }

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

这三个方法和上篇文章里面说到的差不多,只是register(annotatedClasses) 根据class注册Bean 方法变成了 scan(basePackages); 扫描一个包下的Bean.该方法使用了 ClassPathBeanDefinitionScanner 来扫描, 跟一下scan方法

//在指定的基本包内执行扫描。
public int scan(String... basePackages) {
   
    int beanCountAtScanStart = this.registry.getBeanDefinitionCount();

    doScan(basePackages);

    // Register annotation config processors, if necessary.
    if (this.includeAnnotationConfig) {
   
        AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
    }

    return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);
}

这里返回了注册的Bean的个数,通过doScan方法实现包的扫描和Bean的注册

ClassPathBeanDefinitionScanner

protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
   
        Assert.notEmpty(basePackages, "At least one base package must be specified");
        //用来装注册的Bean
        Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
        //循环扫描多个包
        for (String basePackage : basePackages) {
   
            //获取符合条件的Bean,调用 ClassPathScanningCandidateComponentProvider
            Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
            //循环处理扫描到的BeanDefinition
            for (BeanDefinition candidate : candidates) {
   
                //解析Scope元注解
                ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
                //把scope属性设置到BeanDefinition中,默认单利
                candidate.setScope(scopeMetadata.getScopeName());
                //生成Bean的名字
                String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
                if (candidate instanceof AbstractBeanDefinition) {
   
                    //设置Bean的自动注入装配属性等
                    postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
                }
                if (candidate instanceof AnnotatedBeanDefinition) {
   
                    //解析Bean的通用注解,lazy,parimary等
                    AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
                }
                //判断容器中是否已经包含该Bean,不兼容就报错
                if (checkCandidate(beanName, candidate)) {
   
                    //把BeanDefinition封装成BeanDefinitionHolder
                    BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
                    //根据Bean的作用域,生成代理 , 默认不会产生
                    definitionHolder =
                            AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
                    beanDefinitions.add(definitionHolder);
                    //走 BeanDefinitionReaderUtils.registerBeanDefinition 方法注册Bean
                    registerBeanDefinition(definitionHolder, this.registry);
                }
            }
        }
        //返回注册的所有Bean
        return beanDefinitions;
    }

该逻辑大概做了这些事情

  • 循环所有的包路径,从包下面扫描到满足条件的Bean,封装成BeanDefinition返回
  • 通过scopeMetadataResolver解析每个 Bean的scope设置到BeanDefinition中
  • 通过beanNameGenerator生成Bean的名字
  • 通过 AnnotationConfigUtils 解析Bean的公共注解,如lazy,primary等
  • 判断Bean时候已经在容器中,不兼容就报错冲突
  • 调用BeanDefinitionReaderUtils.registerBeanDefinition ,通过BeanDefinitionRegistry 把BeanDefinition注册到DefaultListableBeanFactory的Map中

扫描包:ClassPathScanningCandidateComponentProvider

bean的解析和注册流程和上一篇文章都一样,只是增加了包的扫描过程,我们跟一下ClassPathScanningCandidateComponentProvider#scanCandidateComponents方法

private Set<BeanDefinition> scanCandidateComponents(String basePackage) {
   
        Set<BeanDefinition> candidates = new LinkedHashSet<>();
        try {
   
            //拼接扫描路径: classpath/包路径/*.class ,扫描classpath下的所有class文件
            String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
                    resolveBasePackage(basePackage) + '/' + this.resourcePattern;
            //使用 ResourcePatternResolver 把 class 字节码文件转成Resource[]
            Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);
            boolean traceEnabled = logger.isTraceEnabled();
            boolean debugEnabled = logger.isDebugEnabled();
            for (Resource resource : resources) {
                if (traceEnabled) {
                    logger.trace("Scanning " + resource);
                }
                //resource可读
                if (resource.isReadable()) {
                    try {
                        //获取元数据解析器
                        MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);
                        //是否是满足条件的Bean,
                        //确定给定的类是否不匹配任何排除过滤器excludeFilters,并且匹配至少一个包含过滤器
                        if (isCandidateComponent(metadataReader)) {
                            //把满足条件的bean封装成ScannedGenericBeanDefinition对象,装到set中返回
                            ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
                            sbd.setSource(resource);
                            if (isCandidateComponent(sbd)) {
                                if (debugEnabled) {
                                    logger.debug("Identified candidate component class: " + resource);
                                }
                                candidates.add(sbd);
                            }
                            ...省略...
            }
        }
        catch (IOException ex) {
            throw new BeanDefinitionStoreException("I/O failure during classpath scanning", ex);
        }
        return candidates;
    }

    //根据MetadataReader判断Bean是否满足条件
    protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {
        for (TypeFilter tf : this.excludeFilters) {
        //是否被排除
            if (tf.match(metadataReader, getMetadataReaderFactory())) {
                return false;
            }
        }
        for (TypeFilter tf : this.includeFilters) {
        //是否包含在includeFilters
            if (tf.match(metadataReader, getMetadataReaderFactory())) {
                return isConditionMatch(metadataReader);
            }
        }
        return false;
    }

该方法中大概做了如下事情

  • 该方法中将 classpath 作为基础路径,把传入的包名进行拼接,如:classpath/包/*.class 作为扫描路径,即:包下面的所有class文件,
  • 然后通过ResourcePatternResolver 把该路径下的class文件 转成Resource[]
  • 通过 getMetadataReaderFactory 获取Resource的 MetadataReader 注解解析器,根据MetadataReader判断Bean是否满足条件
  • 确定给定的类是否不匹配任何排除过滤器excludeFilters,并且匹配至少一个包含过滤器
  • 如果满足注册条件就把Bean封装成 ScannedGenericBeanDefinition
  • 然后再次检查ScannedGenericBeanDefinition bean 定义是否有资格作为候选
  • 最后把所有扫描到的BeanDefinition转到set集合返回

总结

最后来做个小结,AnnotationConfigApplicationContext(package) 指定包扫描路径的方式和直接指定类名的方式多了一个包的扫描过程,Bean的解析和注册流程都是一样的。

  • 先是调用ClassPathScanningCandidateComponentProvider扫描给定的包下的class文件,满足条件的就装载成BeanDefinition返回
  • 然后在ClassPathBeanDefinitionScanner主流程中解析Bean的相关注解,Scope,lazy等
  • 最后通过BeanDefinitionRegistry把BeanDefinition注册到容器中。

IOC的启动流程就到这里把,喜欢的话请给个好评哦,你的肯定是我最大的动力!!!

相关文章
|
20天前
|
XML Java 数据格式
【SpringFramework】Spring IoC-基于XML的实现
本文主要讲解SpringFramework中IoC和DI相关概念,及基于XML的实现方式。
106 69
|
19天前
|
Java Spring 容器
【SpringFramework】Spring IoC-基于注解的实现
本文主要记录基于Spring注解实现IoC容器和DI相关知识。
49 21
|
25天前
|
设计模式 XML Java
【23种设计模式·全精解析 | 自定义Spring框架篇】Spring核心源码分析+自定义Spring的IOC功能,依赖注入功能
本文详细介绍了Spring框架的核心功能,并通过手写自定义Spring框架的方式,深入理解了Spring的IOC(控制反转)和DI(依赖注入)功能,并且学会实际运用设计模式到真实开发中。
【23种设计模式·全精解析 | 自定义Spring框架篇】Spring核心源码分析+自定义Spring的IOC功能,依赖注入功能
|
11天前
|
监控 JavaScript 数据可视化
建筑施工一体化信息管理平台源码,支持微服务架构,采用Java、Spring Cloud、Vue等技术开发。
智慧工地云平台是专为建筑施工领域打造的一体化信息管理平台,利用大数据、云计算、物联网等技术,实现施工区域各系统数据汇总与可视化管理。平台涵盖人员、设备、物料、环境等关键因素的实时监控与数据分析,提供远程指挥、决策支持等功能,提升工作效率,促进产业信息化发展。系统由PC端、APP移动端及项目、监管、数据屏三大平台组成,支持微服务架构,采用Java、Spring Cloud、Vue等技术开发。
|
24天前
|
存储 Java 应用服务中间件
【Spring】IoC和DI,控制反转,Bean对象的获取方式
IoC,DI,控制反转容器,Bean的基本常识,类注解@Controller,获取Bean对象的常用三种方式
|
3月前
|
人工智能 自然语言处理 前端开发
SpringBoot + 通义千问 + 自定义React组件:支持EventStream数据解析的技术实践
【10月更文挑战第7天】在现代Web开发中,集成多种技术栈以实现复杂的功能需求已成为常态。本文将详细介绍如何使用SpringBoot作为后端框架,结合阿里巴巴的通义千问(一个强大的自然语言处理服务),并通过自定义React组件来支持服务器发送事件(SSE, Server-Sent Events)的EventStream数据解析。这一组合不仅能够实现高效的实时通信,还能利用AI技术提升用户体验。
290 2
|
3天前
|
XML JavaScript Java
SpringBoot集成Shiro权限+Jwt认证
本文主要描述如何快速基于SpringBoot 2.5.X版本集成Shiro+JWT框架,让大家快速实现无状态登陆和接口权限认证主体框架,具体业务细节未实现,大家按照实际项目补充。
31 11
|
5天前
|
缓存 安全 Java
Spring Boot 3 集成 Spring Security + JWT
本文详细介绍了如何使用Spring Boot 3和Spring Security集成JWT,实现前后端分离的安全认证概述了从入门到引入数据库,再到使用JWT的完整流程。列举了项目中用到的关键依赖,如MyBatis-Plus、Hutool等。简要提及了系统配置表、部门表、字典表等表结构。使用Hutool-jwt工具类进行JWT校验。配置忽略路径、禁用CSRF、添加JWT校验过滤器等。实现登录接口,返回token等信息。
107 12
|
25天前
|
Java 数据库连接 Maven
最新版 | 深入剖析SpringBoot3源码——分析自动装配原理(面试常考)
自动装配是现在面试中常考的一道面试题。本文基于最新的 SpringBoot 3.3.3 版本的源码来分析自动装配的原理,并在文未说明了SpringBoot2和SpringBoot3的自动装配源码中区别,以及面试回答的拿分核心话术。
最新版 | 深入剖析SpringBoot3源码——分析自动装配原理(面试常考)
|
11天前
|
Java 测试技术 应用服务中间件
Spring Boot 如何测试打包部署
本文介绍了 Spring Boot 项目的开发、调试、打包及投产上线的全流程。主要内容包括: 1. **单元测试**:通过添加 `spring-boot-starter-test` 包,使用 `@RunWith(SpringRunner.class)` 和 `@SpringBootTest` 注解进行测试类开发。 2. **集成测试**:支持热部署,通过添加 `spring-boot-devtools` 实现代码修改后自动重启。 3. **投产上线**:提供两种部署方案,一是打包成 jar 包直接运行,二是打包成 war 包部署到 Tomcat 服务器。
37 10