【源码】Springboot2 自动扫描默认包路径来龙去脉
1、注意这里讲的是自动扫描不是自动装配自动扫描是自动装配的必要条件因为springboot灵魂就是注解驱动自动装配可以看我另一篇2、本文会从程序启动一直分析到目标逻辑网上很多直接讲直接逻辑的文章在看的过程中的确也能得到读者的共鸣逻辑流程分析得正确嗯嗯、的确是这样的…但是该特性是在应用生命周期哪个阶段一个main方法怎么就执行到这段代码了这段代码是由注解引入还是我们main方法栈直接调用的和其他的特性时间先后顺序是什么这就一脸懵了所以我觉的从源头入口是一种“负责任”的分析方式也就是碎片化与整体化的转换3、下面直接标注Springboot启动过程中较为核心的逻辑入口源码对应的spring-boot-2.2.2.RELEASE版本【Spring-boot自动装配】可以看我这一篇 文章核心方法在我们使用springboot的时候一般的写法就是类似以下publicstatic void main(String[]args){ SpringApplication.run(xxx.class,args);}最终会执行到这个方法org.springframework.boot.SpringApplication#run(java.lang.String…)publicConfigurableApplicationContextrun(String...args){StopWatchstopWatchnewStopWatch();stopWatch.start();ConfigurableApplicationContextcontextnull;CollectionSpringBootExceptionReporterexceptionReportersnewArrayList();configureHeadlessProperty();SpringApplicationRunListenerslistenersgetRunListeners(args);listeners.starting();try{ApplicationArgumentsapplicationArgumentsnewDefaultApplicationArguments(args);ConfigurableEnvironmentenvironmentprepareEnvironment(listeners,applicationArguments);configureIgnoreBeanInfo(environment);BannerprintedBannerprintBanner(environment);//AcontextcreateApplicationContext();exceptionReportersgetSpringFactoriesInstances(SpringBootExceptionReporter.class,newClass[]{ConfigurableApplicationContext.class},context);//BprepareContext(context,environment,listeners,applicationArguments,printedBanner);//CrefreshContext(context);afterRefresh(context,applicationArguments);stopWatch.stop();if(this.logStartupInfo){newStartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(),stopWatch);}listeners.started(context);callRunners(context,applicationArguments);}catch(Throwableex){handleRunFailure(context,ex,exceptionReporters,listeners);thrownewIllegalStateException(ex);}try{listeners.running(context);}catch(Throwableex){handleRunFailure(context,ex,exceptionReporters,null);thrownewIllegalStateException(ex);}returncontext;}AConfigurationClassPostProcessor等后置处理器注入org.springframework.context.annotation.ConfigurationClassPostProcessor是一个BeanDefinitionRegistryPostProcessor父类是BeanFactoryPostProcessor会在容器初始化好并装载完第一阶段的bean定义后调用我理解的其主要作用是执行一些框架内部方法也让用户自定义再次注入自定义的bean定义它的注册是在SpringApplication.run方法调用后具体调用链是org.springframework.boot.SpringApplication#run(java.lang.Class?,java.lang.String...)-org.springframework.boot.SpringApplication#run(java.lang.String...)-org.springframework.boot.SpringApplication#createApplicationContext//对应上面A标注的地方createApplicationContext 方法里面会默认初始化一个AnnotationConfigServletWebServerApplicationContext对象如果你加了web依赖在其构造方法里会执行一系列的逻辑(这里很重要很容易看漏掉)从 AnnotationConfigServletWebServerApplicationContext 构造方法开始看构造方法里又会创建两个对象public AnnotationConfigServletWebServerApplicationContext() { this.reader new AnnotatedBeanDefinitionReader(this); this.scanner new ClassPathBeanDefinitionScanner(this); }以下是 AnnotatedBeanDefinitionReader 类构造方法分支org.springframework.context.annotation.AnnotatedBeanDefinitionReader#AnnotatedBeanDefinitionReader(org.springframework.beans.factory.support.BeanDefinitionRegistry) - org.springframework.context.annotation.AnnotatedBeanDefinitionReader#AnnotatedBeanDefinitionReader(org.springframework.beans.factory.support.BeanDefinitionRegistry, org.springframework.core.env.Environment) - org.springframework.context.annotation.AnnotationConfigUtils#registerAnnotationConfigProcessors(org.springframework.beans.factory.support.BeanDefinitionRegistry) - org.springframework.context.annotation.AnnotationConfigUtils#registerAnnotationConfigProcessors(org.springframework.beans.factory.support.BeanDefinitionRegistry, java.lang.Object) // registerAnnotationConfigProcessors 这个方法会注入5个bean定义 // 注册具体执行者是AnnotationConfigServletWebServerApplicationContext 的父类 GenericApplicationContext GenericApplicationContext 是 BeanDefinitionRegistry 的子类这个代码真心比较绕建议保持头脑清醒 1. ConfigurationClassPostProcessor.class 2. AutowiredAnnotationBeanPostProcessor.class 3. CommonAnnotationBeanPostProcessor.class 4. EventListenerMethodProcessor.class 5. DefaultEventListenerFactory.class类名主要功能ConfigurationClassPostProcessor(1) Configuration注解的作用是什么Spring是如何解析加了Configuration注解的类(2) Spring在什么时候对ComponentScan、ComponentScans注解进行了解析(3) Spring什么时候解析了Import注解如何解析的(4) Spring什么时候解析了Bean注解都是该类处理AutowiredAnnotationBeanPostProcessor主要功能 支持 Autowired 和 Value 注解的支持也支持 Inject 注解EventListenerMethodProcessorEventListenerMethodProcessor 是 BeanFactory 的一个后置处理器, 用来对 EventListener 提供支持DefaultEventListenerFactoryCommonAnnotationBeanPostProcessorB把启动主类bean定义注入容器被我们标记了SpringBootApplication的启动主类在运行过程中会被包装成一个bean定义放入容器中具体方法调用链org.springframework.boot.SpringApplication#run(java.lang.String...)org.springframework.boot.SpringApplication#prepareContext//对应上面代码标注 B 的地方重要上面方法会调用org.springframework.boot.SpringApplication#getAllSources 获得启动主类org.springframework.boot.SpringApplication#loadorg.springframework.boot.BeanDefinitionLoader#load(java.lang.Object)org.springframework.boot.BeanDefinitionLoader#load(java.lang.Class?)org.springframework.context.annotation.AnnotatedBeanDefinitionReader#registerorg.springframework.context.annotation.AnnotatedBeanDefinitionReader#registerBean(java.lang.Class?)org.springframework.context.annotation.AnnotatedBeanDefinitionReader#doRegisterBean//里面一段代码 如下AnnotatedGenericBeanDefinitionabdnewAnnotatedGenericBeanDefinition(beanClass);//从这个方法里可以看出启动类被包装成了 AnnotatedGenericBeanDefinition实现了AnnotatedBeanDefinition接口这很重要C解析包扫描信息并完成剩余bean注册刚刚在第一步里容器中注入了ConfigurationClassPostProcessor后置处理器后置处理器会在核心方法refresh中执行也就是上面标注C的代码里我们直接来到核心逻辑处调用链由于第二步容器中将启动类包装成AnnotatedGenericBeanDefinition并注入了容器在方法org.springframework.context.annotation.ConfigurationClassParser#parse(java.util.Setorg.springframework.beans.factory.config.BeanDefinitionHolder)会被处理执行后续的包扫描我们直接来到核心代码吧以下是一个反推过程上图中倒数第二行方法签名org.springframework.context.annotation.ComponentScanAnnotationParser#parsepublicSetBeanDefinitionHolderparse(AnnotationAttributes componentScan,final String declaringClass){ ClassPathBeanDefinitionScanner scannernew ClassPathBeanDefinitionScanner(this.registry,componentScan.getBoolean(useDefaultFilters),this.environment,this.resourceLoader);Class? extends BeanNameGeneratorgeneratorClasscomponentScan.getClass(nameGenerator);booleanuseInheritedGenerator(BeanNameGenerator.classgeneratorClass);scanner.setBeanNameGenerator(useInheritedGenerator ? this.beanNameGenerator : BeanUtils.instantiateClass(generatorClass));ScopedProxyMode scopedProxyModecomponentScan.getEnum(scopedProxy);if(scopedProxyMode!ScopedProxyMode.DEFAULT){ scanner.setScopedProxyMode(scopedProxyMode);}else{ Class? extends ScopeMetadataResolverresolverClasscomponentScan.getClass(scopeResolver);scanner.setScopeMetadataResolver(BeanUtils.instantiateClass(resolverClass));} scanner.setResourcePattern(componentScan.getString(resourcePattern));for(AnnotationAttributes filter : componentScan.getAnnotationArray(includeFilters)){for(TypeFilter typeFilter : typeFiltersFor(filter)){ scanner.addIncludeFilter(typeFilter);} }for(AnnotationAttributes filter : componentScan.getAnnotationArray(excludeFilters)){for(TypeFilter typeFilter : typeFiltersFor(filter)){ scanner.addExcludeFilter(typeFilter);} }booleanlazyInitcomponentScan.getBoolean(lazyInit);if(lazyInit){ scanner.getBeanDefinitionDefaults().setLazyInit(true);}// ASetStringbasePackagesnew LinkedHashSet();String[]basePackagesArraycomponentScan.getStringArray(basePackages);for(String pkg : basePackagesArray){ String[]tokenizedStringUtils.tokenizeToStringArray(this.environment.resolvePlaceholders(pkg),ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);Collections.addAll(basePackages,tokenized);}for(Class?clazz : componentScan.getClassArray(basePackageClasses)){ basePackages.add(ClassUtils.getPackageName(clazz));}// Bif(basePackages.isEmpty()){ basePackages.add(ClassUtils.getPackageName(declaringClass));} scanner.addExcludeFilter(new AbstractTypeHierarchyTraversingFilter(false,false){OverrideprotectedbooleanmatchClassName(String className){returndeclaringClass.equals(className);} });returnscanner.doScan(StringUtils.toStringArray(basePackages));}A 声明了一个变量 “basePackages” 即包扫描路径根据代码debug其真正赋值的是如下代码在B这也就是这篇文章的最最核心的代码点了if(basePackages.isEmpty()){ basePackages.add(ClassUtils.getPackageName(declaringClass));}上面的代码意思是在启动类上 ComponentScan 没有指定扫描的包名那就会默认以启动类所在的包名作为扫描的包名后面的具体扫描的逻辑就让 org.springframework.context.annotation.ClassPathBeanDefinitionScanner#doScan方法处理了【Spring-boot自动装配】可以看我这一篇 文章