Register Caffeine Cache in Spring Actuator (CacheManager) - spring-boot

We're using Spring Boot 2 and Spring Actuator. When creating a cache like the following:
#Bean
public CaffeineCache someCache() {
return new CaffeineCache("my-cache",
Caffeine.newBuilder()
.maximumSize(1000)
.expireAfterWrite(10, TimeUnit.SECONDS)
.build());
}
it is registered into Spring Actuator and can be accessed and handle via endpoints:
❯ http GET localhost:8080/actuator/caches
{
"cacheManagers": {
"cacheManager": {
"caches": {
"my-cache": {
"target": "com.github.benmanes.caffeine.cache.BoundedLocalCache$BoundedLocalManualCache"
}
}
}
}
}
However, this is valid when using the annotation #Cacheable - but I would like to create a cache and use it as a map.
Therefore, I can create:
#Bean
public com.github.benmanes.caffeine.cache.Cache<String, MyObject> customCache(QueryServiceProperties config) {
return Caffeine.newBuilder()
.maximumSize(10)
.expireAfterAccess(10, TimeUnit.SECONDS)
.build();
}
And it works but it cannot be discovered by Spring Actuator. Is there any way to register this kind of cache?

Addapted from this Answer I did the following:
#Autowired
private CacheMetricsRegistrar cacheMetricsRegistrar;
private LoadingCache<Key, MyObject> cache;
#PostConstruct
public void init() {
cache = Caffeine.newBuilder()
.maximumSize(10_000)
.refreshAfterWrite(cacheDuration)
.recordStats()
.build(this::loadMyObject);
// trick the compiler
Cache tmp = cache;
cacheMetricsRegistrar.bindCacheToRegistry(new CaffeineCache(CACHE_NAME, tmp), Tag.of("name", CACHE_NAME));
}
The Cache should now show up in the cache actuator endpoints, e.g. "http://localhost:8080/metrics/cache.gets"

use CacheManager
add your custom cache into CacheManager, inject CacheManager and get that cache out for your usage.
see https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-caching.html for some further details

Related

How do you add reactive interceptors to Spring Boot annotated controllers?

I've set up rsocket metrics using rsocket-micrometer on the CLIENT side, by configuring the RSocketConnector with interceptors, like this (Kotlin):
rSocketReqesterBuilder.rsocketConnector { configureConnector(it) }
// ...
private fun configureConnector(rSocketConnector: RSocketConnector) {
rSocketConnector.interceptors { iRegistry ->
// This gives us the rsocket.* counter metrics, like rsocket.frame
iRegistry.forResponder(MicrometerRSocketInterceptor(registry, *localTags.toArray()))
iRegistry.forRequester(MicrometerRSocketInterceptor(registry, *localTags.toArray()))
iRegistry.forConnection(MicrometerDuplexConnectionInterceptor(registry, *localTags.toArray()))
}
}
But on the SERVER side, I'm using an annotated (#MessageMapping) Spring Boot RSocket Controller, like this (Java):
#MessageMapping("replace-channel-controller")
public Flux<TransformResponse> replace(Flux<String> texts) ...
Here, I'm not explicitly in control of the connector.
How do I add interceptors on the server side?
#Configuration
public class RSocketConfig implements RSocketServerCustomizer {
private final MeterRegistry registry;
public RSocketConfig(MeterRegistry registry) {
this.registry = registry;
}
#Override
public void customize(RSocketServer rSocketServer) {
rSocketServer.interceptors(
iRegistry -> {
log.info("Adding RSocket interceptors...");
iRegistry.forResponder(new MicrometerRSocketInterceptor(registry, tags));
iRegistry.forRequester(new MicrometerRSocketInterceptor(registry, tags));
iRegistry.forConnection(new MicrometerDuplexConnectionInterceptor(registry, tags));
}
);
}
}

How to enable distributed/clustered cache when using redis with spring data cache

How to enable distributed/clustered cache when using Redis with spring-boot cache.
Especially when using Redis through spring-boot-starter-data-redis
Enable caching in the spring boot app is very simple. You would need to just follow three steps.
Define cache configuration
Add EnableCaching to any configuration class
Provide a CacheManager bean
For Redis, we've RedisCacheManager that can be configured and created.
Cache Configuration
#Configuration
#Getter
#Setter
#ConfigurationProperties(prefix = "cache")
public class CacheConfigurationProperties {
// Redis host name
private String redisHost;
// Redis port
private int redisPort;
// Default TTL
private long timeoutSeconds;
// TTL per cache, add enties for each cache
private Map<String, Long> cacheTtls;
}
Set their values via properties or yaml file like
cache.redisHost=localhost
cache.redisPort=6379
cache.timeoutSeconds=1000
cache.cacheTtls.cach1=100
cache.cacheTtls.cach2=200
Once you have created configuration, you can create cache config for RedisCacheManger by builder.
#Configuration
#EnableCaching
public class CacheConfig {
private static RedisCacheConfiguration createCacheConfiguration(long timeoutInSeconds) {
return RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofSeconds(timeoutInSeconds));
}
#Bean
public LettuceConnectionFactory redisConnectionFactory(CacheConfigurationProperties properties) {
RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration();
redisStandaloneConfiguration.setHostName(properties.getRedisHost());
redisStandaloneConfiguration.setPort(properties.getRedisPort());
return new LettuceConnectionFactory(redisStandaloneConfiguration);
}
#Bean
public RedisCacheConfiguration cacheConfiguration(CacheConfigurationProperties properties) {
return createCacheConfiguration(properties.getTimeoutSeconds());
}
#Bean
public CacheManager cacheManager(
RedisConnectionFactory redisConnectionFactory, CacheConfigurationProperties properties) {
Map<String, RedisCacheConfiguration> cacheConfigurations = new HashMap<>();
for (Entry<String, Long> cacheNameAndTimeout : properties.getCacheTtls().entrySet()) {
cacheConfigurations.put(
cacheNameAndTimeout.getKey(), createCacheConfiguration(cacheNameAndTimeout.getValue()));
}
return RedisCacheManager.builder(redisConnectionFactory)
.cacheDefaults(cacheConfiguration(properties))
.withInitialCacheConfigurations(cacheConfigurations)
.build();
}
}
If you're using Redis cluster than update cache properties as per that. In this some beans would become primary if you want cache specific bean than make these methods private.

Spring session jdbc - How to add multiple HttpSessionIdResolver for a single application

I have a problem in injecting multiple HttpSessionIdResolver for a single spring application.
For normal web application I would like to use CookieHttpSessionIdResolver
For Rest API I would go for HeaderHttpSessionIdResolver and Rest API url will be like "/api/**"
Internally spring sets a bean and uses that bean for all request(In this case HeaderHttpSessionIdResolver
and my web stopped working because i dont set X-Auth-Token header for every request) but i would like to override it.
Could any one please help me.
Thank you.
#EnableJdbcHttpSession(maxInactiveIntervalInSeconds = 3600)
public class SessionConfig extends AbstractHttpSessionApplicationInitializer{
#Autowired
#Qualifier("userDatabase")
private DataSource dataSource;
#Bean
public DataSource dataSource() {
return dataSource;
}
#Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
#Bean(value = "httpSessionIdResolver")
public HttpSessionIdResolver httpSessionIdResolver() {
return HeaderHttpSessionIdResolver.xAuthToken();
}
#Bean(value = "cookieHttpSessionIdResolver")
public HttpSessionIdResolver cookieHttpSessionIdResolver() {
return new CookieHttpSessionIdResolver();
}
}
I overridden spring session to enable both cookie and header based session.
Now it's working fine.
Currently I'm checking for URL that contains /api/* and if it contains i'm using header based other wise cookie based session.

Spring session with in memory store

Why does not spring.session.store-type has in memory option. ?
Is there any way to use spring session with in memory option without writing my implementation of store ?
I would like to use spring session for rest api with token
#Bean
public HttpSessionIdResolver httpSessionIdResolver() {
return HeaderHttpSessionIdResolver.xAuthToken();
}
I found solution, there is a MapSessionRepository which can accept map.
here is a documentation EnableSpringHttpSession
#EnableSpringHttpSession
#Configuration
public class SpringHttpSessionConfig {
#Bean
public MapSessionRepository sessionRepository() {
return new MapSessionRepository(new ConcurrentHashMap<>());
}
}

Unable to get cache from cachemanager unless cachemanager.getCacheNames() is invoked

My application uses Spring 4.3.x, EhCache 3.6 and javax Cache 1.1.0.
Here is how I've configured javax CacheManager in my application:
<bean id="jCacheManager" class="org.springframework.cache.jcache.JCacheCacheManager">
<property name="cacheManager" ref="appCacheManagerFactoryBean" />
</bean>
<bean id="appCacheManagerFactoryBean" class="com.example.AppCacheManagerFactoryBean"/>
The AppCacheManagerFactoryBean (which is just a customized version of JCacheManagerFactoryBean) helps me to configure a global persistence directory for my app. Here is how it looks:
public class AppCacheManagerFactoryBean implements FactoryBean<CacheManager>, InitializingBean,
DisposableBean {
#Value("${cache.persistenceDir}")
private String persistenceDir;
private CacheManager cacheManager;
#Override
public void afterPropertiesSet() {
this.cacheManager = buildCacheManager();
}
private CacheManager buildCacheManager()
{
EhcacheCachingProvider cachingProvider = (EhcacheCachingProvider) Caching.getCachingProvider();
DefaultConfiguration defaultConfiguration = new DefaultConfiguration(cachingProvider.getDefaultClassLoader(),
new DefaultPersistenceConfiguration(new File(persistenceDir)));
return cachingProvider.getCacheManager(cachingProvider.getDefaultURI(), defaultConfiguration);
}
#Override
public CacheManager getObject() {
return this.cacheManager;
}
#Override
public Class<?> getObjectType() {
return (this.cacheManager != null ? this.cacheManager.getClass() : CacheManager.class);
}
#Override
public boolean isSingleton() {
return true;
}
#Override
public void destroy() {
this.cacheManager.close();
}
}
Here's how I define caches. I use Ehcache API to create my caches as some of the features my caches need are not available through JCache API.
EhcacheManager ehcacheManager = jCacheCacheManager.getCacheManager().unwrap(EhcacheManager.class);
ehcacheManager.createCache("foo", CacheConfigurationBuilder.newCacheConfigurationBuilder(
String.class, Foo.class,
ResourcePoolsBuilder.newResourcePoolsBuilder()
.heap(1)
.offheap(1, MemoryUnit.GB)
.disk(5, MemoryUnit.GB)
));
When I try to retrieve a cache from the CacheManager elsewhere in my app, a null pointer exception is thrown.
Caching.getCachingProvider().getCacheManager().getCache("foo");
However, if I retrieve the cache after invoking the getCacheNames() method in CacheManager, the cache is fetched normally.
Caching.getCachingProvider().getCacheManager().getCacheNames();
Caching.getCachingProvider().getCacheManager().getCache("foo");
What have I missed? Please help me.
My first question would be: "Why not using the built-in support of Spring-cache?" It won't need to do that. The JCacheCacheManager will take care of everything.
Then, your problem is that the cache is created directly in Ehcache without passing through the JSR107 layer. Calling getCacheNames() causes a refresh of the cache list in JSR107 to make it work. However, I'm not sure this global behavior is intended. But it is the way it works.
The solution is to create a cache as expected which is through the JSR107 layer. It looks like this
CacheManager cacheManager = jcacheCacheManager.getCacheManager();
cacheManager.createCache("foo",
Eh107Configuration.fromEhcacheCacheConfiguration(
CacheConfigurationBuilder.newCacheConfigurationBuilder(String.class, Foo.class, ResourcePoolsBuilder.newResourcePoolsBuilder()
.heap(1)
.offheap(1, MemoryUnit.GB)
.disk(5, MemoryUnit.GB))
.build()));

Resources