Поведение, которое вы видите, далеко от неожиданности.
В обычной установке Java почти весь код (кроме базовых низкоуровневых библиотек) использует один и тот же ClassLoader, который находит свои ресурсы с помощью CLASSPATH,Если несколько записей пути к классам содержат ресурс с одинаковым путем, будет возвращен ресурс, принадлежащий записи, которая появляется первой в пути к классам.Из документации URLClassLoader :
URL-адреса будут искать в порядке, указанном для классов и ресурсов, после первого поиска в указанном родительском загрузчике классов.
В вашем случае my-api
должен появляться первым на пути к классам, поэтому всякий раз, когда он содержит ресурс config.properties
, он будет загружен в предпочтение перед тем, который содержится в my-project
.
Вы появляетесьхотеть загрузить config.properties
, который находится в той же записи classpath, что и конкретный вызывающий класс.Это возможно , но требует, чтобы вы вручную определили корень записи classpath, в которой находится вызывающий класс. Он включает в себя довольно много кода и очень трудно охватить все возможные случаи загрузки классов (то есть папки классов против jar-файлов против затененных jar-файлов, созданных нижестоящими проектами, против классов, загруженных по сети, против пользовательских загрузчиков классов, используемых downstream, против менеджеров безопасности и т. д.).Итак, я не собираюсь публиковать подробности о том, как это сделать, так как считаю, что это плохая идея.Если вы действительно хотите пойти по этому пути, начните с:
Foo.class.getProtectionDomain().getCodeSource().getLocation()
Итак, какие есть альтернативы?
У вас есть несколько вариантов, но я бы предпочел просто поставитьразличные config.properties
файлы в разных пакетах.Общая практика - иметь разные базовые пакеты для каждого проекта, поэтому вы можете сделать что-то вроде этого:
parent
|
+ - - my-api
| |
| ` - - com.my.api
| |
| ` - config.properties
|
` - - my-project
|
` - - com.my.project
|
` - config.properties
Затем у вас есть 2 варианта загрузки этих файлов.Если у вас есть класс, который, как вы знаете, находится в том же пакете, что и каждый файл свойств, вы можете загрузить его, используя этот класс:
void loadConfig(Class<?> sibling) {
InputStream config = sibling.getResourceAsStream("config.properties");
//...
}
В качестве альтернативы, вы можете просто передать имя пакета:
void loadConfig(String packageName) {
String path = "/" + packageName.replace(".", "/") + "/config.properties";
InputStream config = this.getClass().getClassLoader().getResourceAsStream(path);
//...
}
Если вы действительно хотите определить пакет от вызываемого абонента, вы можете использовать первый метод вместе с безобразным хаком:
void loadConfig() {
try {
StackTraceElement[] stackTrace = new Throwable().getStackTrace();
Class<?> sibling = Class.forName(stackTrace[1].getClassName());
InputStream config = sibling.getResourceAsStream("config.properties");
//...
} catch (ClassNotFoundException e) {
// Calling class must have been loaded using a different ClassLoader.
// There's might be some complex code that could be used to recover here,
// but this should happen rarely, so maybe just propogate Exception
}
}
Я не рекомендую этопоследний подход.