DEV Community

Cover image for Cache de dados em Java utilizando Proxy Dinâmico e Annotations
josevjunior
josevjunior

Posted on

Cache de dados em Java utilizando Proxy Dinâmico e Annotations

Durante o desenvolvimento de uma aplicação vão existir diversos momentos que será necessário implementarmos algum tipo de cache. Seja para diminuirmos acessos à algum serviço externo como um banco de dados ou webservice, ou para evitarmos a criação desnecessária de objetos ditos como "caros". Exemplos comuns desse tipo de objeto são EntityManagerFactory, DataSources, etc...

Normalmente, temos que modificar trechos de código para utilizarmos o cache em algum ponto de nosso sistema. Pode ser que seja apenas um simples if ou uma modificação mais complexa, não iremos escapar de "sujar" nosso código com alguma verificação ou tratamento.

No exemplo abaixo temos uma rotina antes e depois da implementação de cache utilizando um HashMap:

Antes do cache

Depois do cache

Pode parecer uma alteração simples, mas dependendo da quantidade de métodos que precisam ser cacheados, como os métodos estão organizados ou como os valores são salvo no cache, pode se tornar complexo. No exemplo anterior o identificador para acessar os avisos é o próprio código do usuário para qual o aviso foi destinado, mas pode haver outras situações em que precisaremos identificar por usuário e data, ou somente por data. No balanço das horas tudo pode mudar.

Para evitarmos incluir esse tipo de regra de interesse transversal no nosso código, vamos utilizar 2 funcionalidades bastante utilizadas no mundo java: os proxies dinâmicos e as annotations.

Proxy Dinâmico

É uma classe que pode implementar um conjunto de interfaces definidas em tempo de execução e, em cada chamada feita a um dos método dessas interfaces, um despachante será chamado e ficará responsável por definir o comportamento da execução do método. Ou seja, teremos controle do próximo método a ser chamada antes da ação acontecer.

Proxies são muito usados no mundo dos frameworks de injeção de dependências. Pois, como não criamos nossos objetos explicitamente através do comando new, isso dá liberdade para eles, internamente, criarem proxies de nossos objetos adicionando funcionalidades a eles que em um primeiro momento pode parecer mágica, como abrir e fechar uma transação no BD durante a execução de um método ou chamar outro trecho de código que "intercepta" nosso objeto.

Neste exemplos não usaremos o mecanismo de proxy nativo da jdk mas sim um provido pela biblioteca cglib. Através dessa lib não ficaremos presos a utilização de proxy dinâmico apenas em interfaces e poderemos utilizá-los em classes concretas.

Annotations

As annotations ou anotações, por outro lado, não foram feitas para mudar o comportamento de um objeto em tempo de execução, mas sim definir informações(metadados) sobre determinado objeto que podem ser obtidas durante sua execução ou compilação. Podemos ver a utilização das anotações sempre que implementamos o método de uma interface ou sobrescrevemos um método de alguma superclasse.

No caso do @Override, estamos adicionados a informação que o seguinte método está sendo sobrescrito e que não pertence originalmente a essa classe. Tecnicamente isso não altera em nada o comportamento da aplicação, mas através de um proxy é possível verificar se um método, classe, parâmetro ou atributo possui anotações e a partir disso adicionar um comportamento novo de acordo com as informados obtidas.

Implementado nosso cache

Para iniciar, vamos definir nossas annotations e que tipo de informação elas irão nos passar.

Através da anotação Cacheable informaremos qual método gostaríamos de cachear e qual o identificador dele no cache.

Após ter definido o que identificará um método como cacheável, vamos partir para a api responsável por expor os dados cacheados

A classe CacheValue terá o papel de representar um valor cacheado em nossa api, além de conter informações adicionais como a data de criação do valor.

Para consultar ou editar esses valores implementaremos a interface CacheManager

Por ser uma interface ganhamos certa liberdade para definir o mecanismo/engine de armazenamento de dados. Neste exemplo, vamos salvar os dados em um Map. Se quisermos algo mais elaborado, no futuro basta trocar a implementação para usar algo mais "potente" como o Redis ou até mesmo um SGBD.

A implementação do CacheManager que utilizaremos neste exemplo:

Agora vamos ao proxy...

Como dito anteriormente, utilizaremos a cglib para criar proxy's de classes concretas. Para incluir essa dependência em um projeto maven basta adicionar a dependência abaixo no pom.xml

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.2.4</version>
</dependency>
Enter fullscreen mode Exit fullscreen mode

Com essa lib no classpath, implementamos a interface net.sf.cglib.proxy.MethodInterceptor, que contêm o método intercept(Object o, Method method, Object[] os, MethodProxy mp). Ele, como o nome diz, irá interceptar qualquer método cuja chamada for feita pela instancia do proxy. Através da classe Method temos acesso ao método que está sendo executado. Utilizaremos essas informações para saber quais serão cacheados ou não. A classe CacheProxy abaixo mostra toda essa lógica:

O trecho seguinte mostra o método que utilizaremos para criar os proxys. Nele vemos como definimos a classe que será 'proxiada' e como vinculamos o ´MethodInteceptor´.

Utilizando nosso cache

Vamos criar uma classe fictícia responsável por consultar as mensagens disponíveis para cada usuário e definir um de seus método com a anotação @Cacheable

Os valores serão armazenados em estrutura de Map (Chave => Valor) da seguinte forma:

       CHAVE                  VALOR
 ------------   ---
|"USER_MESSAGE"| 1 | => [MENSAGEM_1, MENSAGEM_2]   
 ------------   ---

 -------------- ---
|"USER_MESSAGE"| 2 | => []   
 -------------- ---
Enter fullscreen mode Exit fullscreen mode

O cacheName definido na anotação, em conjunto com os valores dos parâmetros, serão usados como identificadores para salvar o retorno do método em cache. Dessa forma, cada chamada ao método utilizando um valor diferente no parâmetro será um "registro" cacheado.

Alguns testes foram feitos para validar o funcionamento do nosso mecanismo de cache.

set 05, 2021 6:26:22 PM br.com.simplecache.core.CacheProxy intercept
INFORMAÇÕES: Executando método cacheavel: getMessagesOfUser
set 05, 2021 6:26:22 PM br.com.simplecache.core.CacheProxy intercept
INFORMAÇÕES: Consultando valor no cache...
set 05, 2021 6:26:22 PM br.com.simplecache.core.CacheProxy intercept
INFORMAÇÕES: Não foi encontrado nenhum valor no cache. Executando método original...
set 05, 2021 6:26:22 PM br.com.simplecache.service.MessageService getMessagesOfUser
INFORMAÇÕES: Acessando messagens do usuário...
set 05, 2021 6:26:23 PM br.com.simplecache.core.CacheProxy intercept
INFORMAÇÕES: Salvando retorno no cache...
set 05, 2021 6:26:23 PM br.com.simplecache.core.CacheProxy intercept
INFORMAÇÕES: Executando método cacheavel: getMessagesOfUser
set 05, 2021 6:26:23 PM br.com.simplecache.core.CacheProxy intercept
INFORMAÇÕES: Consultando valor no cache...
set 05, 2021 6:26:23 PM br.com.simplecache.core.CacheProxy intercept
INFORMAÇÕES: Retornando valor cacheado
É a mesma lista: true
set 05, 2021 6:26:23 PM br.com.simplecache.core.SimpleCacheManager removeAll
INFORMAÇÕES: Limpando todo o cache
set 05, 2021 6:26:23 PM br.com.simplecache.core.CacheProxy intercept
INFORMAÇÕES: Executando método cacheavel: getMessagesOfUser
set 05, 2021 6:26:23 PM br.com.simplecache.core.CacheProxy intercept
INFORMAÇÕES: Consultando valor no cache...
set 05, 2021 6:26:23 PM br.com.simplecache.core.CacheProxy intercept
INFORMAÇÕES: Não foi encontrado nenhum valor no cache. Executando método original...
set 05, 2021 6:26:23 PM br.com.simplecache.service.MessageService getMessagesOfUser
INFORMAÇÕES: Acessando messagens do usuário...
set 05, 2021 6:26:23 PM br.com.simplecache.core.CacheProxy intercept
INFORMAÇÕES: Salvando retorno no cache...
É a mesma lista: false
Enter fullscreen mode Exit fullscreen mode

Neste post quis mostrar uma forma de implementar um cache um pouco mais sofisticado, mas sem partir para a utilização de um container CDI, um servidor de aplicação ou migrar para o Spring, já que em sistemas legados não temos tanta liberdade para implementar esse tipo de solução e essa seria um proposta menos invasiva.

Repositório com os fontes: https://github.com/josevjunior/simple-cache-manager

Referências

Top comments (0)