前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【小家Spring】Spring中@PropertySource和@ImportResource的区别,以及各自的实现原理解析

【小家Spring】Spring中@PropertySource和@ImportResource的区别,以及各自的实现原理解析

作者头像
YourBatman
发布2019-09-03 15:40:42
1.9K0
发布2019-09-03 15:40:42
举报
文章被收录于专栏:BAT的乌托邦BAT的乌托邦
前言

@PropertySource@ImportResource或许很多人都用过,并且都还没有用错。但是若真把他俩拿过来一起的时候,却有点傻傻分不清楚了。

是的,他俩都是向容器中导入Bean/属性信息,但是使用起来还是有很大的区别的,因此本文主要针对于他俩的区别,顺便从一定的原理的角度做一个解释,希望大家以后能区分开来。

在讲解之前,可以记住一个通用的的结论:

@PropertySource用于导入.properties的属性配置文件(能导入yaml吗,且继续往下看吧) @ImportResource用于导入.xml的Bean信息的配置文件(能导入,properties吗,且继续看~)

@ImportResource

指示包含要导入的bean定义的一个或多个资源。它的功能比较像@Import注解,就是向容器内导入Bean。只是@ImportResource它导入的是一个xml配置文件,然后通过解析xml文件的方式再把解析好的Bean信息导入到Spring容器内。

我个人认为:这个注解它是Spring拿出来的一个过渡性产品,因为Spring3.0推荐使用全注解驱动后,所有的Bean都完全可以用注解来代替了。而Spring提供这个注解主要是为了向下兼容,便于老项目进行迁移。

其实使用XML是一种非常不好的选择,Java工程师就应该着眼于java应用上,而不是一会schema,一会DTD之类的

当然既然Spring提供了这个功能,有的时候还是非常有用的。比如当DUBBO还没有跟上注解只能使用xml的时候,这个导入注解就能发挥非常重要的作用了~

使用Demo

比如我在classpath下有这个xml文件:

代码语言:javascript
复制
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="myPerson" class="com.fsx.bean.Person">
        <property name="name" value="fsx"/>
        <property name="age" value="18"/>
    </bean>
</beans>

在配置类上导入此资源:

代码语言:javascript
复制
@Configuration
@ImportResource(locations = "classpath:spring-beans.xml")
public class RootConfig {

}

单元测试:

代码语言:javascript
复制
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {RootConfig.class})
public class TestSpringBean {

    @Autowired
    private ApplicationContext applicationContext;

    @Test
    public void test1() {
        Object myPerson = applicationContext.getBean("myPerson");
        System.out.println(myPerson); // Person{name='fsx', age=18}
    }

}

这个myPerson这个Bean能够被我正常获取到。

那么它能够导入非xml文件吗???其实这个待我解释完它的原理后,这个问题就不攻自破了~

实现原理剖析

解析配置类、Bean定义的前部分原理这里就不在叙述了,还不太清楚的建议参见博文:

【小家Spring】Spring解析@Configuration注解的处理器:ConfigurationClassPostProcessor(ConfigurationClassParser)

下面我们直接定位到解析@ImportResource注解的源码处:

代码语言:javascript
复制
class ConfigurationClassParser {
	...
	@Nullable
	protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass)
			throws IOException {
			//1、解析嵌套内部类
			//2、解析@PropertySource  === 这是下面的内容 ====
		// 相当于拿到所有的PropertySource注解,注意PropertySources属于重复注解的范畴~~~
		for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(
				sourceClass.getMetadata(), PropertySources.class,
				org.springframework.context.annotation.PropertySource.class)) {
			
			// 这个判断目前来说是个恒等式~~~  所以的内置实现都是子接口ConfigurableEnvironment的实现类~~~~
			// processPropertySource:这个方法只真正解析这个注解的地方~~~
			if (this.environment instanceof ConfigurableEnvironment) {
				processPropertySource(propertySource);
			} else {
				logger.info("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() +
						"]. Reason: Environment must implement ConfigurableEnvironment");
			}
		}
			//3、解析@ComponentScan
			//4、解析@Import
			//5、解析@ImportResource 
		//拿到这个注解~~~~~~~~~~~
		AnnotationAttributes importResource = AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class);
		if (importResource != null) {
			String[] resources = importResource.getStringArray("locations");
			// readerClass 这个在自定义规则也是非常重要的一块内容~~~~~
			Class<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader");
			for (String resource : resources) {
				
				// 显然它还支持${}这种方式去环境变量里取值的~~~比如spring-beans-${profie}.xml等
				String resolvedResource = this.environment.resolveRequiredPlaceholders(resource);
				// 此处仅仅是吧注解解析掉,然后作为属性添加到configClass里面去,还并不是它真正的执行时机~~~~~
				configClass.addImportedResource(resolvedResource, readerClass);
			}
		}
			//6、解析@Bean
			//7、解析接口default方法~~~ 也可以用@Bean标注
			//8、解析super class父类
	}
}

上面分析了,真正解析这个文件,然后把Bean定义加入到容器的行为:

代码语言:javascript
复制
class ConfigurationClassBeanDefinitionReader {
	
	// 从ConfigurationClass里面真正的加载Bean定义信息~~~
	private void loadBeanDefinitionsForConfigurationClass(
			ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator) {

		if (trackedConditionEvaluator.shouldSkip(configClass)) {
			String beanName = configClass.getBeanName();
			if (StringUtils.hasLength(beanName) && this.registry.containsBeanDefinition(beanName)) {
				this.registry.removeBeanDefinition(beanName);
			}
			this.importRegistry.removeImportingClass(configClass.getMetadata().getClassName());
			return;
		}

		// 如果这个配置类是被@Import的,那就第一个执行了~~~
		if (configClass.isImported()) {
			registerBeanDefinitionForImportedConfigurationClass(configClass);
		}
		// 加载标注了@Bean的~~
		for (BeanMethod beanMethod : configClass.getBeanMethods()) {
			loadBeanDefinitionsForBeanMethod(beanMethod);
		}

		//这个是我们今天关心的:解析@ImportedResource里面具体的Bean定义信息~~~
		loadBeanDefinitionsFromImportedResources(configClass.getImportedResources());
		loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());
	}


	private void loadBeanDefinitionsFromImportedResources(Map<String, Class<? extends BeanDefinitionReader>> importedResources) {

		Map<Class<?>, BeanDefinitionReader> readerInstanceCache = new HashMap<>();

		// 因为可以导入多个资源  所以这里遍历
		importedResources.forEach((resource, readerClass) -> {
			// Default reader selection necessary?
			// 从这里能够看出来,若我们自己没有指定BeanDefinitionReader,那它最终默认会采用XmlBeanDefinitionReader
			// ~~~~~这就是为什么默认情况下,只支持导入xml文件的原因~~~~~
			if (BeanDefinitionReader.class == readerClass) {
				if (StringUtils.endsWithIgnoreCase(resource, ".groovy")) {
					// When clearly asking for Groovy, that's what they'll get...
					readerClass = GroovyBeanDefinitionReader.class;
				}
				else {
					// Primarily ".xml" files but for any other extension as well
					readerClass = XmlBeanDefinitionReader.class;
				}
			}

			BeanDefinitionReader reader = readerInstanceCache.get(readerClass);
			if (reader == null) {
				try {
					// Instantiate the specified BeanDefinitionReader
					// 拿到入有一个入参为BeanDefinitionRegistry的构造函数~~
					reader = readerClass.getConstructor(BeanDefinitionRegistry.class).newInstance(this.registry);
					// Delegate the current ResourceLoader to it if possible
					if (reader instanceof AbstractBeanDefinitionReader) {
						AbstractBeanDefinitionReader abdr = ((AbstractBeanDefinitionReader) reader);
						abdr.setResourceLoader(this.resourceLoader);
						abdr.setEnvironment(this.environment);
					}
					readerInstanceCache.put(readerClass, reader);
				} catch (Throwable ex) {
					throw new IllegalStateException(
							"Could not instantiate BeanDefinitionReader class [" + readerClass.getName() + "]");
				}
			}

			// TODO SPR-6310: qualify relative path locations as done in AbstractContextLoader.modifyLocations
			// 处理classpath:spring-beans.xml这种资源加载进来~~
			// 最终委托给的是`PathMatchingResourcePatternResolver`来加载这个资源,所以支持classpath*  也支持ant风格的通配符
			reader.loadBeanDefinitions(resource);
		});
	}
}

这样这个xml就会被解析完成,里面所有定义的Bean的定义信息就会被加载进容器里。

从源码中可以看出:默认情况下只支持导入xml格式的文件,并且要求遵循**spring-beans.xsd**。除非你在注解里可以自定义**BeanDefinitionReader**。它内置有三个实现类:

  1. PropertiesBeanDefinitionReader:一种简单的属性文件格式的bean definition解析器,提供以Map/Properties类型ResourceBundle类型定义的bean的注册方法。
代码语言:javascript
复制
 employee.(class)=MyClass       // bean is of class MyClass
 employee.(abstract)=true       // this bean can't be instantiated directly
 employee.group=Insurance       // real property
 employee.usesDialUp=false      // real property (potentially overridden)

 salesrep.(parent)=employee     // derives from "employee" bean definition
 salesrep.(lazy-init)=true      // lazily initialize this singleton bean
 salesrep.manager(ref)=tony     // reference to another bean
 salesrep.department=Sales      // real property

 techie.(parent)=employee       // derives from "employee" bean definition
 techie.(scope)=prototype       // bean is a prototype (not a shared instance)
 techie.manager(ref)=jeff       // reference to another bean
 techie.department=Engineering  // real property
 techie.usesDialUp=true         // real property (overriding parent value)

 ceo.$0(ref)=secretary          // inject 'secretary' bean as 0th constructor arg
 ceo.$1=1000000                 // inject value '1000000' at 1st constructor arg

这种方式我想说,其实我不想说什么~尴尬

  1. GroovyBeanDefinitionReader:略
  2. XmlBeanDefinitionReader:读取bean definition属性通过特定的xml文件。这个解析器在基于xml配置时候使用得非常之多,只是最终输给了时间几近被淘汰,此处也不用再举例了。

当然若都不满足你,你可以自己实现一个。(我相信99.99%都是没有必要的吧)。 需要特别注意的是:AnnotatedBeanDefinitionReader在基于注解的Spring项目中使用非常多,但它并不是BeanDefinitionReader的子类。它一般和ClassPathBeanDefinitionScanner一起使用

@ImportResource注解解释
代码语言:javascript
复制
// @since 3.0
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE) // 它只能标注在类上
@Documented
public @interface ImportResource {

	// 路径支持${}这样动态取值~~~~   也支持ant风格的匹配  classpath*也是木有问题的
	@AliasFor("locations")
	String[] value() default {};
	@AliasFor("value")
	String[] locations() default {};

	// 上面说了,一般都不需要自定义,因为一般情况下我们都只会导入xml文件
	Class<? extends BeanDefinitionReader> reader() default BeanDefinitionReader.class;
}

需要特别注意的是,后缀名此处其实无所谓。比如你命名为spring-beans.txt也是没有问题的,但是需要保证里面的内容是xml格式的且遵循Spring Bean的schema:spring-beans.xsd就成~~ 这是需要注意的一点

关联知识

ConfigurationClassUtils里有这么一段代码:

代码语言:javascript
复制
abstract class ConfigurationClassUtils {
	static {
		candidateIndicators.add(Component.class.getName());
		candidateIndicators.add(ComponentScan.class.getName());
		candidateIndicators.add(Import.class.getName());
		candidateIndicators.add(ImportResource.class.getName());
	}	
}

可以看出标注为@ImportResource注解的Bean也会当作成一个配置类,只不过该配置类是Lite模式而已。关于什么叫Full模式什么叫Lite模式,他们有什么区别?请参考:

【小家Spring】Spring解析@Configuration注解的处理器:ConfigurationClassPostProcessor(ConfigurationClassParser)

@PropertySource

Spring框架提供了PropertySource注解,目的是加载指定的属性文件。

这个注解是非常具有实际意义的,特别是在SpringBoot环境下,意义重大

由于SpringBoot默认情况下它会去加载classpath下的application.properties文件,所以我看大绝大多数开发者是这么干的:把所有的配置项都写在这一个配置文件里

这是非常不好的习惯,非常容易造成配置文件的臃肿,不好维护到最后的不能维护。

比如我们常见的一些配置:jdbc的、redis的、feign的、elasticsearch的等等他们的边界都是十分清晰的,因此Spring提供给我们这个注解,能让我们很好的实现隔离性~~

备注:此注解是Spring3.1后提供的,并不属于Spring Boot

使用Demo

我有一个数据库的配置文件:jdbc.properties

代码语言:javascript
复制
## 配置db数据库相关信息
datasource.drivername=com.mysql.jdbc.Driver
datasource.username=vipkid_xb
datasource.password=jmdneyh4m2UT
datasource.url=jdbc:mysql://localhost:3316/test?zeroDateTimeBehavior=convertToNull

#### 连接池相关
datasource.maximum-pool-size=10
datasource.auto-commit=true
datasource.connection-test-query=SELECT 1
datasource.connectionTimeout=20000
datasource.maxLifetime=180000

我们可以这么使用它:采用Spring支持的@Value获取值

代码语言:javascript
复制
@Configuration
@PropertySource(value = "classpath:jdbc.properties", name = "jdbc-config", ignoreResourceNotFound = false, encoding = "UTF-8")
public class JdbcConfig implements TransactionManagementConfigurer {

    @Value("${datasource.username}")
    private String userName;
    @Value("${datasource.password}")
    private String password;
    @Value("${datasource.url}")
    private String url;


    // 此处只是为了演示 所以不用连接池了===========生产环境禁止这么使用==========
    @Bean
    public DataSource dataSource() {
        MysqlDataSource dataSource = new MysqlDataSource();
        dataSource.setUser(userName);
        dataSource.setPassword(password);
        dataSource.setURL(url);
        return dataSource;
    }
}

单元测试:

代码语言:javascript
复制
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {JdbcConfig.class})
public class TestSpringBean {

    @Autowired
    private DataSource dataSource;

    @Test
    public void test1() throws SQLException {
        Connection connection = dataSource.getConnection();
        System.out.println(connection); ////com.mysql.jdbc.JDBC4Connection@6db66836
    }

}

能够正常获取到链接,说明配置生效~~~

其实大多数时候如果你是SpringBoot环境,我建议采用下面这种更优雅的方式,来处理某一类(请保证这一类拥有共同的前缀)属性值:@ConfigurationProperties

代码语言:javascript
复制
@Configuration
@PropertySource(value = "classpath:jdbc.properties", name = "jdbc-config", ignoreResourceNotFound = false, encoding = "UTF-8")
@ConfigurationProperties(prefix = "datasource")
public class JdbcConfig implements TransactionManagementConfigurer {

    private String username;
    private String password;
    private String url;


    public void setUsername(String username) {
        this.username = username;
    }
    public void setPassword(String password) {
        this.password = password;
    }
    public void setUrl(String url) {
        this.url = url;
    }

    // 此处只是为了演示 所以不用连接池了===========生产环境禁止这么使用==========
    @Bean
    public DataSource dataSource() {
        MysqlDataSource dataSource = new MysqlDataSource();
        dataSource.setUser(username);
        dataSource.setPassword(password);
        dataSource.setURL(url);
        return dataSource;
    }
}

这样也是ok的。需要的注意的是:各个属性名和配置文件里的需要对应上。并且需要提供set方法

另外还可以这么使用,直接把@ConfigurationProperties注解放在@Bean上,赋值极其方便

代码语言:javascript
复制
@Configuration
@PropertySource(value = "classpath:jdbc.properties", name = "jdbc-config", ignoreResourceNotFound = false, encoding = "UTF-8")
public class JdbcConfig implements TransactionManagementConfigurer {

    @ConfigurationProperties(prefix = "datasource")
    @Bean
    public DataSource dataSource() {
        //dataSource.setUser(username);
        //dataSource.setPassword(password);
        //dataSource.setURL(url);
        return new MysqlDataSource();
    }
}

这样做极其优雅。但是需要注意的是MysqlDataSource里面对应的属性名称是啥。比如此处为user、password、URL,因此为了对应上你需要做出如下修改才能生效。(如何修改此处省略)

建议:这种方式一般还是用于框架内部(比如我自己写的框架就用到了它,挺方便也好懂),而不是外部使用(因为约定得太多了,不太好太强的约束到使用者,当然我觉得也没啥,规范应该人人遵守

备注:SpringBoot下此种写法不区分大小写,驼峰,-,_等书写形式都是兼容的。但是你的字母必须对应上啊,比如上面的user你不能写成username了。比如我这样写:datasource.u-r-l=xxx也是能够被正常识别的~~~ 具体参照SpringBoot的黑科技类:RelaxedNames

另外,本文重点是@PropertySource而非@ConfigurationProperties~~

实现原理剖析

上面已经贴出了入口,此处直接分析方法(该注解的解析时机还是非常早的)processPropertySource

代码语言:javascript
复制
class ConfigurationClassParser {
	...
	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;
		}
	
		// 这里value代表这locations  我个人感觉  语义可以优化一下
		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接口,就是createPropertySource的工厂,Spring内部只有一个实现:DefaultPropertySourceFactory 
		// 若你不指定默认就是DefaultPropertySourceFactory,否则给你new一个对象出来~(请保证有空的构造函数~)
		PropertySourceFactory factory = (factoryClass == PropertySourceFactory.class ?
				DEFAULT_PROPERTY_SOURCE_FACTORY : BeanUtils.instantiateClass(factoryClass));

		for (String location : locations) {
			try {
				// 显然它也支持占位符,支持classpath*
				String resolvedLocation = this.environment.resolveRequiredPlaceholders(location);
				Resource resource = this.resourceLoader.getResource(resolvedLocation);
				
				// 调用factory的createPropertySource方法根据名字、编码、资源创建出一个PropertySource出来(实际是一个ResourcePropertySource)
				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
				// 若它为true,那没找着就没找着,不会抛异常阻断程序的启动,需要注意~
				if (ignoreResourceNotFound) {
					if (logger.isInfoEnabled()) {
						logger.info("Properties location [" + location + "] not resolvable: " + ex.getMessage());
					}
				}
				else {
					throw ex;
				}
			}
		}
	}


	// 把属性资源添加进来,最终全部要放进MutablePropertySources 里  这点非常重要~~~~ 这个时机
	private void addPropertySource(PropertySource<?> propertySource) {
		String name = propertySource.getName();
		
		// 这个特别的重要,这个其实就是Spring处理配置文件优先级的原理,下面有个截图可以看到
		// 因为这块特别重要,后面还会有专门章节分析~~~
		// MutablePropertySources它维护着一个List<PropertySource<?>> 并且是有序的~~~
		MutablePropertySources propertySources = ((ConfigurableEnvironment) this.environment).getPropertySources();

		// 此处若发现你的同名PropertySource已经有了,还是要继续处理的~~~而不是直接略过
		if (this.propertySourceNames.contains(name)) {
			// We've already added a version, we need to extend it
			// 根据此name拿出这个PropertySource~~~~若不为null
			// 下面就是做一些属性合并的工作~~~~~
			PropertySource<?> existing = propertySources.get(name);
			if (existing != null) {
				PropertySource<?> newSource = (propertySource instanceof ResourcePropertySource ?
						((ResourcePropertySource) propertySource).withResourceName() : propertySource);
				if (existing instanceof CompositePropertySource) {
					((CompositePropertySource) existing).addFirstPropertySource(newSource);
				}
				else {
					if (existing instanceof ResourcePropertySource) {
						existing = ((ResourcePropertySource) existing).withResourceName();
					}
					CompositePropertySource composite = new CompositePropertySource(name);
					composite.addPropertySource(newSource);
					composite.addPropertySource(existing);
					propertySources.replace(name, composite);
				}
				return;
			}
		}

		// 这段代码处理的意思是:若你是第一个自己导入进来的,那就放在最末尾
		// 若你不是第一个,那就把你放在已经导入过的最后一个的前一个里面~~~
		if (this.propertySourceNames.isEmpty()) {
			propertySources.addLast(propertySource);
		} else {
			String firstProcessed = this.propertySourceNames.get(this.propertySourceNames.size() - 1);
			propertySources.addBefore(firstProcessed, propertySource);
		}
		this.propertySourceNames.add(name);
	}
	...
}

比如这样子导入两次,但是名字不同,比如这样子导入:

代码语言:javascript
复制
@PropertySource(value = "classpath:jdbc.properties", name = "jdbc-config", ignoreResourceNotFound = false, encoding = "UTF-8")
@PropertySource(value = "classpath:jdbc.properties", name = "jdbc-config2", ignoreResourceNotFound = false, encoding = "UTF-8")
public class JdbcConfig implements TransactionManagementConfigurer {
	...
}

代码是有顺序的,从上至下执行的。

最终结果为:

就这样,我们导入的属性值们,最终也放进了环境Environment里面。

@PropertySource注解解释
代码语言:javascript
复制
// @since 3.1  它也只能标注在类上面
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Repeatable(PropertySources.class)
public @interface PropertySource {

	// 该配置项PropertySource 的名字。若不指定 则用的是Resource#getDescription()
	// 示例:class path resource [jdbc.properties]
	String name() default "";
	// 配置文件地址。支持${...} placeholders。也支持classpath
	String[] value();
	// @since 4.0  默认是false就是强制要求文件必须存在的~
	boolean ignoreResourceNotFound() default false;
	// 可写:UTF-8
	String encoding() default "";
	// PropertySource的创建工厂,一般性而言,不要自己实现,用默认的即可
	Class<? extends PropertySourceFactory> factory() default PropertySourceFactory.class;

}

至于上面的言论:可不可以导入非properties文件呢?这里答案显然是否定的,只能是key-value形式的属性文件形式。

(显然yaml也是默认不支持的,除非你自己去实现,Spring是提供了这种扩展开口的)

总结

我个人认为这两个注解还是比较有用的,特别是@PropertySource注解在我们分模块开发时候有非常大的作用,它能让你的代码更清晰,配置更隔离,减少出错

说到配置我啰嗦一句:其实任何事都一样,隔离性、自治性都是需要保证的,这样后续才好维护。比如我看到非常多的小伙伴配置@Bean的时候,根本不考虑到底配置在哪个@Confiuration配置类下呢?随便乱扔,虽然可能能正常work,但是若真出了问题,可以说将是灾难性的,会付出很大的代价去查找~~~ 希望小伙伴们能保持一个良好的分层、隔离的编码习惯

我对它体会最深处是我在之前某家公司的时候,对臃肿的的配置文件的重构。当时我还是一个刚好1年经验的java新生,完全不懂的原理,但我知道这么做很好,哈哈~~~

还有就是Spring暴露出来给我们使用的API,我认为都是很有必要去学习、了解的。虽然它有很多也是重复发明轮子,但是人家有这个能力并且你可以很方便的使用它(至少比JavaEE方便),因此熟悉后,这方面的开发效率会大大的提升,最终受益者也是你自己个

小知识点:@Order注解对@Bean、以及@Service都是不能生效的(控制不了Bean的顺序),若你想在Spring环境下控制Filter的顺序,请使用其它方式(SpringBoot可以很友好的控制Servlet三大组件的顺序,这个以后再说~

使用Properties读取xml文件

其实这也是我的一个意外收获。在看SpringBoot源码的时候有这么一个类:

代码语言:javascript
复制
public class PropertiesPropertySourceLoader implements PropertySourceLoader {
	@Override
	public String[] getFileExtensions() {
		return new String[] { "properties", "xml" };
	}
}

发现它不仅加载properties,也加载xml文件。可见Spring团队对jdk底层是非常非常熟悉的。

勾起兴趣后,mark下这个:使用Properties也能读取xml文件。

Demo如下:

xml文件内容(请注意:是有DTD要求的,并且可以看到是sun官方的DTD):

代码语言:javascript
复制
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">

<!-- properties标签下只有这comment和entry两个值 -->
<properties>
    <comment>系统配置</comment>
    <entry key="logo.location"><![CDATA[/image/logo/]]></entry>
    <entry key="mail.host"><![CDATA[webmaster@zlex.org]]></entry>
    <entry key="site.name"><![CDATA[zlex中文网站]]></entry>
    <entry key="welcome"><![CDATA[欢迎您,{0}!]]></entry>
</properties>

读取:

代码语言:javascript
复制
    public static void main(String[] args) throws IOException {
        ClassPathResource resource = new ClassPathResource("my.xml");
        Properties properties = new Properties();
        properties.loadFromXML(resource.getInputStream());
        
        System.out.println(properties); //{logo.location=/image/logo/, site.name=zlex中文网站, welcome=欢迎您,{0}!, mail.host=webmaster@zlex.org}
    }

可以正常读取到内容。此xml内容同下面properties文件。xml没有中文乱码问题,这是它一大优秀的地方

代码语言:javascript
复制
#系统配置  
logo.location=/image/logo/  
mail.host=webmaster@zlex.org  
site.name=zlex中文网站  
welcome=欢迎您,{0}!  

备注:loadFromXML()方法JDK5提供。平时我们使用中还是推荐使用properties文件,但这个可以炫技用,哈哈

关于Properties的读取加载,推荐工具类:org.springframework.core.io.support.PropertiesLoaderUtils,两种格式都能读

关于Properties文件的使用小细节
代码语言:javascript
复制
logo.location=/image/logo/  
mail.host=webmaster@zlex.org  
site.name=zlex中文网站  
welcome=欢迎您,{0}!  

读取程序如下;

代码语言:javascript
复制
    public static void main(String[] args) throws IOException {
        ClassPathResource resource = new ClassPathResource("my.properties");
        Properties properties = new Properties();
        InputStream inputStream = resource.getInputStream();
        properties.load(inputStream);
        properties.forEach((k, v) -> {
            System.out.println(k + "=" + v);
        });
    }

发现是有中文乱码的。

代码语言:javascript
复制
mail.host=webmaster@zlex.org  
welcome=欢迎您,{0}!  
site.name=zlex中文网站  
logo.location=/image/logo/  

1、解决中文乱码问题: 使用Reader读取

代码语言:javascript
复制
    public static void main(String[] args) throws IOException {
        ClassPathResource resource = new ClassPathResource("my.properties");
        Properties properties = new Properties();
        InputStream inputStream = resource.getInputStream();
        //properties.load(inputStream);
        // 解决中文乱码问题  使用Reader (若直接使用inputStream,请把中文使用native2ascii.exe转换~~~)
        Reader reader = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"));
        properties.load(reader);
        properties.forEach((k, v) -> {
            System.out.println(k + "=" + v);
        });
    }

这样中文就没有乱码了。(请保证你的文件编码是utf-8)

2、**Properties** 文件中可议使用**:**吗?

形如下面,使用:配置。

代码语言:javascript
复制
logo.location:/image/logo/  
mail.host:webmaster@zlex.org  
site.name:zlex中文网站  
welcome:欢迎您,{0}!  

最终结果:也是ok的

3、**=/:**中间可以有N个空格吗?

形如这样:

代码语言:javascript
复制
logo.location:     /image/logo/  
mail.host      :webmaster@zlex.org  
site.name     :   zlex中文网站  
welcome:欢迎您,{0}!  

发现打印的结果一模一样。结论:中间有N个空格,都是无所谓的

4、头、尾有空格呢?

代码语言:javascript
复制
    logo.location:/image/logo/
  mail.host:webmaster@zlex.org    
site.name:zlex中文网站
welcome:欢迎您,{0}!

/image/logo/后面有N个空格~~~

输出。结论为:头部N个空格都没关系,但是,但是,但是尾部的空格是会当作值的一部分的。这个特别特备引起注意,很多人在这里踩过坑,因为这个错误还非常的不好找~~~

以上为关于properties配置文件的一些使用细节,希望能帮助到大家。虽然遵循规范是最好,但是若别人使用了一些非工整格式,比如使用:,比如多空格啥的,你要知道咋回事~~这就是高手

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2019年05月28日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • @ImportResource
    • 使用Demo
      • 实现原理剖析
        • @ImportResource注解解释
          • 关联知识
          • @PropertySource
            • 使用Demo
              • 实现原理剖析
                • @PropertySource注解解释
                  • 总结
                      • 使用Properties读取xml文件
                      • 关于Properties文件的使用小细节
                  相关产品与服务
                  容器服务
                  腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
                  领券
                  问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档