JAXBElement:为 class java.lang.Class 提供编解码器(/转换器?)

JAXBElement: providing codec (/converter?) for class java.lang.Class

我一直在评估采用 spring-data-mongodb 作为 project。综上,我的目标是:

  1. 使用现有的 XML 模式文件生成 Java classes。

这里要注意的是ExtensionType contains protected List<Object> any; allowing it to store Objects of any class. In my case, it is amongst the classes named TSDModule_Name_HereModuleType and can be browsed here

  1. 使用spring-data-mongodb作为持久化存储

    • 这是使用简单的 ProductDataRepository

      实现的
      @RepositoryRestResource(collectionResourceRel = "product", path = "product")
      public interface ProductDataRepository extends MongoRepository<TSDProductDataType, String> {
          TSDProductDataType queryByGtin(@Param("gtin") String gtin);
      }
      
    • 未编组的 TSDProductDataType,但是,包含 spring-data-mongodb 似乎无法自行处理的 JAXBElement并抛出 CodecConfigurationException org.bson.codecs.configuration.CodecConfigurationException: Can't find a codec for class java.lang.Class.

这是错误的陈述:

TSDProductDataType tsdProductDataType = jaxbElement.getValue();
repository.save(tsdProductDataType);

我尝试按照 here 的解释使用 spring-data-mongodb 的转换器,但是,似乎我遗漏了一些东西,因为异常是关于 "Codecs" 而不是 "Converters".

感谢任何帮助。

编辑:

为 JAXBElement 添加转换器

注意: 适用于 org.springframework.boot::spring-boot-starter-parent 的 1.5.6.RELEASE 版本。有了 2.0.0.M3 版本,地狱就破灭了

我之前尝试添加转换器时似乎遗漏了什么。所以,我像下面这样添加它进行测试:

@Component
@ReadingConverter
public class JAXBElementReadConverter implements Converter<DBObject, JAXBElement> {
    //@Autowired
    //MongoConverter converter;

    @Override
    public JAXBElement convert(DBObject dbObject) {
        Class declaredType, scope;
        QName name = qNameFromString((String)dbObject.get("name"));
        Object rawValue = dbObject.get("value");
        try {
            declaredType = Class.forName((String)dbObject.get("declaredType"));
        } catch (ClassNotFoundException e) {
            if (rawValue.getClass().isArray()) declaredType = List.class;
            else declaredType = LinkedHashMap.class;
        }
        try {
            scope = Class.forName((String) dbObject.get("scope"));
        } catch (ClassNotFoundException e) {
            scope = JAXBElement.GlobalScope.class;
        }
        //Object value = rawValue instanceof DBObject ? converter.read(declaredType, (DBObject) rawValue) : rawValue;
        Object value = "TODO";
        return new JAXBElement(name, declaredType, scope, value);
    }

    QName qNameFromString(String s) {
        String[] parts = s.split("[{}]");
        if (parts.length > 2) return new QName(parts[1], parts[2], parts[0]);
        if (parts.length == 1) return new QName(parts[0]);
        return new QName("undef");
    }
}


@Component
@WritingConverter
public class JAXBElementWriteConverter implements Converter<JAXBElement, DBObject> {
    //@Autowired
    //MongoConverter converter;

    @Override
    public DBObject convert(JAXBElement jaxbElement) {
        DBObject dbObject = new BasicDBObject();
        dbObject.put("name", qNameToString(jaxbElement.getName()));
        dbObject.put("declaredType", jaxbElement.getDeclaredType().getName());
        dbObject.put("scope", jaxbElement.getScope().getCanonicalName());
        //dbObject.put("value", converter.convertToMongoType(jaxbElement.getValue()));
        dbObject.put("value", "TODO");
        dbObject.put("_class", JAXBElement.class.getName());
        return dbObject;
    }

    public String qNameToString(QName name) {
        if (name.getNamespaceURI() == XMLConstants.NULL_NS_URI) return name.getLocalPart();
        return name.getPrefix() + '{' + name.getNamespaceURI() + '}' + name.getLocalPart();
    }
}


@SpringBootApplication
public class TsdApplication {

    public static void main(String[] args) {
        SpringApplication.run(TsdApplication.class, args);
    }

    @Bean
    public CustomConversions customConversions() {
        return new CustomConversions(Arrays.asList(
                new JAXBElementReadConverter(),
                new JAXBElementWriteConverter()
        ));
    }
}

到目前为止一切顺利。但是,如何实例化 MongoConverter converter;MongoConverter 是一个接口所以我想我需要一个可实例化的 class 坚持这个接口。有什么建议吗?

我理解能够将现有域对象映射到数据库层而无需样板的方便愿望,但即使您没有 JAXB class 结构问题,我仍然会建议不要逐字使用它。除非这是一个简单的一次性项目,否则您几乎肯定会遇到需要更改域模型但持久数据需要保持现有状态的地步。如果您只是直接持久化数据,则没有在较新的域架构和较旧的持久化数据方案之间进行转换的机制。持久数据方案的版本控制也是明智的。

您发布的用于编写客户转换器的 link 是实现此目的的一种方法,并且非常适合 Spring 生态系统。该方法还应该可以解决您遇到的问题(关于底层凌乱的 JAXB 数据结构未干净地转换)。

您无法使用该方法吗?确保使用 @Component 加上自动 class 扫描或通过某些 Configuration class.

手动将它们加载到 Spring 上下文中

编辑以解决您的编辑问题:

将以下内容添加到您的每个转换器中:

private final MongoConverter converter;

public JAXBElement____Converter(MongoConverter converter) {
    this.converter = converter;
}

尝试将您的 bean 定义更改为:

@Bean
public CustomConversions customConversions(@Lazy MongoConverter converter) {
    return new CustomConversions(Arrays.asList(
            new JAXBElementReadConverter(converter),
            new JAXBElementWriteConverter(converter)
    ));
}