2015-10-27 14 views
10

Ich habe eine Spring-Batch-Anwendung, die mehrere Java 8 Zeitobjekte im JobExecutionContext speichert. Ich verwende den Standard-Serializer für mein JobRespository. Ich stehe vor Ausnahmen, wenn die Daten analysiert werden, die in die Tabelle BATCH_STEP_EXECUTION_CONTEXT geschrieben werden. Ich habe eine Local, die als gespeichert wird:Spring Batch-Serialisierungsprobleme mit Java 8-Zeitpaket

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

Dies entspricht eine Ausnahme führt, wenn ich versuche, aus den vorherigen JobExecution Daten zu lesen:

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 

I Frühlings-Batch 3.0.5 verwenden. FREISETZUNG. Ich habe auch versucht, auf die neuesten Versionen von Xstream (1.4.8) und Jettison (1.3.7) zu aktualisieren, aber ich bekomme die gleiche Ausnahme.

Dies scheint ein bekanntes Problem mit XStream (link). Der Vorschlag war, einen benutzerdefinierten Konverter in XStream zu registrieren. Spring-Batch stellt das tatsächliche XStream-Objekt jedoch nicht bereit, um einen Konverter zu registrieren. Irgendwelche Vorschläge zum weiteren Vorgehen?

+0

haben Sie @EnableBatchProcessing-Annotation in einer Konfigurationsklasse hinzugefügt? –

Antwort

3

Mit Spring Batch können Sie Ihren eigenen Serializer für ExecutionContext konfigurieren, indem Sie die Schnittstelle ExecutionContextSerializer implementieren und in die JobRepositoryFactoryBean einspeisen.

Sie haben Recht damit, dass wir Ihnen derzeit nicht erlauben, Ihre eigene XStream-Instanz zu injizieren (obwohl es angesichts dieses Problems ein vernünftiger Erweiterungspunkt zu sein scheint). Im Moment müssten Sie entweder XStreamExecutionContextStringSerializer erweitern oder kopieren und Ihre eigene XStream-Instanz verwenden.

+0

Ich habe meinen eigenen Kunden-Serializer hinzugefügt. Die 'JobRepositoryFactoryBean' verwendet jedoch immer noch den Standard' XStreamExecutionContextStringSerializer', der zu Ausnahmen wie diesem führt, wenn ich versuche, Jobdaten aus dem 'JobExplorer' zu lesen: –

+0

' Erstellt von: com.thoughtworks.xstream.converters.ConversionException: Objekt kann nicht mit neuen readObject()/writeObject() Methoden deserialisiert werden '---- Debugging-Informationen ----' 'class: java.time.LocalDate' ' Erforderlich-Typ: java.time.LocalDate' 'converter-type: com.thoughtworks.xstream.converters.reflection.SerializableConverter' –

+0

Es scheint auch, dass das Drücken der Neustart-Schaltfläche auf der Spring Batch Admin-Benutzeroberfläche 'JobExecutionController.restart' auslöst, die versucht, mit dem Standard' XStreamExecutionContextStringSerializer' zu serialisieren . Ich erhalte die gleichen Ausnahmen in diesem Szenario (und mein Stack-Trace enthält XStreamExecutionContextStringSerializer anstelle meines benutzerdefinierten Serializers). –

1

Ich hatte das gleiche Problem beim Deserialisieren LocalDate von Schrittausführung Kontext.

Also muss ich meine richtigen Konverter machen:

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

Danach habe ich meine richtige XStreamExecutionContextStringSerializer für die Verwendung meiner Konverter

/** 
* 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()); 
    } 
} 

Der letzte Schritt erstellen, ist MyXStreamExecutionContextStringSerializer in der Datei registrieren execution-context.xml, die den Bean-Job registrierenRepository

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