首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >【小家Spring】Spring容器加载Bean定义信息的两员大将:AnnotatedBeanDefinitionReader和ClassPathBeanDefinitionScanner

【小家Spring】Spring容器加载Bean定义信息的两员大将:AnnotatedBeanDefinitionReader和ClassPathBeanDefinitionScanner

作者头像
YourBatman
发布于 2019-09-03 08:33:21
发布于 2019-09-03 08:33:21
2.4K00
举报
文章被收录于专栏:BAT的乌托邦BAT的乌托邦
运行总次数:0
前言

在分析Spring IOC容器启动流程的时候,在加载Bean定义信息BeanDefinition的时候,用到了两个非常关键的类:AnnotatedBeanDefinitionReaderClassPathBeanDefinitionScanner。它俩完成对Bean信息的加载。

因此为了更加顺畅的去理解Bean的加载的一个过程,本文主要介绍Spring的这两员大将的一个初始化过程,以及它俩扮演的重要角色

环境准备

因为我们只需要了解Bean的加载,所以只需要启动一个容器就行,并不需要web环境,因此本文用一个相对简单的环境,来进行讲解,如下:

代码语言:javascript
代码运行次数:0
运行
复制
	@ComponentScan(value = "com.fsx", excludeFilters = {
	        @Filter(type = FilterType.ANNOTATION, classes = {Controller.class}),
	        //排除掉web容器的配置文件,否则会重复扫描
	        @Filter(type = FilterType.ASSIGNABLE_TYPE, classes = {AppConfig.class}),
	})
	@Configuration
	public class RootConfig {
	
	    @Bean
	    public Parent parent() {
	        return new Parent();
	    }
	
	}


    public static void main(String[] args) {
        // 备注:此处只能用RootConfig,而不能AppConfig(启动报错),因为它要web容器支持,比如Tomcat
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(RootConfig.class);
        System.out.println(applicationContext.getBean(Parent.class)); //com.fsx.bean.Parent@639c2c1d
    }

环境准备好了,启动之后也能正常打印出Bean的信息。因此接下来,就是要去分析源码,看看这两大工具起的作用

IOC容器加载Bean定义信息分析

AnnotationConfigApplicationContext(spring-context包下)的继承图谱如下:

需要注意的是,我们在Tomcat等web环境下的容器类为:AnnotationConfigWebApplicationContext,它在spring-web包下

Spring容器里通过BeanDefinition对象来表示Bean,BeanDefinition描述了Bean的配置信息。 而BeanDefinitionRegistry接口提供了向容器注册,删除,获取BeanDefinition对象的方法。

简单来说,BeanDefinitionRegistry可以用来管理BeanDefinition,所以理解AnnotationConfigApplicationContext很关键,它是spring加载bean,管理bean的最重要的类。

源码跟踪:

代码语言:javascript
代码运行次数:0
运行
复制
	public AnnotationConfigApplicationContext(Class<?>... annotatedClasses) {
		this();
		// 把该配置类(们)注册进来
		register(annotatedClasses);
		// 容器启动核心方法
		refresh();
	}

this()如下:

代码语言:javascript
代码运行次数:0
运行
复制
	//AnnotatedBeanDefinitionReader是一个读取注解的Bean读取器,这里将this传了进去。
	public AnnotationConfigApplicationContext() {
		this.reader = new AnnotatedBeanDefinitionReader(this);
		this.scanner = new ClassPathBeanDefinitionScanner(this);
	}
	
	//从上面这个构造函数可以顺便提一句:如果你仅仅是这样ApplicationContext applicationContext = new AnnotationConfigApplicationContext()
	// 容器是不会启动的(也就是不会执行refresh()的),这时候需要自己之后再手动启动容器

进而再看看register()方法:

代码语言:javascript
代码运行次数:0
运行
复制
	public void register(Class<?>... annotatedClasses) {
		Assert.notEmpty(annotatedClasses, "At least one annotated class must be specified");
		this.reader.register(annotatedClasses);
	}

	public void register(Class<?>... annotatedClasses) {
		for (Class<?> annotatedClass : annotatedClasses) {
			registerBean(annotatedClass);
		}
	}

	public void registerBean(Class<?> annotatedClass) {
		doRegisterBean(annotatedClass, null, null, null);
	}

实际逻辑在doRegisterBean()此方法上:

代码语言:javascript
代码运行次数:0
运行
复制
	<T> void doRegisterBean(Class<T> annotatedClass, @Nullable Supplier<T> instanceSupplier, @Nullable String name,
			@Nullable Class<? extends Annotation>[] qualifiers, BeanDefinitionCustomizer... definitionCustomizers) {
		
		// 先把此实体类型转换为一个BeanDefinition
		AnnotatedGenericBeanDefinition abd = new AnnotatedGenericBeanDefinition(annotatedClass);
		//abd.getMetadata() 元数据包括注解信息、是否内部类、类Class基本信息等等
		// 此处由conditionEvaluator#shouldSkip去过滤,此Class是否是配置类。
		// 大体逻辑为:必须有@Configuration修饰。然后解析一些Condition注解,看是否排除~
		if (this.conditionEvaluator.shouldSkip(abd.getMetadata())) {
			return;
		}

		abd.setInstanceSupplier(instanceSupplier);
		// 解析Scope
		ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(abd);
		abd.setScope(scopeMetadata.getScopeName());
		// 得到Bean的名称 一般为首字母小写(此处为AnnotationBeanNameGenerator)
		String beanName = (name != null ? name : this.beanNameGenerator.generateBeanName(abd, this.registry));

		// 设定一些注解默认值,如lazy、Primary等等
		AnnotationConfigUtils.processCommonDefinitionAnnotations(abd);
		// 解析qualifiers,若有此注解  则primary都成为true了
		if (qualifiers != null) {
			for (Class<? extends Annotation> qualifier : qualifiers) {
				if (Primary.class == qualifier) {
					abd.setPrimary(true);
				}
				else if (Lazy.class == qualifier) {
					abd.setLazyInit(true);
				}
				else {
					abd.addQualifier(new AutowireCandidateQualifier(qualifier));
				}
			}
		}
		// 自定义定制信息(一般都不需要)
		for (BeanDefinitionCustomizer customizer : definitionCustomizers) {
			customizer.customize(abd);
		}

		// 下面位解析Scope是否需要代理,最后把这个Bean注册进去
		BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(abd, beanName);
		definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
		BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, this.registry);
	}
AnnotatedBeanDefinitionReader初始化,构造器如下
代码语言:javascript
代码运行次数:0
运行
复制
	public AnnotatedBeanDefinitionReader(BeanDefinitionRegistry registry) {
		this(registry, getOrCreateEnvironment(registry));
	}

	public AnnotatedBeanDefinitionReader(BeanDefinitionRegistry registry, Environment environment) {
		Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
		Assert.notNull(environment, "Environment must not be null");
		this.registry = registry;
			
		//ConditionEvaluator完成条件注解的判断,在后面的Spring Boot中有大量的应用
		this.conditionEvaluator = new ConditionEvaluator(registry, environment, null);
		//这句会把一些自动注解处理器加入到AnnotationConfigApplicationContext下的BeanFactory的BeanDefinitions中  具体见下面
		AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
	}

这里将AnnotationConfigApplicationContext注册为管理BeanDefinition的BeanDefinitionRegistry,也就是说,spring中bean的管理完全交给了AnnotationConfigApplicationContext

AnnotationConfigUtils#registerAnnotationConfigProcessors() 我们要用一些注解比如:@Autowired/@Required/@Resource都依赖于各种各样的BeanPostProcessor来解析(AutowiredAnnotation、RequiredAnnotation、CommonAnnotationBeanPostProcessor等等)

但是向这种非常常用的,让调用者自己去申明,显然使用起来就过重了。所以Spring为我们提供了一种极为方便注册这些BeanPostProcessor的方式(若是xml方式,配置<context:annotation- config/>,若是全注解驱动的ApplicationContext,就默认会执行)

代码语言:javascript
代码运行次数:0
运行
复制
	public static void registerAnnotationConfigProcessors(BeanDefinitionRegistry registry) {
		registerAnnotationConfigProcessors(registry, null);
	}

	public static Set<BeanDefinitionHolder> registerAnnotationConfigProcessors(
			BeanDefinitionRegistry registry, @Nullable Object source) {
		
		// 把我们的beanFactory从registry里解析出来
		DefaultListableBeanFactory beanFactory = unwrapDefaultListableBeanFactory(registry);
		if (beanFactory != null) {
			if (!(beanFactory.getDependencyComparator() instanceof AnnotationAwareOrderComparator)) {
				beanFactory.setDependencyComparator(AnnotationAwareOrderComparator.INSTANCE);
			}
			// 相当于如果没有这个AutowireCandidateResolver,就给设置一份ContextAnnotationAutowireCandidateResolver
			if (!(beanFactory.getAutowireCandidateResolver() instanceof ContextAnnotationAutowireCandidateResolver)) {
				beanFactory.setAutowireCandidateResolver(new ContextAnnotationAutowireCandidateResolver());
			}
		}
		
		//这里初始长度放4  是因为大多数情况下,我们只会注册4个BeanPostProcessor 如下(不多说了)
		// BeanDefinitionHolder解释:持有name和aliases,为注册做准备
		// Spring 4.2之后这个改成6我觉得更准确点
		Set<BeanDefinitionHolder> beanDefs = new LinkedHashSet<>(4);

		if (!registry.containsBeanDefinition(CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME)) {
			RootBeanDefinition def = new RootBeanDefinition(ConfigurationClassPostProcessor.class);
			def.setSource(source);
			beanDefs.add(registerPostProcessor(registry, def, CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME));
		}

		if (!registry.containsBeanDefinition(AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME)) {
			RootBeanDefinition def = new RootBeanDefinition(AutowiredAnnotationBeanPostProcessor.class);
			def.setSource(source);
			beanDefs.add(registerPostProcessor(registry, def, AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME));
		}

		if (!registry.containsBeanDefinition(REQUIRED_ANNOTATION_PROCESSOR_BEAN_NAME)) {
			RootBeanDefinition def = new RootBeanDefinition(RequiredAnnotationBeanPostProcessor.class);
			def.setSource(source);
			beanDefs.add(registerPostProcessor(registry, def, REQUIRED_ANNOTATION_PROCESSOR_BEAN_NAME));
		}

		// Check for JSR-250 support, and if present add the CommonAnnotationBeanPostProcessor.
		// 支持JSR-250的一些注解:@Resource、@PostConstruct、@PreDestroy等
		if (jsr250Present && !registry.containsBeanDefinition(COMMON_ANNOTATION_PROCESSOR_BEAN_NAME)) {
			RootBeanDefinition def = new RootBeanDefinition(CommonAnnotationBeanPostProcessor.class);
			def.setSource(source);
			beanDefs.add(registerPostProcessor(registry, def, COMMON_ANNOTATION_PROCESSOR_BEAN_NAME));
		}

		// Check for JPA support, and if present add the PersistenceAnnotationBeanPostProcessor.
		// 若导入了对JPA的支持,那就注册JPA相关注解的处理器
		if (jpaPresent && !registry.containsBeanDefinition(PERSISTENCE_ANNOTATION_PROCESSOR_BEAN_NAME)) {
			RootBeanDefinition def = new RootBeanDefinition();
			try {
				def.setBeanClass(ClassUtils.forName(PERSISTENCE_ANNOTATION_PROCESSOR_CLASS_NAME,
						AnnotationConfigUtils.class.getClassLoader()));
			}
			catch (ClassNotFoundException ex) {
				throw new IllegalStateException(
						"Cannot load optional framework class: " + PERSISTENCE_ANNOTATION_PROCESSOR_CLASS_NAME, ex);
			}
			def.setSource(source);
			beanDefs.add(registerPostProcessor(registry, def, PERSISTENCE_ANNOTATION_PROCESSOR_BEAN_NAME));
		}
	

		// 下面两个类,是Spring4.2之后加入进来的,为了更好的使用Spring的事件而提供支持 
		// 支持了@EventListener注解,我们可以通过此注解更方便的监听事件了(Spring4.2之后)
		// 具体这个Processor和ListenerFactory怎么起作用的,且听事件专题分解
		if (!registry.containsBeanDefinition(EVENT_LISTENER_PROCESSOR_BEAN_NAME)) {
			RootBeanDefinition def = new RootBeanDefinition(EventListenerMethodProcessor.class);
			def.setSource(source);
			beanDefs.add(registerPostProcessor(registry, def, EVENT_LISTENER_PROCESSOR_BEAN_NAME));
		}
		if (!registry.containsBeanDefinition(EVENT_LISTENER_FACTORY_BEAN_NAME)) {
			RootBeanDefinition def = new RootBeanDefinition(DefaultEventListenerFactory.class);
			def.setSource(source);
			beanDefs.add(registerPostProcessor(registry, def, EVENT_LISTENER_FACTORY_BEAN_NAME));
		}

		return beanDefs;
	}

我们发现:内部定义的class都是带internal的

  • ConfigurationClassPostProcessor是一个BeanFactoryPostProcessorBeanDefinitionRegistryPostProcessor处理器,BeanDefinitionRegistryPostProcessor的处理方法能处理@Configuration等注解。ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry()方法内部处理@Configuration@Import@ImportResource和类内部的@Bean

ConfigurationClassPostProcessor类继承了BeanDefinitionRegistryPostProcessor。BeanDefinitionRegistryPostProcessor类继承了BeanFactoryPostProcessor。 通过BeanDefinitionRegistryPostProcessor可以创建一个特别后置处理器来将BeanDefinition添加到BeanDefinitionRegistry中。它和BeanPostProcessor不同BeanPostProcessor只是在Bean初始化的时候有个钩子让我们加入一些自定义操作;而BeanDefinitionRegistryPostProcessor可以让我们在BeanDefinition中添加一些自定义操作。在Mybatis与Spring的整合中,就利用到了BeanDefinitionRegistryPostProcessor来对Mapper的BeanDefinition进行了后置的自定义处理

  • AutowiredAnnotationBeanPostProcessor是用来处理@Autowired注解和@Value注解的
  • RequiredAnnotationBeanPostProcessor这是用来处理@Required注解
  • CommonAnnotationBeanPostProcessor提供对JSR-250规范注解的支持@javax.annotation.Resource、@javax.annotation.PostConstruct和@javax.annotation.PreDestroy等的支持。
  • EventListenerMethodProcessor提供@PersistenceContext的支持。
  • EventListenerMethodProcessor提供@ EventListener 的支持。@ EventListener实在spring4.2之后出现的,可以在一个Bean的方法上使用@EventListener注解来自动注册一个ApplicationListener

到此AnnotatedBeanDefinitionReader初始化完毕。 总结一下,AnnotatedBeanDefinitionReade读取器用来加载class类型的配置,在它初始化的时候,会预先注册一些BeanPostProcessorBeanFactoryPostProcessor,这些处理器会在接下来的spring初始化流程中被调用

ClassPathBeanDefinitionScanner的初始化:

跟踪构造函数的重载,最终都到这里:

代码语言:javascript
代码运行次数:0
运行
复制
	public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry, boolean useDefaultFilters,
			Environment environment, @Nullable ResourceLoader resourceLoader) {

		Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
		this.registry = registry;
		
		//useDefaultFilters为true,所以此处一般都会执行
		// 当然我们也可以设置为false,比如@ComponentScan里就可以设置为false,只扫描指定的注解/类等等
		if (useDefaultFilters) {
			registerDefaultFilters();
		}
		// 设置环境
		setEnvironment(environment);
		// 详情如下:  这里resourceLoader传值,还是我们的工厂。否则为null
		setResourceLoader(resourceLoader);
	}

registerDefaultFilters()

代码语言:javascript
代码运行次数:0
运行
复制
	protected void registerDefaultFilters() {
		// 这里需要注意,默认情况下都是添加了@Component这个注解的
		//(相当于@Service @Controller @Respository等都会扫描,因为这些注解都属于@Component)  另外@Configuration也属于哦
		this.includeFilters.add(new AnnotationTypeFilter(Component.class));
		ClassLoader cl = ClassPathScanningCandidateComponentProvider.class.getClassLoader();
		
		//下面两个 是兼容JSR-250的@ManagedBean和330的@Named注解
		try {
			this.includeFilters.add(new AnnotationTypeFilter(
					((Class<? extends Annotation>) ClassUtils.forName("javax.annotation.ManagedBean", cl)), false));
			logger.debug("JSR-250 'javax.annotation.ManagedBean' found and supported for component scanning");
		} catch (ClassNotFoundException ex) {
			// JSR-250 1.1 API (as included in Java EE 6) not available - simply skip.
		}
		try {
			this.includeFilters.add(new AnnotationTypeFilter(
					((Class<? extends Annotation>) ClassUtils.forName("javax.inject.Named", cl)), false));
			logger.debug("JSR-330 'javax.inject.Named' annotation found and supported for component scanning");
		} catch (ClassNotFoundException ex) {
			// JSR-330 API not available - simply skip.
		}
		
		// 所以,如果你想Spring连你自定义的注解都扫描,自己实现一个AnnotationTypeFilter就可以啦
	}

ClassPathBeanDefinitionScanner继承于ClassPathScanningCandidateComponentProvider,它内部维护有两个final类型的List:这两个对象在执行本类的scanCandidateComponents()方法时就会起作用。

代码语言:javascript
代码运行次数:0
运行
复制
	//includeFilters中的就是满足过滤规则的
	private final List<TypeFilter> includeFilters = new LinkedList<>();
	//excludeFilters则是不满足过滤规则的
	private final List<TypeFilter> excludeFilters = new LinkedList<>();

ClassPathScanningCandidateComponentProvider#setResourceLoaderResourcePatternResolver,MetadataReaderFactory和CandidateComponentsIndex设定初始值。

代码语言:javascript
代码运行次数:0
运行
复制
	@Override
	public void setResourceLoader(@Nullable ResourceLoader resourceLoader) {
		this.resourcePatternResolver = ResourcePatternUtils.getResourcePatternResolver(resourceLoader);
		this.metadataReaderFactory = new CachingMetadataReaderFactory(resourceLoader);
		// Spring5以后才有这句,优化了bean扫描
		this.componentsIndex = CandidateComponentsIndexLoader.loadIndex(this.resourcePatternResolver.getClassLoader());
	}

备注:若要使用Spring5 的这个功能,需要添加如下包。这样子当工程重新编译的时候(编译期),会在自动生成META-INF/spring-components。然后我们在启动用@ComponentScan扫描时,直接读取这个文件即可,极大的提高了Spring启动的速度。而这期间,可以使用Spring5.0最新提供的注解@Indexed来配合使用。 需要注意的是:这种方式也是存在缺陷的,具体缺陷请参考官方文档:https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#beans-scanning-index

代码语言:javascript
代码运行次数:0
运行
复制
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context-indexer</artifactId>
        <version>5.0.7.RELEASE</version>
        <optional>true</optional>
    </dependency>

然后在运行后会生成一个 META-INF/spring.components 的文件,之后只要运行工程发现这个文件都会直接使用他。可以通过环境变量或工程根目录的spring.properties中设置spring.index.ignore=ture来禁用这个功能 (这个功能如果没有什么明确的需求,慎重使用,会提高工程的管理成本。)

  • ResourcePatternResolver是一个接口,继承了ResourceLoader,可以用来获取Resource 实例。返回的实例为PathMatchingResourcePatternResolver类型
  • MetadataReaderFactory用于解析资源信息对应的元数据,这里返回的实例为:CachingMetadataReaderFactory,带有缓存的
  • CandidateComponentsIndexLoader.loadIndex () 方法是spring5.0以后加入的新特性,Spring Framework 5 改进了扫描和识别组件的方法,使大型项目的性能得到提升。(具体是通过编译器完成扫描,并且往本地写索引,然后启动的时候再去扫描索引即可的思路)

至此,ClassPathBeanDefinitionScanner初始化完毕,总结一下: ClassPathBeanDefinitionScanner是一个扫描指定类路径中注解Bean定义的扫描器,在它初始化的时候,会初始化一些需要被扫描的注解,初始化用于加载包下的资源的Loader

AnnotatedBeanDefinitionReader和ClassPathBeanDefinitionScanner的初始化是spring上线文初始化的起点,很多预加载的类会在spring接下来的初始化中发挥重要作用

包扫描的启动方式
代码语言:javascript
代码运行次数:0
运行
复制
    public static void main(String[] args) {
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext("com.fsx.config");
        System.out.println(applicationContext.getBean(Parent.class)); //com.fsx.bean.Parent@639c2c1d
    }

启动函数为:

代码语言:javascript
代码运行次数:0
运行
复制
	public AnnotationConfigApplicationContext(String... basePackages) {
		this();
		scan(basePackages);
		refresh();
	}

	public void scan(String... basePackages) {
		Assert.notEmpty(basePackages, "At least one base package must be specified");
		// 核心在这个scan方法里,见下面
		this.scanner.scan(basePackages);
	}

下面就是重点看看doScan()方法

代码语言:javascript
代码运行次数:0
运行
复制
	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就放进来了(比如此处只有RootConfig一个Bean定义,是个配置类)
			// 这个是重点,会把该包下面所有的Bean都扫描进去。Spring5和一下的处理方式不一样哦~
			Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
			for (BeanDefinition candidate : candidates) {
				// 拿到Scope元数据:此处为singleton
				ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
				candidate.setScope(scopeMetadata.getScopeName());
				// 生成Bean的名称,默认为首字母小写。此处为"rootConfig"
				String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);

				// 此处为扫描的Bean,为ScannedGenericBeanDefinition,所以肯定为true
				// 因此进来,执行postProcessBeanDefinition(对Bean定义信息做)   如下详解
				// 注意:只是添加些默认的Bean定义信息,并不是执行后置处理器~~~
				if (candidate instanceof AbstractBeanDefinition) {
					postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
				}
				// 显然,此处也是true  也是完善比如Bean上的一些注解信息:比如@Lazy、@Primary、@DependsOn、@Role、@Description   @Role注解用于Bean的分类分组,没有太大的作用
				if (candidate instanceof AnnotatedBeanDefinition) {
					AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
				}
				// 检查这个Bean  比如
				//如果dao包(一般配置的basePakage是这个)下的类是符合mybaits要求的则向spring IOC容器中注册它的BeanDefinition  所以这步检查第三方Bean的时候有必要检查一下
				if (checkCandidate(beanName, candidate)) {
					BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
					
					//AnnotationConfigUtils类的applyScopedProxyMode方法根据注解Bean定义类中配置的作用域@Scope注解的值,为Bean定义应用相应的代理模式,主要是在Spring面向切面编程(AOP)中使用
					definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
					beanDefinitions.add(definitionHolder);

					// 注意 注意 注意:这里已经吧Bean注册进去工厂了,所有doScan()方法不接收返回值,也是没有任何问题的。。。。
					registerBeanDefinition(definitionHolder, this.registry);
				}
			}
		}
		return beanDefinitions;
	}

ClassPathScanningCandidateComponentProvider#findCandidateComponents

代码语言:javascript
代码运行次数:0
运行
复制
	public Set<BeanDefinition> findCandidateComponents(String basePackage) {
		// 上面说过了CandidateComponentsIndex是Spring5提供的优化扫描的功能
		// 显然这里编译器我们没有写META-INF/spring.components索引文件,所以此处不会执行Spring5 的扫描方式,所以我暂时不看了(超大型项目才会使用Spring5的方式)
		if (this.componentsIndex != null && indexSupportsIncludeFilters()) {
			return addCandidateComponentsFromIndex(this.componentsIndex, basePackage);
		} else {
			// Spring 5之前的方式(绝大多数情况下,都是此方式)
			return scanCandidateComponents(basePackage);
		}
	}

scanCandidateComponents:根据basePackage扫描候选的组件们(非常重要)

代码语言:javascript
代码运行次数:0
运行
复制
	private Set<BeanDefinition> scanCandidateComponents(String basePackage) {
		Set<BeanDefinition> candidates = new LinkedHashSet<>();
		try {
			// 1.根据指定包名 生成包搜索路径
			//通过观察resolveBasePackage()方法的实现, 我们可以在设置basePackage时, 使用形如${}的占位符, Spring会在这里进行替换 只要在Enviroment里面就行
			// 本次值为:classpath*:com/fsx/config/**/*.class
			String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
					resolveBasePackage(basePackage) + '/' + this.resourcePattern;
			
			//2. 资源加载器 加载搜索路径下的 所有class 转换为 Resource[]
			// 拿着上面的路径,就可以getResources获取出所有的.class类,这个很强大~~~
			// 真正干事的为:PathMatchingResourcePatternResolver#getResources方法
			// 此处能扫描到两个类AppConfig(普通类,没任何注解标注)和RootConfig。所以接下里就是要解析类上的注解,以及过滤掉不是候选的类(比如AppConfig)
			
			// 注意:这里会拿到类路径下(不包含jar包内的)的所有的.class文件 可能有上百个,然后后面再交给后面进行筛选~~~~~~~~~~~~~~~~(这个方法,其实我们也可以使用)
			// 当然和getResourcePatternResolver和这个模版有关
			Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);

			// 记录日志(下面我把打印日志地方都删除)
			boolean traceEnabled = logger.isTraceEnabled();
			boolean debugEnabled = logger.isDebugEnabled();

			// 接下来的这个for循环:就是把一个个的resource组装成
			for (Resource resource : resources) {
				//文件必须可读 否则直接返回空了
				if (resource.isReadable()) {
					try {

						//读取类的 注解信息 和 类信息 ,两大信息储存到  MetadataReader
						MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);
				
						// 根据TypeFilter过滤排除组件。因为AppConfig没有标准@Component或者子注解,所以肯定不属于候选组件  返回false
						// 注意:这里一般(默认处理的情况下)标注了默认注解的才会true,什么叫默认注解呢?就是@Component或者派生注解。还有javax....的,这里省略啦
						if (isCandidateComponent(metadataReader)) {
						
							//把符合条件的 类转换成 BeanDefinition
							ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
							sbd.setResource(resource);
							sbd.setSource(resource);
							
							// 再次判断 如果是实体类 返回true,如果是抽象类,但是抽象方法 被 @Lookup 注解注释返回true (注意 这个和上面那个是重载的方法) 
							// 这和上面是个重载方法  个人觉得旨在处理循环引用以及@Lookup上
							if (isCandidateComponent(sbd)) {
								candidates.add(sbd);
							}
						}
					} 
				}
			}
		}
		return candidates;
	}

// 备注:此时ComponentScan这个注解还并没有解析

ClassPathBeanDefinitionScanner#postProcessBeanDefinition

代码语言:javascript
代码运行次数:0
运行
复制
	protected void postProcessBeanDefinition(AbstractBeanDefinition beanDefinition, String beanName) {
		// 位Bean定义 执行些默认的信息
		// BeanDefinitionDefaults是个标准的javaBean,有一些默认值
		beanDefinition.applyDefaults(this.beanDefinitionDefaults);

		// 自动依赖注入 匹配路径(此处为null,不进来)
		if (this.autowireCandidatePatterns != null) {
			beanDefinition.setAutowireCandidate(PatternMatchUtils.simpleMatch(this.autowireCandidatePatterns, beanName));
		}
	}

AbstractBeanDefinition#applyDefaults:

	public void applyDefaults(BeanDefinitionDefaults defaults) {
		setLazyInit(defaults.isLazyInit());
		setAutowireMode(defaults.getAutowireMode());
		setDependencyCheck(defaults.getDependencyCheck());
		setInitMethodName(defaults.getInitMethodName());
		setEnforceInitMethod(false);
		setDestroyMethodName(defaults.getDestroyMethodName());
		setEnforceDestroyMethod(false);
	}

就这样,扫描的Bean就全部注册完成了(若你的Config比较多的情况下,用扫描的方式更加简单些~)

总结

观看Spring源码这么久,最大的感受就是:组件繁多,但管理有序。它完美的实现了单一职责的设计原则,谁的事情谁去干,这点体现得非常好。只有遵循了这个原则,才能做到无状态和组件化,达到随意装配的效果~ 另外,还有一种设计思想就是预加载。什么组件在什么时候加载,什么时候可以预加载进去,都是设计的精妙之处

理解ClassPathBeanDefinitionScanner的工作原理,可以帮助理解Spring IOC 容器的初始化过程。 同时对理解MyBatis 的 Mapper 扫描 也是有很大的帮助。 因为 MyBatisMapperScannerConfigurer底层实现也是一个ClassPathBeanDefinitionScanner的子类。就像我们自定义扫描器那样,自定定义了 过滤器的过滤规则

这两名大将还提供了比如扫描包的核心方法,这个在讲解具体IOC容器流程的时候再具体分析~~~

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2019年03月01日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
区块链技术能彻底解决疫苗问题吗?
最近长春长生疫苗造假事件,触动了全国神经,余波至今未休。在此期间,一位程序员殚精竭虑,寻找疫苗流向,却发现困难重重。疫苗采购信息需要在政府网站公示,但他发现,有的政府网站打不开,有的无法搜索,有的要求登录认证才可见。网站数据也有各种问题。疫苗系统存在相当大的封闭性,外界往往无法监督。
用户2357564
2018/08/06
9960
区块链技术能彻底解决疫苗问题吗?
WanShare:如果区块链落地医药行业,疫苗还会如此无下限吗?
事情的起因是国家药品监督管理局(国家药监局)通告称,长春长生生物科技有限责任公司(长春长生)生产的狂犬病疫苗被发现存在生产记录造假等问题,被责令停产,并收回GMP证书(生产质量管理规范证书)。
链小二
2018/07/23
5900
WanShare:如果区块链落地医药行业,疫苗还会如此无下限吗?
【区块链实践】区块链在医疗健康数据、医疗保险、基因组数据的案例
近几年,各种可穿戴设备逐渐流行,从各个品牌的运动手环、到每次发布会都越来越强调健康功能的苹果手表、甚至朋友圈大家每天晒的步数、基因测序项目 23andMe...可见我们越来越重视健康。
辉哥
2019/03/20
4.6K0
【区块链实践】区块链在医疗健康数据、医疗保险、基因组数据的案例
疫苗事件凉透人心 区块链技术开发溯源解决问题
酷热的夏日随着“疫苗事件”曝光让国民一阵心寒,药品监管一直是民生关注的重点,也是国民对生产方品牌的一种信任所在。其实说白了就是“疫苗源头没有实时公证”,而区块链技术开发“溯源”正好能解决这一问题。
区块链开发先驱者
2018/07/23
5910
区块链技术公司浅谈如何解决看病难问题
对医疗的关注源于之前异常火爆的一部电影《我不是药神》,整个故事源于这个天价药。因为天价,有人吃到倾家荡产,有人吃到另寻他方,用低价假药替代,。不是所有的假药都可以像那个印度药一样,以低廉的价格换取同样的药效,现实还有很多的“毒药假药”钻着空子在蔓延。对于老百姓而言,大家对医疗体系中的信任二字似乎总是难得,什么是解决信任最好的办法?想到这儿,很多人便把目光聚焦当下那个自带信任特性的技术---区块链。区块链技术本来就可以解决人与人人与物之间的信任问题,在医疗中区块链解决所谓的信任?我们一起来聊聊一、电子病历系统:
qq2398788267
2018/09/03
6330
区块链场景设计中的南橘北枳——关于伪需求的讨论
在区块链的场景设计中,仍然需要考虑为什么要上区块链,以及区块链技术所能解决什么样痛点的问题。
非哥
2018/08/30
1.2K6
区块链场景设计中的南橘北枳——关于伪需求的讨论
疫苗黑幕为何屡禁不止?聊区块链技术开发溯源
随着问题的逐步曝光,“疫苗事件”像深水炸弹一样激起了民愤。2018年7月15日国家药监局公布关于“长生生物冻干人用狂犬疫苗生产存在造假违规”行为!不仅如此,之前也有武汉生物生产批号为201607050-2的百白破违规疫苗流入市场的问题。那么为何疫苗黑幕屡禁不止?我想只有用区块链技术开发的溯源体系才能实现疫苗审核追责了。
区块链开发先驱者
2018/07/23
6220
区块链深入“健康码”验证、疫苗打假
近日,新加坡政府技术局(GovTech)与卫生部(MOH)合作开发了一套基于区块链的文件验证系统,以提供 COVID-19 阴性检测证明。
深度学习与Python
2021/03/12
9590
区块链深入“健康码”验证、疫苗打假
5大场景带你了解区块链如何给医疗行业动“手术”
制药行业中,分销商库存药品过多的时候,会退一部分给制药商。退货药品在药品总销量中占比约为2%-3%,这个比重看起来并不大,但这部分药品每年的销售额有70- 100亿美元之多。
区块链大本营
2018/09/21
6800
5大场景带你了解区块链如何给医疗行业动“手术”
【行业应用报告】区块链赋能医疗产业报告
整体上,医疗产业可以分为医疗服务和医药商业两大类,其行业痛点主要在于医疗服务方面的数据未能被充分利用,以及医药商业中医药产品的防伪溯源难题。 传统医疗数据在信息化发展方面遇到诸多难题,相比其他传统行业,发展进度极为缓慢。究其根本,是因为目前医疗数据孤岛化且缺乏标准体系、数据安全难以保障、数据确权不明晰导致的传统参与者信息化的意愿低,医疗服务中的医疗数据未能被充分利用。 而医药行业供应链由于参与主体众多,存在大量交互协作,信息被离散地保存在各自环节各自系统中,缺乏透明度。信息的不流畅导致各参与主体难以准确了解相关事项的实时状况及存在的问题,影响供应链协同效率。当各主体间出现纠纷时,举证和追责耗时费力。 而区块链在数据保密、智能合约、生态激励等方面具有天然的优势,与医疗行业具有较高的契合度,能为医疗行业提供多环节安全解决方案,同时也能助推医疗行业智能化发展。
辉哥
2020/02/18
3.1K0
【行业应用报告】区块链赋能医疗产业报告
“区块​链+”系列 | 区块链+网约车
​寒风起,天气是越来越冷了,出门打个车成为了更多人的选择,价钱上不说,不用站在寒风中等待拦停过往的出租车了,的确,网约车的出现给人们的生活带来了太多的便捷。
广州闪链科技
2018/12/13
16.4K0
“区块​链+”系列 | 区块链+网约车
用区块链技术养走地鸡,我认真的
上周日(2018.3.18)上午10点,我们在微信群开展了一场关于区块链技术的“互怼”活动,简言之就是针对“区块链技术现阶段除了比特币到底能不能成功落地,值不值得应用”的问题进行大讨论。 参加讨论的大都是长期关注区块链技术的人,其中很大一部分在此领域已经有一些尝试,也有人出过相关的书籍。现阶段,区块链对大众来说还蒙着一层面纱,人们对区块链技术的了解还仅限于其在比特币方面的实践,对其能不能在日常生活中有所应用还很迷茫。 本次活动,我们还邀请到了一位技术专业人士,来聊聊区块链技术中信息溯源方面的问题,分享几个白
CSDN技术头条
2018/03/26
1.6K0
用区块链技术养走地鸡,我认真的
区块链开发公司 区块链与产业变革
 随着货币改革的失败和连锁改革的兴起,区块链产业逐渐“从虚拟到实际”,也引发了人们从传统商业思维到区块链思维的转变。创新者开始关注区块链技术对相关企业组织生态的转变。区块链主链侧链开发(主链FBA算法渐近安全第一个可证明安全的共识算法低延迟、防止双花攻击 分散控制 去中心化程度高 符合工业4.0) 中心化和去中心化场内场外交易所开发 区块链跨境支付开发 区块链游戏开发 区块链供应链开发 配资软件开发(带三方支付)微信小程序开发 各行各业定制化软件 网络安全支持 电话13986355479 微信15501162665.
用户3126099
2018/10/09
4K0
区块链如何拯救世界?
如果你还不知道区块链是什么以及它是如何运行的,你可以在这里阅读我以前的主题文章......
Bon
2018/03/08
1.3K0
区块链如何拯救世界?
都说区块链可信,到底信了个啥?
要想了解区块链、云原生和机器学习等技术原理,可置顶和关注本公众号 亨利笔记 ( henglibiji ),以免错过更新。
Henry Zhang
2022/05/04
8450
都说区块链可信,到底信了个啥?
你们所关心的七大区块链问题,专家有话说!
你们所关心的七大区块链问题,专家有话说!
数据猿
2018/04/19
1.5K0
你们所关心的七大区块链问题,专家有话说!
家乐福采用区块链,溯源系统原理开发
近日,食品防伪追溯区块链溯源系统正式商用,家乐福签约其溯源系统进行大规模项目建设,主要以提高食品透明度,家乐福采用区块链技术打造建设溯源体系,对接整个生产流程,实现公开透明化监管。
用户2945713
2018/10/10
2.8K0
家乐福采用区块链,溯源系统原理开发
全球首个区块链商品溯源小程序“智臻生活”上线 让信任更近一步
近日,京东“智臻生活”小程序正式上线微信,这是全球首个通过区块链技术打造的商品溯源聚合小程序,旨在通过“京品追溯”商品为消费者带来更值得信赖的消费生活体验。“智臻生活”作为京东可追溯商品的消费入口,聚合了海量的京东防伪追溯商品,可供消费者查询或直接购买,还可以帮助消费者真正了解商品的“前世今生”并留存记录。
京东技术
2018/08/08
1.5K0
区块链在医疗数据管理中的应用:安全与隐私的新纪元
随着医疗行业的数字化转型,医疗数据的管理变得愈发重要。然而,传统的医疗数据管理系统面临着数据安全、隐私保护和数据共享等诸多挑战。区块链技术的引入,为解决这些问题提供了新的思路。本文将详细探讨区块链在医疗数据管理中的应用,分析其优势和潜在问题,并通过代码示例展示其实际应用。
Echo_Wish
2025/01/18
2590
区块链在医疗数据管理中的应用:安全与隐私的新纪元
【行业应用案例】腾讯区块链+电子发票实践
据腾讯科技消息,2018年8月10日,全国首张区块链电子发票在深圳实现落地,其底层技术由腾讯提供。
辉哥
2020/02/18
5.8K0
【行业应用案例】腾讯区块链+电子发票实践
推荐阅读
相关推荐
区块链技术能彻底解决疫苗问题吗?
更多 >
目录
  • 前言
  • 环境准备
  • IOC容器加载Bean定义信息分析
    • AnnotatedBeanDefinitionReader初始化,构造器如下
    • ClassPathBeanDefinitionScanner的初始化:
  • 包扫描的启动方式
  • 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档