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-jcache")
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.jooq:jooq") {
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);
mappings.put(CacheType.GENERIC, GenericCacheConfiguration.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.COUCHBASE, CouchbaseCacheConfiguration.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");
* 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 Infinispan infinispan = new Infinispan();
private final JCache jcache = new JCache();
private final Redis redis = new Redis();
@ -78,6 +80,10 @@ public class CacheProperties {
return this.couchbase;
}
public Infinispan getInfinispan() {
return this.infinispan;
}
public JCache getJcache() {
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.
*/

View File

@ -46,6 +46,11 @@ public enum CacheType {
*/
COUCHBASE,
/**
* Infinispan backed caching.
*/
INFINISPAN,
/**
* 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 org.cache2k.extra.spring.SpringCache2kCacheManager;
import org.infinispan.spring.embedded.provider.SpringEmbeddedCacheManager;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.assertj.AssertableApplicationContext;
@ -120,6 +121,13 @@ abstract class AbstractCacheAutoConfigurationTests {
};
}
@Bean
CacheManagerCustomizer<SpringEmbeddedCacheManager> infinispanCacheManagerCustomizer() {
return new CacheManagerTestCustomizer<>() {
};
}
@Bean
CacheManagerCustomizer<SpringCache2kCacheManager> cache2kCacheManagerCustomizer() {
return new CacheManagerTestCustomizer<SpringCache2kCacheManager>() {

View File

@ -34,6 +34,9 @@ import com.hazelcast.core.Hazelcast;
import com.hazelcast.core.HazelcastInstance;
import com.hazelcast.spring.cache.HazelcastCacheManager;
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.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.mockito.BDDMockito.given;
import static org.mockito.BDDMockito.then;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
/**
* 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
void jCacheCacheWithCachesAndCustomizer() {
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)
@EnableCaching
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") {
group("org.influxdb") {
modules = [

View File

@ -2,7 +2,7 @@
== IO
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>>
* *Mail:* <<io#io.email, Sending Email>>
* *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):
. <<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.infinispan,Infinispan>>
. <<io#io.caching.provider.couchbase,Couchbase>>
. <<io#io.caching.provider.redis,Redis>>
. <<io#io.caching.provider.caffeine,Caffeine>>
@ -75,7 +76,7 @@ A `CacheManager` wrapping all beans of that type is created.
[[io.caching.provider.jcache]]
==== 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`".
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.
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]]
==== Couchbase
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
ehcache
hazelcast
infinispan
}
dependencies {
@ -40,6 +41,21 @@ dependencies {
hazelcast("com.hazelcast:hazelcast")
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(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-data-redis"))
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
}
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) {
description = "Runs the tests against Redis"
classpath = sourceSets.redisTest.runtimeClasspath
@ -76,5 +98,5 @@ def testRedis = tasks.register("testRedis", Test) {
}
tasks.named("check").configure {
dependsOn testCaffeine, testCouchbase, testEhcache, testHazelcast, testRedis
dependsOn testCaffeine, testCouchbase, testEhcache, testHazelcast, testInfinispan, testRedis
}