spring data mongodb:从自定义转换器中访问默认的POJO转换器

时间:2014-09-30 11:53:23

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

我有通过xml设置的spring数据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转换器。 / p>

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

6 个答案:

答案 0 :(得分:4)

此方法在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以找出解决问题的方法。

答案 1 :(得分:3)

If you are converting TO mongo database and want to default some conversions, you could do something like this:

    ...

    @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;
    }

The MappingMongoConverter cannot be directly @Resource (ed) in my case since it's in the process of being constructed when other converters are being built. So, Spring detects a circular reference. I am not sure if there is a better "lazy" method of doing this without all the run-around of ObjectFactory, getter method, and caching.

Now, if someone can figure out a method of defaulting to standard processing while going back (from DBObject to java Object) that would complete this circle.

答案 2 :(得分:0)

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

答案 3 :(得分:0)

这可能不是完全相同的用例,但是我不得不懒惰地修改现有的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 :(得分:0)

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

由于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可以由多个线程初始化(因为它处于竞争状态),但是由于它的作用域为单例,所以没有问题。

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

答案 5 :(得分:0)

这里使用 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);
    }

}