五、component-scan 标签的解析原理

一、component-scan概述

1、Spring中component-scan标签配置

spring5.exercise.demo17.spring.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation=
       "
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd
       ">
    <!-- 自定义标签 扫描包 -->
    <context:component-scan base-package="com.jd.nlp.dev.muzi.spring5.exercise.demo17" />
    
</beans>

描述
上述spring.xml配置中,配置了一个component-scan标签,这个标签的作用是扫描项目代码中com.jd.nlp.dev.muzi.spring5.exercise.demo01下的class类,如果有@Service @Controller @Component等注解的类,将这些类解析成一个个的BeanDefinition,容器初始化时会针对这些类去创建实例对象。

2、component-scan功能案例代码

com.jd.nlp.dev.muzi.spring5.exercise.demo17.ProductService

@Service("productService")
public class ProductService {

    private String name = "Muzi";

    private String well = "开始Spring5的炫酷之旅,(Muzi)书生不读四书五经!";

    public void show() {
        System.out.println(name + "\n" + well);
    }

    public ProductService() {
        this.name = "无参数构造函数";
    }
}

测试容器启动代码

    @Test
    public void run01(){
        // 基于加载XML配置文件的方式,启动spring容器
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath*:spring5.exercise.demo17.spring.xml");
        ProductService productService = (ProductService) context.getBean("productService");
        productService.show();
    }

描述
上述测试代码中,创建了基于解析XML的容器,解析spring5.exercise.demo17.spring.xml配置文件,在配置文件中配置了一个component-scan标签去扫描“com.jd.nlp.dev.muzi.spring5.exercise.demo17”包下面的含有Spring @Component下的类ProductService。
我们测试代码中通过容器获取ProductService的实例,调用show方法。

测试结果

09:54:56.037 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'productService'
无参数构造函数
开始Spring5的炫酷之旅,(Muzi)书生不读四书五经!

那么具体component-scan标签是如何解析的呢?和我跟着源码一点一点阅读吧。

二、component-scan解析源码

1、回顾Spring的自定义标签解析(SPI)

component-scan是一个自定义标签

<context:component-scan base-package="com.jd.nlp.dev.muzi.spring5.exercise.demo17" />

namespace uri是http://www.springframework.org/schema/context

xmlns:context="http://www.springframework.org/schema/context"

所以我们需要去context的jar包去找 spring.handlers文件
spring-context的spring.handlers文件

http\://www.springframework.org/schema/context=org.springframework.context.config.ContextNamespaceHandler
http\://www.springframework.org/schema/jee=org.springframework.ejb.config.JeeNamespaceHandler
http\://www.springframework.org/schema/lang=org.springframework.scripting.config.LangNamespaceHandler
http\://www.springframework.org/schema/task=org.springframework.scheduling.config.TaskNamespaceHandler
http\://www.springframework.org/schema/cache=org.springframework.cache.config.CacheNamespaceHandler

基于上述文件可知,http://www.springframework.org/schema/context这个namespace uri对应的解析类是“org.springframework.context.config.ContextNamespaceHandler”

在ContextNamespaceHandler的init方法中,component-scan这个自定义标签是ComponentScanBeanDefinitionParser这个解析类来进行解析的。

	@Override
	public void init() {
		// 标签元素和解析类的映射关系
		registerBeanDefinitionParser("property-placeholder", new PropertyPlaceholderBeanDefinitionParser());
		registerBeanDefinitionParser("property-override", new PropertyOverrideBeanDefinitionParser());
		registerBeanDefinitionParser("annotation-config", new AnnotationConfigBeanDefinitionParser());
		registerBeanDefinitionParser("component-scan", new ComponentScanBeanDefinitionParser());
		registerBeanDefinitionParser("load-time-weaver", new LoadTimeWeaverBeanDefinitionParser());
		registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
		registerBeanDefinitionParser("mbean-export", new MBeanExportBeanDefinitionParser());
		registerBeanDefinitionParser("mbean-server", new MBeanServerBeanDefinitionParser());
	}

registerBeanDefinitionParser

	protected final void registerBeanDefinitionParser(String elementName, BeanDefinitionParser parser) {
		this.parsers.put(elementName, parser);
	}

最后实际上registerBeanDefinitionParser是把对应标签的解析类注册到parsers这个Map中了。

2、Spring xml解析自定义标签入口

DefaultBeanDefinitionDocumentReader -> parseBeanDefinitions

			/**
			 * 获取根节点中所有的子节点
			 */
			NodeList nl = root.getChildNodes();

			/**
			 * 遍历
			 */
			for (int i = 0; i < nl.getLength(); i++) {
				Node node = nl.item(i);
				if (node instanceof Element) {
					Element ele = (Element) node;
					if (delegate.isDefaultNamespace(ele)) {
						/**
						 * 默认标签解析
						 */
						parseDefaultElement(ele, delegate);
					}else {
						/**
						 * 自定义标签解析,委托给delegate解析
						 */
						delegate.parseCustomElement(ele);
					}
				}
			}
		}

代码描述
在这个方法中,遍历所有标签元素,如果是默认的namespace就走默认标签解析,如否则走自定义标签解析。

BeanDefinitionParserDelegate -> parseCustomElement

	public BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) {

		/**
		 * 1.获取标签元素的NamespaceURI
		 */
		String namespaceUri = getNamespaceURI(ele);
		if (namespaceUri == null) {
			return null;
		}

		/**
		 * 2.通过URI来获得对应的NamespaceHandler
		 */
		NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
		if (handler == null) {
			error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
			return null;
		}

		/**
		 * 3.使用handler来解析该标签,带入 readerContext 进去。
		 */
		return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
	}

代码描述
在这个方法中,首先还是先获取标签的namespace uri,然后通过namespace uri来寻找对应的namespace handler,我们待解析的component-scan标签对应的handler就是之前找到的ContextNamespaceHandler。
得到ContextNamespaceHandler后就会调用他的parse方法进行解析。

上面执行resolve获取NamespaceHandler的方法,最终调用以下方法

	public NamespaceHandler resolve(String namespaceUri) {
	//加载"META-INF/spring.handlers"文件,建立URI和处理类的映射关系
		Map<String, Object> handlerMappings = getHandlerMappings();

           // ... ... 省略
			// 处理类(字符串)反射
			String className = (String) handlerOrClassName;
				/**
				 * 反射这个类
				 */
				Class<?> handlerClass = ClassUtils.forName(className, this.classLoader);
				// ... ... 省略
              // 反射创建handler
				NamespaceHandler namespaceHandler = (NamespaceHandler) BeanUtils.instantiateClass(handlerClass);

				// 调用处理类初始化方法
				namespaceHandler.init();

				// 替换映射关系key对应的值
				handlerMappings.put(namespaceUri, namespaceHandler);
				return namespaceHandler;
			// ... ... 省略

代码描述

  1. 建立各个jar包的META-INF/spring.handlers文件,URI和类路径的映射关系。
  2. 反射创建namespaceHandler对象
  3. 调用namespaceHandler的init方法
  4. 创建好的对象替换URI对应的类路径的值,同一种自定义标签下一次解析就不用反射创建,直接用创建好的。
  5. 返回namespaceHandler

3、ContextNamespaceHandler parse解析

NamespaceHandlerSuppoort -> parse

	public BeanDefinition parse(Element element, ParserContext parserContext) {
		/**
		 * 获取这个自定义标签元素的解析器
		 */
		BeanDefinitionParser parser = findParserForElement(element, parserContext);
		return (parser != null ? parser.parse(element, parserContext) : null);
	}

NamespaceHandlerSuppoort描述
自定义的NamespaceHandelr都会继承官方提供的默认的NamespaceHandlerSuppoort,内部实现好了很多东西,不需要开发者去二次实现。开发者只需要专注于对应标签解析类的开发。
这个方法首先会获取当前待解析标签对应的解析器对象,然后调用对应解析器的解析方法进行解析。

NamespaceHandlerSuppoort -> findParserForElement获取解析器

	private BeanDefinitionParser findParserForElement(Element element, ParserContext parserContext) {
		String localName = parserContext.getDelegate().getLocalName(element);
		/**
		 * 获取解析的parser,parsers这个map是在获取Handler时调用init的时候把parser注册进Map的。
		 */
		BeanDefinitionParser parser = this.parsers.get(localName);
		// ... ...
		return parser;
	}

上述回顾SPI最后提到过,所有的解析器在init的时候会注册到parsers这个Map中,所以当前拿到的是 component-scan 标签对应的 ComponentScanBeanDefinitionParser 类的实例。

4、ComponentScanBeanDefinitionParser 解析compponent-scan标签

ComponentScanBeanDefinitionParser -> parse

	public BeanDefinition parse(Element element, ParserContext parserContext) {
		/**
		 * 扫描注解的解析方法
		 *
		 * base-package 包名路径获取
		 */
		String basePackage = element.getAttribute(BASE_PACKAGE_ATTRIBUTE);
		basePackage = parserContext.getReaderContext().getEnvironment().resolvePlaceholders(basePackage);
		// base-package 可以用,分隔,多个包。
		String[] basePackages = StringUtils.tokenizeToStringArray(basePackage,
				ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);


		/**
		 * 拿到注解扫描器
		 */
		ClassPathBeanDefinitionScanner scanner = configureScanner(parserContext, element);

		/**
		 * 扫描包下的类
		 * 并把这些类的信息封装成BeanDefinitionHolder(BeanDefinition)对象集合
		 * 重要程度:* * * * *
		 */
		Set<BeanDefinitionHolder> beanDefinitions = scanner.doScan(basePackages);

		/**
		 * 注册 @Resource @Autowire @Value 这些注解组件的功能。
		 *
		 * 重要程度:* * * * *
		 *
		 * @Resource @Autowire @Value
		 * 是由N个BeanPostProcessor接口的实现类来实现的
		 * BeanPostProcessor接口是我们用到的最重要的接口
		 * 类接口实现类是通过注册组件注册进去的
		 *
		 * BeanPostProcessor 是Spring中至关重要的接口,大部分功能都是依赖他完成的。
		 */
		registerComponents(parserContext.getReaderContext(), beanDefinitions, element);

		return null;
	}

代码描述

  1. 得到注解扫描器。
  2. 扫描 basepackages 包下的类,并注册BeanDefinition对象。
  3. 注册@Resource @Autowire @Value等注解组件的功能,这些组件是基于BeanPostProcessor来实现的,所以如果要在refresh()中实例化需要先注册BeanDefinition。

4.1 注解扫描器

	protected ClassPathBeanDefinitionScanner configureScanner(ParserContext parserContext, Element element) {

		// 使用默认过滤器
		boolean useDefaultFilters = true;

		/**
		 * @Service @Component @Controller 默认注解
		 */
		if (element.hasAttribute(USE_DEFAULT_FILTERS_ATTRIBUTE)) {
			useDefaultFilters = Boolean.valueOf(element.getAttribute(USE_DEFAULT_FILTERS_ATTRIBUTE));
		}

		/**
		 * 创建注解扫描器
		 */
		ClassPathBeanDefinitionScanner scanner = createScanner(parserContext.getReaderContext(), useDefaultFilters);
		scanner.setBeanDefinitionDefaults(parserContext.getDelegate().getBeanDefinitionDefaults());
		scanner.setAutowireCandidatePatterns(parserContext.getDelegate().getAutowireCandidatePatterns());

		// ... ... 省略

		/*
		 * 扫描 哪些注解
		 * 不扫描 哪些注解
		 */
		parseTypeFilters(element, scanner, parserContext);

		return scanner;
	}

代码描述
注解扫描器一般使用的是默认注解扫描器
createScanner中会调用到registerDefaultFilters

/**
		 * 过滤器中添加需要扫描的注解类型
		 * 为什么只丢Component这个注解 , @Service 和 @Controller都是Component这个注解的类型
		 */
		this.includeFilters.add(new AnnotationTypeFilter(Component.class));

默认注解扫描器会扫描@Componenet注解的类,而@Service 和 @Controller都继承@Componenet注解所以会一并去进行扫描,注解会加入到includeFilters这个集合中。

4.2 扫描包下的类并注册BeanDefinition

ClassPathBeanDefinitionScanner -> doScan

	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) {

			/**
			 * 扫描到有注解的类并封装成BeanDefinition
			 *
			 * 递归方式找被注解的类,封装成BeanDefinition
			 *
			 * 重要程度:* * * * *
			 */
			Set<BeanDefinition> candidates = findCandidateComponents(basePackage);

			/**
			 * 扫描有注解的BeanDefinition对象,并设置BeanDefinition的基础属性值。
			 */
			for (BeanDefinition candidate : candidates) {
				/**
				 * 单例还是原型 属性设置
				 */
				ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
				candidate.setScope(scopeMetadata.getScopeName());
				String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);

				/**
				 * autowireCandidate 是否可以被自动注入属性设置
				 */
				if (candidate instanceof AbstractBeanDefinition) {
					postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
				}
				if (candidate instanceof AnnotatedBeanDefinition) {
					/**
					 * 类上面有哪些注解set哪些值
					 * Role
					 * Lazy
					 * Primary
					 * DependsOn
					 * Description
					 */
					AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
				}

				if (checkCandidate(beanName, candidate)) {
					/**
					 * beanDefinition beanName 的映射
					 */
					BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
					definitionHolder =
							AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
					beanDefinitions.add(definitionHolder);

					/**
					 * 注册BeanDefinition
					 */
					registerBeanDefinition(definitionHolder, this.registry);
				}
			}
		}
		return beanDefinitions;
	}

代码描述

  1. 递归方式找被注解的类,封装成BeanDefinition (有点内容可以看下)
  2. 扫描有注解的BeanDefinition对象,并设置BeanDefinition的基础属性值。
  3. BeanDefinition相关属性设置
  4. beanDefinition beanName 的映射
  5. 注册BeanDefinition (和之前的XML解析一样没什么看的)

逻辑都还比较简单,至此其实自定义扫描一个包下的类就完事了。

递归找文件
扫描class文件并给出初步的BeanDefinition内容的代码。
ClassPathScanningCandidateComponentProvider -> scanCandidateComponents

	private Set<BeanDefinition> scanCandidateComponents(String basePackage) {
		Set<BeanDefinition> candidates = new LinkedHashSet<>();
		try {
			String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
					resolveBasePackage(basePackage) + '/' + this.resourcePattern;
			/**
			 * 递归找类文件
			 * PathMatchingResourcePatternResolver
			 * 重要程度:* * * *
			 */
			Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);
			boolean traceEnabled = logger.isTraceEnabled();
			boolean debugEnabled = logger.isDebugEnabled();
			for (Resource resource : resources) {
				if (traceEnabled) {
					logger.trace("Scanning " + resource);
				}
				if (resource.isReadable()) {
					try {
						/**
						 * 包装了类的基本信息的对象(和类相关)
						 * 如果类上面有 includeFilters 集合装载的注解
						 */
						MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);

						/**
						 * 如果类元数据信息中有includeFilter集合中的注解,就去实例化
						 */
						if (isCandidateComponent(metadataReader)) {

							/**
							 * 把这个类包装成一个GenericBeanDefinition对象
							 *
							 * ScannedGenericBeanDefinition 继承了 GenericBeanDefinition
							 *
							 * 把 metadataReader 元数据信息集合丢给了 ScannedGenericBeanDefinition 对象
							 */
							ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
							sbd.setResource(resource);
							sbd.setSource(resource);
							if (isCandidateComponent(sbd)) {
								if (debugEnabled) {
									logger.debug("Identified candidate component class: " + resource);
								}
								candidates.add(sbd);
							}
							// ... ... 省略
		}
		
		/**
		 * 返回 beanDefinition 的集合
		 */
		return candidates;
	}

4.3 实例化过程中的相关组件注册

BeanPostProcessor 是Spring中至关重要的接口,bean的实例化大部分功能都是依赖他完成的。

ComponentScanBeanDefinitionParser -> registerComponents

	protected void registerComponents(
			XmlReaderContext readerContext, Set<BeanDefinitionHolder> beanDefinitions, Element element) {

		// ... ... 省略
		if (annotationConfig) {
			/**
			 * 生成组件BeanDefinition并注册
			 * AutowireAnnotationBeanPostProcessor,ConfigurationClassPostProcessor,CommonAnnotationBeanPostProcessor
			 * AutowireAnnotationBeanPostProcessor:是Autowire的支撑,是DI的核心处理。
			 *
			 * 组件注册
			 * registerAnnotationConfigProcessors
			 */
			Set<BeanDefinitionHolder> processorDefinitions =
					AnnotationConfigUtils.registerAnnotationConfigProcessors(readerContext.getRegistry(), source);
			for (BeanDefinitionHolder processorDefinition : processorDefinitions) {
				compositeDef.addNestedComponent(new BeanComponentDefinition(processorDefinition));
			}
		}

		readerContext.fireComponentRegistered(compositeDef);
	}

实际的组册组件的代码。
AnnotationConfigUtils -> registerAnnotationConfigProcessors

	public static Set<BeanDefinitionHolder> registerAnnotationConfigProcessors(
			BeanDefinitionRegistry registry, @Nullable Object source) {
		DefaultListableBeanFactory beanFactory = unwrapDefaultListableBeanFactory(registry);
		// ... ... 省略

		Set<BeanDefinitionHolder> beanDefs = new LinkedHashSet<>(8);

		/**
		 * 解析 @ComponentScan @Import @Bean @ImportSource
		 */
		if (!registry.containsBeanDefinition(CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME)) {

			/**
			 * ConfigurationClassPostProcessor 至关重要,解析@ComponentScan @Import @Bean @ImportSource注解的能力。
			 * @Configuration 注解的组件功能注册
			 */
			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)) {

			/**
			 * @Autowire 注解的组件功能注册
			 */
			RootBeanDefinition def = new RootBeanDefinition(AutowiredAnnotationBeanPostProcessor.class);
			def.setSource(source);
			beanDefs.add(registerPostProcessor(registry, def, AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME));
		}

		// Check for JSR-250 support, and if present add the CommonAnnotationBeanPostProcessor.
		if (jsr250Present && !registry.containsBeanDefinition(COMMON_ANNOTATION_PROCESSOR_BEAN_NAME)) {

			/**
			 * @PostConstruct @Resource 等注解的组件功能注册
			 * 包装成BeanDefinition对象,赋值,注册。
			 */
			RootBeanDefinition def = new RootBeanDefinition(CommonAnnotationBeanPostProcessor.class);
			def.setSource(source);
			beanDefs.add(registerPostProcessor(registry, def, COMMON_ANNOTATION_PROCESSOR_BEAN_NAME));
		}
		// ... ... 省略
		return beanDefs;
	}

这里会注册一些我们实例化过程中,或者是扫描BeanDefinition过程中可能会用到的一些组件。

三、总结

扫描包下的类,有很多种方式,如 @ComponentScan(“xxx.xx.x”) 也是基于刚刚注册的组件去解析的,一样也是获取 Scanner 去 doScan,万变不离其宗,我们理解了 component-scan 标签的解析,再去看@ComponentScan(“xxx.xx.x”)注解的解析内容也会变得很简单。

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值