springboot 自定义属性文件加载
*********************
相关类与接口
ConfigDataLocationResolver
public interface ConfigDataLocationResolver<R extends ConfigDataResource> {
boolean isResolvable(ConfigDataLocationResolverContext context, ConfigDataLocation location);
List<R> resolve(ConfigDataLocationResolverContext context, ConfigDataLocation location) throws ConfigDataLocationNotFoundException, ConfigDataResourceNotFoundException;
default List<R> resolveProfileSpecific(ConfigDataLocationResolverContext context, ConfigDataLocation location, Profiles profiles) throws ConfigDataLocationNotFoundException {
return Collections.emptyList();
}
}
ConfigTreeConfigDataLocationResolver
public class ConfigTreeConfigDataLocationResolver implements ConfigDataLocationResolver<ConfigTreeConfigDataResource> {
private static final String PREFIX = "configtree:";
private final LocationResourceLoader resourceLoader;
public ConfigTreeConfigDataLocationResolver(ResourceLoader resourceLoader) {
this.resourceLoader = new LocationResourceLoader(resourceLoader);
}
public boolean isResolvable(ConfigDataLocationResolverContext context, ConfigDataLocation location) {
return location.hasPrefix("configtree:");
}
public List<ConfigTreeConfigDataResource> resolve(ConfigDataLocationResolverContext context, ConfigDataLocation location) {
try {
return this.resolve(location.getNonPrefixedValue("configtree:"));
} catch (IOException var4) {
throw new ConfigDataLocationNotFoundException(location, var4);
}
}
private List<ConfigTreeConfigDataResource> resolve(String location) throws IOException {
Assert.isTrue(location.endsWith("/"), () -> {
return String.format("Config tree location '%s' must end with '/'", location);
});
if (!this.resourceLoader.isPattern(location)) {
return Collections.singletonList(new ConfigTreeConfigDataResource(location));
} else {
Resource[] resources = this.resourceLoader.getResources(location, ResourceType.DIRECTORY);
List<ConfigTreeConfigDataResource> resolved = new ArrayList(resources.length);
Resource[] var4 = resources;
int var5 = resources.length;
for(int var6 = 0; var6 < var5; ++var6) {
Resource resource = var4[var6];
resolved.add(new ConfigTreeConfigDataResource(resource.getFile().toPath()));
}
return resolved;
}
}
}
ConfigDataLocation
public final class ConfigDataLocation implements OriginProvider {
public static final String OPTIONAL_PREFIX = "optional:";
private final boolean optional;
private final String value;
private final Origin origin;
private ConfigDataLocation(boolean optional, String value, Origin origin) {
this.value = value;
this.optional = optional;
this.origin = origin;
}
public boolean isOptional() {
public Origin getOrigin() {
public String getValue() {
public boolean hasPrefix(String prefix) {
public String getNonPrefixedValue(String prefix) {
return this.hasPrefix(prefix) ? this.value.substring(prefix.length()) : this.value;
}
public ConfigDataLocation[] split() {
return this.split(";");
}
public ConfigDataLocation[] split(String delimiter) {
String[] values = StringUtils.delimitedListToStringArray(this.toString(), delimiter);
ConfigDataLocation[] result = new ConfigDataLocation[values.length];
for(int i = 0; i < values.length; ++i) {
result[i] = of(values[i]).withOrigin(this.getOrigin());
}
return result;
}
public boolean equals(Object obj) {
public int hashCode() {
public String toString() {
ConfigDataLocation withOrigin(Origin origin) {
return new ConfigDataLocation(this.optional, this.value, origin);
}
public static ConfigDataLocation of(String location) {
boolean optional = location != null && location.startsWith("optional:");
String value = !optional ? location : location.substring("optional:".length());
return !StringUtils.hasText(value) ? null : new ConfigDataLocation(optional, value, (Origin)null);
}
}
ConfigDataResource
public abstract class ConfigDataResource {
private final boolean optional;
public ConfigDataResource() {
this(false);
}
protected ConfigDataResource(boolean optional) {
this.optional = optional;
}
boolean isOptional() {
return this.optional;
}
}
ConfigTreeConfigDataResource
public class ConfigTreeConfigDataResource extends ConfigDataResource {
private final Path path;
ConfigTreeConfigDataResource(String path) {
Assert.notNull(path, "Path must not be null");
this.path = Paths.get(path).toAbsolutePath();
}
ConfigTreeConfigDataResource(Path path) {
Assert.notNull(path, "Path must not be null");
this.path = path.toAbsolutePath();
}
Path getPath() {
public boolean equals(Object obj) {
public int hashCode() {
public String toString() {
ConfigDataLoader
public interface ConfigDataLoader<R extends ConfigDataResource> {
default boolean isLoadable(ConfigDataLoaderContext context, R resource) {
return true;
}
ConfigData load(ConfigDataLoaderContext context, R resource) throws IOException, ConfigDataResourceNotFoundException;
}
ConfigTreeConfigDataLoader
public class ConfigTreeConfigDataLoader implements ConfigDataLoader<ConfigTreeConfigDataResource> {
public ConfigTreeConfigDataLoader() {
}
public ConfigData load(ConfigDataLoaderContext context, ConfigTreeConfigDataResource resource) throws IOException, ConfigDataResourceNotFoundException {
Path path = resource.getPath();
ConfigDataResourceNotFoundException.throwIfDoesNotExist(resource, path);
String name = "Config tree '" + path + "'";
ConfigTreePropertySource source = new ConfigTreePropertySource(name, path, new Option[]{Option.AUTO_TRIM_TRAILING_NEW_LINE});
return new ConfigData(Collections.singletonList(source), new org.springframework.boot.context.config.ConfigData.Option[0]);
}
}
ConfigData
public final class ConfigData {
private final List<PropertySource<?>> propertySources;
private final ConfigData.PropertySourceOptions propertySourceOptions;
public static final ConfigData EMPTY = new ConfigData(Collections.emptySet(), new ConfigData.Option[0]);
public ConfigData(Collection<? extends PropertySource<?>> propertySources, ConfigData.Option... options) {
public ConfigData(Collection<? extends PropertySource<?>> propertySources, ConfigData.PropertySourceOptions propertySourceOptions) {
public List<PropertySource<?>> getPropertySources() {
public ConfigData.Options getOptions(PropertySource<?> propertySource) {
************
内部枚举:Option
public static enum Option {
IGNORE_IMPORTS,
IGNORE_PROFILES,
PROFILE_SPECIFIC;
private Option() {
}
}
************
内部类:Options
public static final class Options {
public static final ConfigData.Options NONE = new ConfigData.Options(Collections.emptySet());
private final Set<ConfigData.Option> options;
private Options(Set<ConfigData.Option> options) {
Set<ConfigData.Option> asSet() {
public boolean contains(ConfigData.Option option) {
public boolean equals(Object obj) {
public int hashCode() {
public String toString() {
public ConfigData.Options without(ConfigData.Option option) {
return this.copy((options) -> {
options.remove(option);
});
}
public ConfigData.Options with(ConfigData.Option option) {
return this.copy((options) -> {
options.add(option);
});
}
private ConfigData.Options copy(Consumer<EnumSet<ConfigData.Option>> processor) {
EnumSet<ConfigData.Option> options = EnumSet.noneOf(ConfigData.Option.class);
options.addAll(this.options);
processor.accept(options);
return new ConfigData.Options(options);
}
public static ConfigData.Options of(ConfigData.Option... options) {
Assert.notNull(options, "Options must not be null");
return options.length == 0 ? NONE : new ConfigData.Options(EnumSet.copyOf(Arrays.asList(options)));
}
}
************
内部类:AlwaysPropertySourceOptions
private static class AlwaysPropertySourceOptions implements ConfigData.PropertySourceOptions {
private final ConfigData.Options options;
AlwaysPropertySourceOptions(ConfigData.Options options) {
public ConfigData.Options get(PropertySource<?> propertySource) {
************
内部类:PropertySourceOptions
@FunctionalInterface
public interface PropertySourceOptions {
ConfigData.PropertySourceOptions ALWAYS_NONE = new ConfigData.AlwaysPropertySourceOptions(ConfigData.Options.NONE);
ConfigData.Options get(PropertySource<?> propertySource);
static ConfigData.PropertySourceOptions always(ConfigData.Option... options) {
static ConfigData.PropertySourceOptions always(ConfigData.Options options) {
*********************
示例:从redis加载配置信息
redis 使用hash结构存储配置数据
hash key:redis-config
hash value
person.name ==> gtlx
person.value ==> 20
*************
配置文件
application.yml
spring:
redis:
host: 192.168.57.120
port: 6379
config:
import: optional:redis://${spring.redis.host}:${spring.redis.port}/redis-config
*************
pojo 层
Person
@Data
@Component
@ConfigurationProperties("person")
public class Person {
private String name;
private Integer age;
}
*************
config 层
RedisConfigDataResource
@Getter
@EqualsAndHashCode(callSuper = true)
public class RedisConfigDataResource extends ConfigDataResource {
private final String host;
private final String port;
private final String configKey;
public RedisConfigDataResource(String host, String port, String configkey){
this.host=host;
this.port=port;
this.configKey=configkey;
}
}
RedisConfigDataLocationResolver
public class RedisConfigDataLocationResolver implements ConfigDataLocationResolver<RedisConfigDataResource> {
public final String prefix="redis://";
@Override
public boolean isResolvable(ConfigDataLocationResolverContext context, ConfigDataLocation location) {
return location.hasPrefix(prefix);
}
@Override
public List<RedisConfigDataResource> resolve(ConfigDataLocationResolverContext context, ConfigDataLocation location) throws ConfigDataLocationNotFoundException, ConfigDataResourceNotFoundException {
List<RedisConfigDataResource> resources=new ArrayList<>();
String value= location.getNonPrefixedValue(prefix);
String[] s=value.split("/");
String[] s2=s[0].split(":");
String host=s2[0];
String port=s2[1];
String[] keys=s[1].split(",");
for (String key : keys){
resources.add(new RedisConfigDataResource(host,port,key));
}
return resources;
}
}
RedisConfigDataLoader
public class RedisConfigDataLoader implements ConfigDataLoader<RedisConfigDataResource> {
@Override
public ConfigData load(ConfigDataLoaderContext context, RedisConfigDataResource resource) throws IOException, ConfigDataResourceNotFoundException {
RedisStandaloneConfiguration configuration=new RedisStandaloneConfiguration(resource.getHost(),Integer.parseInt(resource.getPort()));
LettuceConnectionFactory connectionFactory=new LettuceConnectionFactory(configuration);
connectionFactory.afterPropertiesSet();
StringRedisTemplate redisTemplate=new StringRedisTemplate(connectionFactory);
Properties properties=new Properties();
Set<Object> hashKeys=redisTemplate.boundHashOps(resource.getConfigKey()).keys();
assert hashKeys != null;
for (Object key : hashKeys){
properties.put(key,redisTemplate.boundHashOps(resource.getConfigKey()).get(key));
}
return new ConfigData(Collections.singletonList(new PropertiesPropertySource("redis-config",properties)));
}
}
*************
controller 层
HelloController
@RestController
public class HelloController {
@Resource
private Person person;
@RequestMapping("/hello")
public Person hello(){
System.out.println(person);
return person;
}
}
*************
META-INF
spring.factories
org.springframework.boot.context.config.ConfigDataLoader=\
com.example.demo.config.RedisConfigDataLoader
org.springframework.boot.context.config.ConfigDataLocationResolver=\
com.example.demo.config.RedisConfigDataLocationResolver
*********************
使用测试
redis 设置数据
[root@centos ~]# docker exec -it redis-single bash
root@c2e2d338ed6b:/data# redis-cli
127.0.0.1:6379> keys *
(empty array)
127.0.0.1:6379> hset redis-config person.name gtlx
(integer) 1
127.0.0.1:6379> hset redis-config person.age 20
(integer) 1
127.0.0.1:6379>
localhost:8080/hello,控制台输出
2021-09-22 22:43:46.018 INFO 3496 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet'
2021-09-22 22:43:46.019 INFO 3496 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Completed initialization in 1 ms
Person(name=gtlx, age=20)
成功读取redis中设置的数据