Immutables Автоматически сгенерированный репозиторий для бросков MongoDB «Не удается найти кодек для интерфейса» CodecConfigurationException - PullRequest
1 голос
/ 20 июня 2019

Описание проблемы.

В дальнейшем я использую упрощенную версию примера, который в настоящее время находится на сайте неизменяемых .

Итак, у меня есть предмет, который я хочу использовать с MongoDB.

@Value.Immutable
@Mongo.Repository("items")
public abstract class Item {
  @Mongo.Id
  public abstract long id();
  public abstract String name();
}

Для простоты я создаю тест Junit. Как в примере. Моя проблема в том, что этот тест junit, хотя он представлен в примере на неизменяемом сайте , выдает исключение:

public class ItemTest{
@Test
public void testRepository() {
try {
 //Simple ItemRepository creation
   ItemRepository items = new ItemRepository(
     RepositorySetup.forUri("mongodb://localhost/test")
     );

// Create item Item item = ImmutableItem.builder()
    .id(1)
    .name("one")
    .build();

    //Insert is async. Returns a Future. 
    FluentFuture<Integer> future=items.insert(item);
    future.get(); // get the result (ensure it was saved)

   }catch(Exception e){
       e.printStackTrace();
       Assert.fail(e.getMessage());
   }
}//end test

}//end class

Выполнение этого теста приведет к следующему исключению:

org.bson.codecs.configuration.CodecConfigurationException: не удается найти кодек для интерфейса spyros.stackoverflow.example.Item

Полная трассировка стека приведена ниже.

java.lang.AssertionError: java.util.concurrent.ExecutionException: **org.bson.codecs.configuration.CodecConfigurationException: Can't find a codec for interface spyros.stackoverflow.example.Item.**
    at com.google.common.util.concurrent.AbstractFuture.getDoneValue(AbstractFuture.java:552)
    at com.google.common.util.concurrent.AbstractFuture.get(AbstractFuture.java:533)
    at com.google.common.util.concurrent.FluentFuture$TrustedFuture.get(FluentFuture.java:82)
    at com.google.common.util.concurrent.ForwardingFuture.get(ForwardingFuture.java:62)
    at spyros.stackoverflow.example.ItemTest.testRepository(ItemTest.java:78)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
    at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
    at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
    at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
    at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
Caused by: org.bson.codecs.configuration.CodecConfigurationException: Can't find a codec for interface spyros.stackoverflow.example.Item.
    at org.bson.codecs.configuration.CodecCache.getOrThrow(CodecCache.java:46)
    at org.bson.codecs.configuration.ProvidersCodecRegistry.get(ProvidersCodecRegistry.java:63)
    at org.bson.codecs.configuration.ProvidersCodecRegistry.get(ProvidersCodecRegistry.java:37)
    at com.mongodb.MongoCollectionImpl.getCodec(MongoCollectionImpl.java:591)
    at com.mongodb.MongoCollectionImpl.insertMany(MongoCollectionImpl.java:333)
    at com.mongodb.MongoCollectionImpl.insertMany(MongoCollectionImpl.java:322)
    at org.immutables.mongo.repository.Repositories$Repository$2.call(Repositories.java:130)
    at org.immutables.mongo.repository.Repositories$Repository$2.call(Repositories.java:127)
    at com.google.common.util.concurrent.TrustedListenableFutureTask$TrustedFutureInterruptibleTask.runInterruptibly(TrustedListenableFutureTask.java:125)
    at com.google.common.util.concurrent.InterruptibleTask.run(InterruptibleTask.java:69)
    at com.google.common.util.concurrent.TrustedListenableFutureTask.run(TrustedListenableFutureTask.java:78)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
    at java.lang.Thread.run(Thread.java:748)


    at org.junit.Assert.fail(Assert.java:88)
    at spyros.stackoverflow.example.ItemTest.testRepository(ItemTest.java:84)

Я использую Gradle, а build.gradle выглядит следующим образом.

plugins {
    id 'java'
    id 'java-library'
}


sourceCompatibility = 1.8
targetCompatibility = 1.8

repositories {
    mavenLocal()
    mavenCentral()
    jcenter()
}

dependencies {
    implementation 'org.apache.logging.log4j:log4j-slf4j-impl:2.7'
    implementation 'org.apache.logging.log4j:log4j-core:2.7'
    implementation  'javax.persistence:javax.persistence-api:2.2'
    implementation  'javax.xml.bind:jaxb-api:2.1'
    implementation 'com.fasterxml.jackson.core:jackson-databind:2.9.8'
    implementation 'com.google.guava:guava:27.1-jre'


    annotationProcessor 'org.immutables:value:2.7.4'
    implementation 'org.immutables:value:2.7.4' 
    implementation 'org.immutables:mongo:2.7.4'


    implementation 'org.apache.commons:commons-lang3:3.4'
    implementation 'org.apache.commons:commons-collections4:4.1'
    implementation 'org.apache.commons:commons-configuration2:2.3'

   testCompile group: 'junit', name: 'junit', version: '4.12'
}

Выявленная причина

Библиотека JSON Immutables должна иметь возможность находить автоматически сгенерированные GsonAdaptersItem.class, implements com.google.gson.TypeAdapterFactory Фактически для каждого interface или abstract class с именем XXX с такой же аннотацией, что и в «элементе класса» (см. Выше), Immutables генерирует аналогичное GsonAdapterXXX, что implements com.google.gson.TypeAdapterFactory

Чтобы иметь возможность автоматически находить эти классы, RepositorySetup использует java.util.ServiceLoader, чтобы автоматически находить их. Это связанная часть кода:

    GsonBuilder gsonBuilder = new GsonBuilder();
    // there are no longer auto-registed from class-path, but from here or if added manually.
    gsonBuilder.registerTypeAdapterFactory(new TypeAdapters());

    for (TypeAdapterFactory factory : ServiceLoader.load(TypeAdapterFactory.class)) {
      gsonBuilder.registerTypeAdapterFactory(factory);
    }
    return gsonBuilder.create();

Причиной исключения является то, что по какой-то причине это не работает, и выдается следующее исключение.

UPDATE Дальнейшие исследования показывают, что пример работает, но только если баночка была скомпилирована. Это связано с тем, что Immutables создает следующий файл, который фактически будет использоваться для поиска всех необходимых GsonAdapterXXX классов.

 build\classes\java\main\META-INF\services\com.google.gson.TypeAdapterFactory

org.bson.codecs.configuration.CodecConfigurationException: не удается найти кодек для интерфейса spyros.stackoverflow.example.Item Это постоянная ссылка на исходный код.

Работа вокруг Простая работа заключается в том, чтобы специально зарегистрировать GsonAdaptersItem в GsonBuilder. Следующий тест Junit работает.

   @Test
    public void testItemVerbose() {
        try {
            //repeat what the RepositorySetup#createGson does
            GsonBuilder gsonBuilder = new GsonBuilder();
            //iterating in this loop
            for (TypeAdapterFactory factory : ServiceLoader.load(TypeAdapterFactory.class)) {
                //You can see that the ServiceLoader does not find the GsonAdaptersItem
                //System.out.println("Factory:"+factory.getClass().getCanonicalName()+" "+factory.toString());
                gsonBuilder.registerTypeAdapterFactory(factory);
            }

            //add the GsonAdaptersItem manually
            gsonBuilder.registerTypeAdapterFactory(new GsonAdaptersItem());
            Gson gson = gsonBuilder.create();



            Item item = ImmutableItem.builder()
                    .id(1)
                    .name("one")
                    .build();

            final MongoClient mongo = new MongoClient( "localhost" , 27017 );
            final MongoDatabase mongoDatabase=mongo.getDatabase("test");
            ItemRepository items =new ItemRepository(
                RepositorySetup.builder()
                        .executor(MoreExecutors.newDirectExecutorService())
                        .database(mongoDatabase)
                        .gson(gson).build()
                        );
            // Insert async and get
            items.insert(item).get(); // returns future, works


        }catch (final Exception e){
             e.printStackTrace();
           Assert.fail(e.getMessage());
        }

    }

Этот обходной путь может быть использован для ручного создания универсального API, и программист должен предоставить GsonAdapterXXX. Например. ItemRepository itemRepository=reporitoryProvider.getRepositoryWithRegisteredGsonAdapter(new GsonAdaptersItem()); Также могут быть созданы более конкретные XXXRepository провайдеры.

public ItemRepository getItemRepository(){
return reporitoryProvider.getRepositoryWithRegisteredGsonAdapter(new GsonAdaptersItem());
}

Тем не менее, все вышеперечисленное требует обслуживания, более подвержено ошибкам и в любом случае потребует написания стандартного кода, чего ожидает тот, кто использует Immutables.

Вопрос

Я ожидаю, что пример должен работать, как показано на неизменяемом сайте . Мое лучшее предположение состоит в том, что, поскольку я использую gradle вместо maven, возможно, мне нужно изменить некоторые настройки или зависимости. Вопрос в том: Что нужно изменить, чтобы работало простое создание ItemRepository (показано ниже)?

 //Simple ItemRepository creation
ItemRepository items = new ItemRepository(
     RepositorySetup.forUri("mongodb://localhost/test")
     );

Можно ли сделать это с помощью Immutables и без написания вручную кода, который регистрирует класс кодеков по классам или пакет за пакетом? Я был бы очень благодарен за объяснение того, почему это не сработало.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...