Max Nanasy
Max Nanasy

Reputation: 6141

Can Spring's @Cacheable annotation have the same scope as the annotated method's bean?

Is there an easy way to use Spring's @Cacheable annotation with a non-singleton (e.g. session-scoped) bean and have the cache have the same scope as said bean?

Example:

import javax.inject.Inject;
import javax.inject.Named;

import org.springframework.cache.annotation.Cacheable;
import org.springframework.context.annotation.Scope;
import org.springframework.security.core.context.SecurityContextHolder;

@Named
@Scope("session")
public class UserNameRetriever {

    @Inject private UserDao userDao;

    @Cacheable("userName")
    public String getUserName() {
        return userDao.getUserByLogin((String)SecurityContextHolder.getContext().getAuthentication().getPrincipal()).getName();
    }

}

Ideally, UserNameRetriever.getUserName() would fetch the username from the UserDao once per session, but this code actually caches application-wide.

Upvotes: 8

Views: 13208

Answers (4)

razor
razor

Reputation: 2867

You can create CacheManager bean with Session (or Request if you want only for a request) scope. It works perfectly fine.

  @Bean
  @SessionScope
  public CacheManager requestCacheManager() {
    return new ConcurrentMapCacheManager();
  }

Consider limiting/setting bounds for you cache (as some ppl mentioned about memory 'leaks') So you can try using CaffeineCacheManager, where you can set maximum size, expire ttl etc.

Upvotes: 0

Bozho
Bozho

Reputation: 597106

If you want a session cache, use the session. That is, store the user in a private field in the session-scoped bean, or access the HttpSession object directly. @Cacheable is generally meant for application-wide resources.

Well, you can use key="#user.id", and to invalidate the cache (manually or with @CacheEvict) on a @PreDestroy method, but it would be better if you don't mix the two - caching and session data.

Upvotes: 5

sinuhepop
sinuhepop

Reputation: 20316

I haven't tried it, but according to Spring reference I think this would work:

@Cacheable(value = "userName", key = "#root.target")

Upvotes: 2

Tomasz Nurkiewicz
Tomasz Nurkiewicz

Reputation: 340733

I think you are approaching the problem from the wrong side. Instead of having "local" session-scoped caches storing only one value have one global cache storing a map from user name to User values.

In your case drop @Cacheable on getUserName() and put it on UserDao:

public class UserDao {
  @Cacheable("users")
  public User getUserByLogin(String user) {
    //...
  }
}

Not only this is more idiomatic usage of cache, but also it will prove to be easier to maintain and better performing.

Back to your original question: no, this is not possible out-of-the-box. You would have to write your own CacheManager - probably a around existing implementation.

Upvotes: 2

Related Questions