服务器之家:专注于服务器技术及软件下载分享
分类导航

PHP教程|ASP.NET教程|Java教程|ASP教程|编程技术|正则表达式|C/C++|IOS|C#|Swift|Android|VB|R语言|JavaScript|易语言|vb.net|

服务器之家 - 编程语言 - Java教程 - Spring占位符Placeholder的实现原理解析

Spring占位符Placeholder的实现原理解析

2021-08-26 10:54morris131 Java教程

这篇文章主要介绍了Spring占位符Placeholder的实现原理,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

占位符Placeholder的使用

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="
  6. http://www.springframework.org/schema/beans
  7. http://www.springframework.org/schema/beans/spring-beans.xsd
  8. http://www.springframework.org/schema/context
  9. http://www.springframework.org/schema/context/spring-context.xsd"
  10. default-lazy-init="false">
  11.  
  12. <context:property-placeholder location="classpath:application.properties"/>
  13.  
  14. <bean id="user" class="com.morris.spring.entity.Author">
  15. <property name="name" value="${author.name}" />
  16. </bean>
  17. </beans>

实现原理

前面在Spring中自定义标签的解析中分析到context:property-placeholder这种自定义标签的解析流程如下:

  • 基于SPI机制,扫描所有类路径下jar中/META-INFO/spring.handlers文件,并将这些文件读取为一个key为namespace,value为具体NameSpaceHandler的Map结构。
  • 根据bean标签名获得xml上方的namespace,然后根据namespace从第一步中的map中获得具体的NameSpaceHandler。
  • 调用NameSpaceHandler的init()方法进行初始化,此方法一般会将负责解析各种localName的BeanDefinitionParser解析器注册到一个map中。
  • 根据localName=property-placeholder从上一步中获得具体的BeanDefinitionParser解析器,并调用其parse()方法进行解析。

在这里NameSpaceHandler为ContextNamespaceHandler,而BeanDefinitionParser解析器为PropertyPlaceholderBeanDefinitionParser,所以我们观察的重点为PropertyPlaceholderBeanDefinitionParser的parse()方法。

注册PropertySourcesPlaceholderConfigurer

parse()方法位于父类AbstractBeanDefinitionParser,先来看下继承关系,后面的代码使用了大量的模板方法模式,将会在这几个类中来回切换:

Spring占位符Placeholder的实现原理解析

org.springframework.beans.factory.xml.AbstractBeanDefinitionParser#parse

  1. public final BeanDefinition parse(Element element, ParserContext parserContext) {
  2. // 调用子类org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser.parseInternal
  3. AbstractBeanDefinition definition = parseInternal(element, parserContext);
  4. if (definition != null && !parserContext.isNested()) {
  5. try {
  6. // 生成一个ID
  7. String id = resolveId(element, definition, parserContext);
  8. if (!StringUtils.hasText(id)) {
  9. parserContext.getReaderContext().error(
  10. "Id is required for element '" + parserContext.getDelegate().getLocalName(element)
  11. + "' when used as a top-level tag", element);
  12. }
  13. String[] aliases = null;
  14. if (shouldParseNameAsAliases()) {
  15. String name = element.getAttribute(NAME_ATTRIBUTE);
  16. if (StringUtils.hasLength(name)) {
  17. aliases = StringUtils.trimArrayElements(StringUtils.commaDelimitedListToStringArray(name));
  18. }
  19. }
  20. BeanDefinitionHolder holder = new BeanDefinitionHolder(definition, id, aliases);
  21. // 注册BD
  22. registerBeanDefinition(holder, parserContext.getRegistry());
  23. if (shouldFireEvents()) {
  24. BeanComponentDefinition componentDefinition = new BeanComponentDefinition(holder);
  25. postProcessComponentDefinition(componentDefinition);
  26. parserContext.registerComponent(componentDefinition);
  27. }
  28. }
  29. catch (BeanDefinitionStoreException ex) {
  30. String msg = ex.getMessage();
  31. parserContext.getReaderContext().error((msg != null ? msg : ex.toString()), element);
  32. return null;
  33. }
  34. }
  35. return definition;
  36. }

org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser#parseInternal

  1. protected final AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) {
  2. BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition();
  3. String parentName = getParentName(element);
  4. if (parentName != null) {
  5. builder.getRawBeanDefinition().setParentName(parentName);
  6. }
  7. // 获取子类PropertyPlaceholderBeanDefinitionParser返回的PropertySourcesPlaceholderConfigurer
  8. Class<?> beanClass = getBeanClass(element);
  9. if (beanClass != null) {
  10. builder.getRawBeanDefinition().setBeanClass(beanClass);
  11. }
  12. else {
  13. String beanClassName = getBeanClassName(element);
  14. if (beanClassName != null) {
  15. builder.getRawBeanDefinition().setBeanClassName(beanClassName);
  16. }
  17. }
  18. builder.getRawBeanDefinition().setSource(parserContext.extractSource(element));
  19. BeanDefinition containingBd = parserContext.getContainingBeanDefinition();
  20. if (containingBd != null) {
  21. // Inner bean definition must receive same scope as containing bean.
  22. builder.setScope(containingBd.getScope());
  23. }
  24. if (parserContext.isDefaultLazyInit()) {
  25. // Default-lazy-init applies to custom bean definitions as well.
  26. builder.setLazyInit(true);
  27. }
  28. // 又是一个模板方法模式
  29. /**
  30. * @see org.springframework.context.config.PropertyPlaceholderBeanDefinitionParser#doParse(org.w3c.dom.Element, org.springframework.beans.factory.xml.ParserContext, org.springframework.beans.factory.support.BeanDefinitionBuilder)
  31. */
  32. doParse(element, parserContext, builder);
  33. return builder.getBeanDefinition();
  34. }

org.springframework.context.config.PropertyPlaceholderBeanDefinitionParser#getBeanClass

  1. protected Class<?> getBeanClass(Element element) {
  2. if (SYSTEM_PROPERTIES_MODE_DEFAULT.equals(element.getAttribute(SYSTEM_PROPERTIES_MODE_ATTRIBUTE))) {
  3. return PropertySourcesPlaceholderConfigurer.class; // 新版返回这个
  4. }
  5.  
  6. return org.springframework.beans.factory.config.PropertyPlaceholderConfigurer.class;
  7. }

org.springframework.context.config.PropertyPlaceholderBeanDefinitionParser#doParse

  1. protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
  2. // 调用父类的doParse
  3. super.doParse(element, parserContext, builder);
  4.  
  5. builder.addPropertyValue("ignoreUnresolvablePlaceholders",
  6. Boolean.valueOf(element.getAttribute("ignore-unresolvable")));
  7.  
  8. String systemPropertiesModeName = element.getAttribute(SYSTEM_PROPERTIES_MODE_ATTRIBUTE);
  9. if (StringUtils.hasLength(systemPropertiesModeName) &&
  10. !systemPropertiesModeName.equals(SYSTEM_PROPERTIES_MODE_DEFAULT)) {
  11. builder.addPropertyValue("systemPropertiesModeName", "SYSTEM_PROPERTIES_MODE_" + systemPropertiesModeName);
  12. }
  13.  
  14. if (element.hasAttribute("value-separator")) {
  15. builder.addPropertyValue("valueSeparator", element.getAttribute("value-separator"));
  16. }
  17. if (element.hasAttribute("trim-values")) {
  18. builder.addPropertyValue("trimValues", element.getAttribute("trim-values"));
  19. }
  20. if (element.hasAttribute("null-value")) {
  21. builder.addPropertyValue("nullValue", element.getAttribute("null-value"));
  22. }
  23. }

org.springframework.context.config.AbstractPropertyLoadingBeanDefinitionParser#doParse

  1. protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
  2. // 解析<context:property-placeholder>标签的各种属性
  3. String location = element.getAttribute("location");
  4. if (StringUtils.hasLength(location)) {
  5. location = parserContext.getReaderContext().getEnvironment().resolvePlaceholders(location);
  6. String[] locations = StringUtils.commaDelimitedListToStringArray(location);
  7. builder.addPropertyValue("locations", locations);
  8. }
  9.  
  10. String propertiesRef = element.getAttribute("properties-ref");
  11. if (StringUtils.hasLength(propertiesRef)) {
  12. builder.addPropertyReference("properties", propertiesRef);
  13. }
  14.  
  15. String fileEncoding = element.getAttribute("file-encoding");
  16. if (StringUtils.hasLength(fileEncoding)) {
  17. builder.addPropertyValue("fileEncoding", fileEncoding);
  18. }
  19.  
  20. String order = element.getAttribute("order");
  21. if (StringUtils.hasLength(order)) {
  22. builder.addPropertyValue("order", Integer.valueOf(order));
  23. }
  24.  
  25. builder.addPropertyValue("ignoreResourceNotFound",
  26. Boolean.valueOf(element.getAttribute("ignore-resource-not-found")));
  27.  
  28. builder.addPropertyValue("localOverride",
  29. Boolean.valueOf(element.getAttribute("local-override")));
  30.  
  31. builder.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
  32. }

总结一下,其实上面这么多代码调来调去,只有一个目的,就是向spring容器中注入一个BeanDefinition,这个BeanDefinition有两个最重要的属性:

  • BeanClass为PropertySourcesPlaceholderConfigurer。
  • 有一个属性为location,对应properties文件的位置。

PropertySourcesPlaceholderConfigurer的调用

上面向spring容器中注入一个PropertySourcesPlaceholderConfigurer类型BeanDefinition,先来看下这个类的继承关系:

Spring占位符Placeholder的实现原理解析

从上图的继承关系可以看出PropertySourcesPlaceholderConfigurer实现了BeanFactoryPostProcessor,所以这个类的核心方法为postProcessBeanFactory()。

  1. public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
  2. if (this.propertySources == null) { // null
  3. this.propertySources = new MutablePropertySources();
  4. if (this.environment != null) {
  5.  
  6. // environment中存储的是系统属性和环境变量
  7. this.propertySources.addLast(
  8. new PropertySource<Environment>(ENVIRONMENT_PROPERTIES_PROPERTY_SOURCE_NAME, this.environment) {
  9. @Override
  10. @Nullable
  11. public String getProperty(String key) {
  12. return this.source.getProperty(key);
  13. }
  14. }
  15. );
  16. }
  17. try {
  18. // 加载application.properties为Properties,包装为PropertySource
  19. PropertySource<?> localPropertySource =
  20. new PropertiesPropertySource(LOCAL_PROPERTIES_PROPERTY_SOURCE_NAME, mergeProperties());
  21. if (this.localOverride) {
  22. this.propertySources.addFirst(localPropertySource);
  23. }
  24. else {
  25. this.propertySources.addLast(localPropertySource);
  26. }
  27. }
  28. catch (IOException ex) {
  29. throw new BeanInitializationException("Could not load properties", ex);
  30. }
  31. }
  32.  
  33. // 处理占位符
  34. processProperties(beanFactory, new PropertySourcesPropertyResolver(this.propertySources));
  35. this.appliedPropertySources = this.propertySources;
  36. }

上面的方法的前面一大截的主要作用为将系统属性、环境变量以及properties文件中的属性整合到MutablePropertySources中,这样就可以直接调用MutablePropertySources.getProperties()方法根据属性名拿到对应的属性值了。MutablePropertySources里面其实是一个Map的链表,这样就可以先遍历链表,然后再根据属性名从Map中找到对应的属性值。

  1. protected void processProperties(ConfigurableListableBeanFactory beanFactoryToProcess,
  2. final ConfigurablePropertyResolver propertyResolver) throws BeansException {
  3.  
  4. propertyResolver.setPlaceholderPrefix(this.placeholderPrefix); // ${
  5. propertyResolver.setPlaceholderSuffix(this.placeholderSuffix); // }
  6. propertyResolver.setValueSeparator(this.valueSeparator); // :
  7.  
  8. // 下面的doProcessProperties会回调这个lambda表达式
  9. // 真正的解析逻辑在resolveRequiredPlaceholders
  10. /**
  11. * @see AbstractPropertyResolver#resolveRequiredPlaceholders(java.lang.String)
  12. */
  13. StringValueResolver valueResolver = strVal -> {
  14. String resolved = (this.ignoreUnresolvablePlaceholders ?
  15. propertyResolver.resolvePlaceholders(strVal) :
  16. propertyResolver.resolveRequiredPlaceholders(strVal));
  17. if (this.trimValues) {
  18. resolved = resolved.trim();
  19. }
  20. return (resolved.equals(this.nullValue) ? null : resolved);
  21. };
  22.  
  23. // 这里会遍历所有的BD,挨个处理占位符
  24. doProcessProperties(beanFactoryToProcess, valueResolver);
  25. }

org.springframework.beans.factory.config.PlaceholderConfigurerSupport#doProcessProperties

  1. protected void doProcessProperties(ConfigurableListableBeanFactory beanFactoryToProcess,
  2. StringValueResolver valueResolver) {
  3.  
  4. BeanDefinitionVisitor visitor = new BeanDefinitionVisitor(valueResolver);
  5.  
  6. String[] beanNames = beanFactoryToProcess.getBeanDefinitionNames();
  7. for (String curName : beanNames) {
  8. // Check that we're not parsing our own bean definition,
  9. // to avoid failing on unresolvable placeholders in properties file locations.
  10. if (!(curName.equals(this.beanName) && beanFactoryToProcess.equals(this.beanFactory))) {
  11. BeanDefinition bd = beanFactoryToProcess.getBeanDefinition(curName);
  12. try {
  13. // 遍历BD
  14. visitor.visitBeanDefinition(bd);
  15. }
  16. catch (Exception ex) {
  17. throw new BeanDefinitionStoreException(bd.getResourceDescription(), curName, ex.getMessage(), ex);
  18. }
  19. }
  20. }
  21.  
  22. // New in Spring 2.5: resolve placeholders in alias target names and aliases as well.
  23. beanFactoryToProcess.resolveAliases(valueResolver);
  24.  
  25. // New in Spring 3.0: resolve placeholders in embedded values such as annotation attributes.
  26. beanFactoryToProcess.addEmbeddedValueResolver(valueResolver);
  27. }

org.springframework.beans.factory.config.BeanDefinitionVisitor#visitBeanDefinition

  1. public void visitBeanDefinition(BeanDefinition beanDefinition) {
  2. visitParentName(beanDefinition);
  3. visitBeanClassName(beanDefinition);
  4. visitFactoryBeanName(beanDefinition);
  5. visitFactoryMethodName(beanDefinition);
  6. visitScope(beanDefinition);
  7. if (beanDefinition.hasPropertyValues()) {
  8. // 遍历所有的属性
  9. visitPropertyValues(beanDefinition.getPropertyValues());
  10. }
  11. if (beanDefinition.hasConstructorArgumentValues()) {
  12. ConstructorArgumentValues cas = beanDefinition.getConstructorArgumentValues();
  13. visitIndexedArgumentValues(cas.getIndexedArgumentValues());
  14. visitGenericArgumentValues(cas.getGenericArgumentValues());
  15. }
  16. }

org.springframework.beans.factory.config.BeanDefinitionVisitor#visitPropertyValues

  1. protected void visitPropertyValues(MutablePropertyValues pvs) {
  2. PropertyValue[] pvArray = pvs.getPropertyValues();
  3. for (PropertyValue pv : pvArray) {
  4. // 解析占位符
  5. Object newVal = resolveValue(pv.getValue());
  6. if (!ObjectUtils.nullSafeEquals(newVal, pv.getValue())) {
  7. // 将新的value替换BD中旧的
  8. pvs.add(pv.getName(), newVal);
  9. }
  10. }
  11. }

resolveValue()方法中会回调到之前的lambda表达式StringValueResolv真正开始解析,也就是根据属性名从PropertySources中取值。

总结一下PropertySourcesPlaceholderConfigurer#postProcessBeanFactory()方法:这个方法会在Bean实例化之前完成对Spring容器中所有BeanDefinition中带有占位符的属性进行解析,这样在Bean实例化后就能被赋予正确的属性了。

到此这篇关于Spring占位符Placeholder的实现原理的文章就介绍到这了,更多相关Spring占位符Placeholder内容请搜索服务器之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持服务器之家!

原文链接:https://blog.csdn.net/u022812849/article/details/114319908

延伸 · 阅读

精彩推荐