【问题标题】:spring data mongodb: access default POJO converter from within custom converterspring data mongodb:从自定义转换器中访问默认 POJO 转换器
【发布时间】:2014-09-30 11:53:22
【问题描述】:

我通过 xml 设置了 spring data mongo 自定义转换器,如下所示

<mongo:mapping-converter id="mongoConverter" db-factory-ref="mongoDbFactory">
    <mongo:custom-converters>
        <mongo:converter ref="customWriteConverter" />
        <mongo:converter ref="customReadConverter" />
    </mongo:custom-converters>
</mongo:mapping-converter>

<bean id="mongoTemplate" class="org.springframework.data.mongodb.core.MongoTemplate">
    <constructor-arg ref="mongoDbFactory"/>
    <constructor-arg ref="mongoConverter"/>
</bean>

<bean id="customWriteConverter" class="package.WriteConverter" />
<bean id="customReadConverter" class="package.ReadConverter" />

在自定义读/写转换器中,我想重用spring-data-mongo的默认pojo转换器来将某些属性保存为子文档。

考虑一个简化的例子 -

class A {
    B b;
    String var1;
    int var2;
}

class B {
    String var3;
    String var4;
}

我想使用 customWriteConvertercustomReadConverter 处理 A 类的转换,但在我的自定义转换器中,我还想将 B 类的转换委托回 spring-data-mongo 的默认 POJO 转换器。

我该怎么做?我无法成功地将 MongoConverter 或 MongoTemplate 自动装配到自定义转换器中,因为在尝试创建自定义转换器时正在创建 MongoConverter/MongoTemplate bean。是否可以访问默认转换器并在自定义转换器中使用它?

【问题讨论】:

  • 你找到解决方案了吗?
  • @jacob - 不,幸运的是我要序列化的课程很小,所以我最终为它编写了一个自定义转换器。
  • 你查看this question了吗?我相信这将帮助您使用自定义转换器

标签: spring spring-data spring-data-mongodb


【解决方案1】:

此方法用于MongoTemplate 类中获取默认转换器。

private static final MongoConverter getDefaultMongoConverter(MongoDbFactory factory) {
    DbRefResolver dbRefResolver = new DefaultDbRefResolver(factory);
    MappingMongoConverter converter = new MappingMongoConverter(dbRefResolver, new MongoMappingContext());
    converter.afterPropertiesSet();
    return converter;
}

MappingMongoConverter 不是最终的,因此可以为特定目的被覆盖。正如我在上面的评论中提到的,请查看this question 以找到解决您问题的方法。

【讨论】:

  • 如果我理解正确,我应该从我的自定义转换器中调用这个静态方法。但是如何从我的自定义转换器中获取 MongoDbFactory 以将其传递给这个静态方法?
【解决方案2】:

如果您正在转换 TO mongo 数据库并希望默认一些转换,您可以执行以下操作:

    ...

    @Resource
    private ObjectFactory<MappingMongoConverter>
        mappingMongoConverterObjectFactory;

    private MappingMongoConverter
        mappingMongoConverter;

    ...

    //Otherwise, use default MappingMongoConverter
    //
    if (result == null)
        result =
            getMappingMongoConverter()
                .convertToMongoType(
                    value
                );

    ...

    MappingMongoConverter getMappingMongoConverter() {
        if (mappingMongoConverter == null)
            mappingMongoConverter =
                mappingMongoConverterObjectFactory.getObject();
        return
            mappingMongoConverter;
    }

在我的情况下,MappingMongoConverter 不能直接使用 @Resource (ed),因为在构建其他转换器时它正在构建过程中。因此,Spring 检测到循环引用。我不确定是否有更好的“惰性”方法来执行此操作,而无需 ObjectFactory、getter 方法和缓存的所有运行。

现在,如果有人能找出一种在返回(从 DBObject 到 java Object)时默认为标准处理的方法,这将完成这个循环。

【讨论】:

  • 如果知道目标类型,那么FROM就是:result = getMappingMongoConverter().read(targetType, dbObject);
【解决方案3】:

这可能不是完全相同的用例,但我不得不在懒惰的基础上修改现有的 mongo 文档(不使用 $project 等)。 基本上,我复制了 Spring 的 getDefaultMongoConverter 方法(该方法在此处的较早答案之后发生了变化,并且将来可能会再次更改)并添加了一个参数来传递自定义转换器。在创建自定义转换器本身 (FooConverter) 时,我为客户转换器传递了一个空列表(如果您有额外的子文档转换器,这可能会有所不同)。然后在创建最终转换器时,我传入我的 FooConverter。

这是一些(未经测试的)示例代码。这假设启用了自动配置,因此 MongoDbFactory 已经连接。如果没有,您将创建自己的 MongoDbFactory bean,但其他一切都几乎相同。

@Bean
public MongoTemplate mongoTemplate(final MongoDbFactory mongoDbFactory) throws Exception {
    FooReadConverter fooConverter = new FooReadConverter(mongoDbFactory);
    MongoConverter converter = getMongoConverter(mongoDbFactory, List.of(fooConverter));

    return new MongoTemplate(mongoDbFactory, converter);
}

/**
 * Get a mongo converter
 * @see org.springframework.data.mongodb.core.MongoTemplate#getDefaultMongoConverter
 */
static MongoConverter getMongoConverter(MongoDbFactory factory, List<?> customConverters) {

    DbRefResolver dbRefResolver = new DefaultDbRefResolver(factory);
    MongoCustomConversions conversions = new MongoCustomConversions(customConverters);

    MongoMappingContext mappingContext = new MongoMappingContext();
    mappingContext.setSimpleTypeHolder(conversions.getSimpleTypeHolder());
    mappingContext.afterPropertiesSet();

    MappingMongoConverter converter = new MappingMongoConverter(dbRefResolver, mappingContext);
    converter.setCustomConversions(conversions);
    converter.setCodecRegistryProvider(factory);
    converter.afterPropertiesSet();

    return converter;
}

@ReadingConverter
static class FooReadConverter implements Converter<Document, Foo> {

    private final MongoConverter defaultConverter;

    public FooReadConverter(MongoDbFactory dbFactory) {
        this.defaultConverter = getMongoConverter(dbFactory, List.of());
    }

    @Override
    public Foo convert(Document source) {
        boolean isOldFoo = source.containsKey("someKeyOnlyInOldFoo");
        Foo foo;
        if (isOldFoo) {
            OldFoo oldFoo = defaultConverter.read(OldFoo.class, source);
            foo = oldFoo.toNewFoo();
        } else {
            foo = defaultConverter.read(Foo.class, source);
        }

        return foo;
    }
}

【讨论】:

    【解决方案4】:

    尝试在您的转换器中注入 BeanFactory,并在转换方法中从 BeanFactory 获取 mongoTemplate...

    【讨论】:

      【解决方案5】:

      我知道已经很晚了,但是今天我刚刚遇到了这个问题,我想也许最好在这里分享一下。

      由于 MongoTemplate(及其默认转换器)是在我们的自定义转换器之后初始化的,因此无法将它们直接注入到我们的转换器中,但我们可以通过在转换器中实现 ApplicationContextAware 来访问它们。 访问mongoTemplate后,我们可以分别调用mongoTemplate.getConverter().readmongoTemplate.getConverter().write方法将读写转换委托给它。

      让我们看一个例子。假设我们有两个 POJO:

      public class Outer {
          public String var1;
          public int var2;
      
          public Inner inner;
      }
      
      public class Inner {
          public String var3;
          public String var4;
      }
      

      WriteConverter 可能是这样的:

      import org.bson.Document;
      import org.springframework.beans.BeansException;
      import org.springframework.context.ApplicationContext;
      import org.springframework.context.ApplicationContextAware;
      import org.springframework.core.convert.converter.Converter;
      import org.springframework.data.mongodb.core.MongoTemplate;
      import org.springframework.stereotype.Component;
      
      @Component
      public class CustomWriteConverter implements Converter<Outer, Document>, ApplicationContextAware {
      
          private ApplicationContext applicationContext;
          private MongoTemplate mongoTemplate;
      
          @Override
          public Document convert(Outer source) {
              // initialize the mongoTemplate
              if (mongoTemplate == null) {
                  this. mongoTemplate = applicationContext.getBean(MongoTemplate.class);
              }
      
              // do some custom stuff
      
              Document document = new Document();
              document.put("var1", source.var1);
              document.put("var2", source.var2);
      
              // Using MongoTemplate's converters
              Document inner = new Document();
              mongoTemplate.getConverter().write(source.inner, inner);
      
              document.put("inner", inner);
      
      
              return document;
          }
      
          @Override
          public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
              this.applicationContext = applicationContext;
          }
      
      }
      

      还有 ReadConverter:

      import org.bson.Document;
      import org.springframework.beans.BeansException;
      import org.springframework.context.ApplicationContext;
      import org.springframework.context.ApplicationContextAware;
      import org.springframework.core.convert.converter.Converter;
      import org.springframework.data.mongodb.core.MongoTemplate;
      import org.springframework.stereotype.Component;
      
      @Component
      public class CustomReadConverter implements Converter<Document, Outer>, ApplicationContextAware {
      
          private ApplicationContext applicationContext;
          private MongoTemplate mongoTemplate;
      
          @Override
          public Outer convert(Document source) {
              // initialize the mongoTemplate
              if (mongoTemplate == null) {
                  this. mongoTemplate = applicationContext.getBean(MongoTemplate.class);
              }
      
              // do some custom stuff
      
              Outer outer = new Outer();
              outer.var1 = source.getString("var1");
              outer.var2 = source.getInteger("var2");
      
              // Using MongoTemplate's converters
              Inner inner = mongoTemplate.getConverter().read(Inner.class, (Document) source.get("inner"));
              outer.inner = inner;
      
              return outer;
          }
      
          @Override
          public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
              this.applicationContext = applicationContext;
          }
      
      }
      

      mongoTemplate 可以被多个线程初始化(因为它处于竞争状态),但是由于它的作用域是单例,所以没有问题。

      现在唯一要做的就是注册我们的转换器。

      【讨论】:

        【解决方案6】:

        这里使用spring-boot-starter-data-mongodb 版本2.5.2

        package com.example.mongo;
        
        import java.util.List;
        
        import org.springframework.beans.factory.annotation.Autowired;
        import org.springframework.beans.factory.annotation.Value;
        import org.springframework.context.annotation.Configuration;
        import org.springframework.core.convert.converter.Converter;
        import org.springframework.data.mongodb.MongoDatabaseFactory;
        import org.springframework.data.mongodb.config.AbstractMongoClientConfiguration;
        import org.springframework.data.mongodb.core.convert.DbRefResolver;
        import org.springframework.data.mongodb.core.convert.DefaultDbRefResolver;
        import org.springframework.data.mongodb.core.convert.MappingMongoConverter;
        import org.springframework.data.mongodb.core.convert.MongoConverter;
        import org.springframework.data.mongodb.core.convert.MongoCustomConversions.MongoConverterConfigurationAdapter;
        import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
        
        @Configuration
        public class MongoConfig extends AbstractMongoClientConfiguration {
        
            private @Value("${spring.data.mongodb.database}") String database;
            private @Autowired MongoDatabaseFactory mongoDatabaseFactory;
        
            @Override
            protected String getDatabaseName() {
                return database;
            }
        
            @Override
            protected void configureConverters(MongoConverterConfigurationAdapter converterConfigurationAdapter) {
                DbRefResolver dbRefResolver = new DefaultDbRefResolver(mongoDatabaseFactory);
                MongoConverter mongoConverter = new MappingMongoConverter(dbRefResolver, new MongoMappingContext());
                converterConfigurationAdapter.registerConverters(customConverters(mongoConverter));
            }
        
            public List<Converter<?, ?>> customConverters(MongoConverter mongoConverter) {
                MyCustomConverter custom = new MyCustomConverter(mongoConverter);
                return List.of(custom);
            }
        
        }
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2016-03-16
          • 2019-11-26
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多