Spring Проблемы пакетной сериализации с пакетом времени Java 8

У меня есть приложение Spring -batch, в котором хранятся несколько объектов времени Java 8 в JobExecutionContext. Я использую сериализатор по умолчанию для моего JobRespository. Я сталкиваюсь с исключениями при анализе данных, которые записываются в таблицу BATCH_STEP_EXECUTION_CONTEXT. У меня есть LocalDateTime, который хранится как:

{
    "@resolves-to": "java.time.Ser",
    "byte": [5,
    8,
    18,
    8,
    45,
    50],
    "int": [2015,
    10000000]
}

Это приводит к исключению, когда я пытаюсь читать из предыдущих данных JobExecution:

Caused by: java.lang.ClassCastException: java.lang.Byte cannot be cast to java.lang.Integer
at com.thoughtworks.xstream.core.util.CustomObjectInputStream.readInt(CustomObjectInputStream.java:144) ~[xstream-1.4.8.jar:1.4.8]
at java.time.LocalDate.readExternal(LocalDate.java:2070) ~[na:1.8.0_45]
at java.time.LocalDateTime.readExternal(LocalDateTime.java:2002) ~[na:1.8.0_45]
at java.time.Ser.readInternal(Ser.java:259) ~[na:1.8.0_45]
at java.time.Ser.readExternal(Ser.java:246) ~[na:1.8.0_45]
at com.thoughtworks.xstream.converters.reflection.ExternalizableConverter.unmarshal(ExternalizableConverter.java:167) ~[xstream-1.4.8.jar:1.4.8]
at com.thoughtworks.xstream.core.TreeUnmarshaller.convert(TreeUnmarshaller.java:72) ~[xstream-1.4.8.jar:na]
... 97 common frames omitted

Я использую Spring -batch 3.0.5.RELEASE. Я также попытался перейти на последние версии xstream (1.4.8) и Jettison (1.3.7), но я получаю то же исключение.

Это, похоже, известная проблема с XStream (ссылка). Было предложено зарегистрировать собственный конвертер в XStream. Однако Spring -batch не предоставляет фактический объект XStream для регистрации конвертера. Любые предложения о том, как действовать?

Ответ 1

Spring Пакет позволяет вам настроить собственный сериализатор для ExecutionContext, реализовав интерфейс ExecutionContextSerializer и введя его в JobRepositoryFactoryBean.

Вы правы в том, что мы не позволяем вам вводить собственный экземпляр XStream в настоящее время (хотя это кажется разумной точкой распространения с учетом этой проблемы). На данный момент вам нужно либо расширить, либо скопировать XStreamExecutionContextStringSerializer и использовать собственный экземпляр XStream.

Ответ 2

У меня была та же проблема при десериализации LocalDate из контекста выполнения сценария.

Поэтому я должен сделать свой правильный конвертер:

public class DateConverter implements Converter {

    private static final String            DEFAULT_DATE_PATTERN = "yyyy-MM-dd";
    private static final DateTimeFormatter DEFAULT_DATE_FORMATTER = DateTimeFormatter.ofPattern(DEFAULT_DATE_PATTERN);

    public DateConverter() {
        super();
    }

    public boolean canConvert(Class clazz) {
        return LocalDate.class.isAssignableFrom(clazz);
    }

    /**
     * Convert LocalDate to String
     */
    public void marshal(Object value, HierarchicalStreamWriter writer, MarshallingContext context) {
        LocalDate  date = (LocalDate) value;
        String result = date.format(DEFAULT_DATE_FORMATTER);
        writer.setValue(result);
    }

    /**
     * convert Xml to LocalDate
     */
    public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {
        LocalDate result = LocalDate.parse(reader.getValue(), DEFAULT_DATE_FORMATTER);
        return result;
    }
}

После этого я должен создать свой собственный XStreamExecutionContextStringSerializer для использования моего конвертера

/**
 * My XStreamExecutionContextStringSerializer
 * @since 1.0
 */
public class MyXStreamExecutionContextStringSerializer implements ExecutionContextSerializer, InitializingBean {

    private ReflectionProvider reflectionProvider = null;

    private HierarchicalStreamDriver hierarchicalStreamDriver;

    private XStream xstream;

    public void setReflectionProvider(ReflectionProvider reflectionProvider) {
        this.reflectionProvider = reflectionProvider;
    }

    public void setHierarchicalStreamDriver(HierarchicalStreamDriver hierarchicalStreamDriver) {
        this.hierarchicalStreamDriver = hierarchicalStreamDriver;
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        init();
    }

    public synchronized void init() throws Exception {
        if (hierarchicalStreamDriver == null) {
            this.hierarchicalStreamDriver = new JettisonMappedXmlDriver();
        }
        if (reflectionProvider == null) {
            xstream =  new XStream(hierarchicalStreamDriver);
        }
        else {
            xstream = new XStream(reflectionProvider, hierarchicalStreamDriver);
        }

        // Convert LocalDate
        xstream.registerConverter(new DateConverter());
    }

    /**
     * Serializes the passed execution context to the supplied OutputStream.
     *
     * @param context
     * @param out
     * @see Serializer#serialize(Object, OutputStream)
     */
    @Override
    public void serialize(Map<String, Object> context, OutputStream out) throws IOException {
        Assert.notNull(context);
        Assert.notNull(out);

        out.write(xstream.toXML(context).getBytes());
    }

    /**
     * Deserializes the supplied input stream into a new execution context.
     *
     * @param in
     * @return a reconstructed execution context
     * @see Deserializer#deserialize(InputStream)
     */
    @SuppressWarnings("unchecked")
    @Override
    public Map<String, Object> deserialize(InputStream in) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(in));

        StringBuilder sb = new StringBuilder();

        String line;
        while ((line = br.readLine()) != null) {
            sb.append(line);
        }

        return (Map<String, Object>) xstream.fromXML(sb.toString());
    }
}

Последний шаг - зарегистрировать MyXStreamExecutionContextStringSerializer в файле execute-context.xml, который зарегистрирует bean jobRepository

<bean id="jobRepository"
    class="org.springframework.batch.core.repository.support.JobRepositoryFactoryBean">
    <property name="dataSource" ref="dataSource" />
    <property name="transactionManager" ref="transactionManager" />
    <property name="tablePrefix" value="${batch.table.prefix:BATCH.BATCH_}" />
    <property name="serializer"> <bean class="com.batch.config.MyXStreamExecutionContextStringSerializer"/> </property>
</bean>