Springboot@ConditionalOnResource解决⽆法读取外部配置⽂件问题
前⾔
最近在开发存储层基础中间件的过程中,使⽤到了@ConditionalOnResource这个注解,使⽤该注解的⽬的是,注解在Configuration bean上,在其加载之前对指定资源进⾏校验,是否存在,如果不存在,抛出异常;该注解⽀持传⼊多个变量,但是当我们希望本地代码中不存在配置⽂件,依赖配置中⼼去加载外部的配置⽂件启动时,在注解中传⼊⼀个外部变量,⼀个本地变量(⽅便本地开发)时,会抛出异常,导致项⽬⽆法启动,因此需要解决这个问题。
原因分析
我们⾸先来分析⼀下ConditionalOnResource这个注解,源码如下:
/**
* {@link Conditional} that only matches when the specified resources are on the秦牛正威吴亦凡
* classpath.
*
* @author Dave Syer
*/
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnResourceCondition.class)
public @interface ConditionalOnResource {
/**
* The resources that must be present.
读取配置文件失败
* @return the resource paths that must be present.
*/
String[] resources() default {};
}
我们可以看到,该注解⽀持resource的⼊参,是⼀个数组形式,是可以传⼊多个变量的,但是注意看注释:
that only matches when the specified resources are on the classpath.
好吧,我们貌似看出来⼀些端倪了,该注解会加载classpath中指定的⽂件,但是当我们希望加载外部的配置⽂件的时候,为什么会抛异常呢?我们来看⼀下这个注解是如何被处理的:
class OnResourceCondition extends SpringBootCondition {
private final ResourceLoader defaultResourceLoader = new DefaultResourceLoader();
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context,
AnnotatedTypeMetadata metadata) {
MultiValueMap<String, Object> attributes = metadata
.getAllAnnotationAttributes(Name(), true);
ResourceLoader loader = ResourceLoader() == null
this.defaultResourceLoader : ResourceLoader();
List<String> locations = new ArrayList<String>();
collectValues(locations, ("resources"));
Assert.isTrue(!locations.isEmpty(),
"@ConditionalOnResource annotations must specify at "
+ "least one resource location");
List<String> missing = new ArrayList<String>();
for (String location : locations) {
String resource = Environment().resolvePlaceholders(location);
if (!Resource(resource).exists()) {
missing.add(location);
}
}
if (!missing.isEmpty()) {
Match(ConditionMessage
.forCondition(ConditionalOnResource.class)
.didNotFind("resource", "resources").items(Style.QUOTE, missing));
}
return ConditionOutcome
.
match(ConditionMessage.forCondition(ConditionalOnResource.class)
.found("location", "locations").items(locations));
}
private void collectValues(List<String> names, List<Object> values) {
for (Object value : values) {
for (Object item : (Object[]) value) {
names.add((String) item);
}
}
}
}
这个类是@ConditionalOnResource处理类,getMatchOutcome()⽅法中去处理逻辑,主要逻辑很简单,去扫描注解了ConditionalOnResource的类,拿到其resources,分别判断其路径下是否存在对应的⽂件,如果不存在,抛出异常。可以看到,它是使⽤DefaultResourceLoader去加载的⽂件,但是这个类只可以加载classpath下的⽂件,⽆法加载外部路径的⽂件,这个就有点尴尬了,明显⽆法满⾜我的需求。
解决⽅案
了解决⽅案,发现Spring貌似也没提供其他合适的注解解决,因此,我想⾃⼰去实现⼀个处理类。
废话不多说,上源代码:
@ConditionalOnFile:
/**
* 替换Spring ConditionalOnResource,
* ⽀持多⽂件⽬录扫描,如果⽂件不存在,跳过继续扫描
* Created by xuanguangyao on 2018/11/15.
*/
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnConditionalOnFile.class)
public @interface ConditionalOnFile {
/**
* The resources that must be present.
* @return the resource paths that must be present.
*/
String[] resources() default {};
}
OnConditionalOnFile:
public class OnConditionalOnFile extends SpringBootCondition {
private final ResourceLoader fileSystemResourceLoader = new FileSystemResourceLoader();
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
MultiValueMap<String, Object> attributes = metadata
灵活就业人员社保缴费
.getAllAnnotationAttributes(Name(), true);
List<String> locations = new ArrayList<>();
collectValues(locations, ("resources"));
Assert.isTrue(!locations.isEmpty(),
"@ConditionalOnFile annotations must specify at "
+ "least one resource location");
for (String location : locations) {
String resourceLocation = Environment().resolvePlaceholders(location);
Resource fileResource = Resource(resourceLocation);
if (ists()) {
return ConditionOutcome
.match(ConditionMessage.forCondition(ConditionalOnFile.class)
.found("location", "locations").items(location));
}
}
Match(ConditionMessage
.forCondition(ConditionalOnFile.class)
.didNotFind("resource", "resources").items(ConditionMessage.Style.QUOTE, locations));
}
private void collectValues(List<String> names, List<Object> values) {
for (Object value : values) {住宅公寓区别
for (Object item : (Object[]) value) {
names.add((String) item);
分时成交明细}
}
}
}
OK,我⾃⼰实现了⼀个注解,叫做@ConditionalOnFile,然后⾃⾏实现了⼀个注解的处理类,叫做OnConditionalOnFile,该类需要实现SpringBootCondition,这样Springboot才会去扫描。
由于原ConditionalOnResource的处理类是使⽤的DefaultResourceLoader,只可以加载classpath下⾯的⽂件,但是我需要扫描我指定路径下的外部配置⽂件,因此,我使⽤FileSystemResourceLoader,这个加载器,去加载我的外部配置⽂件。
需要注意的是,如果指定外部配置⽂件启动的话,需要在启动时,指定启动参数:
--fig.location=/myproject/conf/ --spring.profiles.active=production
这样,才可以顺利读取到外部的配置⽂件。
测试
OK,我们测试⼀下,通过
java -jar myproject.jar --fig.location=/myproject/conf/ --spring.profiles.active=production
2018-11-15 20:57:51,131 main ERROR Console contains an invalid element or attribute "encoding"
.  ____          _            __ _ _
/\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/  ___)| |_)| | | | | || (_| |  ) ) ) )
'  |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/身份证件类型
:: Spring Boot ::        (v1.4.1.RELEASE)
[] 2018-11-15 20:57:51 - [INFO] [SpringApplication:665 logStartupProfileInfo] The following profiles are active All right,看到springboot的启动画⾯,证明没有问题。