Spring 3.1 RC1 – Cache Abstraction

Spring Cache Abstraction



Abordamos um das novas funcionalidades do Spring 3.1 RC1, profiles e environments. Ainda existem outras funcionalidades, mas hoje iremos dar uma olhada em Cache Abstraction.

Cache Abstraction é literalmente uma abstração out of the box para adicionar uma camada de cache sobre seus beans, usando uma arquitetura AOP para gerenciar o que deve e o que não deve ser feito o cache.
Usar a nova camada de cache é muito fácil se você já está habituado com Spring, e veremos uma das diferentes maneiras de configurar seus beans.

Baixando a Denpendência

Para quem utiliza maven, basta adicionar a seguinte dependência no pom do seu projeto:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
    <repositories>
        <repository>
            <id>org.springframework.maven.milestone</id>
            <name>Spring Maven Milestone Repository</name>
            <url>http://maven.springframework.org/milestone</url>
        </repository>
    </repositories>

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>3.1.0.RC1</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>3.1.0.RC1</version>
        </dependency>
    </dependencies>



Se você perferir, pode baixar os jar direto do site do spring.
Sem segredos aqui, basta adicionar as dependências ao projeto e está pronto para usar.

Entendendo o funcionamento



A maneira com que o cache funciona é bem simples. Você pode enxergar o cache como um mapa chave-valor, onde a chave é o conjunto de argumentos do seu método, e o valor é o valor devolvido pelo seu método. Pensando assim fica fácil entender o funcionamento que irei mostrar no exemplo.
Referente a configuração do Spring, é necessário instanciar um gerenciador de cache, ou na linguagem spring, cacheManager. Existe algumas implementações de cache manager disponí­vel no spring, portanto iremos utilizar uma delas em nosso exemplo.
Vamos escrever um teste unitário com JUnit 4.8.1 para ilustrar o comportamento do cache.
Comece criando um arquivo padrão de beans do spring, mas com um namespace a mais:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
</br>
Repare que estamos utilizando o namespace de cache além dos usuais.
A única configuração que iremos fazer aqui, é a do cache manager. Crie um bean da classe <i>SimpleCacheManager</i> e adicione o seguinte bean como cache gerenciado:
</br>
[cc lang="XML"[
    <bean id="cacheManager" class="org.springframework.cache.support.SimpleCacheManager">
        <property name="caches">
            <set>
                <bean class="org.springframework.cache.concurrent.ConcurrentMapCache">
                    <constructor-arg value="property" />
                </bean>
            </set>
        </property>
    </bean>



O que fizemos foi criar um cache manager simples do spring, e dentro configuramos o cache que iremos utilizar, associando a ele o nome de property. Repare também que estamos utilizando o cache implementado com um ConcurrentMap do java, garantindo o acesso thread safe ao cache.

Configuração por Anotação



Agora que configuramos o cache, vamos criar nossos beans que serão cacheados.
Vamos abordar primeiro a configuração por anotação em nossos beans. Para isso adicione a seguinte tag no seu xml de beans:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
</br>
Assim será criado o proxy de cache em torno de todos os beans anotados.
Como não estou usando nenhuma biblioteca de proxy dinâmico, vamos criar uma interface para anotar nossas configurações:
</br>
[cc lang="java"]
public interface CacheableTest {

    @CacheEvict(value = "property", allEntries = true)
    void evictCache();

    @Cacheable("property")
    String getForCache(String s);

    String getProperty();

    void setProperty(String property);

}



Repare no getter e setter que usaremos no teste, e na anotação @Cacheable(“property”). Esta anotação está dizendo que o valor que este método devolver será armazenado no cache que configuramos previamente como property. Fato importante é que este valor é referente ao argumento passado como parâmetro no método.
A outra anotação @CacheEvict(value = “property”, allEntries = true) descreve o método que chamaremos para esvaziar o cache. Note que passamos o nome do cache, e que ele deve limpar todos os valores.
ɉ importante notar também que pode ser passado mais de um nome de cache em ambas as anotações.
Uma implementação básica para nosso bean pode ser:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Component("cacheBean")
public class CacheableBean implements CacheableTest {

    private String property;

    public String getForCache(String s) {
        return this.property;
    }

    public String getProperty() {
        return property;
    }

    public void setProperty(String property) {
        this.property = property;
    }

    public void evictCache() {
        // Não faz nada
    }
}



Estou usando o package scan para instanciar o bean. Agora nosso teste:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public class CacheTest {

    @Test
    public void testCache() {

        ApplicationContext ctx = new ClassPathXmlApplicationContext(
                "spring31-test-beans.xml");

        CacheableTest bean = ctx.getBean("cacheBean", CacheableTest.class);

        bean.setProperty("teste");

        Assert.assertEquals("teste", bean.getProperty());
        Assert.assertEquals("teste", bean.getForCache("a"));

        bean.setProperty("teste2");
        Assert.assertEquals("teste2", bean.getProperty());
        Assert.assertEquals("teste", bean.getForCache("a"));
        Assert.assertEquals("teste2", bean.getForCache("b"));
       
        bean.setProperty("teste3");
        Assert.assertEquals("teste3", bean.getProperty());
        Assert.assertEquals("teste", bean.getForCache("a"));
        Assert.assertEquals("teste2", bean.getForCache("b"));
       
        bean.evictCache();
        bean.setProperty("teste4");
        Assert.assertEquals("teste4", bean.getProperty());
        Assert.assertEquals("teste4", bean.getForCache("a"));
        Assert.assertEquals("teste4", bean.getForCache("b"));

    }

}



O arquivo de beans se chama spring31-test-beans.xml.
Repare que estamos invocando o método getForCache passando alguns valores diferentes, e o valor devolvido é sempre igual, mesmo que depois setamos um valor diferente ao bean. Para atualizar o valor e limpar o cache, basta invocar o método evictCache que haví­amos anotado com @CacheEvict.
Vale a pena brincar um pouco com o funcionamento do cache, e até mesmo criar outros caches e ver o comportamento do evict em diferentes métodos.

ښltimas Considerações



As mesmas configurações que fizemos com anotações, pode-se fazer direto no XML. Não entrarei no detalhe pois o funcionamento é exatamente o mesmo, mas se você preferir basta olhar a documentação que é bem straightforward.
Lembre-se que esta camada de cache não possui nenhuma relação com o banco de dados, e deve ser usada com muito cuidado em tais casos, pois alguns erros de concistência podem aparecer devido a um cache desatualizado.
Vale a pena brincar com o cache manager, pois o spring suporta o uso do EhCache, o que pode ser muito útil se você já possui alguma configuração pré-definida para sua aplicação.

Por enquanto é isso, qualquer dúvida mande nos comentários que responderei assim que possí­vel.

Por @Gust4v0_H4xx0r