Cache no Spring com anotações do EhCache
Publicados: 2022-03-11O EhCache é um cache Java puro amplamente usado que pode ser facilmente integrado às estruturas Java mais populares, como Spring e Hibernate. Muitas vezes, é considerado a escolha mais conveniente para aplicativos Java, pois pode ser facilmente integrado a projetos. Em particular:
- Ele pode ser configurado simplesmente incluindo o JAR em seu projeto. Nenhuma etapa de instalação adicional é necessária.
- Ele é executado no mesmo processo com o aplicativo, então é rápido. Nenhum serviço adicional é necessário para ser executado.
Resumindo, o EhCache é uma ótima opção para qualquer aplicativo Java puro.
Além disso, o EhCache Spring Annotations permite uma integração perfeita em qualquer aplicativo Spring simplesmente adicionando anotações a métodos que podem ser armazenados em cache, sem modificar as implementações do método.
Embora o EhCache forneça APIs ricas e diretas para manipular o cache programaticamente, este artigo se concentra principalmente em impulsionar seus aplicativos Spring de uma maneira menos intrusiva com o EhCache Spring Annotations. Vamos configurar um projeto Spring MVC e implantar um serviço web RESTful no Tomcat. Em seguida, o EhCache será integrado ao serviço web.
Visão Geral do Projeto
Demonstraremos as anotações do EhCache no contexto de um projeto de exemplo. Vamos configurar um serviço web baseado em Spring MVC hospedado em um servidor Tomcat 8.
Desenvolvi o projeto no Eclipse, que pode ser instalado seguindo as instruções aqui.
A última versão estável do Tomcat, Tomcat 8, pode ser baixada aqui.
Obviamente, essas plataformas específicas não são um requisito para o EhCache; você sempre pode escolher seu IDE e servidor favorito.
O EhCache Spring Annotations JAR está disponível aqui. Como podemos ver, existem dois JARs para cada versão: um com dependências e outro sem. Aquele com dependências também inclui EhCache 2 e Spring 3, que são necessários para que as anotações do EhCache funcionem. É mais fácil de configurar se baixarmos aquele com dependências e o adicionarmos ao nosso caminho de construção.
O EhCache Spring Annotations também é compatível com o Spring 4, embora deva ser configurado separadamente. Não está claro se o projeto suportará o EhCache 3 em um futuro próximo. Para aqueles que estão usando ou pretendem usar o EhCache 3, a abordagem de anotação discutida neste artigo não é recomendada.
Por fim, usaremos o Maven para gerenciar tudo. O Maven vem pré-empacotado com a maioria das instalações do Eclipse, mas também pode ser obtido aqui. As dependências Spring MVC e EhCache Spring Annotations podem ser adicionadas com bastante facilidade, conforme mostrado posteriormente neste artigo.
Configuração do projeto
Se você nunca configurou um projeto Spring antes, você também pode achar o post de Stefan Varga sobre o assunto informativo.
Para esta demonstração, vamos configurar um projeto básico usando o arquétipo do Maven maven-archetype-webapp
. A estrutura geral do arquivo ficará assim:
Crie um diretório, src/main/java
, com três pacotes: com.toptal.blog
, com.toptal.blog.cache
e com.toptal.blog.service
. Nossa fonte de aplicação irá nestes pacotes, conforme descrito mais abaixo.
Vamos definir um servlet do Tomcat chamado “springrest” em web.xml
:
<web-app> ... <servlet> <servlet-name>springrest</servlet-name> <servlet-class> org.springframework.web.servlet.DispatcherServlet </servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>springrest</servlet-name> <url-pattern>/*</url-pattern> </servlet-mapping> </web-app>
A menos que explicitamente especificado de outra forma, um Spring MVC DispatcherServlet
procurará um arquivo de configuração XML chamado {servlet-name}-servlet.xml
no diretório WEB-INF
. Vamos criar um arquivo de configuração chamado springrest-servlet.xml
. Para habilitar os métodos do controlador de processo Spring anotados com @RequestMapping
, vamos simplesmente adicionar <mvc:annotation-driven />
a este arquivo. Além disso, vamos definir o pacote base para o Spring para escanear e registrar automaticamente beans adicionando <context:component-scan base-package="com.toptal.blog" />
. A configuração springrest-servlet.xml
se torna:
<beans ... > <mvc:annotation-driven /> <context:component-scan base-package="com.toptal.blog" /> </beans>
Um serviço Web RESTful simples
Agora que nosso projeto está configurado corretamente, vamos implementar uma API simples de “serviço de mensagens”. Em nosso pacote base, project.toptal.blog
, adicionaremos SpringRestControllerWithEhCache.java
, com um método GET para obter uma mensagem por ID e um método POST para definir uma mensagem por ID:
@RestController @RequestMapping( "/" ) public class SpringRestControllerWithEhCache { @Autowired MessageService messageService; @RequestMapping( value = "/message/{id}", method = RequestMethod.GET ) public String getMessage( @PathVariable Integer id ) { String message = messageService.getMessage( id ); System.out.println( "get message ["+message+"] at "+new Date() ); return message; } @RequestMapping( value = "/message/set/{id}/{message}", method = RequestMethod.POST ) public String setMessage( @PathVariable Integer id, @PathVariable String message ) { System.out.println( "set message ["+message+"] at "+new Date() ); messageService.setMessage( id, message ); return message; } }
Vamos definir a classe MessageService
em com.toptal.blog.service
. Ele acessará as mensagens armazenadas em nosso Sistema de Registros (SOR). Em um aplicativo de produção, o SOR seria algo como um banco de dados relacional. Para simplificar, usaremos um HashMap
:
@Service public class MessageService { private ConcurrentHashMap<Integer, String> messages = new ConcurrentHashMap<Integer, String>(); public String getMessage( Integer id ) { System.out.println( "Getting data from SOR......" ); return messages.get( id ); } public void setMessage( Integer id, String message ){ messages.put( id, message ); } }
Agora, se exportarmos o projeto como um WAR e o implantarmos no Tomcat, poderemos definir uma mensagem, por exemplo “test_message”, para ID=1, criando uma solicitação HTTP POST em http://localhost:8080/EhCacheExample/message/set/1/test_message
. Devemos então ser capazes de obter “test_message” de volta com uma solicitação HTTP GET em http://localhost:8080/EhCacheExample/message/1
. Eu usei o Insomnia como um cliente REST conveniente para fazer meu teste.
Plug In EhCache Spring Anotações
Agora vamos fazer o EhCache trabalhar para nós. Leva apenas alguns passos rápidos para configurar nosso projeto para executar o EhCache corretamente.
Etapa 1: Atualize as dependências para usar as anotações do EhCache Spring
Adicione a dependência EhCache Spring Annotations no pom.xml
do Maven:
<!-- ehcache --> <dependency> <groupId>com.googlecode.ehcache-spring-annotations</groupId> <artifactId>ehcache-spring-annotations</artifactId> <version>1.2.0</version> </dependency>
Etapa 2: configurar um gerenciador de cache personalizado
O Spring possui um gerenciador de cache EhCache integrado, org.springframework.cache.ehcache.EhCacheManagerFactoryBean
. Isso é adequado para a maioria das situações de cache, mas descobri que definir um gerenciador de cache personalizado é útil porque me permite controlar o cache programaticamente ou com anotações, usando o mesmo gerenciador de cache. Este artigo se concentra em anotações, mas vamos em frente e definir um gerenciador de cache personalizado para estarmos prontos caso precisemos. Se você preferir ficar com o gerenciador de cache padrão, pule esta etapa.

Vamos definir a nova classe em com.toptal.blog.cache.CustomCacheManager
:
public class CustomCacheManager extends net.sf.ehcache.CacheManager{ public CustomCacheManager(){ super(); } /* Add your own cache methods here. * * public void myCustomCacheMethod(){ * // your code here * } * */ }
Habilite-o atualizando springrest-servlet.xml
da seguinte forma:
... <ehcache:annotation-driven cache-manager="customCacheManager" /> <bean class="com.toptal.blog.cache.CustomCacheManager" scope="singleton"></bean> ...
Etapa 3: configurar o EhCache
Por fim, crie o arquivo de configuração do EhCache ehcache.xml
no classpath. Por padrão, o Eclipse incluirá src/main/resources
no classpath e colocaremos o arquivo aqui. Este arquivo é necessário para que o EhCache funcione corretamente. Ele define os nomes de cache e algumas propriedades de cada cache, como o timeToLiveSeconds
:
<ehcache xmlms:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="ehcache.xsd"> <diskStore path="cache" /> <cache name="messageCache" maxElementsInMemory="10000" eternal="false" timeToIdleSeconds="0" timeToLiveSeconds="10" overflowToDisk="false" memoryStoreEvictionPolicy="LFU" /> </ehcache>
Etapa 4: testar o cache
Agora, com tudo configurado e pronto, usar o EhCache deve ser um trabalho fácil e feliz. Podemos simplesmente adicionar @Cacheable
ao método ou classe que queremos armazenar em cache. Por exemplo, adicionei @Cacheable
ao método getMessage
em MessageService
. É tão fácil!
@Cacheable( cacheName = "messageCache" ) public String getMessage( Integer id ) { System.out.println( "Getting data from SOR......" ); return messages.get( id ); }
Para testar se nosso cache está funcionando, podemos criar uma mensagem para ID=1 emitindo uma solicitação HTTP POST em http://localhost:8080/EhCacheExample/message/set/1/newMessage
e, em seguida, obter a mensagem para ID= 1 várias vezes, com solicitações GET para http://localhost:8080/EhCacheExample/message/1
. Como você pode ver na saída do console abaixo, o serviço da Web solicita que o SOR receba a mensagem na primeira vez que solicitamos a mensagem, mas não nas próximas duas solicitações, retornando a mensagem em cache. Como definimos o timeToLiveSeconds
como 10, o serviço da web chama o SOR para receber a mensagem novamente após 10 segundos:
set message [newMessage] at Sun Dec 06 23:55:39 MST 2015 get message [newMessage] at Sun Dec 06 23:55:42 MST 2015 Getting data from SOR...... get message [newMessage] at Sun Dec 06 23:55:47 MST 2015 get message [newMessage] at Sun Dec 06 23:55:49 MST 2015 get message [newMessage] at Sun Dec 06 23:55:54 MST 2015 Getting data from SOR......
Atualizando o cache
Agora, estamos aproveitando a velocidade e a conveniência que um cache nos oferece, e o EhCache é bom o suficiente para atualizar sozinho a cada 10 segundos. Mas e se quisermos atualizá-lo imediatamente após a atualização do nosso SOR? EhCache Spring Annotation oferece @TriggersRemove
para remover chaves especificadas do cache quando o método anotado é chamado. Em nossa API de serviço de mensagens, a mensagem em cache deve ser removida do cache quando setMessage
é chamado. Assim, da próxima vez que uma solicitação getMessage
chegar, o cache buscará um novo registro do SOR:
@Cacheable( cacheName = "messageCache", keyGenerator = @KeyGenerator ( // method name is not included in cache key to work with @TriggersRemove name = "HashCodeCacheKeyGenerator", properties = @Property( name="includeMethod", value="false" ))) public String getMessage( Integer id ) { System.out.println( "Getting data from SOR......" ); return messages.get( id ); } @TriggersRemove( cacheName = "messageCache", keyGenerator = @KeyGenerator ( name = "HashCodeCacheKeyGenerator", properties = @Property( name="includeMethod", value="false" ))) public void setMessage( @PartialCacheKey Integer id, String message ) { messages.put( id, message ); }
Um gerador de chave é usado pelo gerenciador de cache para gerar a chave de cache. Uma lista de geradores de chave de cache predefinidos pode ser encontrada aqui. Por padrão, @KeyGenerator
consome o nome do método e os parâmetros passados para gerar a chave de cache. Mas como queremos que o método setMessage
gere a mesma chave que getMessage
e exclua o valor armazenado em cache associado a essa chave, devemos usar apenas o ID da mensagem como chave e eliminar o nome do método para geração de chave. Portanto, definimos a propriedade includeMethod
do gerador de chaves como false
para ambos os métodos. Além disso, como setMessage
tem dois argumentos, usamos a anotação @PartialCacheKey do @PartialCacheKey
no parâmetro id
para especificar que é o único que deve ser usado pelo gerador de chaves. Por fim, lembre-se de que configuramos um cache dedicado, messageCache
, para esse tipo de recurso, portanto, usar apenas o ID para a chave não apresenta perigo de conflitos com outros tipos de recursos.
Agora, se fizermos várias requisições HTTP para a mensagem com ID=1, da seguinte forma:
HTTP POST: http://localhost:8080/EhCacheExample/message/set/1/newMessage1 HTTP GET:http://localhost:8080/EhCacheExample/message/1 HTTP POST: http://localhost:8080/EhCacheExample/message/set/1/newMessage2 HTTP GET:http://localhost:8080/EhCacheExample/message/1
O console mostrará:
set message [newMessage1] at Tue Dec 08 17:53:44 MST 2015 get message [newMessage1] at Tue Dec 08 17:53:47 MST 2015 Getting data from SOR...... set message [newMessage2] at Tue Dec 08 17:53:50 MST 2015 get message [newMessage2] at Tue Dec 08 17:53:53 MST 2015 Getting data from SOR......
Conclusão
A estrutura final do projeto fica assim:
Neste exemplo, primeiro criamos um aplicativo Web Spring MVC RESTful simples. Sem modificar nem mesmo uma linha do código do aplicativo existente, integramos perfeitamente o EhCache no aplicativo usando o EhCache Spring Annotations. Demonstramos que o EhCache Spring Annotations é fácil de instalar (adicionando sua dependência Maven) e elegante de usar (adicionando anotações aos métodos).
Leitura adicional
A documentação do EhCache pode ser encontrada aqui e a documentação do EhCache Spring Annotations está aqui.
Além disso, confira o projeto de exemplo descrito neste artigo no GitHub.