Um novo
documento de pesquisa apareceu no site do OpenJDK que descreve a idéia de introduzir uma nova serialização aprimorada no idioma para substituir o antigo.
A serialização em Java existe desde a versão 1.1, ou seja, quase desde o momento em que nasceu. Por um lado, a serialização é um mecanismo muito conveniente que permite tornar rápida e facilmente qualquer classe serializável, herdando essa classe da interface java.io.Serializable. Talvez até essa simplicidade tenha se tornado uma das principais razões pelas quais o Java ganhou uma popularidade tão grande no mundo, porque permitiu que você escrevesse aplicativos de rede com rapidez e eficiência.
Por outro lado, a maneira como a serialização é implementada em Java envolve um grande número de problemas que aumentam o custo do suporte a aplicativos, reduzem sua segurança e retardam a evolução da plataforma.
O que há de errado com a serialização em Java? Listamos os problemas mais sérios:
- A serialização (e desserialização) ignora os mecanismos de linguagem. Ele ignora os modificadores de acesso ao campo (privado, protegido) e cria objetos sem o uso de construtores, o que significa que ignora os invariantes que podem estar presentes nesses construtores. Um invasor pode explorar essa vulnerabilidade substituindo dados por dados inválidos, e eles são engolidos com êxito durante a desserialização.
- Ao escrever classes serializáveis, o compilador não ajuda em nada e não detecta erros. Por exemplo, você não pode garantir estaticamente que todos os campos de uma classe serializável são eles mesmos serializáveis. Ou você pode digitar os nomes dos métodos readObject, writeObject, readResolve etc., e esses métodos simplesmente não serão utilizados durante a serialização.
- A serialização não suporta o mecanismo de versão normal, portanto, é muito difícil modificar as classes serializáveis para que elas permaneçam compatíveis com suas versões antigas.
- A serialização está fortemente ligada à codificação / decodificação de streaming, o que significa que é muito difícil alterar o formato de codificação para um formato diferente do padrão. Além disso, o formato padrão não é compacto, nem eficiente, nem legível por humanos.
O erro fundamental da serialização existente em Java é que ela tenta ser "invisível" demais para o programador. Ele simplesmente herda de java.io.Serializable e recebe alguma mágica implícita que é executada pela máquina virtual.
Pelo contrário, o programador deve escrever explicitamente construções responsáveis por construir e desconstruir objetos. Essas construções devem estar no nível do idioma e devem ser gravadas através do acesso ao campo estático, não da reflexão.
Outro erro de serialização é que ele está tentando fazer muito. Ele define a tarefa de ser capaz de serializar qualquer gráfico arbitrário de objetos (que pode conter loops) e desserializá-lo de volta sem interromper seu estado.
Esse erro pode ser corrigido simplificando a tarefa e serializando não um gráfico de objetos, mas uma árvore de dados na qual não haverá conceito de identidade (como no JSON).
Como fazer a serialização que se ajusta naturalmente ao modelo de objeto, usa construtores para desserialização, é separada do formato de codificação e suporta controle de versão? Para esse
fim, as anotações chegam ao resgate e a possibilidade de uma linguagem ainda não incluída no Java:
correspondência de padrões . Por exemplo:
public class Range { int lo; int hi; private Range(int lo, int hi) { if (lo > hi) throw new IllegalArgumentException(String.format("(%d,%d)", lo, hi)); this.lo = lo; this.hi = hi; } @Serializer public pattern Range(int lo, int hi) { lo = this.lo; hi = this.hi; } @Deserializer public static Range make(int lo, int hi) { return new Range(lo, hi); } }
Neste exemplo, a classe Range é declarada, que está pronta para serialização por meio de dois membros especiais da classe: um serializador e um desserializador marcados com as anotações @Serializer e @Deserializer. O serializador é implementado através do desconstrutor do padrão, e o desserializador é implementado através do método estático no qual o construtor é chamado. Assim, durante a desserialização, o invariante hi> = lo especificado no construtor é inevitavelmente verificado.
Não há mágica nessa abordagem, e anotações regulares são usadas, portanto, qualquer estrutura pode fazer serialização, e não apenas a plataforma Java. Isso significa que o formato de codificação também pode ser absolutamente qualquer coisa (binário, XML, JSON, YAML etc.).
Como serializadores e desserializadores são métodos comuns, o programador tem grande liberdade em sua implementação. Por exemplo, ele pode escolher uma representação de um objeto diferente da maneira como o objeto é representado na memória. Por exemplo, o LinkedList pode ser serializado não em uma cadeia de links, mas em uma matriz contínua, o que tornará a apresentação mais simples, mais eficiente e mais compacta.
O controle de versão nesta abordagem é implementado usando o campo de versão especial das anotações @Serializer e @Deserializer:
class C { int a; int b; int c; @Deserializer(version = 3) public C(int a, int b, int c) { this a = a; this.b = b; this.c = c; } @Deserializer(version = 2) public C(int a, int b) { this(a, b, 0); } @Deserializer(version = 1) public C(int a) { this(a, 0, 0); } @Serializer(version = 3) public pattern C(int a, int b, int c) { a = this.a; b = this.b; c = this.c; } }
Neste exemplo, um dos três desserializadores será chamado, dependendo da versão.
E se não quisermos serializadores e desserializadores disponíveis para ninguém que não seja para fins de serialização? Para fazer isso, podemos torná-los privados. Entretanto, nesse caso, uma estrutura de serialização específica não poderá acessá-los através da reflexão se esse código estiver dentro do módulo no qual o pacote não está aberto para acesso reflexivo profundo. Nesse caso, propõe-se introduzir outra nova construção no idioma: abrir os alunos. Por exemplo:
class Foo { private final InternalState is; public Foo(ExternalState es) { this(new InternalState(es)); } @Deserializer private open Foo(InternalState is) { this.is = is; } @Serializer private open pattern serialize(InternalState is) { is = this.is; } }
Aqui, serializadores e desserializadores são marcados com a palavra-chave open, que os abre para setAccessible.
Assim, a nova abordagem é fundamentalmente diferente da antiga: nela, as classes são projetadas como serializáveis, e não são dadas à plataforma como são. Isso requer um esforço extra, mas torna a serialização mais previsível, mais segura e independente do formato de codificação e da estrutura de serialização.
Amigos do PS, se você quiser receber notícias semelhantes sobre Java de maneira mais rápida e conveniente, assine o
meu canal no Telegram.