Back-End

7 nov, 2012

Armazenamento em cache de Spring 3.1 e @Cacheable

Publicidade

Os caches têm estado no mundo do software por muito tempo. Eles são uma dessas coisas realmente úteis e, uma vez que você começa a usá-los, você imagina como foi que você se deu bem sem eles antes. Então, parece um pouco estranho que os caras do Spring só chegaram à adição de um cache para a implementação no núcleo do Spring na versão 3.1. Estou supondo que antes isso não era visto como uma prioridade e antes da introdução das anotações do Java, uma das dificuldades de armazenamento em cache foi o acoplamento de cache de código com o código da regra de negócio, o que pode muitas vezes se tornar bastante confuso.

No entanto, os caras do Spring já desenvolveram um sistema de cache simples de usar baseado em algumas anotações: @Cacheable e @CacheEvict.

A ideia da anotação @Cacheable é que você pode usá-la para marcar os valores de retorno do método que serão armazenados no cache. Essa anotação pode ser aplicada tanto na declaração do método quanto na declaração do tipo. Quando aplicado na declaração do método, o valor de retorno do método que recebeu a anotação  armazenado em cache. Quando aplicado na declaração do tipo, então o valor de retorno de cada método é armazenado em cache.

O código abaixo demonstra como aplicar @Cacheable a uma declaração de método:

@Cacheable(value = "employee")
public class EmployeeDAO {public Person findEmployee(String firstName, String surname, int age) {

return new Person(firstName, surname, age);
}

public Person findAnotherEmployee(String firstName, String surname, int age) {

return new Person(firstName, surname, age);
}
}

A anotação Cacheable recebe três argumentos: value, que é obrigatório, juntamente com key e condition. O primeiro deles, value, é usado para especificar o nome do cache (ou caches) no qual o valor de retorno de um método é armazenado.

@Cacheable(value = "employee")
public Person findEmployee(String firstName, String surname, int age) {

return new Person(firstName, surname, age);
}

O código acima assegura que o novo objeto Person seja armazenado no cache “employee”.

Todos os dados armazenados em um cache necessitam de uma chave para sua recuperação rápida. O Spring, por padrão, cria chaves de armazenamento em cache usando a assinatura do método anotado como demonstrado pelo código acima. Você pode substituir isso usando segundo parâmetro do @Cacheable: key. Para definir uma chave personalizada, você utiliza uma expressão SpEL.

@Cacheable(value = "employee", key = "#surname")
public Person findEmployeeBySurname(String firstName, String surname, int age) {

return new Person(firstName, surname, age);
}

No código findEmployeeBySurname(…), a string ‘#surname’ é uma expressão SpEL que significa “ir e criar uma chave usando o argumento surname do método findEmployeeBySurname(…)“.

O argumento final de @Cacheable é o argumento opcional condition. Mais uma vez, isso faz referência a uma expressão SPeL, mas desta vez especifica uma condição que é utilizada para determinar se o seu valor de retorno do método é adicionado ou não ao cache.

@Cacheable(value = "employee", condition = "#age < 25")
public Person findEmployeeByAge(String firstName, String surname, int age) {

return new Person(firstName, surname, age);
}

No código acima, eu apliquei a regra de negócio de só fazer armazenamento em cache de objetos Person se o empregado tiver menos do que 25 anos de idade.

Após ter demonstrado rapidamente como aplicar alguns caches, a próxima coisa a fazer é dar uma olhada no que tudo isso significa.

@Test
public void testCache() {

Person employee1 = instance.findEmployee("John", "Smith", 22);
Person employee2 = instance.findEmployee("John", "Smith", 22);

assertEquals(employee1, employee2);
}

O teste acima demonstra armazenamento em cache na sua forma mais simples. Na primeira chamada para findEmployee(…), o resultado ainda não é armazenado em cache, então o meu código será chamado e Spring irá armazenar o valor de retorno no cache. Na segunda chamada para findEmployee(…), o meu código não é chamado e o Spring retorna o valor em cache; daí o employee1 variável local se refere à mesma referência de objeto como empoyee2, o que significa que o seguinte é verdadeiro:

assertEquals(employee1, employee2);

Mas as coisas nem sempre são tão evidentemente claras. Lembre-se que de em findEmployeeBySurname eu modifiquei a chave de armazenamento em cache para que o argumento surname fosse usado para criar a chave, e a coisa a se observar ao criar seu próprio algoritmo de codificação é garantir que qualquer chave se refira a um único objeto.

@Test
public void testCacheOnSurnameAsKey() {

Person employee1 = instance.findEmployeeBySurname("John", "Smith", 22);
Person employee2 = instance.findEmployeeBySurname("Jack", "Smith", 55);

assertEquals(employee1, employee2);
}

O código acima encontra duas instâncias Person que se referem claramente a diferentes funcionários, no entanto, por eu estar fazendo o armazenamento em cache apenas do sobrenome, o Spring irá retornar uma referência para o objeto que é criado durante a minha primeira chamada para findEmployeeBySurname(…). Esse não é um problema com o Spring, mas com a minha definição deficiente de chave de cache.

Um cuidado parecido deve ser tomado quando nos referimos a objetos criados por métodos que têm uma condição aplicada à anotação @Cachable. No meu exemplo de código, eu apliquei a condição arbitrária para armazenar em cache apenas instâncias Person, nas quais o empregado tem menos de 25 anos de idade.

@Test
public void testCacheWithAgeAsCondition() {

Person employee1 = instance.findEmployeeByAge("John", "Smith", 22);
Person employee2 = instance.findEmployeeByAge("John", "Smith", 22);

assertEquals(employee1, employee2);
}

No código acima, as referências a employee1 e employee2 são iguais porque, na segunda chamada para findEmployeeByAge(…), o Spring retorna a sua instância em cache.

@Test
public void testCacheWithAgeAsCondition2() {

Person employee1 = instance.findEmployeeByAge("John", "Smith", 30);
Person employee2 = instance.findEmployeeByAge("John", "Smith", 30);

assertFalse(employee1 == employee2);
}

Do mesmo modo, no código de teste unitário acima, as referências a employee1 e employee2 e se referem a diferentes objetos, já que, nesse caso, John Smith é superior a 25.

Isso praticamente cobre @Cacheable, mas e quanto a @CacheEvict e limpeza de itens do cache? Além disso, existe a questão de adicionar o armazenamento em cache nas configurações do Spring e escolher a implementação de cache mais adequada. . No entanto, falaremos mais sobre isso depois…

***

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