Spring Caching Example with Java and XML Configuration
Spring Caching Annotations
In this tutorial we show you how you can leverage the spring framework caching abstraction to improve the performance of your system. At its core, the abstraction applies caching to Java methods, reducing the number of invocations based on the information available in the cache. You can decorate your java methods with Java annotations or XML Configuration thus minimizing the impact on the code.
How the caching works
Each time a targeted method is invoked, the abstraction will apply a caching behavior checking whether the method has been already executed for the given arguments. If it has, then the cached result is returned without having to execute the actual method; if it has not, then method is executed, the result cached and returned to the user so that, the next time the method is invoked the cached result is returned. This way, expensive methods (whether CPU or IO bound) can be executed only once for a given set of parameters and the result reused without having to actually execute the method again. The caching logic is applied transparently without any interference to the invoker.
Supported Caching Providers
The cache abstraction of the Spring Framework does not provide an actual store and relies on one of the following supported caching providers. If you haven’t defined a bean of type CacheManager or a CacheResolver, Spring Boot tries to detect the following providers (in this order).
- Generic
- JCache (JSR-107) (EhCache 3, Hazelcast, Infinispan, etc)
- EhCache 2
- Hazelcast
- Infinispan
- Couchbase
- Redis
- Caffeine
- Simple
Enable caching annotations
You can easily enable caching by adding the @EnableCaching annotation to one of your @Configuration classes. You can also completely disable caching by removing this annotation. Meaning, whenever you’re debugging an issue and you want to make sure it’s not a caching issue.. just remove this @EnableCaching annotation.
@Configuration @EnableCaching public class CacheConfig { }
Alternatively for XML configuration use the cache:annotation-driven element:
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:cache="http://www.springframework.org/schema/cache" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache.xsd"> <cache:annotation-driven /> </beans>
@Cacheable annotation
Caching a method in Spring is as simple as annotating a method with the @Cacheable annotation.
@Cacheable("instruments") public ListfindAll() { ... }
In the snippet above, the method findAll is associated with the cache named instruments. When the method is executed for the first time, the result is stored into the cache so on subsequent invocations with the same arguments, the value in the cache is returned without invoking the method.
While in most cases one cache is enough, the spring framework supports multiple caches.
@Cacheable(cacheNames = {"instruments", "directory"}) public ListfindAll() { ... }
In this case, each of the caches will be checked before executing the method – if at least one cache is hit, then the associated value will be returned. All the other caches that do not contain the value will be updated as well.
Conditional Caching
You can optionally use a condition when a method is not suitable for caching all the time. This condition takes a SpEL expression that is evaluated to a boolean condition. If true the result is called.
@Cacheable(condition = "#instrument.equals('trombone')") public String play(String instrument) { .. }
In addition the condition parameter, the unless parameter can be used to veto the adding of a value to the cache. Unlike condition, unless expressions are evaluated after the method has been called.
@Cacheable(unless = "#result.size() > 25") public ListfindAll() { .. }
@CachePut annotation
You can use the @CachePut annotation to populate the cache. The method will always be executed and its result placed into the cache (according to the @CachePut options).
@CachePut(cacheNames="instruments", allEntries=true) public void save(String instrument) { .. }
ImportantBe carefully when mixing @CachePut and @Cacheable annotations on the same method. While the @Cacheable annotation causes the method execution to be skipped by using the cache, the @CachePut annotation forces the execution in order to execute a cache update. This can lead to unexpected behavior.
@CacheEvict annotation
The @CacheEvict is useful for removing stale or unused data from the cache. This method act as a trigger for removing data from the cache.
@CacheEvict public void delete(String instrument) { .. }
You can optionally pass an extra parameter allEntries=true to wipe alle the entries from the cache.
@CacheEvict(allEntries=true) public void delete(String instrument) { .. }
You can also pass in a beforeInvocation=true parameter. When true the eviction always occurs before the method is executed.
@CacheEvict(beforeInvocation=true) public void delete(String instrument) { .. }
@Caching annotation
The @Caching annotation allows multiple nested @Cacheable, @CachePut and @CacheEvict annotations to be used on the same method.
@Caching(cacheable = { @Cacheable(condition = "#index == 1"), @Cacheable(condition = "#index == 2"), }) public String findByIndex(int index) { .. }
@CacheConfig annotation
You can annotate your class with the @CacheConfig annotation to specify cache configurations for the entire class.
@CacheConfig(cacheNames = {"directory", "instruments"}) public class MusicServiceIml implements MusicService {..}
Spring Caching Example Java and XML Configuration
In the previous section we saw different caching annotations of the spring framework. In the following section we demonstrate how to use them with a simple example configuration.
Maven Dependencies
We use Apache Maven to manage our project dependencies. Start by adding the following dependencies to your project.
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.memorynotfound.springboot.caching</groupId> <artifactId>caching-annotations</artifactId> <version>1.0.0-SNAPSHOT</version> <url>https://memorynotfound.com</url> <name>Spring Boot - ${project.artifactId}</name> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.4.RELEASE</version> </parent> <dependencies> <!-- Spring Framework Caching Support --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-cache</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
Cashing Service Methods
To demonstrate the spring framework caching abstraction, we created a simple service.
package com.memorynotfound.springboot; import java.util.List; public interface MusicService { List<String> findAll(); String findByIndex(int index); void save(String instrument); void delete(String instrument); void deleteAll(); String play( final String instrument ); }
package com.memorynotfound.springboot; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.cache.annotation.*; import org.springframework.cache.annotation.CacheConfig; import org.springframework.stereotype.Service; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @Service @CacheConfig(cacheNames = {"directory", "instruments"}) public class MusicServiceIml implements MusicService { private static Logger log = LoggerFactory.getLogger(Application.class); private static List<String> instruments = new ArrayList<String>(Arrays.asList("Guitar", "Bass", "Keyboard")); @Cacheable(unless = "#result.size() > 25") @Override public List<String> findAll() { log.info("Executing: " + this.getClass().getSimpleName() + ".findAll();"); return instruments; } @Cacheable @Override public String findByIndex(int index) { log.info("Executing: " + this.getClass().getSimpleName() + ".findByIndex(\"" + index + "\");"); return instruments.get(index); } @CachePut @Override public void save(String instrument) { log.info("Executing: " + this.getClass().getSimpleName() + ".save(\"" + instrument + "\");"); instruments.add(instrument); } @CacheEvict @Override public void delete(String instrument) { log.info("Executing: " + this.getClass().getSimpleName() + ".delete(\"" + instrument + "\");"); instruments.remove(instrument); } @CacheEvict(allEntries = true) @Override public void deleteAll() { log.info("Executing: " + this.getClass().getSimpleName() + ".deleteAll();"); instruments.removeAll(instruments); } @Cacheable(condition = "#instrument.equals('trombone')") public String play(String instrument) { log.info("Executing: " + this.getClass().getSimpleName() + ".play(\"" + instrument + "\");"); return "paying " + instrument + "!"; } }
Spring Cache Configuration
This example uses a simple ConcurrentMapCache to demonstrate the caching mechanism. The following is the java configuration.
Spring Cache Java Configurationpackage com.memorynotfound.springboot;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.concurrent.ConcurrentMapCache;
import org.springframework.cache.support.SimpleCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.Arrays;
@Configuration
@EnableCaching
public class CacheConfig {
@Bean
public CacheManager cacheManager() {
SimpleCacheManager cacheManager = new SimpleCacheManager();
cacheManager.setCaches(Arrays.asList(
new ConcurrentMapCache("directory"),
new ConcurrentMapCache("instruments")));
return cacheManager;
}
}
And here is the equivalent Spring XML Caching Configuration:
@ImportResource("classpath:cache-config.xml")
<beans xmlns="http://www.springframework.org/schema/beans" 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-4.2.xsd http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache-4.2.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <cache:annotation-driven/> <context:annotation-config/> <bean id="cacheManager" class="org.springframework.cache.support.SimpleCacheManager"> <property name="caches"> <set> <bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean" name="directory"/> <bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean" name="addresses"/> </set> </property> </bean> <!-- define caching behavior --> <cache:advice cache-manager="cacheManager"> <cache:caching cache="instruments, directory"> <cache:cacheable method="findAll" unless="#result.size() > 25"/> <cache:cacheable method="findByIndex"/> <cache:cache-put method="save"/> <cache:cache-evict method="delete"/> <cache:cache-evict method="deleteAll" all-entries="true"/> <cache:cacheable method="play" condition="#instrument.equals('trombone')"/> </cache:caching> </cache:advice> </beans>
Bootstrap Spring Application
The following application demonstrates the spring caching by executing a couple of methods annotated with various caching annotations.
package com.memorynotfound.springboot; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.CommandLineRunner; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class Application implements CommandLineRunner { private static Logger log = LoggerFactory.getLogger(Application.class); @Autowired private MusicService musicService; public static void main(String[] args) throws Exception { SpringApplication.run(Application.class, args); } @Override public void run(String... args) throws Exception { log.info("Spring Boot Conditional Caching and Other Caching Annotations Example"); String service = MusicServiceIml.class.getSimpleName(); log.info("Calling: " + service + ".findAll();"); log.info("Occurrences: " + musicService.findAll()); log.info("Calling: " + service + ".findAll();"); log.info("Occurrences: " + musicService.findAll()); log.info("Calling: " + service + ".findByIndex();"); musicService.findByIndex(1); log.info("Calling: " + service + ".findByIndex();"); musicService.findByIndex(1); log.info("Calling: " + service + ".findByIndex();"); musicService.findByIndex(0); log.info("Calling: " + service + ".findByIndex();"); musicService.findByIndex(0); log.info("Calling: " + service + ".save();"); musicService.save("Saxophone"); log.info("Calling: " + service + ".findAll();"); log.info("Occurrences: " + musicService.findAll()); log.info("Calling: " + service + ".delete();"); musicService.delete("Bass"); log.info("Calling: " + service + ".findAll();"); log.info("Occurrences: " + musicService.findAll()); log.info("Calling: " + service + ".deleteAll();"); musicService.deleteAll(); log.info("Calling: " + service + ".findAll();"); log.info("Occurrences: " + musicService.findAll()); play("trombone"); play("guitar"); play("trombone"); play("bass"); play("trombone"); } private void play(String instrument){ log.info("Calling: " + MusicServiceIml.class.getSimpleName() + ".play(\"" + instrument + "\");"); musicService.play(instrument); } }
Running Spring Application
We can run our application using the following command.
mvn spring-boot:run
Output
The previous application prints the following output to the console.
. ____ _ __ _ _ /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ \\/ ___)| |_)| | | | | || (_| | ) ) ) ) ' |____| .__|_| |_|_| |_\__, | / / / / =========|_|==============|___/=/_/_/_/ :: Spring Boot :: (v1.5.4.RELEASE) DEBUG - Running with Spring Boot v1.5.4.RELEASE, Spring v4.3.9.RELEASE INFO - Spring Boot Conditional Caching and Other Caching Annotations Example INFO - Calling: MusicServiceIml.findAll(); INFO - Executing: MusicServiceIml.findAll(); INFO - Occurrences: [Guitar, Bass, Keyboard] INFO - Calling: MusicServiceIml.findAll(); INFO - Occurrences: [Guitar, Bass, Keyboard] INFO - Calling: MusicServiceIml.findByIndex(); INFO - Executing: MusicServiceIml.findByIndex("1"); INFO - Calling: MusicServiceIml.findByIndex(); INFO - Calling: MusicServiceIml.findByIndex(); INFO - Executing: MusicServiceIml.findByIndex("0"); INFO - Calling: MusicServiceIml.findByIndex(); INFO - Executing: MusicServiceIml.findByIndex("0"); INFO - Calling: MusicServiceIml.save(); INFO - Executing: MusicServiceIml.save("Saxophone"); INFO - Calling: MusicServiceIml.findAll(); INFO - Occurrences: [Guitar, Bass, Keyboard, Saxophone] INFO - Calling: MusicServiceIml.delete(); INFO - Executing: MusicServiceIml.delete("Bass"); INFO - Calling: MusicServiceIml.findAll(); INFO - Occurrences: [Guitar, Keyboard, Saxophone] INFO - Calling: MusicServiceIml.deleteAll(); INFO - Executing: MusicServiceIml.deleteAll(); INFO - Calling: MusicServiceIml.findAll(); INFO - Executing: MusicServiceIml.findAll(); INFO - Occurrences: [] INFO - Calling: MusicServiceIml.play("trombone"); INFO - Executing: MusicServiceIml.play("trombone"); INFO - Calling: MusicServiceIml.play("guitar"); INFO - Executing: MusicServiceIml.play("guitar"); INFO - Calling: MusicServiceIml.play("trombone"); INFO - Calling: MusicServiceIml.play("bass"); INFO - Executing: MusicServiceIml.play("bass"); INFO - Calling: MusicServiceIml.play("trombone");
Download
From:一号门
COMMENTS