web-dev-qa-db-ja.com

MonetaryException:MonetaryAmountsSingletonSpiが読み込まれていません

問題の説明

Java _org.javamoney:moneta:1.3_からのGradle依存関係を持つプロジェクト)があります。

また、2つのKubernetesクラスタがあります。 Javaアプリケーションをdocker-containerを使用してデプロイします。

firstKubernetesクラスターにアプリをデプロイすると、すべてがうまくいきます。しかし、アプリ(同じdocker-container)をsecondKubernetesクラスターにデプロイすると、次のエラーが表示されます。

_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)
_

次のコードに表示されます。

_MonetaryAmount amount = javax.money.Monetary.getDefaultAmountFactory()
    .setCurrency("USD")
    .setNumber(1L)
    .create();
_

ソフトウェアのバージョン

  • モネータ :_1.3_。
  • Gradle:_6.0.1_。
  • ベースdocker-image:_openjdk:11.0.7-jdk-slim_。
  • 春のブーツ:_2.2.7.RELEASE_。
  • Kubernetes(両方のクラスターで同じバージョン):_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)

私が試したこと

Gradleの依存関係を異なる方法で宣言する

私は この質問 を見つけました。それは私にGradle依存関係をいくつかの異なる方法で宣言しようとするアイデアを与えました。私が試してみました:

  • _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'_

残念ながら、それは肯定的な結果を与えませんでした。

Monetaのサービスローダー構成のコピーと貼り付け

このコメント サービスローダーの設定 Monetaから を次のプロジェクトディレクトリにコピーしようとしました:_src/main/resources/META-INF/services_。

残念ながら、それは役に立ちませんでした。

春なしでカスタム通貨を初期化する

Mainクラスだけで試してみましたが、問題は解決しませんでした。

ご質問

  1. この問題の根本的な原因は何ですか?
  2. この問題の適切な解決策は何ですか?
4
Maksim Iakunin

TL; DR

問題は同時モネタにありましたSPI初期化Java 11。

問題解決

この問題は、MonetaryAmountFactoryをspring-beanに抽出し、必要な場所に注入することで解決できます。

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

このファクトリを直接使用する代わりに:

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

Kubernetesクラスターで問題が発生するのはなぜですか?

上記のKubernetes-clusterに異なる resource limit configuration があることがわかりました。

例外のあるクラスター:

Limits:
  cpu:     6
  memory:  20G
Requests:
  cpu:      3
  memory:   20G

例外なくクラスター:

Limits:
  cpu:     2
  memory:  2G
Requests:
  cpu:      2
  memory:   128Mi

より多くのリソースを持つクラスターは、同時に発生するモネタ初期化により多くの機会を与えるようです。

最小限の再現可能な例

最小限の再現可能な例は this github-repository にあります。

バグがJava 8.で再現されないことは言及する価値があります。

2
Maksim Iakunin

回避策として、次のようなサービスプロバイダーを作成できます。

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

とお金のライブラリクラスの呼び出しを使用する前に

Bootstrap.init(new CbplMonetaServiceProvider());

これにより、通貨エラーも修正されます。

priorityAwareServiceProviderと比較して追加したプロバイダーの唯一の変更された行はこの行です

for(T service:ServiceLoader.load(serviceType, Monetary.class.getClassLoader())){

クラスローダーを指定したので、Thread.getCurrentThread()。getClassLoader()の代わりに、提供されているクラスローダーを使用しています。

1
Utkan Ozyurek