2015-10-27 3 views
10

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

{ 
    "@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 

Я использую Весна-пакет 3.0.5. ВЫПУСК. Я также попытался перейти на последние версии xstream (1.4.8) и Jettison (1.3.7), но я получаю то же исключение.

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

+0

вы добавляете аннотацию @EnableBatchProcessing в классе конфигурации? –

ответ

3

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

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

+0

Я добавил свой собственный сериализатор клиентов. Однако «JobRepositoryFactoryBean» по-прежнему использует по умолчанию «XStreamExecutionContextStringSerializer», что приводит к таким исключениям, когда я пытаюсь читать данные задания из «JobExplorer»: –

+0

'Caused by: com.thoughtworks.xstream.converters.ConversionException: Невозможно десериализовать объект с помощью новых методов readObject()/writeObject(). '---- Отладочная информация ----' 'class: java.time.LocalDate' ' required-type: java.time.LocalDate' 'convert-type: com.thoughtworks.xstream.converters.reflection.SerializableConverter' –

+0

Также появляется сообщение о том, что нажатие кнопки перезапуска в пользовательском интерфейсе Spring Batch Admin запускает' JobExecutionController.restart', который пытается сериализовать с помощью по умолчанию 'XStreamExecutionContextStringSerializer' , Я получаю те же исключения в этом сценарии (и моя трассировка стека включает XStreamExecutionContextStringSerializer, а не мой пользовательский сериализатор). –

1

У меня была такая же проблема при десериализации 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, которые регистрируют работу бобиныRepository

<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>