这一次搞懂Spring自定义标签以及注解解析原理

作者 |夜勿语
来源 | urlify.cn/iqAn2e
前言在上一篇文章中分析了Spring是如何解析默认标签的 , 并封装为BeanDefinition注册到缓存中 , 这一篇就来看看对于像context这种自定义标签是如何解析的 。 同时我们常用的注解如:@Service、@Component、@Controller标注的类也是需要在xml中配置才能自动注入到IOC容器中 , 所以本篇也会重点分析注解解析原理 。
正文自定义标签解析原理在上一篇分析默认标签解析时看到过这个类DefaultBeanDefinitionDocumentReader的方法parseBeanDefinitions:
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {if (delegate.isDefaultNamespace(root)) {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.parseCustomElement(ele);}}}}else {delegate.parseCustomElement(root);} }现在我们就来看看parseCustomElement这个方法 , 但在点进去之前不妨想想自定义标签解析应该怎么做 。
public BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) {String namespaceUri = getNamespaceURI(ele);if (namespaceUri == null) {return null;}NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);if (handler == null) {error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);return null;}return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd)); }可以看到和默认标签解析是一样的 , 只不过由decorate方法改为了parse方法 , 但具体是如何解析的呢?这里我就以component-scan标签的解析为例 , 看看注解是如何解析为BeanDefinition对象的 。
注解解析原理进入到parse方法中 , 首先会进入NamespaceHandlerSupport类中:
public BeanDefinition parse(Element element, ParserContext parserContext) {BeanDefinitionParser parser = findParserForElement(element, parserContext);return (parser != null ? parser.parse(element, parserContext) : null); }首先通过findParserForElement方法去找到对应的解析器 , 然后委托给解析器ComponentScanBeanDefinitionParser解析 。 在往下看之前 , 我们先想一想 , 如果是我们自己要去实现这个注解解析过程会怎么做 。 是不是应该首先通过配置的basePackage属性 , 去扫描该路径下所有的class文件 , 然后判断class文件是否符合条件 , 即是否标注了@Service、@Component、@Controller等注解 , 如果有 , 则封装为BeanDefinition对象并注册到容器中去?下面就来验证我们的猜想:
public BeanDefinition parse(Element element, ParserContext parserContext) {String basePackage = element.getAttribute(BASE_PACKAGE_ATTRIBUTE);basePackage = parserContext.getReaderContext().getEnvironment().resolvePlaceholders(basePackage);String[] basePackages = StringUtils.tokenizeToStringArray(basePackage,ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);// Actually scan for bean definitions and register them.// 创造ClassPathBeanDefinitionScanner对象 , 用来扫描basePackage包下符合条件(默认是@Component标注的类)的类 ,// 并创建BeanDefinition类注册到缓存中ClassPathBeanDefinitionScanner scanner = configureScanner(parserContext, element);Set beanDefinitions = scanner.doScan(basePackages);registerComponents(parserContext.getReaderContext(), beanDefinitions, element);return null; }可以看到流程和我们猜想的基本一致 , 首先创建了一个扫描器ClassPathBeanDefinitionScanner对象 , 然后通过这个扫描器去扫描classpath下的文件并注册 , 最后调用了registerComponents方法 , 这个方法的作用稍后来讲 , 我们先来看看扫描器是如何创建的:
protected ClassPathBeanDefinitionScanner configureScanner(ParserContext parserContext, Element element) {boolean useDefaultFilters = true;if (element.hasAttribute(USE_DEFAULT_FILTERS_ATTRIBUTE)) {useDefaultFilters = Boolean.valueOf(element.getAttribute(USE_DEFAULT_FILTERS_ATTRIBUTE));}// Delegate bean definition registration to scanner class.ClassPathBeanDefinitionScanner scanner = createScanner(parserContext.getReaderContext(), useDefaultFilters);scanner.setBeanDefinitionDefaults(parserContext.getDelegate().getBeanDefinitionDefaults());scanner.setAutowireCandidatePatterns(parserContext.getDelegate().getAutowireCandidatePatterns());if (element.hasAttribute(RESOURCE_PATTERN_ATTRIBUTE)) {scanner.setResourcePattern(element.getAttribute(RESOURCE_PATTERN_ATTRIBUTE));}...parseTypeFilters(element, scanner, parserContext);return scanner; } public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry, boolean useDefaultFilters,Environment environment, @Nullable ResourceLoader resourceLoader) {Assert.notNull(registry, "BeanDefinitionRegistry must not be null");this.registry = registry;if (useDefaultFilters) {registerDefaultFilters();}setEnvironment(environment);setResourceLoader(resourceLoader); } protected void registerDefaultFilters() {this.includeFilters.add(new AnnotationTypeFilter(Component.class));ClassLoader cl = ClassPathScanningCandidateComponentProvider.class.getClassLoader();try {this.includeFilters.add(new AnnotationTypeFilter(((Class