We are calling Identity federation service to acquire user tokens very frequently and almost running load test on Identity service.
A potential solution is to cache the user tokens in existing application, however with native spring-cache, can we expire individual cache entries ?
With below example, I was able to clear the cache, removing all entries, however I am trying to expire individual entries.
@Service
@CacheConfig(cacheNames = {"userTokens"})
public class UserTokenManager {
static HashMap<String, String> userTokens = new HashMap<>();
@Cacheable
public String getUserToken(String userName){
String userToken = userTokens.get(userName);
if(userToken == null){
// call Identity service to acquire tokens
System.out.println("Adding UserName:" + userName + " Token:" + userToken);
userTokens.put(userName, userToken);
}
return userToken;
}
@CacheEvict(allEntries = true, cacheNames = { "userTokens"})
@Scheduled(fixedDelay = 3600000)
public void removeUserTokens() {
System.out.println("##############CACHE CLEANING##############, " +
"Next Cleanup scheduled at : " + new Date(System.currentTimeMillis()+ 3600000));
userTokens.clear();
}
}
Spring-boot application class is as below:
@SpringBootApplication
@EnableCaching
@EnableScheduling
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
You can expire a single cache entry by using @CacheEvict
on a method that takes your cache key. Also, by using Spring's cache and @Cacheable
, there's no need for the HashMap code (as that's really just a secondary cache).
@Service
@CacheConfig(cacheNames = {"userTokens"})
public class UserTokenManager {
private static Logger log = LoggerFactory.getLogger(UserTokenManager.class);
@Cacheable(cacheNames = {"userTokens"})
public String getUserToken(String userName) {
log.info("Fetching user token for: {}", userName);
String token = ""; //replace with call for token
return token;
}
@CacheEvict(cacheNames = {"userTokens"})
public void evictUserToken(String userName) {
log.info("Evicting user token for: {}", userName);
}
@CacheEvict(cacheNames = {"userTokens"}, allEntries = true)
public void evictAll() {
log.info("Evicting all user tokens");
}
}
For example:
getUserToken("Joe") -> no cache, calls API
getUserToken("Alice") -> no cache, calls API
getUserToken("Joe") -> cached
evictUserToken("Joe") -> evicts cache for user "Joe"
getUserToken("Joe") -> no cache, calls API
getUserToken("Alice") -> cached (as it has not been evicted)
evictAll() -> evicts all cache
getUserToken("Joe") -> no cache, calls API
getUserToken("Alice") -> no cache, calls API
If you want your tokens to be cached for a certain amount of time, you'll need another CacheManager
besides the native Spring one. There are a variety of cache options that work with Spring's @Cacheable
. I'll give an example using Caffeine, a high performance caching library for Java 8. For example, if you know you want to cache a token for 30 minutes, you'll likely want to go with this route.
First, add the following dependencies to your build.gradle
(or if using Maven, translate the following and put it in your pom.xml
). Note that you'll want to use the latest versions, or the ones that match with your current Spring Boot version.
compile 'org.springframework.boot:spring-boot-starter-cache:2.1.4'
compile 'com.github.ben-manes.caffeine:caffeine:2.7.0'
Once you've added those two dependencies, all you have to do is configure the caffeine
spec in your application.properties
file:
spring.cache.cache-names=userTokens
spring.cache.caffeine.spec=expireAfterWrite=30m
Change expireAfterWrite=30m
to whatever value you'd like tokens to live for. For example, if you wanted 400 seconds, you could change it to expireAfterWrite=400s
.
Useful links: