Spring Data MongoDB: 如何在自定义转换器中访问默认的POJO转换器?

15

我已经通过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 bean的创建正在进行时,因此无法成功地将MongoConverter或MongoTemplate自动装配到自定义转换器中。是否可能从自定义转换器内部访问默认转换器并使用它?


你找到解决方案了吗? - jacob
@jacob - 不,我要序列化的类很小,所以我最终编写了一个自定义转换器。 - ashutosh
你有看过这个问题吗?我相信这会帮助你使用自定义转换器。 - Harsh Poddar
6个回答

5

这种方法在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不是final的,因此可以针对特定目的进行覆盖。如我上面的评论所提到的,查看这个问题,可能会找到解决方案。


2
如果我理解正确的话,我应该从我的自定义转换器中调用这个静态方法。但是,我如何在我的自定义转换器内部获取MongoDbFactory并将其传递到这个静态方法中呢? - ashutosh

3
如果您正在将数据转换为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。所以,Spring检测到循环引用。我不确定是否有更好的“懒惰”方法来避免使用ObjectFactory、getter方法和缓存。

现在,如果有人能够想出一种从DBObject返回Java对象时默认为标准处理的方法,那就可以完成这个循环了。


如果您知道目标类型,那么FROM就是:result = getMappingMongoConverter().read(targetType, dbObject); - pbatey

1
这可能不是完全相同的用例,但我必须基于懒惰的原则修改现有的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;
    }
}

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

}

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

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 可以被多个线程初始化(因为它处于竞争状态),但由于它是单例范围,所以不会有问题。

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接