Seções iMasters
Java

Spring 3.1: Cache e EhCache

Se você procurar na web por exemplos do uso do cache built in do Spring 3.1, então normalmente você vai esbarrar no SimpleCacheManager do Spring, que os caras do Spring dizem ser “útil para testar ou para declarações de cache  simples”. Na verdade, eu prefiro pensar em SimpleCacheManager como leve em vez de simples; útil em situações nas quais quer um cache in memory pequeno em uma base por JVM. Se os caras do Spring estivessem gerenciando um supermercado, então o SimpleCacheManager estaria em sua própria gama de produtos da marca “básico”.

Se, por outro lado, você precisar de um cache mais pesado, um que seja escalonável, persistente e distribuído, o Spring também vem com um wrapper ehCache built in.

A boa notícia é que a troca entre as implementações de cache  do Spring é fácil. Em teoria, é tudo uma questão de configuração e, para provar que a teoria está correta, peguei o exemplo de código deste artigo e executei usando uma implementação EhCache.

As etapas da configuração são semelhantes às descritas neste artigo, e para isso você ainda precisa especificar:

<cache:annotation-driven />

…no seu arquivo de configuração do Spring para mudar o armazenamento em cache. Você também precisa definir um bean com uma id do cacheManager, só que desta vez você faz referência à classe EhCacheCacheManager do Spring em vez de SimpleCacheManager.

<bean id="ehcache"
class="org.springframework.cache.ehcache.EhCacheChacheManager"
p:cacheManager-ref="ehcache"/>

O exemplo acima mostra uma configuração do EhCacheCacheManager. Observe que ela faz referência a um segundo bean com uma id do ‘ehcache’. Ele é configurado assim:

<bean id="ehcache" class="org.springframework.cache.ehcache.EhCacheCacheManagerFactoryBean"
p:configLocation="ehcache.xml"
p:shared="true"/>

O “ehcache” tem duas propriedades: configLocation e shared.

“configLocation” é um atributo opcional que é usado para especificar a localização de um arquivo de configuração ehcache. Eu usei no meu código teste o seguinte arquivo de exemplo:

<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd">
<defaultCache eternal="true" maxElementsInMemory="100" overflowToDisk="false" />
<cache name="employee" maxElementsInMemory="10000" eternal="true" overflowToDisk="false" />
</ehcache>

… o qual cria dois caches: um cache padrão e um chamado “employee”.

Se esse arquivo estiver faltando, então o EhCacheManagerFactoryBean simplesmente pega um arquivo de configuração padrão ehcache: ehcache-failsafe.xml, que está localizado no arquivo jar ehcache-core do ehcache.

O outro atributo do EhCacheManagerFactoryBean é “shared”. Isso é para ser opcional, já que a documentação afirma que ele define “se o EHCache CacheManager deve ser compartilhado (como um singleton no nível VM) ou independente (tipicamente local dentro do aplicativo). O padrão é ‘false’, criando uma instância independente”. No entanto, se isso for definido como falso, você terá a seguinte exceção:

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.cache.interceptor.CacheInterceptor#0': Cannot resolve reference to bean 'cacheManager' while setting bean property 'cacheManager'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'cacheManager' defined in class path resource [ehcache-example.xml]: Cannot resolve reference to bean 'ehcache' while setting bean property 'cacheManager'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'ehcache' defined in class path resource [ehcache-example.xml]: Invocation of init method failed; nested exception is net.sf.ehcache.CacheException: Another unnamed CacheManager already exists in the same VM. Please provide unique names for each CacheManager in the config or do one of following:
1. Use one of the CacheManager.create() static factory methods to reuse same CacheManager with same name or create one if necessary
2. Shutdown the earlier cacheManager before creating new one with same name.
The source of the existing CacheManager is: InputStreamConfigurationSource [stream=java.io.BufferedInputStream@424c414]
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:328)
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveValueIfNecessary(BeanDefinitionValueResolver.java:106)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyPropertyValues(AbstractAutowireCapableBeanFactory.java:1360)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1118)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:517)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:456)
... stack trace shortened for clarity
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'cacheManager' defined in class path resource [ehcache-example.xml]: Cannot resolve reference to bean 'ehcache' while setting bean property 'cacheManager'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'ehcache' defined in class path resource [ehcache-example.xml]: Invocation of init method failed; nested exception is net.sf.ehcache.CacheException: Another unnamed CacheManager already exists in the same VM. Please provide unique names for each CacheManager in the config or do one of following:
1. Use one of the CacheManager.create() static factory methods to reuse same CacheManager with same name or create one if necessary
2. Shutdown the earlier cacheManager before creating new one with same name.
The source of the existing CacheManager is: InputStreamConfigurationSource [stream=java.io.BufferedInputStream@424c414]
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:328)
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveValueIfNecessary(BeanDefinitionValueResolver.java:106)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyPropertyValues(AbstractAutowireCapableBeanFactory.java:1360)
... stack trace shortened for clarity
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:193)
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:322)
... 38 more
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'ehcache' defined in class path resource [ehcache-example.xml]: Invocation of init method failed; nested exception is net.sf.ehcache.CacheException: Another unnamed CacheManager already exists in the same VM. Please provide unique names for each CacheManager in the config or do one of following:
1. Use one of the CacheManager.create() static factory methods to reuse same CacheManager with same name or create one if necessary
2. Shutdown the earlier cacheManager before creating new one with same name.
The source of the existing CacheManager is: InputStreamConfigurationSource [stream=java.io.BufferedInputStream@424c414]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1455)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:519)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:456)
at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:294)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:225)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:291)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:193)
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:322)
... 48 more
Caused by: net.sf.ehcache.CacheException: Another unnamed CacheManager already exists in the same VM. Please provide unique names for each CacheManager in the config or do one of following:
1. Use one of the CacheManager.create() static factory methods to reuse same CacheManager with same name or create one if necessary
2. Shutdown the earlier cacheManager before creating new one with same name.
The source of the existing CacheManager is: InputStreamConfigurationSource [stream=java.io.BufferedInputStream@424c414]
at net.sf.ehcache.CacheManager.assertNoCacheManagerExistsWithSameName(CacheManager.java:521)
at net.sf.ehcache.CacheManager.init(CacheManager.java:371)
at net.sf.ehcache.CacheManager.(CacheManager.java:339)
at org.springframework.cache.ehcache.EhCacheManagerFactoryBean.afterPropertiesSet(EhCacheManagerFactoryBean.java:104)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1514)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1452)
... 55 more

… quando você tentar executar um monte de testes unitários.

Eu acho que isso se resume a um simples bug do gerenciador factory do ehCache do Spring, já que ele está tentando criar múltiplas instâncias de cache usando new() em vez de usar, como a exceção estabelece, “um dos métodos factory estáticos CacheManager.create()”, que permitem reutilizar o mesmo CacheManager com o mesmo nome. Daí, meu primeiro teste JUnit funciona bem, mas todos os outros falham.

unit test

 

A linha de código é:

this.cacheManager = (this.shared ? CacheManager.create() : new CacheManager());

Meu arquivo de configuração XML completo é listado abaixo:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:cache="http://www.springframework.org/schema/cache"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd
http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache.xsd">

<!-- Switch on the Caching -->
<cache:annotation-driven />

<!-- Do the component scan path -->
<context:component-scan base-package="caching" />

<bean id="cacheManager"
p:cacheManager-ref="ehcache"/>

<bean id="ehcache"
p:configLocation="ehcache.xml"
p:shared="true"/>
</beans>

Na utilização do ehcache, os únicos outros detalhes de configuração a serem considerados são as dependências Maven. Elas são bastante simples, já que os caras do Ehcache combinaram todos os vários ehcache jars em um módulo Maven POM. Esse módulo POM pode ser adicionado ao arquivo POM do seu projeto usando o XML abaixo:

<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
<version>2.6.0</version>
<type>pom</type>
<scope>test</scope>
</dependency>

Finalmente, os arquivos ehcache JAR estão disponíveis tanto na Central Maven quanto em repositórios Sourceforge:

<repositories>
        <repository>
            <id>sourceforge</id>
            <url>http://oss.sonatype.org/content/groups/sourceforge/</url>
            <releases>
                <enabled>true</enabled>
            </releases>
            <snapshots>
                <enabled>true</enabled>
            </snapshots>
        </repository>
    </repositories>

 

***

Texto original disponível em http://www.captaindebug.com/2012/09/spring-31-caching-and-ehcache.html#.UPcebyf7KSo

Qual a sua opinião?