У меня был этот тест - который я обрезал для MVCE - некоторое время, и сегодня я попытался проверить его в Java 11. Он выдает ConcurrentModificationException
, типично, когда коллекция изменяется одновременно.
Зная немного, как Guice работает внутри, я заподозрил проблему повторного входа в мой computeIfAbsent
звонок, поэтому избавился от своего второго computeIfAbsent()
, и это сработало.Итак, я как бы ожидаю, что сейчас это не получится, но почему это удалось на Java 8?
Кроме того, я подозреваю, что я написал что-то неправильно в том, как должны быть написаны области.Итак, чтобы избежать этой проблемы, как мой механизм видимости должен быть обновлен, чтобы быть готовым к будущему?
Используемые версии:
- Guice: 4.2.2
- Java 8: Oracle JDK 1.8.0_191
- Java 11: OpenJDK 11.0.2
Код:
package scopetest;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import static java.util.Objects.requireNonNull;
import static org.assertj.core.api.Assertions.assertThat;
import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.OutOfScopeException;
import com.google.inject.Provider;
import com.google.inject.Scope;
import com.google.inject.ScopeAnnotation;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import java.util.HashMap;
import java.util.Map;
import org.junit.jupiter.api.Test;
public class ScopeTest {
@Test void test() {
Injector injector = Guice.createInjector(new FooModule());
GuiceFooScope guiceScope = injector.getInstance(GuiceFooScope.class);
Key<Bar<String>> barKey = new Key<Bar<String>>(){};
FooScope fooScopeOne = new FooScope();
guiceScope.enter(fooScopeOne);
Foo fooOne = injector.getInstance(Foo.class);
Bar<String> fooOneBar = injector.getInstance(barKey);
guiceScope.leave();
FooScope fooScopeTwo = new FooScope();
guiceScope.enter(fooScopeTwo);
Bar<String> fooTwoBar = injector.getInstance(barKey); // FAILS HERE!
guiceScope.leave();
assertThat(fooOneBar.foo).isSameAs(fooOne);
assertThat(fooTwoBar).isNotSameAs(fooOneBar);
assertThat(fooTwoBar.foo).isNotSameAs(fooOne);
}
static class FooScope { }
@FooScoped static class Foo {
@Inject Foo() { }
}
@FooScoped static class Bar<T> {
private final Foo foo;
@Inject Bar(Foo foo) {
this.foo = foo;
}
}
static class FooModule extends AbstractModule {
@Override protected void configure() {
GuiceFooScope scope = new GuiceFooScope();
bindScope(FooScoped.class, scope);
bind(GuiceFooScope.class).toInstance(scope);
}
}
@Target({TYPE, METHOD}) @Retention(RUNTIME) @ScopeAnnotation
public @interface FooScoped { }
static class GuiceFooScope implements Scope {
private final Map<FooScope, Map<Key<?>, Object>> allScopedObjects = new HashMap<>();
private FooScope currentScope;
@Override public <T> Provider<T> scope(Key<T> key, Provider<T> unscoped) {
return () -> {
FooScope scope = currentScope;
if (scope == null) {
throw new OutOfScopeException("");
}
return (T) allScopedObjects.computeIfAbsent(scope, x -> new HashMap<>())
.computeIfAbsent(key, x -> unscoped.get());
};
}
void enter(FooScope current) {
currentScope = requireNonNull(current);
}
void leave() {
currentScope = null;
}
}
}
Верная ошибка, которую я получаю под Java 11,следующие (строки правильны относительно исходного файла):
-------------------------------------------------------------------------------
Test set: scopetest.ScopeTest
-------------------------------------------------------------------------------
Tests run: 1, Failures: 0, Errors: 1, Skipped: 0, Time elapsed: 0.001 s <<< FAILURE! - in scopetest.ScopeTest
test Time elapsed: 0.001 s <<< ERROR!
com.google.inject.ProvisionException:
Unable to provision, see the following errors:
1) Error in custom provider, java.util.ConcurrentModificationException
at scopetest.ScopeTest$Bar.class(ScopeTest.java:54)
while locating scopetest.ScopeTest$Bar<java.lang.String>
1 error
at scopetest.ScopeTest.test(ScopeTest.java:40)
Caused by: java.util.ConcurrentModificationException
at scopetest.ScopeTest.test(ScopeTest.java:40)
Новый, рабочий метод scope
следующий:
@Override public <T> Provider<T> scope(Key<T> key, Provider<T> unscoped) {
return () -> {
FooScope scope = currentScope;
if (scope == null) {
throw new OutOfScopeException("");
}
Map<Key<?>, Object> scopedObject = allScopedObjects.computeIfAbsent(scope, x -> new HashMap<>());
Object instance = scopedObject.get(key); // Cannot computeIfAbsent because of reentrance.
if (instance == null && !scopedObject.containsKey(key)) {
instance = unscoped.get();
scopedObject.put(key, instance);
}
return (T)instance;
};
}