Implementing a cache, even a basic one, used to require lots of architectural discussion, meetings, evaluations, and a significant amount of development time. With Spring Boot, those days are far behind us! With a small amount of configuration, dependency management, and a few annotations any developer can have caching set up in their application in a few minutes.
Generating a new application
Using either the Spring Boot CLI or the Spring Initializr, create a new application using the following (unless specified, use the defaults):
- Gradle Project
- You could use Maven also, just replace the
./gradlew
commands withmvn
- You could use Maven also, just replace the
- Version: 1.4.2
- Higher versions should also work, this was just the most current version as of this writing
- Language: Groovy
- Not required but I prefer writing less verbose code
- Packages
- Cache (Core)
- Web (Web)
- Thymeleaf (Template Engines)
Write a simple web app
For this demo we are going to build a simple HTML page that displays the current time. Exciting right?! For this, we will need a build a controller and a view.
The controller and the view
Create a new Groovy class in the com.example
package named TimeController with the following content.
package com.example
import org.springframework.stereotype.Controller
import org.springframework.ui.Model
import org.springframework.web.bind.annotation.RequestMapping
@Controller
class TimeController {
@RequestMapping
def index(Model model) {
model.addAllAttributes now: (new Date().time)
'index'
}
}
Next, create the view in src/main/resources/templates
directory and named index.html with the following content.
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Caffeine Cache Demo</title>
</head>
<body>
<p th:text="'Now: ' + ${now}"/>
</body>
</html>
Run the application
From a terminal, use the Gradle wrapper to start the application.
./gradlew bootRun
Once the application has started up, you should be able to view it running at localhost:8080. The page should show the current time in milliseconds. Each time you refresh the page the time should be updated.
Add basic cache functionality
We already added the dependency for Spring Cache all we need to do now is enable the cache and cache something. This is the easy part. Well, all of it is the easy part.
Enable caching
Modify com.example.DemoApplication
so that it matches the following (note the @EnableCaching
)
package com.example
import org.springframework.boot.SpringApplication
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.cache.annotation.EnableCaching
@SpringBootApplication
@EnableCaching
class DemoApplication {
static void main(String[] args) {
SpringApplication.run DemoApplication, args
}
}
Create a service for getting time
We want to better encapsulate how our application gets time. This will help us apply caching in a more organized way.
Create a Groovy class in the com.example
package named TimeService with the following content.
package com.example
import org.springframework.stereotype.Service
@Service
class TimeService {
long getTimeNow() {
new Date().time
}
}
Modify the com.example.TimeController
class to get the current time from the TimeService. Note the addition of TimeService
being autowired as well as the call to timeService.timeNow
in the map of attributes being added to the model.
package com.example
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Controller
import org.springframework.ui.Model
import org.springframework.web.bind.annotation.RequestMapping
@Controller
class TimeController {
@Autowired
TimeService timeService
@RequestMapping
def index(Model model) {
model.addAllAttributes now: timeService.timeNow
'index'
}
}
Add a cached method
Running the application at this point will yield the same behavior we had previously. We have not added anything to that needs to be cached. Lets do that now. In the com.example.TimeService
class add a new method named getTimeCached and have it return a call to getTimeNow. Lets also add a new annotation, @Cacheable
to the method that tells Spring Boot to cache the results of the method. It should now look like the following.
package com.example
import org.springframework.cache.annotation.Cacheable
import org.springframework.stereotype.Service
@Service
class TimeService {
long getTimeNow() {
new Date().time
}
@Cacheable('timeCached')
long getTimeCached() {
timeNow
}
}
By adding the @Cacheable
annotation to the method, we are instructing Spring Boot to manage a HashMap cache with the method’s result. The name timeCached in the annotation is the name of the cache key we want to use. This makes things easier when you want to manipulate specific cached data.
Use the cached method and render the result in the view
The last step is to start using the new method and render its result in the view. Back in the com.example.TimeController
class, add a new key to the attributes map. It should now look like this:
model.addAllAttributes now: timeService.timeNow, cached: timeService.timeCached
And in src/resources/templates/index.html
add the following line above the closing </body>
tag.
<p th:text="'Cached: ' + ${cached}"/>
Test it out
Restart the application and refresh the page in your browser. You’ll notice that in addition to the time now we are also showing the cached time. If you refresh the page only the time now will change and the cached time will stay the same indefinitely. Congratulations, now you’re caching with ease!
Improve caching with Caffeine
Having a cache that lives for the life of the application might fit your use case but it also comes with some issues. What if the data you are caching changes over time? What if you want to control the size of the cache? In this section, we are going to implement a cache library called Caffeine. Caffeine is an in-memory cache aimed at replacing Google’s Guava. It is high performance and light weight, perfect for our simple application.
Add the Caffeine library to the application
In your build.gradle
file, add the following line to the dependencies block.
compile 'com.github.ben-manes.caffeine:caffeine'
Maven users can add the following to their pom.xml
.
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
</dependency>
Technically that is all you need to do for Spring Boot to start using Caffeine as the cache provider. However, it will operate exactly the same as before. Lets add some configuration to override the default behavior.
Configure Caffeine
By default Spring Boot comes with a configuration file named application.properties in the src/resources
directory. I am not a fan of typing the same words over and over again so I prefer to use YAML (you can read more about Spring Boot’s configuration files on its project page). Start by renaming application.properties to application.yml. Spring Boot will automatically recognize this as a valid configuration file. Open the file and add the following content.
spring:
cache:
cache-names: timeCached
caffeine:
spec: expireAfterAccess=30s
This bit of configuration instructs Spring Boot to configure Caffeine with a cache named timeCached that expires after 30 seconds of inactivity.
Run it
Restart the application (if it is still running) and navigate to localhost:8080. You’ll see the same output as the last time we checked in on our application. Refresh the page a few times to verify that we are still caching the time. Now, wait 30 or more seconds and try refreshing the page. You should see that the cached time has been updated. Refresh a few more times to verify that the new time has been cached.
Use a better eviction policy
Great, we have a cache that expires after 30 seconds. Or does it? Re-read that last paragraph. You will note that I mentioned the cache will expire after 30 seconds of inactivity. This means that if the cache is written to or read from at any point during those 30 seconds the eviction timer will reset. This will quickly turn into a cache that appears to never expire in applications that access the cache more frequently that the eviction policy dictates. This could be bad. What we really want, is a cache that expires 30 seconds after it has been written. Lets make a small tweak to the application.yml file.
spring:
cache:
cache-names: timeCached
caffeine:
spec: expireAfterWrite=30s
With this change the cache data will be evicted 30 seconds after it has been written. You can read more about eviction policies on the Caffeine wiki.
Conclusion
Congratulations, you made it! You have a simple application that caches some data with a time based eviction policy. Spring Boot supports a number of different caching providers including, EHCache, Redis, and Hazlecast. Most of these can be configured in a similar way but all of them will leverage the same implementation in your code. The @Cacheable
and @EnableCaching
annotations abstract away the implementation details so that you can focus on your application and the minimal configuration. Also, if you got stuck anywhere, feel free to refer back to the finished sample application on GitHub.
Good luck and happy caching!