Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Show issue with serialization when upgrading to Spring Boot 3 #1

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
144 changes: 144 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,130 @@
<maven.compiler.target>${java.version}</maven.compiler.target>
</properties>

<dependencyManagement>
<dependencies>
<!-- Spring Boot -->
<dependency>
<groupId>com.fasterxml.jackson</groupId>
<artifactId>jackson-bom</artifactId>
<version>2.15.2</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<type>pom</type>
<scope>import</scope>
<version>3.1.0</version>
</dependency>

<!-- Logging -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.7</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.4.8</version>
</dependency>
<dependency>
<groupId>ch.qos.logback.contrib</groupId>
<artifactId>logback-jackson</artifactId>
<version>0.1.5</version>
</dependency>
<dependency>
<groupId>ch.qos.logback.contrib</groupId>
<artifactId>logback-json-classic</artifactId>
<version>0.1.5</version>
</dependency>

<!-- Utils & Operations -->
<dependency>
<groupId>jakarta.validation</groupId>
<artifactId>jakarta.validation-api</artifactId>
<version>3.0.2</version>
</dependency>
<dependency>
<groupId>org.openapitools</groupId>
<artifactId>jackson-databind-nullable</artifactId>
<version>0.2.6</version>
</dependency>

<!-- Test -->
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>3.24.2</version>
<scope>test</scope>
</dependency>
</dependencies>
</dependencyManagement>

<dependencies>
<!-- Spring Boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<!-- Security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>

<!-- Logging -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
</dependency>
<dependency>
<groupId>ch.qos.logback.contrib</groupId>
<artifactId>logback-jackson</artifactId>
</dependency>
<dependency>
<groupId>ch.qos.logback.contrib</groupId>
<artifactId>logback-json-classic</artifactId>
</dependency>

<!-- Utils & Operations -->
<dependency>
<groupId>jakarta.validation</groupId>
<artifactId>jakarta.validation-api</artifactId>
</dependency>
<dependency>
<groupId>org.openapitools</groupId>
<artifactId>jackson-databind-nullable</artifactId>
</dependency>

<!-- Test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<resources>
<resource>
Expand All @@ -36,6 +160,26 @@
<filtering>true</filtering>
</testResource>
</testResources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.1.2</version>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.7.12</version>
<executions>
<execution>
<goals>
<goal>build-info</goal>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>

</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package tk.dmanstrator.sb3_serialization_test;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Application {

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

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package tk.dmanstrator.sb3_serialization_test.entity;

import java.io.Serializable;
import java.util.List;

public record CustomObject(String hello, List<String> world) implements Serializable {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package tk.dmanstrator.sb3_serialization_test.exporter;

import tk.dmanstrator.sb3_serialization_test.entity.CustomObject;
import tk.dmanstrator.sb3_serialization_test.serialization.SerializationHandlerFactory;

import java.io.IOException;

/**
* Class responsible for exporting entities.
*/
public class EntityExporter {

private EntityExporter() {
// hide default constructor
}

public static String exportCustomObject(final CustomObject customObject) throws IOException {
return SerializationHandlerFactory.getDefaultHandlerWithPrettyPrint()
.writeObjectAsString(customObject);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package tk.dmanstrator.sb3_serialization_test.serialization;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Serializable;

public interface SerializationHandler {

boolean isBinary();

String getFileNameExtension();

<T extends Serializable> void writeObject(final T object, final OutputStream os) throws IOException;

<T extends Serializable> String writeObjectAsString(final T object) throws IOException;

<T extends Serializable> T readObject(final Class<T> clazz, final InputStream is) throws IOException;

<T extends Serializable> T readObjectFromString(final Class<T> clazz, final String input) throws IOException;

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package tk.dmanstrator.sb3_serialization_test.serialization;

import tk.dmanstrator.sb3_serialization_test.serialization.impl.JsonSerializationHandler;

public final class SerializationHandlerFactory {

private static final SerializationHandler DEFAULT_HANDLER = new JsonSerializationHandler(false);
private static final SerializationHandler DEFAULT_HANDLER_WITH_PRETTY_PRINT = new JsonSerializationHandler(true);

private SerializationHandlerFactory() {}

public static SerializationHandler getDefaultHandler() {
return DEFAULT_HANDLER;
}

public static SerializationHandler getDefaultHandlerWithPrettyPrint() {
return DEFAULT_HANDLER_WITH_PRETTY_PRINT;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package tk.dmanstrator.sb3_serialization_test.serialization.impl;

import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.json.JsonMapper;
import com.fasterxml.jackson.databind.jsontype.BasicPolymorphicTypeValidator;
import com.fasterxml.jackson.databind.jsontype.PolymorphicTypeValidator;
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import tk.dmanstrator.sb3_serialization_test.serialization.SerializationHandler;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Serializable;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

public abstract class AbstractJsonSerializationHandler implements SerializationHandler {

protected static final ThreadLocal<ObjectMapper> OBJECT_MAPPER_THREAD_LOCAL =
ThreadLocal.withInitial(AbstractJsonSerializationHandler::newObjectMapper);

private final Map<Class<?>, ObjectReader> readers = new ConcurrentHashMap<>();
private final Map<Class<?>, ObjectWriter> writers = new ConcurrentHashMap<>();

private final boolean doPrettyPrint;

protected AbstractJsonSerializationHandler(final boolean doPrettyPrint) {
this.doPrettyPrint = doPrettyPrint;
}

@Override
public <T extends Serializable> void writeObject(final T object, final OutputStream os) throws IOException {
ObjectWriter writer = getWriter(object.getClass());

if (doPrettyPrint) {
writer = writer.withDefaultPrettyPrinter();
}

writer.writeValue(os, object);
}

@Override
public <T extends Serializable> String writeObjectAsString(final T object) throws IOException {
ObjectWriter writer = getWriter(object.getClass());

if (doPrettyPrint) {
writer = writer.withDefaultPrettyPrinter();
}

return writer.writeValueAsString(object);
}

@Override
public <T extends Serializable> T readObject(final Class<T> clazz, final InputStream is) throws IOException {
return getReader(clazz).readValue(is);
}

@Override
public <T extends Serializable> T readObjectFromString(final Class<T> clazz, final String input)
throws IOException {
return getReader(clazz).readValue(input);
}

private static ObjectMapper newObjectMapper() {
final PolymorphicTypeValidator ptv = BasicPolymorphicTypeValidator
.builder()
.allowIfBaseType(List.class)
.allowIfBaseType(Map.class)
.allowIfBaseType(Set.class)
.allowIfSubType(List.class)
.allowIfSubType(Map.class)
.build();
return JsonMapper.builder()
.enable(MapperFeature.BLOCK_UNSAFE_POLYMORPHIC_BASE_TYPES)
.activateDefaultTyping(ptv)
.addModule(new JavaTimeModule())
.addModule(new Jdk8Module())
.visibility(PropertyAccessor.ALL, Visibility.NONE)
.visibility(PropertyAccessor.FIELD, Visibility.ANY)
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
.serializationInclusion(Include.NON_NULL)
.build();
}

protected abstract ObjectMapper createObjectMapper();

private ObjectReader getReader(final Class<?> clazz) {
return readers.computeIfAbsent(clazz, c -> OBJECT_MAPPER_THREAD_LOCAL.get().readerFor(c));
}

private ObjectWriter getWriter(final Class<?> clazz) {
return writers.computeIfAbsent(clazz, c -> OBJECT_MAPPER_THREAD_LOCAL.get().writerFor(c));
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package tk.dmanstrator.sb3_serialization_test.serialization.impl;

import com.fasterxml.jackson.databind.ObjectMapper;

public class JsonSerializationHandler extends AbstractJsonSerializationHandler {

/**
* Creates a new JSON Serializer. Won't apply pretty-printing when writing objects.
*/
public JsonSerializationHandler() {
this(false);
}

/**
* Creates a new JSON Serializer.
*
* @param doPrettyPrint Flag to tell whether pretty-printing should be performed when writing objects or not.
*/
public JsonSerializationHandler(final boolean doPrettyPrint) {
super(doPrettyPrint);
}

@Override
public boolean isBinary() {
return false;
}

@Override
public String getFileNameExtension() {
return ".json";
}

public static ObjectMapper getStandardObjectMapper() {
return OBJECT_MAPPER_THREAD_LOCAL.get();
}

@Override
protected ObjectMapper createObjectMapper() {
return new ObjectMapper();
}

}
Loading