3

问题描述

我有一个带有 gradle 依赖关系的 java 项目org.javamoney:moneta:1.3

我还有两个 Kubernetes 集群。我使用 docker-container 部署我的 java 应用程序。

当我在第一个Kubernetes 集群中部署我的应用程序时,一切都很好。但是当我在第二个Kubernetes 集群中部署我的应用程序(相同的 docker-container)时,会出现以下错误:

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
  • 摇篮:6.0.1
  • 基础泊坞窗图像: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 -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-dependency

我发现了这个问题,它给了我一个想法,尝试以某种不同的方式声明 gradle-dependency。我努力了:

  • 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-class 中这样做,但它并没有解决问题。

问题

  1. 这个问题的根本原因是什么?
  2. 这个问题的正确解决方案是什么?
4

2 回答 2

2

TL;博士

问题在于 Java 11 中的并发 moneta SPI 初始化。

问题方案

这个问题可以通过提取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 集群上存在不同的资源限制配置。

集群异常:

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

集群无一例外:

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

似乎具有更多资源的集群为发生并发 moneta 初始化提供了更多机会。

最小的可重现示例

可以在这个 github-repository中找到最小的可重现示例。

值得一提的是,该错误不会在 Java 8 上重现。

于 2020-05-10T16:30:10.413 回答
1

作为一种解决方法,您可以创建一个服务提供商,例如

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

于 2020-06-04T17:54:18.167 回答