Spring em Seam, Parte 2: Quando há colisão entre stateless e stateful – Etapa 03

por: Dan Allen


Procurando o balanço correto com injeção

Beans Spring com escopo definido no Seam podem ser injetados em outros beans Spring usando a sintaxe familiar <ref bean="beanId"> (ou equivalente). Neste caso, Spring está de forma transparente atingindo o container Seam para obter a instância do bean fora do escopo do Seam. A transparência é atingida através do mecanismo de escopo personalizado, que é uma versão do Spring de um solucionador de variável. Este cenário é o oposto do que aprendemos sobre isso no primeiro artigo, onde Seam foi de forma transparente obtendo beans a partir do container Spring. A diferença é apenas uma escolha de qual container a instância é armazenada, como indicado pelo escopo definido definição do bean Spring. Caso contrário, um componente híbrido Spring-Seam pode se mascarar como um componente nativo em cada container. Isso significa que podemos injetar qualquer componente Seam (incluindo um outro componente híbrido Spring-Seam) em uma propriedade de um componente híbrido a Spring-Seam usando a anotação @In.

Isso nos traz a um ponto importante sobre injeção que seria interessante ser destacado. Enquanto um componente híbrido Spring-Seam pode influenciar o mecanismo de injeção a partir de cada container, podemos rapidamente chegar a um problema se não entendermos como e quando as injeções são aplicadas em cada caso. Por exemplo, vamos assumir que estamos injetando os critérios de busca no bean tournamentManager. Podemos decidir usar uma referência do bean quando inicializamos a propriedade na definição do bean, como descrito na Listagem 6.

 

Listagem 6. Injetando um bean com escopo do Seam usando uma referência de bean

<bean id="tournamentManager"

  class="org.open18.partner.business.impl.TournamentManagerImpl"

  scope="seam.APPLICATION">

  <property name="tournamentSearchCriteria" ref="tournamentSearchCriteria"/>

</bean>


Você pode também realizar a injeção através do uso da anotação @In, como na Listagem 7.

 

Listagem 7. Injetando um bean com escopo Seam usando injeção

public class TournamentManagerImpl implements TournamentManager {

    @In

    private TournamentSearchCriteria tournamentSearchCriteria;

    ...

}


A forma na qual essas injeções são aplicadas pelos dois containeres é muito diferente. E nenhuma delas é apropriada para injetar componentes stateful em um singleton com escopo de uma aplicação! Spring injeta dependências uma vez no momento da criação, considerando que o Seam realiza injeção dinamicamente, antes da invocação de todo método. Esta diferença na filosofia de projeto pode cair em seus ombros como um desenvolvedor, conduzindo a um paradoxo conhecido como impedância de escopo. Se declarar suas injeções sem qualquer cuidado, isso pode causar sérios problemas. Na próxima seção, aprenderemos como contornar esta armadilha.

Injeção e Impedância de Escopo

Impedância de Escopo é a situação onde um bean sobrevive ao seu tempo de vida desejado como um resultado da operação de anexar a ele um componente de maior duração. Tendo o escopo Seam na mão quando estiver configurando o Spring, você pode ser tentado a definir um bean em um escopo reduzido e injetá-lo em um singleton Spring, que está com o escopo definido para o tempo de vida do container (o contexto da aplicação), usando <ref bean="beanId">. Sem quaisquer outras provisões, isso leva à impedância de escopo. Vamos explorar este problema.

Para descrever o cenário do nosso próximo exemplo, iremos assumir o uso de dois beans. O bean com amplo escopo é um singleton com o escopo da aplicação, tal como um objeto da camada de serviço (ex: tournamentManager). O bean com escopo reduzido está armazenado no escopo da requisição (ex: selectedTournament). O bean no escopo da requisição é dependente do singleton. Quando o singleton é instanciado, o bean no escopo da requisição, povoado pela requisição chamada, é injetado em uma das propriedades do singleton. No entanto, uma vez que a requisição é finalizada, o singleton ainda carregará uma referência ao bean no escopo da requisição. Agora este bean possui sobreviveu ao seu tempo de vida projetado inicialmente. Não apenas isso, ele está sendo compartilhado entre todos os usuários da aplicação porque ele está anexado diretamente a um singleton no escopo da aplicação!

Então por que o Seam não está gerenciando o componente no escopo da requisição de forma que ele seja limpo após a requisição ter sido finalizada, como normalmente seria feito? A razão disso é que a instância do bean no escopo da requisição é associada diretamente a uma propriedade do singleton. Quando o escopo da requisição termina, o Seam simplesmente remove as instâncias do bean do contexto da requisição. Os valores da propriedade dos objetos do singleton não são afetados. Como o singleton está mantendo uma referência à instância do bean no escopo da requisição, o objeto nunca será enviado para o coletor de lixo (garbage collection). Na próxima requisição, o singleton retém sua referência para a instância obsoleta. Como resultado, o singleton pode tomar decisões inapropriadas baseadas no estado da instância obsoleta, o que é especialmente problemático se a próxima requisição não vier do mesmo usuário! Esta situação não apenas causa comportamentos inesperados, mas pode também comprometer a segurança da aplicação.

Se você sabe como bijeção funciona, você pode decidir passar a usar a anotação @In nas propriedades da classe em vez de usar um elemento <ref> na definição do bean (se você não se importar em usar anotações Seam em seus beans Spring). A anotação @In injeta dependências antes da cada chamada de método e as limpa quando a chamada é completa. Isso definitivamente nos deixa fora do predicamento de impedância de escopo. No entanto, injetar componentes stateful utilizando bijeção só é uma operação segura para componentes sincronizados.

Seam serializa o acesso a sessões e componentes de escopo de conversação automaticamente, e componentes de escopo de requisição são naturalmente thread-safe (ou seja, com segurança nas threads), pois eles apenas servem as threads de requisição que estão aguardando. No entanto, Seam não serialize o acesso aos componentes de escopo de aplicação, porque isso causaria um enforcamento incrível na aplicação. Assim, o uso da anotação @In em componentes híbridos Spring-Seam de escopo de aplicação (ou componentes Seam normais) somente é apropriado para injetar outros componentes de escopo de aplicação. Caso contrário, estaremos mais uma vez estar permitindo múltiplas requisições para acessar as mesmas referências simultaneamente.

Isso pode até assustar a todos quando foi mencionado o potencial de criação de uma falha de segurança, especialmente porque ele pode ser tal como uma armadilha fácil de cair. Felizmente, o Seam oferece uma elegante rede segura para nos ajudar a evitar ambos os problemas de impedância de escopo e segurança de thread. Em vez de se injetar uma instância de um bean diretamente usando um elemento <ref>, injetamos um proxy usando a tag <seam:instance>, como feito na Listagem 8 (ver Nota 1).

 

Listagem 8. Injetando um proxy de componente

<bean id="tournamentManager"

  class="org.open18.partner.business.impl.TournamentManagerImpl"

  scope="seam.APPLICATION">

  <property name="tournament">

    <seam:instance name="selectedTournament" proxy="true"/>

  </property>

  <property name="tournamentSearchCriteria">

    <seam:instance name="tournamentSearchCriteria" proxy="true"/>

  </property>

  <property name="currentUser">

    <seam:instance name="currentUser" proxy="true" scope="SESSION"/>

  </property>

</bean>

 

Nota 1. <seam:instance> do Seam x <aop:scoped-proxy> do Spring

Se você está familiarizado com os escopos personalizados do Seam 2.0, você pode
reconhecer que a tag <seam:instance> serve para o mesmo propósito da tag <aop:scoped-proxy>. A tag <aop:scoped-proxy> foi também desenvolvida para contra-atacar impedância de escopo para escopos nativos do Spring. No entanto, esta tag específica do Spring não é compatível com componentes híbridos Spring-Seam. Quando um componente híbrido está sendo injetado, devemos usar a tag <seam:instance> no lugar da tag <aop:scoped-proxy> para obter o comportamento desejado.


Cada vez que o proxy é referenciado, ele consulta o container Seam para garantir que ele possui a instância correta do componente, e não uma instância obsoleta a partir de requisições anteriores ou a partir de uma outra requisição simultânea. Mesmo apesar das propriedades nos componentes de escopo com maior duração ainda permanecem inalteradas quando os escopos limitados terminam, a instância de escopo limitado pode agora ser recolhida pelo coletor de lixo, pois o componente de escopo com maior duração não possui uma referência direta para ela. O proxy atua como uma referência fraca, o que é perfeito para evitar impedância de escopo. Desta forma, estamos efetivamente trabalhando com estado em objetos stateless.

Lembre-se, se você não quer se preocupar com tudo a respeito desse assunto de proxy, você pode simplesmente usar anotações de bijeção, enquanto o bean destino for também um componente stateful. A impedância de escopo surge quando usamos uma injeção de dependência estática do Spring para ligar componentes em diferentes escopos. O conceito de segurança de thread é violado quando injetamos componentes stateful em singletons sem usar um proxy.

Agora que conhecemos um pouco sobre a tag <seam:instance>, você pode estar interessado em saber que ela também pode ser usada para injetar componentes nativos do Seam em beans Spring. Vamos olhar agora como o Spring pode se beneficiar a partir de componentes Seam já existentes.