2019独角兽企业重金招聘Python工程师标准>>>

前言

作为标签解析的第二分支,也正是因为自定义标签的存在,才让Spring框架的诸多功能在短短的几行配置代码后,就生效了。

源码解读

承接上篇,我们来看看 parseCustomElement 是如何实现的。

public class BeanDefinitionParserDelegate {@Nullablepublic BeanDefinition parseCustomElement(Element ele) {return parseCustomElement(ele, null);}@Nullablepublic BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) {// 获取节点的命名空间String namespaceUri = getNamespaceURI(ele);// 获取指定命名空间的 NamespaceHandlerNamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);if (handler == null) {error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);return null;}// 调用 NamespaceHandler.parse对节点进行解析return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));}
}

让我们来看下是 如何获取到指定命名空间,对应的 NamespaceHandler。

public class DefaultNamespaceHandlerResolver implements NamespaceHandlerResolver {private final String handlerMappingsLocation;public DefaultNamespaceHandlerResolver(ClassLoader classLoader) {// 会给 handlerMappingsLocation赋值:"META-INF/spring.handlers"this(classLoader, DEFAULT_HANDLER_MAPPINGS_LOCATION);}@Overridepublic NamespaceHandler resolve(String namespaceUri) {// 获取命名空间和对应 NamespaceHandler映射关系Map<String, Object> handlerMappings = getHandlerMappings();Object handlerOrClassName = handlerMappings.get(namespaceUri);if (handlerOrClassName == null) {return null;} else if (handlerOrClassName instanceof NamespaceHandler) {return (NamespaceHandler) handlerOrClassName;} else {String className = (String) handlerOrClassName;try {Class<?> handlerClass = ClassUtils.forName(className, this.classLoader);if (!NamespaceHandler.class.isAssignableFrom(handlerClass)) {throw new FatalBeanException("Class [" + className + "] for namespace [" + namespaceUri +"] does not implement the [" + NamespaceHandler.class.getName() + "] interface");}NamespaceHandler namespaceHandler = (NamespaceHandler) BeanUtils.instantiateClass(handlerClass);namespaceHandler.init();handlerMappings.put(namespaceUri, namespaceHandler);return namespaceHandler;} catch (ClassNotFoundException ex) {throw new FatalBeanException("NamespaceHandler class [" + className + "] for namespace [" +namespaceUri + "] not found", ex);} catch (LinkageError err) {throw new FatalBeanException("Invalid NamespaceHandler class [" + className + "] for namespace [" +namespaceUri + "]: problem with handler class file or dependent class", err);}}}private Map<String, Object> getHandlerMappings() {// 仅会在首次调用的时候初始化,锁防并发if (this.handlerMappings == null) {synchronized (this) {if (this.handlerMappings == null) {try {// 加载所有配置文件:META-INF/spring.handlersProperties mappings =PropertiesLoaderUtils.loadAllProperties(this.handlerMappingsLocation, this.classLoader);if (logger.isDebugEnabled()) {logger.debug("Loaded NamespaceHandler mappings: " + mappings);}Map<String, Object> handlerMappings = new ConcurrentHashMap<String, Object>(mappings.size());/*** 工具类:CollectionUtils* mergePropertiesIntoMap将 Properties解析成 Map*/CollectionUtils.mergePropertiesIntoMap(mappings, handlerMappings);this.handlerMappings = handlerMappings;} catch (IOException ex) {throw new IllegalStateException("Unable to load NamespaceHandler mappings from location [" + this.handlerMappingsLocation + "]", ex);}}}}return this.handlerMappings;}
}

映射关系存在不同模块的“META-INF/spring.handlers”,使用工具类将其最终转化为 Map结构,key 是命名空间的名字,让我们来看看这些映射关系。

# spring-context模块下 META-INF/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
# spring-beans模块下 META-INF/spring.handlers
http\://www.springframework.org/schema/c=org.springframework.beans.factory.xml.SimpleConstructorNamespaceHandler
http\://www.springframework.org/schema/p=org.springframework.beans.factory.xml.SimplePropertyNamespaceHandler
http\://www.springframework.org/schema/util=org.springframework.beans.factory.xml.UtilNamespaceHandler
# spring-aop模块下 META-INF/spring.handlers
http\://www.springframework.org/schema/aop=org.springframework.aop.config.AopNamespaceHandler
# spring-jdbc模块下 META-INF/spring.handlers
http\://www.springframework.org/schema/jdbc=org.springframework.jdbc.config.JdbcNamespaceHandler
# spring-oxm模块下 META-INF/spring.handlers
http\://www.springframework.org/schema/oxm=org.springframework.oxm.config.OxmNamespaceHandler

这些对应的 NamespaceHandler 都是 NamespaceHandlerSupport 的子类,其中解析的逻辑在抽象父类中已经实现了:

public abstract class NamespaceHandlerSupport implements NamespaceHandler {// 存放了自定义节点和对应解析器的映射private final Map<String, BeanDefinitionParser> parsers =new HashMap<String, BeanDefinitionParser>();@Overridepublic BeanDefinition parse(Element element, ParserContext parserContext) {return findParserForElement(element, parserContext).parse(element, parserContext);}private BeanDefinitionParser findParserForElement(Element element, ParserContext parserContext) {// 调用 Node.getLocalNameString localName = parserContext.getDelegate().getLocalName(element);// 获取对应的解析器BeanDefinitionParser parser = this.parsers.get(localName);if (parser == null) {parserContext.getReaderContext().fatal("Cannot locate BeanDefinitionParser for element [" + localName + "]", element);}return parser;}/*** 调用这个方法进行解析器的注册*/protected final void registerBeanDefinitionParser(String elementName, BeanDefinitionParser parser) {this.parsers.put(elementName, parser);}
}

可以看出解析逻辑委托给 BeanDefinitionParser.parse 实现,注册由子类实现:

public class UtilNamespaceHandler extends NamespaceHandlerSupport {@Overridepublic void init() {registerBeanDefinitionParser("constant", new ConstantBeanDefinitionParser());registerBeanDefinitionParser("property-path", new PropertyPathBeanDefinitionParser());registerBeanDefinitionParser("list", new ListBeanDefinitionParser());registerBeanDefinitionParser("set", new SetBeanDefinitionParser());registerBeanDefinitionParser("map", new MapBeanDefinitionParser());registerBeanDefinitionParser("properties", new PropertiesBeanDefinitionParser());}
}
public class ContextNamespaceHandler extends NamespaceHandlerSupport {@Overridepublic 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());}
}
public class CacheNamespaceHandler extends NamespaceHandlerSupport {@Overridepublic void init() {registerBeanDefinitionParser("annotation-driven", new AnnotationDrivenCacheBeanDefinitionParser());registerBeanDefinitionParser("advice", new CacheAdviceParser());}
}
public class TaskNamespaceHandler extends NamespaceHandlerSupport {@Overridepublic void init() {this.registerBeanDefinitionParser("annotation-driven", new AnnotationDrivenBeanDefinitionParser());this.registerBeanDefinitionParser("executor", new ExecutorBeanDefinitionParser());this.registerBeanDefinitionParser("scheduled-tasks", new ScheduledTasksBeanDefinitionParser());this.registerBeanDefinitionParser("scheduler", new SchedulerBeanDefinitionParser());}
}
public class JeeNamespaceHandler extends NamespaceHandlerSupport {@Overridepublic void init() {registerBeanDefinitionParser("jndi-lookup", new JndiLookupBeanDefinitionParser());registerBeanDefinitionParser("local-slsb", new LocalStatelessSessionBeanDefinitionParser());registerBeanDefinitionParser("remote-slsb", new RemoteStatelessSessionBeanDefinitionParser());}
}

这里就不一一列举了,只需要找到对应模块下的 “META-INF/spring.handlers”,找到对应命名空间的 NamespaceHandler.init 方法,就可以看到自定义标签和解析器的对应关系了。

<component-scan>解析

由于自定义标签过多,这里就用<component-scan>举例分析。这个标签的作用就是指定包路径,<context:component-scan>会默认打开<context:annotation-config>:

  • 前者支持@Component、@Repository、@Service、@Controller
  • 后者支持@Required、@Autowired、 @PostConstruct、@PersistenceContext、@Resource、@PreDestroy
public class ComponentScanBeanDefinitionParser implements BeanDefinitionParser {// 标签/子标签相关的属性private static final String BASE_PACKAGE_ATTRIBUTE = "base-package";private static final String RESOURCE_PATTERN_ATTRIBUTE = "resource-pattern";private static final String USE_DEFAULT_FILTERS_ATTRIBUTE = "use-default-filters";private static final String ANNOTATION_CONFIG_ATTRIBUTE = "annotation-config";private static final String NAME_GENERATOR_ATTRIBUTE = "name-generator";private static final String SCOPE_RESOLVER_ATTRIBUTE = "scope-resolver";private static final String SCOPED_PROXY_ATTRIBUTE = "scoped-proxy";private static final String EXCLUDE_FILTER_ELEMENT = "exclude-filter";private static final String INCLUDE_FILTER_ELEMENT = "include-filter";private static final String FILTER_TYPE_ATTRIBUTE = "type";private static final String FILTER_EXPRESSION_ATTRIBUTE = "expression";@Override@Nullablepublic 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);ClassPathBeanDefinitionScanner scanner = configureScanner(parserContext, element);// 这里进行具体的包扫描Set<BeanDefinitionHolder> beanDefinitions = scanner.doScan(basePackages);// 注册扫描的 beanDefinitionsregisterComponents(parserContext.getReaderContext(), beanDefinitions, element);return null;}
}

主要的实现在扫描逻辑在 ClassPathBeanDefinitionScanner 中实现:

public class ClassPathBeanDefinitionScanner extends ClassPathScanningCandidateComponentProvider {public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry, boolean useDefaultFilters,Environment environment, @Nullable ResourceLoader resourceLoader) {Assert.notNull(registry, "BeanDefinitionRegistry must not be null");this.registry = registry;/*** use-default-filters默认为 true* 会执行 registerDefaultFilters,即注册 @Component、@ManagedBean、@Named筛选器* 如果设置为 false,则会根据 include-filter/exclude-filter来过滤*/if (useDefaultFilters) {registerDefaultFilters();}setEnvironment(environment);setResourceLoader(resourceLoader);}protected Set<BeanDefinitionHolder> doScan(String... basePackages) {Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<BeanDefinitionHolder>();// 遍历指定的包路径for (String basePackage : basePackages) {// 找到符合条件的 BeanDefinitionSet<BeanDefinition> candidates = findCandidateComponents(basePackage);for (BeanDefinition candidate : candidates) {ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);candidate.setScope(scopeMetadata.getScopeName());/*** 用 @Component举例* 如果指定了 value,则使用该值作为 beanName* 否则使用类名,首字母小写*/String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);// 除了扫描检索出的 bean信息,再添加一些默认配置if (candidate instanceof AbstractBeanDefinition) {postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);}// @Lazy、@Primary、@DependsOn、@Role、@Descriptionif (candidate instanceof AnnotatedBeanDefinition) {AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);}if (checkCandidate(beanName, candidate)) {BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);definitionHolder =AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);beanDefinitions.add(definitionHolder);// 注册 BeanDefinitionregisterBeanDefinition(definitionHolder, this.registry);}}}return beanDefinitions;}public Set<BeanDefinition> findCandidateComponents(String basePackage) {Set<BeanDefinition> candidates = new LinkedHashSet<BeanDefinition>();try {// classpath*:basePackage/**/*.classString packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +resolveBasePackage(basePackage) + '/' + this.resourcePattern;Resource[] resources = this.resourcePatternResolver.getResources(packageSearchPath);boolean traceEnabled = logger.isTraceEnabled();boolean debugEnabled = logger.isDebugEnabled();for (Resource resource : resources) {if (traceEnabled) {logger.trace("Scanning " + resource);}if (resource.isReadable()) {try {MetadataReader metadataReader = this.metadataReaderFactory.getMetadataReader(resource);if (isCandidateComponent(metadataReader)) {ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);sbd.setResource(resource);sbd.setSource(resource);// 筛选出被 @Component、@ManagedBean、@Named标识的类if (isCandidateComponent(sbd)) {if (debugEnabled) {logger.debug("Identified candidate component class: " + resource);}candidates.add(sbd);} else {...// 省略日志}} else {...// 省略日志}} catch (Throwable ex) {throw new BeanDefinitionStoreException("Failed to read candidate component class: " + resource, ex);}} else {...// 省略日志}}} catch (IOException ex) {throw new BeanDefinitionStoreException("I/O failure during classpath scanning", ex);}return candidates;}}

构造器会初始化一些过滤条件,默认的会扫描@Component、@ManagedBean、@Named 表示的类,如果指定了<context:component-scan base-package="com.xxx.xxx" use-default-filters="false">,就可以自定义需要扫描的类型、以及过滤掉不需要扫描的类型,比如下面这样:

<context:component-scan base-package="com.xxx.xxx" use-default-filters="false">  <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller" />
</context:component-scan>  

总结

这一节讲了自定义标签的解析过程,配合上篇的默认标签解析,容器的创建基本上就算完成了,这些准备工作,就是为了实例创建而收集“资料”(BeanDefinition),那接下来利用这些资料来创建实例了。

转载于:https://my.oschina.net/marvelcode/blog/1831369

Spring源码解析(五)——自定义标签解析相关推荐

  1. c++报错:引用了未定义标签_大牛带你解读Spring源码,编写自定义标签,您能学会吗?...

    Spring源码阅读之编写自定义标签 基于昨天的那篇文章,我们今天来聊一个比较简单轻松点的话题,今天我们来实现一个自定义的标签. (本文来自公众号:z小赵) 先明确下整个项目的结构,网上有很多关于自定 ...

  2. 修改meta标签 查看源码没效果怎么办_Spring 源码学习(三)-自定义标签

    又来填坑啦,上一篇讲完默认标签的解析,这篇笔记记录一下自定义标签的解析吧. 我们知道,Spring 源码的核心模块是 Spring-core 和 Spring-beans,在此基础上衍生出其他模块,例 ...

  3. spring 源码分析(1)-xml文件解析

    我们在最开始接触spring的时候,看到不少书spring入门的例子如下 ApplicationContext atx = new ClassPathXmlApplicationContext(&qu ...

  4. 使用base标签后图片无法加载_Spring 源码学习(二)-默认标签解析

    `Spring` 解析默认标签~ 从上一篇笔记可以看出,在容器注册 bean 信息的时候,做了很多解析操作,而 xml 文件中包含了很多标签.属性,例如 bean . import 标签, meta ...

  5. spring源码分析06-spring配置类解析

    什么是spring配置类? 类上有注解:@Configuration .@Component.@ComponentScan.@Import.@ImportResource 或者类中的任意方法有@Bea ...

  6. 【Spring源码】4. 自己搞个标签?~自定义标签保姆级全过程(图解向,堆图预警)

    [Spring源码系列- IOC] 1 [Spring源码]0.安装Gradle环境 2 [Spring源码]1.下载与编译_pom relocation to an other version nu ...

  7. Spring源码-AOP(六)-自动代理与DefaultAdvisorAutoProxyCreator

    2019独角兽企业重金招聘Python工程师标准>>> Spring AOP 源码解析系列,建议大家按顺序阅读,欢迎讨论 Spring源码-AOP(一)-代理模式 Spring源码- ...

  8. Spring源码分析(三)

    Spring源码分析 第三章 手写Ioc和Aop 文章目录 Spring源码分析 前言 一.模拟业务场景 (一) 功能介绍 (二) 关键功能代码 (三) 问题分析 二.使用ioc和aop重构 (一) ...

  9. Spring源码分析之Bean的创建过程详解

    前文传送门: Spring源码分析之预启动流程 Spring源码分析之BeanFactory体系结构 Spring源码分析之BeanFactoryPostProcessor调用过程详解 本文内容: 在 ...

  10. Spring源码分析——汇总全集

    文章目录 一.背景 二.源码分析目录 三.源码番外篇(补充) 更新时间 更新内容 备注 2022-04-01 Spring源码分析目录和计划 2022-04-10 Spring源码分析一:容器篇-re ...

最新文章

  1. 转 -- 推荐几本云计算的经典书籍
  2. 过分了,这样阅读Datasheet(数据手册)合适吗?
  3. C++ cin.sync()和cin.ignore()
  4. 个人帐目管理系统java_Java 项目 个人帐目管理系统
  5. 如何在windows机器上安装apache ab
  6. 设计配色专辑,很值得设计师拥有
  7. 矩正式键盘程序c语言,单片机键盘程序(4×4矩阵式)
  8. 中安证件识别系统介绍
  9. AUTOSAR——AUTOSAR基础
  10. Idea 中最常用的10款插件(提高开发效率),一定要学会使用!
  11. 那一年,我进传销了……(作者亲身经历)
  12. Java Web中乱码问题
  13. 利用开区间覆盖的约简给出有限覆盖定理的一个新证明
  14. x64 汇编跟 rax , eax ,ax ,ah ,al 的关系
  15. 伤害世界稳定服务器,伤害世界哪个服务器好_伤害世界怎么选服务器_牛游戏网...
  16. 怎么找网图本人_怎么通过照片找人-请问如何用相片在网络查找个人资料就是利用相 – 手机爱问...
  17. 市盈率不足20倍 34只大盘蓝筹股尽显低估值效应
  18. 与化学相关的计算机应用情况,计算机化学的发展、应用与展望
  19. 玛里苟斯[清华集训2014 Day1]
  20. Android获取手机联系人的基本信息(如姓名、电话、邮箱、备注)

热门文章

  1. javaweb开发后端常用技术_Java Web开发后端常用技术汇总
  2. java登录抓取网页_java模拟登录内部系统抓取网页内容
  3. 项目管理文档_项目管理和团队协同的轻量级工具——PMS,来了
  4. oracle 多表查询_【Oracle】多表查询
  5. linux过滤某个mac的包,macOS 下使用 tcpdump 抓包
  6. Andorid AlertDialog 点击后自动消失_为何孙悟空成佛后,金箍儿就会自动消失?金箍儿究竟代表什么...
  7. 打开桌面计算机窗口闪动,电脑进去桌面就一直闪
  8. python django mysql_Python之模块、函数和缩进
  9. IIS出现server application error解决方案
  10. 301与302页面重定向