Reinstate support for Infinispan

Closes gh-32556
This commit is contained in:
Andy Wilkinson 2022-10-04 11:11:43 +01:00
parent e0b67889a8
commit 4f86f685c5
11 changed files with 285 additions and 5 deletions

View File

@ -115,6 +115,17 @@ dependencies {
optional("org.hibernate.orm:hibernate-core") optional("org.hibernate.orm:hibernate-core")
optional("org.hibernate.orm:hibernate-jcache") optional("org.hibernate.orm:hibernate-jcache")
optional("org.hibernate.validator:hibernate-validator") optional("org.hibernate.validator:hibernate-validator")
optional("org.infinispan:infinispan-commons-jakarta")
optional("org.infinispan:infinispan-component-annotations")
optional("org.infinispan:infinispan-core-jakarta")
optional("org.infinispan:infinispan-jcache") {
exclude group: "org.infinispan", module: "infinispan-commons"
exclude group: "org.infinispan", module: "infinispan-core"
}
optional("org.infinispan:infinispan-spring5-embedded") {
exclude group: "org.infinispan", module: "infinispan-commons"
exclude group: "org.infinispan", module: "infinispan-core"
}
optional("org.influxdb:influxdb-java") optional("org.influxdb:influxdb-java")
optional("org.jooq:jooq") { optional("org.jooq:jooq") {
exclude group: "javax.xml.bind", module: "jaxb-api" exclude group: "javax.xml.bind", module: "jaxb-api"

View File

@ -37,6 +37,7 @@ final class CacheConfigurations {
Map<CacheType, String> mappings = new EnumMap<>(CacheType.class); Map<CacheType, String> mappings = new EnumMap<>(CacheType.class);
mappings.put(CacheType.GENERIC, GenericCacheConfiguration.class.getName()); mappings.put(CacheType.GENERIC, GenericCacheConfiguration.class.getName());
mappings.put(CacheType.HAZELCAST, HazelcastCacheConfiguration.class.getName()); mappings.put(CacheType.HAZELCAST, HazelcastCacheConfiguration.class.getName());
mappings.put(CacheType.INFINISPAN, InfinispanCacheConfiguration.class.getName());
mappings.put(CacheType.JCACHE, JCacheCacheConfiguration.class.getName()); mappings.put(CacheType.JCACHE, JCacheCacheConfiguration.class.getName());
mappings.put(CacheType.COUCHBASE, CouchbaseCacheConfiguration.class.getName()); mappings.put(CacheType.COUCHBASE, CouchbaseCacheConfiguration.class.getName());
mappings.put(CacheType.REDIS, RedisCacheConfiguration.class.getName()); mappings.put(CacheType.REDIS, RedisCacheConfiguration.class.getName());

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2012-2021 the original author or authors. * Copyright 2012-2022 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -50,6 +50,8 @@ public class CacheProperties {
private final Couchbase couchbase = new Couchbase(); private final Couchbase couchbase = new Couchbase();
private final Infinispan infinispan = new Infinispan();
private final JCache jcache = new JCache(); private final JCache jcache = new JCache();
private final Redis redis = new Redis(); private final Redis redis = new Redis();
@ -78,6 +80,10 @@ public class CacheProperties {
return this.couchbase; return this.couchbase;
} }
public Infinispan getInfinispan() {
return this.infinispan;
}
public JCache getJcache() { public JCache getJcache() {
return this.jcache; return this.jcache;
} }
@ -144,6 +150,26 @@ public class CacheProperties {
} }
/**
* Infinispan specific cache properties.
*/
public static class Infinispan {
/**
* The location of the configuration file to use to initialize Infinispan.
*/
private Resource config;
public Resource getConfig() {
return this.config;
}
public void setConfig(Resource config) {
this.config = config;
}
}
/** /**
* JCache (JSR-107) specific cache properties. * JCache (JSR-107) specific cache properties.
*/ */

View File

@ -46,6 +46,11 @@ public enum CacheType {
*/ */
COUCHBASE, COUCHBASE,
/**
* Infinispan backed caching.
*/
INFINISPAN,
/** /**
* Redis backed caching. * Redis backed caching.
*/ */

View File

@ -0,0 +1,90 @@
/*
* Copyright 2012-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.autoconfigure.cache;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import org.infinispan.configuration.cache.ConfigurationBuilder;
import org.infinispan.manager.DefaultCacheManager;
import org.infinispan.manager.EmbeddedCacheManager;
import org.infinispan.spring.embedded.provider.SpringEmbeddedCacheManager;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.cache.CacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.Resource;
import org.springframework.util.CollectionUtils;
/**
* Infinispan cache configuration.
*
* @author Eddú Meléndez
* @author Stephane Nicoll
* @author Raja Kolli
* @since 1.3.0
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(SpringEmbeddedCacheManager.class)
@ConditionalOnMissingBean(CacheManager.class)
@Conditional(CacheCondition.class)
public class InfinispanCacheConfiguration {
@Bean
public SpringEmbeddedCacheManager cacheManager(CacheManagerCustomizers customizers,
EmbeddedCacheManager embeddedCacheManager) {
SpringEmbeddedCacheManager cacheManager = new SpringEmbeddedCacheManager(embeddedCacheManager);
return customizers.customize(cacheManager);
}
@Bean(destroyMethod = "stop")
@ConditionalOnMissingBean
public EmbeddedCacheManager infinispanCacheManager(CacheProperties cacheProperties,
ObjectProvider<ConfigurationBuilder> defaultConfigurationBuilder) throws IOException {
EmbeddedCacheManager cacheManager = createEmbeddedCacheManager(cacheProperties);
List<String> cacheNames = cacheProperties.getCacheNames();
if (!CollectionUtils.isEmpty(cacheNames)) {
cacheNames.forEach((cacheName) -> cacheManager.defineConfiguration(cacheName,
getDefaultCacheConfiguration(defaultConfigurationBuilder.getIfAvailable())));
}
return cacheManager;
}
private EmbeddedCacheManager createEmbeddedCacheManager(CacheProperties cacheProperties) throws IOException {
Resource location = cacheProperties.resolveConfigLocation(cacheProperties.getInfinispan().getConfig());
if (location != null) {
try (InputStream in = location.getInputStream()) {
return new DefaultCacheManager(in);
}
}
return new DefaultCacheManager();
}
private org.infinispan.configuration.cache.Configuration getDefaultCacheConfiguration(
ConfigurationBuilder defaultConfigurationBuilder) {
if (defaultConfigurationBuilder != null) {
return defaultConfigurationBuilder.build();
}
return new ConfigurationBuilder().build();
}
}

View File

@ -23,6 +23,7 @@ import java.util.Map;
import com.hazelcast.spring.cache.HazelcastCacheManager; import com.hazelcast.spring.cache.HazelcastCacheManager;
import org.cache2k.extra.spring.SpringCache2kCacheManager; import org.cache2k.extra.spring.SpringCache2kCacheManager;
import org.infinispan.spring.embedded.provider.SpringEmbeddedCacheManager;
import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.assertj.AssertableApplicationContext; import org.springframework.boot.test.context.assertj.AssertableApplicationContext;
@ -120,6 +121,13 @@ abstract class AbstractCacheAutoConfigurationTests {
}; };
} }
@Bean
CacheManagerCustomizer<SpringEmbeddedCacheManager> infinispanCacheManagerCustomizer() {
return new CacheManagerTestCustomizer<>() {
};
}
@Bean @Bean
CacheManagerCustomizer<SpringCache2kCacheManager> cache2kCacheManagerCustomizer() { CacheManagerCustomizer<SpringCache2kCacheManager> cache2kCacheManagerCustomizer() {
return new CacheManagerTestCustomizer<SpringCache2kCacheManager>() { return new CacheManagerTestCustomizer<SpringCache2kCacheManager>() {

View File

@ -34,6 +34,9 @@ import com.hazelcast.core.Hazelcast;
import com.hazelcast.core.HazelcastInstance; import com.hazelcast.core.HazelcastInstance;
import com.hazelcast.spring.cache.HazelcastCacheManager; import com.hazelcast.spring.cache.HazelcastCacheManager;
import org.cache2k.extra.spring.SpringCache2kCacheManager; import org.cache2k.extra.spring.SpringCache2kCacheManager;
import org.infinispan.configuration.cache.ConfigurationBuilder;
import org.infinispan.jcache.embedded.JCachingProvider;
import org.infinispan.spring.embedded.provider.SpringEmbeddedCacheManager;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.BeanCreationException; import org.springframework.beans.factory.BeanCreationException;
@ -73,7 +76,9 @@ import org.springframework.test.util.ReflectionTestUtils;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given; import static org.mockito.BDDMockito.given;
import static org.mockito.BDDMockito.then;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
/** /**
* Tests for {@link CacheAutoConfiguration}. * Tests for {@link CacheAutoConfiguration}.
@ -535,6 +540,71 @@ class CacheAutoConfigurationTests extends AbstractCacheAutoConfigurationTests {
}); });
} }
@Test
void infinispanCacheWithConfig() {
this.contextRunner.withUserConfiguration(DefaultCacheConfiguration.class)
.withPropertyValues("spring.cache.type=infinispan", "spring.cache.infinispan.config=infinispan.xml")
.run((context) -> {
SpringEmbeddedCacheManager cacheManager = getCacheManager(context,
SpringEmbeddedCacheManager.class);
assertThat(cacheManager.getCacheNames()).contains("foo", "bar");
});
}
@Test
void infinispanCacheWithCustomizers() {
this.contextRunner.withUserConfiguration(DefaultCacheAndCustomizersConfiguration.class)
.withPropertyValues("spring.cache.type=infinispan")
.run(verifyCustomizers("allCacheManagerCustomizer", "infinispanCacheManagerCustomizer"));
}
@Test
void infinispanCacheWithCaches() {
this.contextRunner.withUserConfiguration(DefaultCacheConfiguration.class)
.withPropertyValues("spring.cache.type=infinispan", "spring.cache.cacheNames[0]=foo",
"spring.cache.cacheNames[1]=bar")
.run((context) -> assertThat(getCacheManager(context, SpringEmbeddedCacheManager.class).getCacheNames())
.containsOnly("foo", "bar"));
}
@Test
void infinispanCacheWithCachesAndCustomConfig() {
this.contextRunner.withUserConfiguration(InfinispanCustomConfiguration.class)
.withPropertyValues("spring.cache.type=infinispan", "spring.cache.cacheNames[0]=foo",
"spring.cache.cacheNames[1]=bar")
.run((context) -> {
assertThat(getCacheManager(context, SpringEmbeddedCacheManager.class).getCacheNames())
.containsOnly("foo", "bar");
then(context.getBean(ConfigurationBuilder.class)).should(times(2)).build();
});
}
@Test
void infinispanAsJCacheWithCaches() {
String cachingProviderClassName = JCachingProvider.class.getName();
this.contextRunner.withUserConfiguration(DefaultCacheConfiguration.class)
.withPropertyValues("spring.cache.type=jcache",
"spring.cache.jcache.provider=" + cachingProviderClassName, "spring.cache.cacheNames[0]=foo",
"spring.cache.cacheNames[1]=bar")
.run((context) -> assertThat(getCacheManager(context, JCacheCacheManager.class).getCacheNames())
.containsOnly("foo", "bar"));
}
@Test
void infinispanAsJCacheWithConfig() {
String cachingProviderClassName = JCachingProvider.class.getName();
String configLocation = "infinispan.xml";
this.contextRunner.withUserConfiguration(DefaultCacheConfiguration.class)
.withPropertyValues("spring.cache.type=jcache",
"spring.cache.jcache.provider=" + cachingProviderClassName,
"spring.cache.jcache.config=" + configLocation)
.run((context) -> {
Resource configResource = new ClassPathResource(configLocation);
assertThat(getCacheManager(context, JCacheCacheManager.class).getCacheManager().getURI())
.isEqualTo(configResource.getURI());
});
}
@Test @Test
void jCacheCacheWithCachesAndCustomizer() { void jCacheCacheWithCachesAndCustomizer() {
String cachingProviderFqn = HazelcastServerCachingProvider.class.getName(); String cachingProviderFqn = HazelcastServerCachingProvider.class.getName();
@ -848,6 +918,19 @@ class CacheAutoConfigurationTests extends AbstractCacheAutoConfigurationTests {
} }
@Configuration(proxyBeanMethods = false)
@EnableCaching
static class InfinispanCustomConfiguration {
@Bean
ConfigurationBuilder configurationBuilder() {
ConfigurationBuilder builder = mock(ConfigurationBuilder.class);
given(builder.build()).willReturn(new ConfigurationBuilder().build());
return builder;
}
}
@Configuration(proxyBeanMethods = false) @Configuration(proxyBeanMethods = false)
@EnableCaching @EnableCaching
static class CustomCacheManagerConfiguration { static class CustomCacheManagerConfiguration {

View File

@ -443,6 +443,13 @@ bom {
] ]
} }
} }
library("Infinispan", "14.0.0.Final") {
group("org.infinispan") {
imports = [
"infinispan-bom"
]
}
}
library("InfluxDB Java", "2.23") { library("InfluxDB Java", "2.23") {
group("org.influxdb") { group("org.influxdb") {
modules = [ modules = [

View File

@ -2,7 +2,7 @@
== IO == IO
If your application needs IO capabilities, see one or more of the following sections: If your application needs IO capabilities, see one or more of the following sections:
* *Caching:* <<io#io.caching, Caching support with EhCache, Hazelcast and more>> * *Caching:* <<io#io.caching, Caching support with EhCache, Hazelcast, Infinispan, and more>>
* *Quartz:* <<io#io.quartz, Quartz Scheduling>> * *Quartz:* <<io#io.quartz, Quartz Scheduling>>
* *Mail:* <<io#io.email, Sending Email>> * *Mail:* <<io#io.email, Sending Email>>
* *Validation:* <<io#io.validation, JSR-303 Validation>> * *Validation:* <<io#io.validation, JSR-303 Validation>>

View File

@ -37,8 +37,9 @@ The cache abstraction does not provide an actual store and relies on abstraction
If you have not defined a bean of type `CacheManager` or a `CacheResolver` named `cacheResolver` (see {spring-framework-api}/cache/annotation/CachingConfigurer.html[`CachingConfigurer`]), Spring Boot tries to detect the following providers (in the indicated order): If you have not defined a bean of type `CacheManager` or a `CacheResolver` named `cacheResolver` (see {spring-framework-api}/cache/annotation/CachingConfigurer.html[`CachingConfigurer`]), Spring Boot tries to detect the following providers (in the indicated order):
. <<io#io.caching.provider.generic,Generic>> . <<io#io.caching.provider.generic,Generic>>
. <<io#io.caching.provider.jcache,JCache (JSR-107)>> (EhCache 3, Hazelcast, and others) . <<io#io.caching.provider.jcache,JCache (JSR-107)>> (EhCache 3, Hazelcast, Infinispan, and others)
. <<io#io.caching.provider.hazelcast,Hazelcast>> . <<io#io.caching.provider.hazelcast,Hazelcast>>
. <<io#io.caching.provider.infinispan,Infinispan>>
. <<io#io.caching.provider.couchbase,Couchbase>> . <<io#io.caching.provider.couchbase,Couchbase>>
. <<io#io.caching.provider.redis,Redis>> . <<io#io.caching.provider.redis,Redis>>
. <<io#io.caching.provider.caffeine,Caffeine>> . <<io#io.caching.provider.caffeine,Caffeine>>
@ -75,7 +76,7 @@ A `CacheManager` wrapping all beans of that type is created.
[[io.caching.provider.jcache]] [[io.caching.provider.jcache]]
==== JCache (JSR-107) ==== JCache (JSR-107)
https://jcp.org/en/jsr/detail?id=107[JCache] is bootstrapped through the presence of a `javax.cache.spi.CachingProvider` on the classpath (that is, a JSR-107 compliant caching library exists on the classpath), and the `JCacheCacheManager` is provided by the `spring-boot-starter-cache` "`Starter`". https://jcp.org/en/jsr/detail?id=107[JCache] is bootstrapped through the presence of a `javax.cache.spi.CachingProvider` on the classpath (that is, a JSR-107 compliant caching library exists on the classpath), and the `JCacheCacheManager` is provided by the `spring-boot-starter-cache` "`Starter`".
Various compliant libraries are available, and Spring Boot provides dependency management for Ehcache 3 and Hazelcast. Various compliant libraries are available, and Spring Boot provides dependency management for Ehcache 3, Hazelcast, and Infinispan.
Any other compliant library can be added as well. Any other compliant library can be added as well.
It might happen that more than one provider is present, in which case the provider must be explicitly specified. It might happen that more than one provider is present, in which case the provider must be explicitly specified.
@ -114,6 +115,32 @@ If a `HazelcastInstance` has been auto-configured, it is automatically wrapped i
[[io.caching.provider.infinispan]]
==== Infinispan
https://infinispan.org/[Infinispan] has no default configuration file location, so it must be specified explicitly.
Otherwise, the default bootstrap is used.
[source,yaml,indent=0,subs="verbatim",configprops,configblocks]
----
spring:
cache:
infinispan:
config: "infinispan.xml"
----
Caches can be created on startup by setting the configprop:spring.cache.cache-names[] property.
If a custom `ConfigurationBuilder` bean is defined, it is used to customize the caches.
To be compatible with Spring Boot's Jakarta EE 9 baseline, Infinispan's `-jakarta` modules must be used.
For every module with a `-jakarta` variant, the variant must be used in place of the standard module.
For example, `infinispan-core-jakarta` and `infinispan-commons-jakarta` must be used in place of `infinispan-core` and `infinispan-commons` respectively.
NOTE: The support of Infinispan in Spring Boot is restricted to the embedded mode and is quite basic.
If you want more options, you should use the official Infinispan Spring Boot starter instead.
See https://github.com/infinispan/infinispan-spring-boot[Infinispan's documentation] for more details.
[[io.caching.provider.couchbase]] [[io.caching.provider.couchbase]]
==== Couchbase ==== Couchbase
If Spring Data Couchbase is available and Couchbase is <<data#data.nosql.couchbase,configured>>, a `CouchbaseCacheManager` is auto-configured. If Spring Data Couchbase is available and Couchbase is <<data#data.nosql.couchbase,configured>>, a `CouchbaseCacheManager` is auto-configured.

View File

@ -17,6 +17,7 @@ configurations {
couchbase couchbase
ehcache ehcache
hazelcast hazelcast
infinispan
} }
dependencies { dependencies {
@ -40,6 +41,21 @@ dependencies {
hazelcast("com.hazelcast:hazelcast") hazelcast("com.hazelcast:hazelcast")
hazelcast("com.hazelcast:hazelcast-spring") hazelcast("com.hazelcast:hazelcast-spring")
infinispan(enforcedPlatform(project(":spring-boot-project:spring-boot-dependencies")))
infinispan("javax.cache:cache-api")
infinispan("org.infinispan:infinispan-commons-jakarta")
infinispan("org.infinispan:infinispan-component-annotations")
infinispan("org.infinispan:infinispan-core-jakarta")
infinispan("org.infinispan:infinispan-jcache")
modules {
module("org.inifinispan:infinispan-commons") {
replacedBy("org.infinispan:infinispan-commons-jakarta", "Java EE 9 baseline")
}
module("org.inifinispan:infinispan-core") {
replacedBy("org.infinispan:infinispan-core-jakarta", "Java EE 9 baseline")
}
}
redisTestImplementation(enforcedPlatform(project(":spring-boot-project:spring-boot-parent"))) redisTestImplementation(enforcedPlatform(project(":spring-boot-project:spring-boot-parent")))
redisTestImplementation(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-data-redis")) redisTestImplementation(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-data-redis"))
redisTestImplementation(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-test")) redisTestImplementation(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-test"))
@ -69,6 +85,12 @@ def testHazelcast = tasks.register("testHazelcast", Test) {
classpath = sourceSets.test.runtimeClasspath + configurations.hazelcast classpath = sourceSets.test.runtimeClasspath + configurations.hazelcast
} }
def testInfinispan = tasks.register("testInfinispan", Test) {
description = "Runs the tests against Infinispan"
classpath = sourceSets.test.runtimeClasspath + configurations.infinispan
systemProperties = ["spring.cache.jcache.config" : "classpath:infinispan.xml"]
}
def testRedis = tasks.register("testRedis", Test) { def testRedis = tasks.register("testRedis", Test) {
description = "Runs the tests against Redis" description = "Runs the tests against Redis"
classpath = sourceSets.redisTest.runtimeClasspath classpath = sourceSets.redisTest.runtimeClasspath
@ -76,5 +98,5 @@ def testRedis = tasks.register("testRedis", Test) {
} }
tasks.named("check").configure { tasks.named("check").configure {
dependsOn testCaffeine, testCouchbase, testEhcache, testHazelcast, testRedis dependsOn testCaffeine, testCouchbase, testEhcache, testHazelcast, testInfinispan, testRedis
} }