Springboot自动扫描默认包路径来龙去脉

1、注意这里讲的是自动扫描,不是自动装配;自动扫描是自动装配的必要条件,因为springboot灵魂就是注解驱动,自动装配可以看我另一篇
2、本文会从程序启动一直分析到目标逻辑,网上很多直接讲直接逻辑的文章,在看的过程中的确也能得到读者的共鸣(逻辑流程分析得正确,嗯嗯、的确是这样的…),
但是该特性是在应用生命周期哪个阶段?一个main方法怎么就执行到这段代码了?这段代码是由注解引入还是我们main方法栈直接调用的?和其他的特性时间先后顺序是什么?
这就一脸懵了,所以我觉的从源头入口是一种“负责任”的分析方式,也就是碎片化与整体化的转换
3、下面直接标注Springboot启动过程中较为核心的逻辑入口,源码对应的spring-boot-2.2.2.RELEASE版本

【Spring-boot自动装配】可以看我这一篇 文章

核心方法

在我们使用springboot的时候,一般的写法就是类似以下:

public static void main(String[] args) {
        SpringApplication.run(xxx.class, args);
    }

最终会执行到这个方法:org.springframework.boot.SpringApplication#run(java.lang.String…)

public ConfigurableApplicationContext run(String... args) {
    
    
		StopWatch stopWatch = new StopWatch();
		stopWatch.start();
		ConfigurableApplicationContext context = null;
		Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
		configureHeadlessProperty();
		SpringApplicationRunListeners listeners = getRunListeners(args);
		listeners.starting();
		try {
    
    
			ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
			ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
			configureIgnoreBeanInfo(environment);
			Banner printedBanner = printBanner(environment);
			//@A
			context = createApplicationContext();
			exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
					new Class[] {
    
     ConfigurableApplicationContext.class }, context);
			//@B
			prepareContext(context, environment, listeners, applicationArguments, printedBanner);
			//@C
			refreshContext(context);
			afterRefresh(context, applicationArguments);
			stopWatch.stop();
			if (this.logStartupInfo) {
    
    
				new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
			}
			listeners.started(context);
			callRunners(context, applicationArguments);
		}
		catch (Throwable ex) {
    
    
			handleRunFailure(context, ex, exceptionReporters, listeners);
			throw new IllegalStateException(ex);
		}

		try {
    
    
			listeners.running(context);
		}
		catch (Throwable ex) {
    
    
			handleRunFailure(context, ex, exceptionReporters, null);
			throw new IllegalStateException(ex);
		}
		return context;
	}

@A:ConfigurationClassPostProcessor等后置处理器注入

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 注解
EventListenerMethodProcessor EventListenerMethodProcessor 是 BeanFactory 的一个后置处理器, 用来对 @EventListener 提供支持
DefaultEventListenerFactory
CommonAnnotationBeanPostProcessor

@B:把启动主类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#load
org.springframework.boot.BeanDefinitionLoader#load(java.lang.Object)
org.springframework.boot.BeanDefinitionLoader#load(java.lang.Class<?>)
org.springframework.context.annotation.AnnotatedBeanDefinitionReader#register
org.springframework.context.annotation.AnnotatedBeanDefinitionReader#registerBean(java.lang.Class<?>)
org.springframework.context.annotation.AnnotatedBeanDefinitionReader#doRegisterBean

//里面一段代码 如下:
AnnotatedGenericBeanDefinition abd = new AnnotatedGenericBeanDefinition(beanClass);
//从这个方法里可以看出,启动类被包装成了  AnnotatedGenericBeanDefinition(实现了AnnotatedBeanDefinition接口,这很重要)

@C:解析包扫描信息并完成剩余bean注册

刚刚在第一步里,容器中注入了ConfigurationClassPostProcessor后置处理器,后置处理器会在核心方法refresh中执行,也就是上面标注@C的代码里;

我们直接来到核心逻辑处,调用链:
ConfigurationClassPostProcessor方法被调用
由于第二步容器中将启动类包装成AnnotatedGenericBeanDefinition并注入了容器,在方法
org.springframework.context.annotation.ConfigurationClassParser#parse(java.util.Set<org.springframework.beans.factory.config.BeanDefinitionHolder>)会被处理执行后续的包扫描

我们直接来到核心代码吧,以下是一个反推过程:
上图中倒数第二行方法签名:

org.springframework.context.annotation.ComponentScanAnnotationParser#parse

public Set<BeanDefinitionHolder> parse(AnnotationAttributes componentScan, final String declaringClass) {
		ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(this.registry,
				componentScan.getBoolean("useDefaultFilters"), this.environment, this.resourceLoader);

		Class<? extends BeanNameGenerator> generatorClass = componentScan.getClass("nameGenerator");
		boolean useInheritedGenerator = (BeanNameGenerator.class == generatorClass);
		scanner.setBeanNameGenerator(useInheritedGenerator ? this.beanNameGenerator :
				BeanUtils.instantiateClass(generatorClass));

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

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

		boolean lazyInit = componentScan.getBoolean("lazyInit");
		if (lazyInit) {
			scanner.getBeanDefinitionDefaults().setLazyInit(true);
		}
// @A
		Set<String> basePackages = new LinkedHashSet<>();
		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);
		}
		for (Class<?> clazz : componentScan.getClassArray("basePackageClasses")) {
			basePackages.add(ClassUtils.getPackageName(clazz));
		}
// @B		
		if (basePackages.isEmpty()) {
			basePackages.add(ClassUtils.getPackageName(declaringClass));
		}

		scanner.addExcludeFilter(new AbstractTypeHierarchyTraversingFilter(false, false) {
			@Override
			protected boolean matchClassName(String className) {
				return declaringClass.equals(className);
			}
		});
		return scanner.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自动装配】可以看我这一篇 文章

Guess you like

Origin blog.csdn.net/Aqu415/article/details/111455366