Springが使えない@ProptySource配合@ConfigrationPropties注入対象属性問題解析

22889 ワード

記事の目次
  • 、問題背景
  • 、再現問題
  • 2.1作成`datasource.yml`ファイル
  • .作成`DataSourceProperties`
  • .編纂`DynamicDataSourceProperties`
  • 2.4注入不能原因解析
  • 、ソリューション
  • 3.1カスタム`ProptySourceFactory`
  • 3.2使用`@ProptySource`注
  • 一、問題の背景
    プロジェクトには多データソースが必要なので、datasource.ymlの構成ファイルを自分で書いてデータソースを配置したいです.springの常用解析方法を採用しています.成功していません.
    二、問題を再現する
    2.1 datasource.ymlファイルを作成する
    dynamic:
      datasource:
        druids:
          write:
            driverClassName: com.p6spy.engine.spy.P6SpyDriver
            url: jdbc:p6spy:mysql://118.89.104.67:3306/saas_db?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
            username: root
            password: mysql!@root##123
            filters: stat
          read:
            driverClassName: com.p6spy.engine.spy.P6SpyDriver
            url: jdbc:p6spy:mysql://localhost:3306/saas_db?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
            username: root
            password: 1234
            filters: stat
    
    2.2作成DataSourceProperties
    @Data
    public class DataSourceProperties {
        private String driverClassName;
        private String url;
        private String username;
        private String password;
    }
    
    2.3作成DynamicDataSourcePropertiesspringを用いて提供された@ConfigurationPropertiesは、@PropertySourceに注釈を合わせてdatasource.ymlDynamicDataSourcePropertiesのMapに解析した.
    @Data
    @PropertySource(value = {"classpath:datasource.yml"})
    @ConfigurationProperties(prefix = "dynamic.datasource")
    public class DynamicDataSourceProperties {
    	//          Map
        private Map<String, DataSourceProperties> druids = new LinkedHashMap<>();
    }
    
    しかし、結果は理想的ではなく、druidsを起動した後、springbootは空のセットであることがわかった.
    2.4注入不能原因解析driudsのソースコードを追跡することによって、彼の解析位置は@PropertySourceから始まりました.ConfigurationClassParser方法で解決します.
    protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass)
    
    引き続き彼のソースコードを観察します.
    // Process any @PropertySource annotations
    		for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(
    				sourceClass.getMetadata(), PropertySources.class,
    				org.springframework.context.annotation.PropertySource.class)) {
    			if (this.environment instanceof ConfigurableEnvironment) {
    				processPropertySource(propertySource);
    			}
    			else {
    				logger.info("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() +
    						"]. Reason: Environment must implement ConfigurableEnvironment");
    			}
    		}
    
    ここではdoProcessConfigurationClassの方法を使って、引き続き追跡します.
    	/**
    	 * Process the given @PropertySource annotation metadata.
    	 * @param propertySource metadata for the @PropertySource annotation found
    	 * @throws IOException if loading a property source failed
    	 */
    	private void processPropertySource(AnnotationAttributes propertySource) throws IOException {
    		String name = propertySource.getString("name");
    		if (!StringUtils.hasLength(name)) {
    			name = null;
    		}
    		String encoding = propertySource.getString("encoding");
    		if (!StringUtils.hasLength(encoding)) {
    			encoding = null;
    		}
    		String[] locations = propertySource.getStringArray("value");
    		Assert.isTrue(locations.length > 0, "At least one @PropertySource(value) location is required");
    		boolean ignoreResourceNotFound = propertySource.getBoolean("ignoreResourceNotFound");
    
    		Class<? extends PropertySourceFactory> factoryClass = propertySource.getClass("factory");
    		PropertySourceFactory factory = (factoryClass == PropertySourceFactory.class ?
    				DEFAULT_PROPERTY_SOURCE_FACTORY : BeanUtils.instantiateClass(factoryClass));
    
    		for (String location : locations) {
    			try {
    				String resolvedLocation = this.environment.resolveRequiredPlaceholders(location);
    				Resource resource = this.resourceLoader.getResource(resolvedLocation);
    				addPropertySource(factory.createPropertySource(name, new EncodedResource(resource, encoding)));
    			}
    			catch (IllegalArgumentException | FileNotFoundException | UnknownHostException ex) {
    				// Placeholders not resolvable or resource not found when trying to open it
    				if (ignoreResourceNotFound) {
    					if (logger.isInfoEnabled()) {
    						logger.info("Properties location [" + location + "] not resolvable: " + ex.getMessage());
    					}
    				}
    				else {
    					throw ex;
    				}
    			}
    		}
    	}
    
    processPropertySourceサイクルにはこのような関数があると見られます.
    内部の具体的な解析属性ファイルの関数はforによって実行され、追跡していくと、addPropertySource(factory.createPropertySource(name, new EncodedResource(resource, encoding)));は一つのデフォルトPropertySourceFactory factoryしか提供されていないことが分かります.このデフォルトの工場を見ると、彼が使っているSpringで属性ファイルをロードしていることが分かります.最後に発見されたのを追跡して、DefaultPropertySourceFactoryのクラスで属性を読み取ります.
        public synchronized void load(InputStream inStream) throws IOException {
            load0(new LineReader(inStream));
        }
    
    これにより、PropertiesLoaderUtils.loadProperties(resource)から提供されたPropertiesが、Springファイルの解析に対応していないことが分かり、彼は一行の読み取りができる.
    city:
    	name:   
    	area: 200
    
    上のymlは解析されます.
    city = ""
    name = "  "
    area = "200"
    
    私たちが欲しいのは
    city.name = "  "
    city.area = "200"
    
    三、解決策
    3.1カスタム@PropertySource
    public class YamlPropertySourceFactory implements PropertySourceFactory {
        @Override
        public PropertySource<?> createPropertySource(String name, EncodedResource resource) throws IOException {
            YamlPropertySourceLoader loader = new YamlPropertySourceLoader();
            List<PropertySource<?>> load = loader.load(name, resource.getResource());
            if (load!=null && !load.isEmpty()) {
                return load.get(0);
            }
            return null;
        }
    }
    
    
    3.2 ymlを使用して注釈を付ける
    使用時に解析を指定するPropertySourceFactory
    
    @PropertySource(value = {"classpath:datasource.yml"}, 
    							 factory = YamlPropertySourceFactory.class)