J'ai un Java avec une dépendance gradle de org.javamoney:moneta:1.3
.
J'ai également deux clusters Kubernetes. Je déploie mon application Java) à l'aide de docker-container.
Lorsque je déploie mon application dans le premier cluster Kubernetes, tout va bien. Mais lorsque je déploie mon application (le même docker-container) dans le deuxième cluster Kubernetes, l'erreur suivante apparaît:
javax.money.MonetaryException: No MonetaryAmountsSingletonSpi loaded.
at javax.money.Monetary.lambda$getDefaultAmountFactory$13(Monetary.Java:291)
at Java.base/Java.util.Optional.orElseThrow(Optional.Java:408)
at javax.money.Monetary.getDefaultAmountFactory(Monetary.Java:291)
Il apparaît dans le code suivant:
MonetaryAmount amount = javax.money.Monetary.getDefaultAmountFactory()
.setCurrency("USD")
.setNumber(1L)
.create();
1.3
.6.0.1
.openjdk:11.0.7-jdk-slim
.2.2.7.RELEASE
.Server Version: version.Info{Major:"1", Minor:"15", GitVersion:"v1.15.3", GitCommit:"2d3c76f9091b6bec110a5e63777c332469e0cba2", GitTreeState:"clean", BuildDate:"2019-08-19T11:05:50Z", GoVersion:"go1.12.9", Compiler:"gc", Platform:"linux/AMD64"}
.Java -version openjdk version "11.0.7" 2020-04-14 OpenJDK Runtime Environment 18.9 (build 11.0.7+10) OpenJDK 64-Bit Server VM 18.9 (build 11.0.7+10, mixed mode)
.J'ai trouvé cette question et cela m'a donné une idée d'essayer de déclarer la dépendance gradle d'une manière différente. J'ai essayé:
implementation 'org.javamoney:moneta:1.3'
compile group: 'org.javamoney', name: 'moneta', version: '1.3', ext: 'pom'
compile 'org.javamoney:moneta:1.3'
runtimeOnly 'org.javamoney:moneta:1.3'
Malheureusement, il n'a donné aucun résultat positif.
Comme mentionné dans ce commentaire j'ai essayé de copier la configuration du chargeur de service de Moneta dans le répertoire de projet suivant: src/main/resources/META-INF/services
.
Malheureusement, cela n'a pas aidé.
J'ai essayé de le faire uniquement dans la classe principale, mais cela n'a pas résolu le problème.
Le problème était dans l'initialisation simultanée de moneta SPI dans Java 11.
Le problème peut être résolu en extrayant MonetaryAmountFactory
dans spring-bean et en l'injectant si nécessaire:
@Bean
public MonetaryAmountFactory<?> money() {
return Monetary.getDefaultAmountFactory();
}
@Component
@RequiredArgsConstructor
public static class Runner implements CommandLineRunner {
private final MonetaryAmountFactory<?> amountFactory;
@Override
public void run(String... args) {
var monetaryAmount = this.amountFactory
.setCurrency("EUR")
.setNumber(1)
.create();
System.out.println("monetaryAmount = " + monetaryAmount);
}
}
au lieu d'utiliser directement cette usine:
public static class Runner implements CommandLineRunner {
@Override
public void run(String... args) {
var monetaryAmount = Monetary.getDefaultAmountFactory()
.setCurrency("EUR")
.setNumber(1)
.create();
System.out.println("monetaryAmount = " + monetaryAmount);
}
}
J'ai découvert qu'il y avait diferrent configuration de limite de ressources sur les clusters Kubernetes mentionnés ci-dessus.
Cluster avec exception:
Limits:
cpu: 6
memory: 20G
Requests:
cpu: 3
memory: 20G
Cluster sans exception:
Limits:
cpu: 2
memory: 2G
Requests:
cpu: 2
memory: 128Mi
Il semble que le cluster avec plus de ressources donne plus de possibilités d'initialisation simultanée de moneta.
L'exemple minimal reproductible peut être trouvé dans this github-repository .
Il est à noter que le bogue n'est pas reproduit sur Java 8.
comme solution de contournement, vous pouvez créer un fournisseur de services comme
public class MyServiceLoader implements ServiceProvider {
/**
* List of services loaded, per class.
*/
private final ConcurrentHashMap<Class<?>, List<Object>> servicesLoaded = new ConcurrentHashMap<>();
private static final int PRIORITY = 10;
/**
* Returns a priority value of 10.
*
* @return 10, overriding the default provider.
*/
@Override
public int getPriority() {
return PRIORITY;
}
/**
* Loads and registers services.
*
* @param serviceType The service type.
* @param <T> the concrete type.
* @return the items found, never {@code null}.
*/
@Override
public <T> List<T> getServices(final Class<T> serviceType) {
@SuppressWarnings("unchecked")
List<T> found = (List<T>) servicesLoaded.get(serviceType);
if (found != null) {
return found;
}
return loadServices(serviceType);
}
public static int compareServices(Object o1, Object o2) {
int prio1 = 0;
int prio2 = 0;
Priority prio1Annot = o1.getClass().getAnnotation(Priority.class);
if (prio1Annot != null) {
prio1 = prio1Annot.value();
}
Priority prio2Annot = o2.getClass().getAnnotation(Priority.class);
if (prio2Annot != null) {
prio2 = prio2Annot.value();
}
if (prio1 < prio2) {
return 1;
}
if (prio2 < prio1) {
return -1;
}
return o2.getClass().getSimpleName().compareTo(o1.getClass().getSimpleName());
}
/**
* Loads and registers services.
*
* @param serviceType The service type.
* @param <T> the concrete type.
* @return the items found, never {@code null}.
*/
private <T> List<T> loadServices(final Class<T> serviceType) {
List<T> services = new ArrayList<>();
try {
for (T t : ServiceLoader.load(serviceType, Monetary.class.getClassLoader())) {
services.add(t);
}
services.sort(CbplMonetaServiceProvider::compareServices);
@SuppressWarnings("unchecked") final List<T> previousServices = (List<T>) servicesLoaded.putIfAbsent(serviceType, (List<Object>) services);
return Collections.unmodifiableList(previousServices != null ? previousServices : services);
} catch (Exception e) {
Logger.getLogger(CbplMonetaServiceProvider.class.getName()).log(Level.WARNING,
"Error loading services of type " + serviceType, e);
services.sort(CbplMonetaServiceProvider::compareServices);
return services;
}
}
}
et avant d'utiliser un appel de cours de bibliothèque d'argent
Bootstrap.init(new CbplMonetaServiceProvider());
cela corrigera également l'erreur de devise.
la seule ligne modifiée sur le fournisseur que nous avons ajoutée par rapport à PriorityAwareServiceProvider est cette ligne
for(T service:ServiceLoader.load(serviceType, Monetary.class.getClassLoader())){
nous venons de spécifier le chargeur de classe donc au lieu de Thread.getCurrentThread (). getClassLoader (), il utilise le chargeur de classe que nous fournissons.