Я экспериментировал с базовым кластером infinispan и обнаружил загадочную ошибку.
Я в основном реализую общую карту, содержащую только одно целое число.
Вот код моего сервиса
package sandbox.infinispan.test.service;
import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
import javax.inject.Named;
import org.infinispan.Cache;
@Named("useThisOne")
@ApplicationScoped
public class CounterService implements ICounterService {
private static final String KEY = "key";
@Inject
private Cache<String, Integer> cache;
@Override
public void inc(final int amount) {
this.cache.put(KEY, Integer.valueOf(this.get() + amount));
}
@Override
public int get() {
return this.cache.computeIfAbsent(KEY, k -> Integer.valueOf(0)).intValue();
}
}
Кэш создается следующим образом:
package sandbox.infinispan.test.config;
import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.context.Dependent;
import javax.enterprise.inject.Produces;
import org.infinispan.configuration.cache.CacheMode;
import org.infinispan.configuration.cache.Configuration;
import org.infinispan.configuration.cache.ConfigurationBuilder;
import org.infinispan.configuration.global.GlobalConfiguration;
import org.infinispan.configuration.global.GlobalConfigurationBuilder;
import org.infinispan.manager.DefaultCacheManager;
import org.infinispan.manager.EmbeddedCacheManager;
@Dependent
class CacheProvider {
@Produces
@ApplicationScoped
private EmbeddedCacheManager defaultClusteredCacheManager() {
final GlobalConfiguration g = new GlobalConfigurationBuilder() //
.clusteredDefault() //
.transport() //
.nodeName(this.getNodeName()) //
.clusterName("infinispanTestCluster") //
.build();
final Configuration cfg = new ConfigurationBuilder() //
.clustering() //
.cacheMode(CacheMode.REPL_SYNC) ///
.build();
return new DefaultCacheManager(g, cfg);
}
}
Если в кластере есть как минимум два сервера, computeIfAbsent завершается ошибкой с
15:48:50,253 ERROR [org.infinispan.interceptors.impl.InvocationContextInterceptor] (jgroups-7,myhostname-14393) ISPN000136: Error executing command ComputeIfAbsentCommand, writing keys [key]: org.infinispan.remoting.RemoteException: ISPN000217: Received exception from otherhostname-44445, see cause for remote stack trace
который сводится к:
Caused by: java.lang.NoSuchMethodException: sandbox.infinispan.test.service.CounterService.$deserializeLambda$(java.lang.invoke.SerializedLambda)
и, наконец, к:
Caused by: java.lang.IllegalArgumentException: Invalid lambda deserialization
at sandbox.infinispan.test.service.CounterService.$deserializeLambda$(CounterService.java:10)
... 68 more
Caused by: an exception which occurred:
in object of type java.lang.invoke.SerializedLambda
Если я перепишу свой довольно красивый модный код, сделав его уродливым, он сработает.
@Override
public int get() {
Integer value = this.cache.get(KEY);
if (value == null) {
value = Integer.valueOf(0);
this.cache.put(KEY, value);
}
return value.intValue();
}
Как я могу использовать симпатичный метод computeIfAbsent в наши дни?
Eclipse 2018-12, WildFly 14, java 10 на члене-разработчике кластера, CentOs 7, OpenJdk 10, WildFly 14 на удаленном члене кластера.
Спасибо за вашу помощь
Решено (вроде)
Благодаря помощи, которую я получил здесь, я преобразовал лямбду во внутренний класс:
static class OhWell implements Serializable {
static Integer zero(final String t) {
return Integer.valueOf(0);
}
}
@Override
public int get() {
return this.cache.computeIfAbsent(KEY, OhWell::zero).intValue();
}
Теперь это работает, но гораздо менее приятно, чем аккуратная лямбда. Так что я буду придерживаться старомодного способа, если только кто-нибудь не придумает лучший способ сделать это.
Дальнейшие результаты:
Следующий внутренний класс static с методом static работает
static class StaticOhWell implements Serializable {
static Integer apply(final String t) {
return Integer.valueOf(0);
}
}
@Override
public int get() {
return this.cache.computeIfAbsent(KEY, StaticOhWell::apply).intValue();
}
Следующий нестатический внутренний класс с нестатическим методом завершается ошибкой:
class NotStaticOhWell implements SerializableFunction<String, Integer> {
@Override
public Integer apply(final String t) {
return Integer.valueOf(0);
}
}
@Override
public int get() {
return this.cache.computeIfAbsent(KEY, new NotStaticOhWell()::apply).intValue();
}
Это завершается ошибкой с этим сообщением об ошибке NotSerializableException: org.infinispan.cache.impl.EncoderCache:
13:41:29,221 ERROR [org.infinispan.interceptors.impl.InvocationContextInterceptor] (default task-1) ISPN000136: Error executing command ComputeIfAbsentCommand, writing keys [value]: org.infinispan.commons.marshall.NotSerializableException: org.infinispan.cache.impl.EncoderCache
Caused by: an exception which occurred:
in field sandbox.infinispan.test.service.CounterService.cache
in object sandbox.infinispan.test.service.CounterService@4612a6c3
in field sandbox.infinispan.test.service.CounterService$NotStaticOhWell.this$0
in object sandbox.infinispan.test.service.CounterService$NotStaticOhWell@4effd362
in field java.lang.invoke.SerializedLambda.capturedArgs
in object java.lang.invoke.SerializedLambda@e62f08a
in object sandbox.infinispan.test.service.CounterService$$Lambda$1195/1060417313@174a143b
Заключительные слова (?)
Использование «статической лямбды» (внутренний статический класс, реализующий интерфейс SerializableFunction) также работало.
static class StaticSerializableFunction implements SerializableFunction<String, Integer> {
@Override
public Integer apply(final String t) {
return Integer.valueOf(0);
}
}
@Override
public int get() {
return this.cache.computeIfAbsent(KEY, new StaticSerializableFunction()::apply).intValue();
}
И победителем становится…
Делая класс действительно сериализуемым путем «перехода» Cache, можно просто использовать метод этого класса. Нет необходимости создавать внутренний класс!
package sandbox.infinispan.test.service;
import java.io.Serializable;
import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
import javax.inject.Named;
import org.infinispan.Cache;
@Named("useThisOne")
@ApplicationScoped
public class CounterService implements ICounterService, Serializable {
private static final String KEY = "value";
@SuppressWarnings("cdi-ambiguous-dependency")
@Inject
private transient Cache<String, Integer> cache;
@Override
public void inc(final int amount) {
this.cache.put(KEY, Integer.valueOf(this.get() + amount));
}
@Override
public int get() {
return this.cache.computeIfAbsent(KEY, this::zero).intValue();
}
private Integer zero(@SuppressWarnings("unused") final String unused) {
return Integer.valueOf(0);
}
@Override
public void reset() {
this.cache.clear();
}
}
Спасибо всем!
CounterService
на удаленном узле? Вы пытались изменить лямбду на анонимный внутренний класс? - person Dan Berindei   schedule 02.04.2019OhWell::zero
все еще лямбда. Но поскольку он ссылается только на статический метод во вложенном классе, он ничего не фиксирует и определенно сериализуем. - person Dan Berindei   schedule 03.04.2019Caused by: java.lang.NoSuchMethodException: sandbox.infinispan.test.service.CounterService.$deserializeLambda$(java.lang.invoke.SerializedLambda)
означает, что классCounterService
, развернутый на удаленном узле, был скомпилирован до того, как вы добавили лямбду, и ваша исходная лямбда будет работать, если вы развернете один и тот же классCounterService
на всех узлах. - person Dan Berindei   schedule 03.04.2019Cache.computeIfAbsent()
, я понимаю, что реализация неанонимного класса, реализующегоSerializableFunction
, — это то, что разработчики Infinispan считают правильным. - person pxcv7r   schedule 05.01.2021