Springboot @ConditionalOnResource 解决无法读取外部配置文件问题

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/wtopps/article/details/84110904

前言

最近在开发存储层基础中间件的过程中,使用到了@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(ConditionalOnResource.class.getName(), true);
		ResourceLoader loader = context.getResourceLoader() == null
				? this.defaultResourceLoader : context.getResourceLoader();
		List<String> locations = new ArrayList<String>();
		collectValues(locations, attributes.get("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 = context.getEnvironment().resolvePlaceholders(location);
			if (!loader.getResource(resource).exists()) {
				missing.add(location);
			}
		}
		if (!missing.isEmpty()) {
			return ConditionOutcome.noMatch(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(ConditionalOnFile.class.getName(), true);
        List<String> locations = new ArrayList<>();
        collectValues(locations, attributes.get("resources"));
        Assert.isTrue(!locations.isEmpty(),
                "@ConditionalOnFile annotations must specify at "
                        + "least one resource location");
        for (String location : locations) {
            String resourceLocation = context.getEnvironment().resolvePlaceholders(location);
            Resource fileResource = this.fileSystemResourceLoader.getResource(resourceLocation);
            if (fileResource.exists()) {
                return ConditionOutcome
                        .match(ConditionMessage.forCondition(ConditionalOnFile.class)
                                .found("location", "locations").items(location));
            }
        }
        return ConditionOutcome.noMatch(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,这个加载器,去加载我的外部配置文件。

需要注意的是,如果指定外部配置文件启动的话,需要在启动时,指定启动参数:

--spring.config.location=/myproject/conf/ --spring.profiles.active=production

这样,才可以顺利读取到外部的配置文件。

测试

OK,我们测试一下,通过

java -jar myproject.jar --spring.config.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的启动画面,证明没有问题。

猜你喜欢

转载自blog.csdn.net/wtopps/article/details/84110904