Skip to content

5) Serializible, Externalizable

Сериализация в контексте протокола HTTP:

Сериализация в контексте HTTP - это процесс преобразования объекта Java в формат, который можно передать по сети с использованием протокола HTTP. Обычно объект преобразуется в JSON, XML или другой текстовый формат, который можно включить в тело HTTP-запроса или ответа.

// Используем библиотеку Jackson для сериализации в JSON
ObjectMapper objectMapper = new ObjectMapper();

User user = new User("John", "Doe");
String jsonString = objectMapper.writeValueAsString(user);

System.out.println(jsonString); // {"firstName":"John","lastName":"Doe"}

Десериализация в контексте протокола HTTP:

Десериализация - это обратный процесс сериализации. В контексте HTTP это преобразование данных, полученных в теле HTTP-запроса или ответа (например, JSON или XML), обратно в объект Java.


String jsonString = "{\"firstName\":\"John\",\"lastName\":\"Doe\"}";

User user = objectMapper.readValue(jsonString, User.class);

System.out.println(user.getFirstName()); // John

HttpMessageConverter:

HttpMessageConverter - это интерфейс в Spring Framework, который отвечает за преобразование HTTP-запросов и ответов. Он играет ключевую роль в процессах сериализации и десериализации. Подробная информация о HttpMessageConverter:

Основная задача: преобразование объектов Java в HTTP-сообщения и обратно. Работает в обоих направлениях: от Java-объекта к HTTP-сообщению (для ответов) и от HTTP-сообщения к Java-объекту (для запросов). Spring предоставляет несколько реализаций для работы с различными форматами данных (JSON, XML, форм-данные и т.д.). Разработчики могут создавать собственные реализации для поддержки специфических форматов данных.

Spring поставляет несколько реализаций HttpMessageConverter для различных типов данных:

MappingJackson2HttpMessageConverter — для JSON.
Jaxb2RootElementHttpMessageConverter — для XML.
StringHttpMessageConverter — для строковых данных.

Примеры для лучшего понимания:

Использование Jackson для JSON:

import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.client.RestTemplate;

public class HttpConverterExample {
    public static void main(String[] args) {
        RestTemplate restTemplate = new RestTemplate();

        // Добавляем Jackson конвертер в RestTemplate
        restTemplate.getMessageConverters().add(new MappingJackson2HttpMessageConverter());

        // Теперь RestTemplate может автоматически сериализовать и десериализовать JSON
        MyObject myObject = restTemplate.getForObject("http://example.com/api/data", MyObject.class);

        // Отправка объекта на сервер (сериализация в JSON)
        restTemplate.postForObject("http://example.com/api/data", myObject, String.class);
    }
}

class MyObject {
    private String name;
    private int age;

    // геттеры и сеттеры
}

В этом примере MappingJackson2HttpMessageConverter автоматически обрабатывает сериализацию объекта MyObject в JSON при отправке POST-запроса и десериализацию JSON в MyObject при получении GET-запроса.

Создание пользовательского HttpMessageConverter:

Иногда может потребоваться создать собственный конвертер для обработки специфического формата данных.

import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.AbstractHttpMessageConverter;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;

import java.io.IOException;
import java.nio.charset.StandardCharsets;

public class CustomHttpMessageConverter extends AbstractHttpMessageConverter<MyCustomObject> {

    public CustomHttpMessageConverter() {
        super(new MediaType("application", "x-custom", StandardCharsets.UTF_8));
    }

    @Override
    protected boolean supports(Class<?> clazz) {
        return MyCustomObject.class.isAssignableFrom(clazz);
    }

    @Override
    protected MyCustomObject readInternal(Class<? extends MyCustomObject> clazz, HttpInputMessage inputMessage) 
            throws IOException, HttpMessageNotReadableException {
        // Реализация десериализации
        String body = StreamUtils.copyToString(inputMessage.getBody(), StandardCharsets.UTF_8);
        // Пример простой десериализации
        String[] parts = body.split("\\|");
        return new MyCustomObject(parts[0], Integer.parseInt(parts[1]));
    }

    @Override
    protected void writeInternal(MyCustomObject myCustomObject, HttpOutputMessage outputMessage) 
            throws IOException, HttpMessageNotWritableException {
        // Реализация сериализации
        String output = myCustomObject.getName() + "|" + myCustomObject.getAge();
        outputMessage.getBody().write(output.getBytes(StandardCharsets.UTF_8));
    }
}

class MyCustomObject {
    private String name;
    private int age;

    // конструктор, геттеры и сеттеры
}

Этот пользовательский конвертер обрабатывает объекты типа MyCustomObject, сериализуя их в строку формата "name|age" и десериализуя из этого же формата. Для использования этого конвертера его нужно зарегистрировать в конфигурации Spring:

@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        converters.add(new CustomHttpMessageConverter());
    }
}

Эти примеры демонстрируют, как HttpMessageConverter играет ключевую роль в процессах сериализации и десериализации в контексте HTTP-протокола, позволяя легко преобразовывать Java-объекты в формат, подходящий для передачи по сети, и обратно.


Вид сериализации:

В данном примере используется стандартная сериализация Java (Java Serialization). Этот механизм позволяет преобразовывать объекты Java в последовательность байтов, которую можно сохранить в файл или передать по сети, а затем восстановить объект из этой последовательности байтов.

Ключевые аспекты реализации:

a) Интерфейс Serializable: Класс User реализует интерфейс Serializable. Это маркерный интерфейс, который указывает JVM, что объекты этого класса могут быть сериализованы. b) serialVersionUID: Определение private static final long serialVersionUID = 1L; используется для контроля версий сериализованных объектов. Это помогает при десериализации определить, совместима ли текущая версия класса с сериализованными данными. c) Транзиентные поля: Ключевое слово transient используется для поля password. Это означает, что данное поле не будет сериализовано стандартным способом, что полезно для конфиденциальных данных. d) Пользовательская сериализация: Методы writeObject и readObject реализуют пользовательскую логику сериализации и десериализации. Это позволяет контролировать процесс и обрабатывать транзиентные поля особым образом.

Процесс сериализации и десериализации:

a) Сериализация:

Создается объект ObjectOutputStream, связанный с FileOutputStream. Метод writeObject() вызывается для записи объекта в поток. При этом автоматически вызывается пользовательский метод writeObject() класса User.

b) Десериализация:

Создается объект ObjectInputStream, связанный с FileInputStream. Метод readObject() вызывается для чтения объекта из потока. При этом автоматически вызывается пользовательский метод readObject() класса User.

Особенности и преимущества использованного подхода:

a) Безопасность: Пользовательская сериализация позволяет безопасно обрабатывать конфиденциальные данные (пароль). Несмотря на то, что поле password объявлено как transient, оно все равно сериализуется, но в зашифрованном виде. b) Гибкость: Методы writeObject и readObject позволяют реализовать любую нестандартную логику сериализации и десериализации. c) Совместимость: Использование serialVersionUID обеспечивает контроль версий, что важно при обновлении класса и работе с ранее сериализованными данными.

Смежные использования:

a) Сохранение состояния объектов: Этот механизм может использоваться для сохранения состояния приложения, например, для реализации функции "сохранить/загрузить" в играх или приложениях. b) Передача объектов по сети: Сериализация часто используется в распределенных системах для передачи объектов между разными JVM, например, в RMI (Remote Method Invocation). c) Кэширование: Сериализованные объекты могут быть сохранены в кэше для быстрого доступа без необходимости повторного создания объекта. d) Глубокое клонирование: Сериализация и последующая десериализация объекта может использоваться для создания глубокой копии объекта. Этот пример демонстрирует мощь и гибкость механизма сериализации в Java, позволяя разработчикам контролировать процесс сохранения и восстановления состояния объектов, что особенно важно при работе с конфиденциальными данными или сложными структурами объектов.

import java.io.*;

class User implements Serializable {
    private static final long serialVersionUID = 1L;
    private String name;
    private int age;
    private transient String password;

    // Конструктор, геттеры и сеттеры опущены для краткости

    private void writeObject(ObjectOutputStream out) throws IOException {
        out.defaultWriteObject();
        out.writeObject("SECRET_KEY:" + password);
    }

    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
        in.defaultReadObject();
        String secretPassword = (String) in.readObject();
        password = secretPassword.substring(11);
    }
}

public class Main {
    public static void main(String[] args) {
        User user = new User("John", 30, "password123");

        // Сериализация
        try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("user.ser"))) {
            out.writeObject(user);
            System.out.println("Объект User сериализован");
        } catch (IOException e) {
            e.printStackTrace();
        }

        // Десериализация
        try (ObjectInputStream in = new ObjectInputStream(new FileInputStream("user.ser"))) {
            User deserializedUser = (User) in.readObject();
            System.out.println("Объект User десериализован");
            System.out.println("Имя: " + deserializedUser.getName());
            System.out.println("Возраст: " + deserializedUser.getAge());
            System.out.println("Пароль: " + deserializedUser.getPassword());
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}
мой пример

https://gitlab.com/synergy9980417/razdel2/5_5/-/blob/main/src/main/java/org/example/Main.java?ref_type=heads