Melhor prática para serialização para beans EJB e CDI

Ainda não experimentei nenhum problema relacionado à serialização. Mas PMD e Findbugs detectam um monte de problemas potenciais em relação à seriação. Um caso típico é um registrador injetado que está sendo detectado como não serializável. mas há muitos mais – EntityManager e vários beans CDI.

Não encontrei nenhuma prática recomendada sobre como lidar corretamente com a serialização.

  • Os campos, injetados por @Inject e @PersistenceContext serão reinjetados na desserialização?
  • eles devem ser marcados como transient ?
  • ou devo apenas ignorar / desligar as verificações de código?
  • devo realmente fornecer assessores para todos os campos como o PMD aconselha?

Esta resposta irá detalhar a semântica de serialização / passivação para o EJB 3.2 ( JSR 345 ), JPA 2.1 ( JSR 338 ) e CDI 1.2 ( JSR 346 ). Vale ressaltar que a especificação guarda-chuva Java EE 7 ( JSR 342 ), a especificação Managed Beans 1.0 ( JSR 316 ) e a especificação Commons Annotations 1.2 ( JSR 250 ) não tem nada a dizer que nos interessa em relação à serialização / passivação.

Eu também vou abordar o tópico dos analisadores de código estático.

EJB

As seções relevantes são “4.2 Estado conversacional de um bean de session com informações de estado” e “4.2.1 Instância de passivação e estado de conversação”.

@Stateless instâncias @Stateless e @Singleton nunca são passivadas.

@Stateful instâncias de estado podem ser passivadas. Desde o EJB 3.2, o desenvolvedor da class pode desativar a passivação usando @Stateful(passivationCapable=false) .

A especificação EJB observa explicitamente que as referências a itens como UserTransaction , EntityManagerFactory e EntityManager gerenciado por contêiner são atendidas pelo contêiner. Uma instância @Stateful que usa um contexto de persistência estendido não será passivado a menos que todas as entidades no contexto de persistência e a implementação EntityManager sejam serializáveis.

Observe que um EntityManager gerenciado por aplicativo sempre usa um contexto de persistência estendido. Além disso, uma instância @Stateful é o único tipo de instância de session EJB que pode usar um EntityManager gerenciado por contêiner com um contexto de persistência estendido. Esse contexto de persistência estaria vinculado ao ciclo de vida da instância @Stateful em vez de uma única transação JTA.

A especificação EJB não aborda explicitamente o que acontece com um EntityManager gerenciado por contêiner com um contexto de persistência estendido. Meu entendimento é o seguinte: se houver um contexto de persistência estendido, esse indivíduo deve ser considerado serializável ou não de acordo com as regras definidas anteriormente e, se for, a passivação continuará. Se a passivação prosseguir, então o desenvolvedor da class @Stateful só precisa se preocupar com referências a gerenciadores de entidades gerenciados por aplicativos.

A especificação EJB não especifica o que acontece com os campos transitórios além de descrever uma suposição que nós, como desenvolvedores, deveríamos fazer.

Seção 4.2.1 diz:

O Provedor do Bean deve assumir que o conteúdo dos campos transitórios pode ser perdido entre as notifications PrePassivate e PostActivate.

[…]

Embora o contêiner não precise usar o protocolo Serialization para a linguagem de programação Java para armazenar o estado de uma instância de session passivada, ele deve alcançar o resultado equivalente. A única exceção é que os contêineres não precisam redefinir o valor dos campos temporários durante a ativação. Declarar os campos do bean de session como transitórios é, em geral, desencorajado.

Exigir que o contêiner “alcance o resultado equivalente” como o protocolo de serialização Javas, ao mesmo tempo deixando-o totalmente não especificado quanto ao que acontece com os campos transitórios, é muito triste, para ser honesto. A lição para levar para casa é que nada deve ser marcado como transitório. Para campos que o contêiner não pode manipular, use @PrePassivate para gravar um null e @PostActivate para restaurar.

JPA

A palavra “passivação” não ocorre na especificação JPA. A JPA também não define a semântica de serialização para tipos como EntityManagerFactory , EntityManager , Query e Parameter . A única frase na especificação relevante para nós é esta (seção “6.9 Execução de Consulta”):

Os objects CriteriaQuery, CriteriaUpdate e CriteriaDelete devem ser serializáveis.

CDI

A seção “6.6.4. Escopos de passivação” define um escopo de passivação como um escopo explicitamente anotado como @NormalScope(passivating=true) . Esta propriedade é padronizada como falsa.

Uma implicação é que @Dependent – que é um pseudo-escopo – não é um escopo capaz de passivar. Também digno de nota é que javax.faces.view.ViewScoped não é um escopo capaz de passivar que, por qualquer motivo, a maioria da Internet parece acreditar. Por exemplo, a seção “17-2. Desenvolvendo um aplicativo JSF” no livro “Receitas Java 9: ​​uma abordagem problema-solução”.

Um escopo capaz de passivação requer que instâncias de classs declaradas “com o escopo são capazes de passivar” (seção “6.6.4. Escopos passivadores”). A seção “6.6.1. Beans com capacidade de passivação” define tal instância de object simplesmente como sendo transferível para armazenamento secundário. Anotações ou interfaces de class especiais não são um requisito explícito.

As instâncias do EJB: s @Stateless e @Singleton não são “beans com capacidade de passivação”. @ Stateful pode ser (stateful é o único tipo de session EJB que faz sentido deixar o CDI gerenciar o ciclo de vida de – ou seja, nunca colocar um escopo de CDI em um @Stateless ou @Singleton). Outros “beans gerenciados” são apenas “beans com capacidade de passivação” se eles e seus interceptores e decoradores são todos serializáveis.

Não ser definido como um “bean com capacidade de passivação” não significa que coisas como stateless, singleton, EntityManagerFactory, EntityManager, Event e BeanManager não possam ser usadas como dependencies dentro de uma instância com capacidade de passivação que você cria. Em vez disso, essas coisas são definidas como “dependencies com capacidade de passivação” (consulte a seção “6.6.3. Dependências com capacidade de passivação” e “3.8. Beans internos adicionais”).

O CDI torna essas depedências passíveis de capacidade através do uso de proxies capazes de passivar (veja o último item com marcadores na seção “5.4. Client proxies” e a seção “7.3.6. Ciclo de vida dos resources”). Observe que, para resources Java EE, como EntityManagerFactory e EntityManager, serem passíveis de serem passados, eles devem ser declarados como um campo produtor CDI (seção “3.7.1. Declarando um Recurso”), eles não suportam nenhum outro escopo que @Dependent (veja a seção “3.7. Recursos”) e eles devem ser pesquisados ​​no lado do cliente usando @Inject.

Outras instâncias @Dependent – embora não declaradas com um escopo normal e não obrigadas a serem fronteadas por um “proxy cliente” de CDI – também podem ser usadas como dependência passiva capaz se a instância for transferível para armazenamento secundário, isto é, serializável. Esse cara será serializado junto com o cliente (veja o último item com marcadores na seção “5.4. Client proxies”).

Para ser perfeitamente claro e fornecer alguns exemplos; Uma instância @Stateless, uma referência a um EntityManager produzido por CDI e uma instância serializável @Dependent podem ser usadas como campos de instância dentro de sua class, anotados com um escopo capaz de passivar.

Analisadores de código estático

Analisadores de código estático são estúpidos. Eu acho que para os desenvolvedores seniores, eles são mais um motivo de preocupação do que ser um ajudante. As flags falsas levantadas por esses analisadores para problemas de serialização / passivação suspeitos são certamente de valor muito limitado porque o CDI requer que o contêiner valide que a instância “realmente é capaz de passivar e que, além disso, suas dependencies são capazes de passivar” ou subclass de javax.enterprise.inject.spi.DeploymentException “(seção” 6.6.5. Validação de beans e dependencies com capacidade de passivação “e” 2.9. Problemas detectados automaticamente pelo contêiner “).

Finalmente, como outros apontaram, vale a pena repetir: nós provavelmente nunca deveríamos marcar um campo como transient .

Eu percebo que essa é uma pergunta antiga, mas acredito que a única resposta fornecida esteja incorreta.

Os campos, injetados por @Inject e @PersistenceContext, serão reinjetados na desserialização?

Não, eles não vão. Eu pessoalmente experimentei isso com o JBoss em um ambiente de cluster. Se o bean é capaz de passivar, então o container deve injetar um proxy serializável. Esse proxy é serializado e desserializado. Uma vez desserializado, ele localizará a injeção adequada e a reconectará. No entanto, se você marcar o campo transitório, o proxy não será serializado e você verá NPEs quando o recurso injetado for acessado.

Deve-se notar que o recurso injetado ou bean não precisa ser Serializable, porque o proxy será. A única exceção é para os beans com escopo definido pelo @Dependent que precisam ser serializáveis ​​ou o transiente de injeção. Isso ocorre porque um proxy não é usado neste caso.

eles devem ser marcados como transitórios?

Não, veja acima.

ou devo apenas ignorar / desligar as verificações de código?

Isso depende de você, mas é o que eu faria.

devo realmente fornecer assessores para todos os campos como o PMD aconselha?

Não, eu não faria. Em nossos projetos, desabilitamos essa verificação quando sabemos que estamos usando o CDI.

O PMD e o FindBugs estão apenas verificando as interfaces e também não possuem informações sobre o ambiente no qual seu código será executado. Para acalmar as ferramentas, você pode marcá-las como transitórias, mas todas elas serão reinjetadas adequadamente após a desserialização e o primeiro uso, independentemente da palavra-chave transitória.