jar包相对路径_SpringBoot如何加载jar包外⾯的配置⽂件?
原创:⼩味道(ID:xjjdog),欢迎分享,转载请保留出处。
虽然现在springboot提供了多环境的⽀持,但是通常修改⼀下配置⽂件,都需要重新打包。
在开发springboot框架集成时,我遇到⼀个问题,就是如何让@PropertySource能够“扫描”和加载jar包外⾯的properties⽂件。
这样,我就可以随时随地的修改配置⽂件,不需要重新打包。
最粗暴的⽅式,就是⽤—classpath指定这些⽂件。但是这引⼊了其他问题,“易于部署”、“与容器⽆关”,让⼈棘⼿。⽽且这个问题在测试环境、多机房部署、以及与配置中⼼协作时还是很难巧妙解决,因为这⾥⾯涉及到不少的硬性规范、甚⾄沟通成本。
回到技术的本质,我希望基于spring容器,开发⼀个兼容性套件,能够扫描jar外部的properties⽂件,考虑到实施便捷性,我们约定这些properties⽂件总是位于jar⽂件的临近⽬录中。
设计前提
1、⽂件⽬录
⽂件⽬录就类似于下⾯的样式。可以看到配置⽂件是和jar包平⾏的。
----application.jar (springboot项⽬,jarLaucher)
|
| sample.properties
| config/
|
| sample.properties
2、扫描策略(涉及到覆盖优先级问题)
1)我们约定默认配置⽂件⽬录为config,也就是最优先的。其余application.jar同级;相对路径起始位置为jar路径。
2)⾸先查./config/sample.properties⽂件是否存在,如果存在则加载。
3)查./sample.properties⽂件是否存在,如果存在则加载。
4)否则,使⽤classpath加载此⽂件。
3、开发策略
1)尽可能使⽤spring机制,即Resource加载机制,⽽不适⽤本地⽂件或者部署脚本⼲预等。
2)通过研究,扩展⾃定义的ResourceLoader可以达成此⽬标,但是潜在风险很⾼,因为springboot、cloud框架内部,对各种Context的⽀持都有各⾃的ResourceLoader实现,如果我们再扩展⾃⼰的loader会不会导致某些未知问题?于是放弃了此策略。
3)spring提供了ProtocolResolver机制,⽤于匹配⾃定义的⽂件schema来加载⽂件;⽽且不⼲扰ResourceLoader的机制,最重要的是它会添加到spring环境下的所有的loader中。我们只需要扩展⼀个ProtocolResolver类,并将它在合适的实际加⼊到ResourceLoader即可,此后加载properties⽂件时我们的ProtocolResolver总会被执⾏。
代码arraycopy方法的作用
下⾯是具体的代码实现。最主要的,就是配置⽂件解析器的编写。注释很详细,就不多做介绍了。
1、XPathProtocolResolver.java
import io.ProtocolResolver;
import io.Resource;
import io.ResourceLoader;
import org.springframework.util.ResourceUtils;
import java.util.Collection;
import java.util.LinkedHashSet;
/**
* ⽤于加载jar外部的properties⽂件,扩展classpath : xjjdog
* -- app.jar
* -- config/a.property INSIDE order=3
* -- a.property INSIDE order=4
* -- config/a.property OUTSIDE order=1
* -- a.property OUTSIDE order=2
*分布式锁redis和zk区别
* 例如:* 1、@PropertySource("::a.property")
* 查路径为:./config/a.property,./a.property,如果不到则返回null,路径相对于app.jar
* 2、@PropertySource("::x/a.property")
* 查路径为:./config/x/a.property,./x/a.property,路径相对于app.jar
* 3、@PropertySource("*:a.property")
* 查路径为:./config/a.property,./a.property,CLASSPATH:/config/a.property,CLASSPATH:/a.property
jsthis的理解* 4、@PropertySource("*:x/a.property")
* 查路径为:./config/x/a.property,./x/a.property,CLASSPATH:/config/x/a.property,CLASSPATH:/x/a.property *
* 如果指定了customConfigPath,上述路径中的/config则会被替换
*
* @author xjjdog
**/
public class XPathProtocolResolver implements ProtocolResolver {
/**
* 查OUTSIDE的配置路径,如果不到,则返回null
*/
private static final String X_PATH_OUTSIDE_PREFIX = "::";
/**
* 查OUTSIDE 和inside,其中inside将会转换为CLASS_PATH
*/
private static final String X_PATH_GLOBAL_PREFIX = "*:";
private String customConfigPath;
public XPathProtocolResolver(String configPath) {
this.customConfigPath = configPath;
}
@Override
public Resource resolve(String location, ResourceLoader resourceLoader) {
if (!location.startsWith(X_PATH_OUTSIDE_PREFIX) && !location.startsWith(X_PATH_GLOBAL_PREFIX)) { return null;
}
String real = path(location);
Collection fileLocations = searchLocationsForFile(real);
for (String path : fileLocations) {
Resource resource = Resource(path);
if (resource != null && ists()) {
return resource;
}
}
boolean global = location.startsWith(X_PATH_GLOBAL_PREFIX);
if (!global) {
return null;
}
Collection classpathLocations = searchLocationsForClasspath(real);
for (String path : classpathLocations) {
Resource resource = Resource(path);
Resource resource = Resource(path);
if (resource != null && ists()) {
return resource;
}
}
Resource(real);
}
private Collection searchLocationsForFile(String location) {
Collection locations = new LinkedHashSet<>();
String _location = shaping(location);
if (customConfigPath != null) {
String prefix = ResourceUtils.FILE_URL_PREFIX + customConfigPath;
if (!dsWith("/")) {
locations.add(prefix + "/" + _location);
} else {
locations.add(prefix + _location);
}
} else {
locations.add(ResourceUtils.FILE_URL_PREFIX + "./config/" + _location);
}
locations.add(ResourceUtils.FILE_URL_PREFIX + "./" + _location);
return locations;
}
private Collection searchLocationsForClasspath(String location) {
Collection locations = new LinkedHashSet<>();
String _location = shaping(location);
if (customConfigPath != null) {
String prefix = ResourceUtils.CLASSPATH_URL_PREFIX + customConfigPath; if (!dsWith("/")) {
locations.add(prefix + "/" + _location);
eval input 函数} else {
locations.add(prefix + _location);
}
} else {
locations.add(ResourceUtils.CLASSPATH_URL_PREFIX + "/config/" + _location); }
locations.add(ResourceUtils.CLASSPATH_URL_PREFIX + "/" + _location);
return locations;
}
private String shaping(String location) {
if (location.startsWith("./")) {
return location.substring(2);
}
if (location.startsWith("/")) {
return location.substring(1);
}
return location;
}
/**
特效行者* remove protocol
*
* @param location
* @return
*/
private String path(String location) {
return location.substring(2);
}
}
2、ResourceLoaderPostProcessor.java
import t.ApplicationContextInitializer;
import t.ConfigurableApplicationContext;
import Ordered;
import nv.Environment;
/**
properties是什么文件
* @author xjjdog
* 调整优化环境变量,对于boot框架会默认覆盖⼀些环境变量,此时我们需要在processor中执⾏
* 我们不再需要使⽤单独的yml⽂件来解决此问题。原则:* 1)所有设置为系统属性的,初衷为"对系统管理员可见"、"对外部接⼊组件可见"(⽐如starter或者⽇志组件等 * 2)对设置为lastSource,表⽰"当⽤户没有通过yml"配置选项时的默认值--担保策略。**/
public class ResourceLoaderPostProcessor implements ApplicationContextInitializer, Ordered {
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
Environment environment = Environment();
String configPath = Property("CONF_PATH");
if (configPath == null) {
configPath = Property("config.path");
}
applicationContext.addProtocolResolver(new XPathProtocolResolver(configPath));
}
@Override
public int getOrder() {
return HIGHEST_PRECEDENCE + 100;
}
}
加上spring.factories,我们越来越像是在做⼀个starter了。没错,就是要做⼀个。
3、spring.factories
t.ApplicationContextInitializer=\
com.github.xjjdogmons.spring.io.ResourceLoaderPostProcessor
PropertyConfiguration.java (springboot环境下,properties加载器)
@Configuration
@PropertySources(
{
@PropertySource("*:login.properties"),
@PropertySource("*:ldap.properties")
}
)
public class PropertyConfiguration {
@Bean
@ConfigurationProperties(prefix = "login")
public LoginProperties loginProperties() {
return new LoginProperties();
}
@Bean
@ConfigurationProperties(prefix = "ldap")
public LdapProperties ldapProperties() {
return new LdapProperties();
}
}
这样,我们的⾃定义加载器就完成了。我们也为SpringBoot组件,增加了新的功能。
End
SpringBoot通过设置”spring.profiles.active”可以指定不同的环境,但是需求总是多变的。⽐如本⽂的配置需求,可能就是某个公司蛋疼的约定。
SpringBoot提供了多种扩展⽅式来⽀持这些⾃定义的操作,这也是魅⼒所在。没有什么,不是开发⼀个spring boot starter不能解决的。
来源:⼩味道(ID:xjjdog)
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论