web-dev-qa-db-fra.com

MonetaryException: aucun MonetaryAmountsSingletonSpi chargé

Description du problème

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();

Versions de logiciel

  • Moneta : 1.3.
  • Gradle: 6.0.1.
  • Image-docker de base: openjdk:11.0.7-jdk-slim.
  • Botte de printemps: 2.2.7.RELEASE.
  • Kubernetes (la même version sur les deux clusters): 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: 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).

Ce que j'ai essayé

Déclarer différemment la dépendance gradle

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.

Copier-coller les configurations du chargeur de service pour Moneta

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é.

Initiez la devise personnalisée sans ressort

J'ai essayé de le faire uniquement dans la classe principale, mais cela n'a pas résolu le problème.

Des questions

  1. Quelle est la cause première de ce problème?
  2. Quelle est la bonne solution à ce problème?
4
Maksim Iakunin

TL; DR

Le problème était dans l'initialisation simultanée de moneta SPI dans Java 11.

Solution du problème

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);
    }
}

Pourquoi un problème se produit sur les clusters Kubernetes?

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.

Exemple reproductible minimal

L'exemple minimal reproductible peut être trouvé dans this github-repository .

Il est à noter que le bogue n'est pas reproduit sur Java 8.

2
Maksim Iakunin

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.

1
Utkan Ozyurek