经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » Java相关 » Spring » 查看文章
详解Spring如何解析占位符
来源:jb51  时间:2021/6/21 16:10:01  对本文有异议

什么是Spring的占位符?

在以前的Spring Xml配置中我们可能会有如下配置:

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xmlns:context="http://www.springframework.org/schema/context"
  5. xsi:schemaLocation="http://www.springframework.org/schema/beans
  6. http://www.springframework.org/schema/beans/spring-beans.xsd
  7. http://www.springframework.org/schema/context
  8. http://www.springframework.org/schema/context/spring-context.xsd>
  9. <context:property-placeholder ignore-unresolvable="true" location="classpath:jdbc.properties"/>
  10.  
  11. <bean id="jdbc" class="com.john.properties.jdbcBean" >
  12. <property name="url" value="${jdbc.url}"/>
  13. </bean></beans>

在上面的配置中jdbc这个Bean的url属性值${jdbc.url}就代表占位符,占位符的真实值就存放在上述配置中的自定义元素的location属性所代表的配置文件jdbc.properties中,这个配置文件里就一行内容:

  1. jdbc.url=127.0.0.1

那问题就来了,Spring又是在什么阶段去解析并且把占位符替换为实际值的呢?

Spring什么时候去解析并占位符

从我们就可以知道它是一个自定义xml标签,那Spring势必要解析它,我就直接黏贴Spring解析这个自定义元素的入口代码给各位看官。该代码就在BeanDefinitionParserDelegate这个类中:

  1. /**
  2. * Parse the elements at the root level in the document:
  3. * "import", "alias", "bean".
  4. * @param root the DOM root element of the document
  5. */
  6. //todo doRegisterBeanDefinitions -> parseBeanDefinitions -> parseDefaultElement
  7. protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
  8. if (delegate.isDefaultNamespace(root)) {
  9. NodeList nl = root.getChildNodes();
  10. for (int i = 0; i < nl.getLength(); i++) {
  11. Node node = nl.item(i);
  12. if (node instanceof Element) {
  13. Element ele = (Element) node;
  14.  
  15. //如果属于beans命名空间
  16. if (delegate.isDefaultNamespace(ele)) {
  17. //处理默认标签
  18. parseDefaultElement(ele, delegate);
  19. }
  20. else {
  21. //自定义标签
  22. //用到了parser
  23. //todo parser内部 去注册BeanDefinition 2021-3-15
  24. delegate.parseCustomElement(ele);
  25. }
  26. }
  27. }
  28. }
  29. else {
  30. delegate.parseCustomElement(root);
  31. }
  32. }

主要关注点:delegate.parseCustomElement(ele);

  1. @Nullable
  2. public BeanDefinition parseCustomElement(Element ele) {
  3. return parseCustomElement(ele, null);
  4. }
  5.  
  6. //todo property 子元素 也有可能 解析自定义元素 parsePropertySubElement
  7. @Nullable
  8. public BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) {
  9. String namespaceUri = getNamespaceURI(ele);
  10. if (namespaceUri == null) {
  11. return null;
  12. }
  13. //resolve里有初始化过程
  14. //根据命名空间uri获取 NamespaceHandler
  15.  
  16. //todo 获取命名空间下的自定义handler 比如 ContextNamespaceHandler
  17. NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
  18. if (handler == null) {
  19. //todo 如果在spring.handlers配置文件 里没有定义这个命令空间的handler就会 报这个错 2020-09-14
  20. error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
  21. return null;
  22. }
  23. //调用parse方法
  24. //这里ParserContext注入registry
  25. //readerContext里 reader->XmlBeanDefinitionReader 里包含了 registry
  26. //TODO 会初始化一个ParserContext进去
  27. return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
  28. }

到这里我们又要关注这个NamespaceHandler是怎么获取到的,从上面代码可知,Spring会从当前readerContext获取到NamespaceHandlerResolver后通过其resolve方法并根据传入的当前namespaceUri就可以获得当前适合的NamespaceHandler。

  1. /**
  2. * Create the {@link XmlReaderContext} to pass over to the document reader.
  3. */
  4. public XmlReaderContext createReaderContext(Resource resource) {
  5. //把当前reader放进去 ,reader存放了 beanRegistry
  6. //beanRegistry 里定义了 registerBeanDefinition 这个重要的方法
  7. return new XmlReaderContext(resource, this.problemReporter, this.eventListener,
  8. this.sourceExtractor, this, getNamespaceHandlerResolver());
  9. }
  10.  
  11. /**
  12. * Lazily create a default NamespaceHandlerResolver, if not set before.
  13. * 解析自定义标签时用到
  14. * @see #createDefaultNamespaceHandlerResolver()
  15. */
  16. public NamespaceHandlerResolver getNamespaceHandlerResolver() {
  17. if (this.namespaceHandlerResolver == null) {
  18. this.namespaceHandlerResolver = createDefaultNamespaceHandlerResolver();
  19. }
  20. return this.namespaceHandlerResolver;
  21. }
  22.  
  23. /**
  24. * Create the default implementation of {@link NamespaceHandlerResolver} used if none is specified.
  25. * <p>The default implementation returns an instance of {@link DefaultNamespaceHandlerResolver}.
  26. * @see DefaultNamespaceHandlerResolver#DefaultNamespaceHandlerResolver(ClassLoader)
  27. */
  28. protected NamespaceHandlerResolver createDefaultNamespaceHandlerResolver() {
  29. ClassLoader cl = (getResourceLoader() != null ? getResourceLoader().getClassLoader() : getBeanClassLoader());
  30. return new DefaultNamespaceHandlerResolver(cl);
  31. }
  32.  
  33. //返回默认的
  34. public DefaultNamespaceHandlerResolver(@Nullable ClassLoader classLoader) {
  35. this(classLoader, DEFAULT_HANDLER_MAPPINGS_LOCATION);
  36. }

从上面的代码中可知Spring使用的是默认的DefaultNamespaceHandlerResolver,它当然也给开发者留了自定义NamespaceHandlerResolver的机会。那我们现在就可以看看DefaultNamespaceHandlerResolver如何根据namespaceUri解析到对应的NamespaceHandler的。

首先获取到的就是context命名空间,完整路径为http\://www.springframework.org/schema/context。我们从DefaultNamespaceHandlerResolver类中可以看到它是如何解析这个命名空间的。

  1. /**
  2. * Locate the {@link NamespaceHandler} for the supplied namespace URI
  3. * from the configured mappings.
  4. * @param namespaceUri the relevant namespace URI
  5. * @return the located {@link NamespaceHandler}, or {@code null} if none found
  6. */
  7. @Override
  8. @Nullable
  9. public NamespaceHandler resolve(String namespaceUri) {
  10. Map<String, Object> handlerMappings = getHandlerMappings();
  11. Object handlerOrClassName = handlerMappings.get(namespaceUri);
  12. if (handlerOrClassName == null) {
  13. return null;
  14. }
  15. else if (handlerOrClassName instanceof NamespaceHandler) {
  16. return (NamespaceHandler) handlerOrClassName;
  17. }
  18. else {
  19. String className = (String) handlerOrClassName;
  20. try {
  21. Class<?> handlerClass = ClassUtils.forName(className, this.classLoader);
  22. if (!NamespaceHandler.class.isAssignableFrom(handlerClass)) {
  23. throw new FatalBeanException("Class [" + className + "] for namespace [" + namespaceUri +
  24. "] does not implement the [" + NamespaceHandler.class.getName() + "] interface");
  25. }
  26. NamespaceHandler namespaceHandler = (NamespaceHandler) BeanUtils.instantiateClass(handlerClass);
  27. //todo 命名空间处理器 调用初始化过程 2020-09-04
  28. namespaceHandler.init();
  29. handlerMappings.put(namespaceUri, namespaceHandler);
  30. return namespaceHandler;
  31. }
  32. catch (ClassNotFoundException ex) {
  33. throw new FatalBeanException("Could not find NamespaceHandler class [" + className +
  34. "] for namespace [" + namespaceUri + "]", ex);
  35. }
  36. catch (LinkageError err) {
  37. throw new FatalBeanException("Unresolvable class definition for NamespaceHandler class [" +
  38. className + "] for namespace [" + namespaceUri + "]", err);
  39. }
  40. }
  41. }
  42.  
  43. /**
  44. * Load the specified NamespaceHandler mappings lazily.
  45. */
  46. private Map<String, Object> getHandlerMappings() {
  47. Map<String, Object> handlerMappings = this.handlerMappings;
  48. if (handlerMappings == null) {
  49. synchronized (this) {
  50. handlerMappings = this.handlerMappings;
  51. if (handlerMappings == null) {
  52. if (logger.isTraceEnabled()) {
  53. logger.trace("Loading NamespaceHandler mappings from [" + this.handlerMappingsLocation + "]");
  54. }
  55. try {
  56. //todo handlerMappings为空 才去 获取所有属性映射 2020-09-04
  57. //spring-aop spring-beans spring-context
  58. Properties mappings =
  59. PropertiesLoaderUtils.loadAllProperties(this.handlerMappingsLocation, this.classLoader);
  60. if (logger.isTraceEnabled()) {
  61. logger.trace("Loaded NamespaceHandler mappings: " + mappings);
  62. }
  63. handlerMappings = new ConcurrentHashMap<>(mappings.size());
  64. CollectionUtils.mergePropertiesIntoMap(mappings, handlerMappings);
  65. this.handlerMappings = handlerMappings;
  66. }
  67. catch (IOException ex) {
  68. throw new IllegalStateException(
  69. "Unable to load NamespaceHandler mappings from location [" + this.handlerMappingsLocation + "]", ex);
  70. }
  71. }
  72. }
  73. }
  74. return handlerMappings;
  75. }

上面代码中的handlerMappingsLocation一般就是Spring默认的路径:

  1. //指定了默认的handler路径 ,可以传入指定路径改变
  2. /**
  3. * The location to look for the mapping files. Can be present in multiple JAR files.
  4. */
  5. public static final String DEFAULT_HANDLER_MAPPINGS_LOCATION = "META-INF/spring.handlers";

我们就回到spring-context项目工程下的resoures/META-INF文件夹下的spring.handlers文件里定义了如下规则:

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

可以看到context自定义命名空间就是对应的ContextNamespaceHandler。我们打开这个类瞧一瞧:

  1. public class ContextNamespaceHandler extends NamespaceHandlerSupport {
  2.  
  3. @Override
  4. public void init() {
  5. //调用抽象类NamespaceHandlerSupport的注册解析器方法
  6. registerBeanDefinitionParser("property-placeholder", new PropertyPlaceholderBeanDefinitionParser());
  7. registerBeanDefinitionParser("property-override", new PropertyOverrideBeanDefinitionParser());
  8. registerBeanDefinitionParser("annotation-config", new AnnotationConfigBeanDefinitionParser());
  9. registerBeanDefinitionParser("component-scan", new ComponentScanBeanDefinitionParser());
  10. registerBeanDefinitionParser("load-time-weaver", new LoadTimeWeaverBeanDefinitionParser());
  11. registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
  12. registerBeanDefinitionParser("mbean-export", new MBeanExportBeanDefinitionParser());
  13. registerBeanDefinitionParser("mbean-server", new MBeanServerBeanDefinitionParser());
  14. }
  15. }

发现它只定义了一个init方法,顾名思义就是初始化的意思,那想当然的就是它啥时候会执行初始化呢?我们回到DefaultNamespaceHandler的resolve方法,发现它内部有一处namespaceHandler.init();, 这里就执行了对应命名空间处理器的初始化方法。接下来我们又要看看初始化了做了些啥,我们发现它调用了同一个方法registerBeanDefinitionParser也就是注册

Bean定义解析器,到这里我们先按下暂停键,再捋下上面的整体流程:

  1. Spring在解析自定义标签的时候会根据自定义命名空间去查找合适的NamespaceHandler.
  2. 自定义的NamespaceHandler是由NamespaceHandlerResolver去解析得到的。
  3. NamespaceHandlerResolver会根据classLoader.getResources查找所有类路径下的spring.handlers。
  4. 查找到后把文件内容转换成handlerMappings,然后根据传入的自定义命名空间匹配到NamespaceHandler
  5. 执行NamespaceHandler的init方法注册BeanDefinitionParser。

那这个parser做了点什么强大的功能呢?我们下回分解。

以上就是详解Spring如何解析占位符的详细内容,更多关于Spring 解析占位符的资料请关注w3xue其它相关文章!

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号