Is it good practice to store state in Spring Singleton Bean (with thread-safe mechanism added)

Viewed 1880

From Spring doc:

7.5.2 (...) As a rule, use the prototype scope for all stateful beans and the singleton scope for stateless beans.

The case:
I have standalone application which needs to store state (e.g. some VERY simple cache implemented as simple Map). In my opinion the simplest solution is to create Sping Bean with singleton scope and put there HashMap/ConcurrentHashMap and add get/set methods which are thread-safe.

Is is good solution/practice or not? If not - what is proper way to do it?

3 Answers

Yes and no. Using custom built Spring components will probably result in a mess (race conditions, loss of cache...).

What to do then?

Why not simply use Spring's CacheManager? It can be injected manually as a bean. The cache can be "set" or "get". And you can make it fully annotation driven with cache eviction policies:

// Cache collection after the first invocation
@Cacheable("people")
public List<Person> getPeople() {...}


// Refresh entries after altering or inserting a new instance of Person
@CacheEvict(value="people", allEntries = true)
public Person save(Person person) {...}

The default CacheManager implementation uses ConcurentHashMap in the background.

If you simply have to cache a few keys and you mean to use ConcurrentHashMap to solve concurrency (but you can even use synchronised blocks if the performance on this cache is not a concern), then it's ok. There are more out of the box solutions like the Spring @Cached annotation see Spring Caching or other in memory caches like Caffeine that not only take care of concurrency and other important things like values eviction.

But more in general a cache is pretty much like a repository, so yes the singleton scope is the right one for such a component.

From this answer It seems that the term 'stateful' is for the scope of single dependency.

Meaning, when we want a state that will not be shared among two other beans that inject that bean, and that's why to use prototype scope - new instance for each inject.

So for saving an app-level cache the default scope singleton seems the one.

I like this approach because it's straight-forward and controllable, just keep in mind the thread safeness issues.

(tip: AtomicReference is a 'concurrent wrapper' in case you don't need a map)

Related