Spring 启动测试尝试第二次初始化 cache2k 但失败
Spring Boot test tries to initialize cache2k for the 2nd time and fails
将 cache2k 添加到我的项目后,一些 @SpringBootTest
停止工作并出现错误:
java.lang.IllegalStateException: Cache already created: 'cache'
下面我提供了最小的例子来重现:
转到start.spring.io并使用Cache starter创建一个最简单的Maven项目,然后添加cache2k依赖项:
<properties>
<java.version>1.8</java.version>
<cache2k-version>1.2.2.Final</cache2k-version>
</properties>
<dependencies>
<dependency>
<groupId>org.cache2k</groupId>
<artifactId>cache2k-api</artifactId>
<version>${cache2k-version}</version>
</dependency>
<dependency>
<groupId>org.cache2k</groupId>
<artifactId>cache2k-core</artifactId>
<version>${cache2k-version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.cache2k</groupId>
<artifactId>cache2k-spring</artifactId>
<version>${cache2k-version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
现在配置最简单的缓存:
@SpringBootApplication
@EnableCaching
public class CachingDemoApplication {
public static void main(String[] args) {
SpringApplication.run(CachingDemoApplication.class, args);
}
@Bean
public CacheManager springCacheManager() {
SpringCache2kCacheManager cacheManager = new SpringCache2kCacheManager();
cacheManager.addCaches(b -> b.name("cache"));
return cacheManager;
}
}
并添加任何服务(我们将 @MockBean
在我们的一项测试中:
@Service
public class SomeService {
public String getString() {
System.out.println("Executing service method");
return "foo";
}
}
现在需要两次 @SpringBootTest
测试才能重现该问题:
@SpringBootTest
@RunWith(SpringRunner.class)
public class SpringBootAppTest {
@Test
public void getString() {
System.out.println("Empty test");
}
}
@RunWith(SpringRunner.class)
@SpringBootTest
public class WithMockedBeanTest {
@MockBean
SomeService service;
@Test
public void contextLoads() {
}
}
请注意,第二个测试模拟了 @MockBean
。这会导致错误(下面的堆栈跟踪)。
Caused by: java.lang.IllegalStateException: Cache already created: 'cache'
at org.cache2k.core.CacheManagerImpl.newCache(CacheManagerImpl.java:174)
at org.cache2k.core.InternalCache2kBuilder.buildAsIs(InternalCache2kBuilder.java:239)
at org.cache2k.core.InternalCache2kBuilder.build(InternalCache2kBuilder.java:182)
at org.cache2k.core.Cache2kCoreProviderImpl.createCache(Cache2kCoreProviderImpl.java:215)
at org.cache2k.Cache2kBuilder.build(Cache2kBuilder.java:837)
at org.cache2k.extra.spring.SpringCache2kCacheManager.buildAndWrap(SpringCache2kCacheManager.java:205)
at org.cache2k.extra.spring.SpringCache2kCacheManager.lambda$addCache(SpringCache2kCacheManager.java:143)
at java.util.concurrent.ConcurrentHashMap.compute(ConcurrentHashMap.java:1853)
at org.cache2k.extra.spring.SpringCache2kCacheManager.addCache(SpringCache2kCacheManager.java:141)
at org.cache2k.extra.spring.SpringCache2kCacheManager.addCaches(SpringCache2kCacheManager.java:132)
at com.example.cachingdemo.CachingDemoApplication.springCacheManager(CachingDemoApplication.java:23)
at com.example.cachingdemo.CachingDemoApplication$$EnhancerBySpringCGLIB$dce99ca.CGLIB$springCacheManager[=15=](<generated>)
at com.example.cachingdemo.CachingDemoApplication$$EnhancerBySpringCGLIB$dce99ca$$FastClassBySpringCGLIB$$bbd240c0.invoke(<generated>)
at org.springframework.cglib.proxy.MethodProxy.invokeSuper(MethodProxy.java:244)
at org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor.intercept(ConfigurationClassEnhancer.java:363)
at com.example.cachingdemo.CachingDemoApplication$$EnhancerBySpringCGLIB$dce99ca.springCacheManager(<generated>)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:154)
... 52 more
如果删除 @MockBean
,两个测试都会通过。
如何在我的测试套件中避免这个错误?
您的第二个测试代表完全不同的 ApplicationContext
,因此测试框架将为其启动一个专用测试。如果 cache2k
是有状态的(例如为给定的类加载器共享 CacheManager
,如果它已经存在),第二个上下文将尝试创建一个新的 CacheManager
,而第一个仍然处于活动状态。
您需要将其中一个测试标记为脏(请参阅 @DirtiesContext
),这将关闭上下文并关闭 CacheManager
,或者您可以通过以下选项替换缓存基础结构不需要所有这些,请参阅 @AutoConfigureCache
.
如果 cache2k
的工作方式需要您弄脏上下文,我强烈建议您使用后面的选项来交换它。
由于我不想在测试中使用任何自定义行为,只是想消除此错误,因此解决方案是使用如下唯一名称创建 CacheManager:
@Bean
public CacheManager springCacheManager() {
SpringCache2kCacheManager cacheManager = new SpringCache2kCacheManager("spring-" + hashCode());
cacheManager.addCaches(b -> b.name("cache"));
return cacheManager;
}
我在Spring Dev Tools中使用cache2k时遇到了同样的错误,最终得到以下代码作为解决方案:
@Bean
public CacheManager cacheManager() {
SpringCache2kCacheManager cacheManager = new SpringCache2kCacheManager();
// To avoid the "Caused by: java.lang.IllegalStateException: Cache already created:"
// error when Spring DevTools is enabled and code reloaded
if (cacheManager.getCacheNames().stream()
.filter(name -> name.equals("cache"))
.count() == 0) {
cacheManager.addCaches(
b -> b.name("cache")
);
}
return cacheManager;
}
将 cache2k 添加到我的项目后,一些 @SpringBootTest
停止工作并出现错误:
java.lang.IllegalStateException: Cache already created: 'cache'
下面我提供了最小的例子来重现:
转到start.spring.io并使用Cache starter创建一个最简单的Maven项目,然后添加cache2k依赖项:
<properties>
<java.version>1.8</java.version>
<cache2k-version>1.2.2.Final</cache2k-version>
</properties>
<dependencies>
<dependency>
<groupId>org.cache2k</groupId>
<artifactId>cache2k-api</artifactId>
<version>${cache2k-version}</version>
</dependency>
<dependency>
<groupId>org.cache2k</groupId>
<artifactId>cache2k-core</artifactId>
<version>${cache2k-version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.cache2k</groupId>
<artifactId>cache2k-spring</artifactId>
<version>${cache2k-version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
现在配置最简单的缓存:
@SpringBootApplication
@EnableCaching
public class CachingDemoApplication {
public static void main(String[] args) {
SpringApplication.run(CachingDemoApplication.class, args);
}
@Bean
public CacheManager springCacheManager() {
SpringCache2kCacheManager cacheManager = new SpringCache2kCacheManager();
cacheManager.addCaches(b -> b.name("cache"));
return cacheManager;
}
}
并添加任何服务(我们将 @MockBean
在我们的一项测试中:
@Service
public class SomeService {
public String getString() {
System.out.println("Executing service method");
return "foo";
}
}
现在需要两次 @SpringBootTest
测试才能重现该问题:
@SpringBootTest
@RunWith(SpringRunner.class)
public class SpringBootAppTest {
@Test
public void getString() {
System.out.println("Empty test");
}
}
@RunWith(SpringRunner.class)
@SpringBootTest
public class WithMockedBeanTest {
@MockBean
SomeService service;
@Test
public void contextLoads() {
}
}
请注意,第二个测试模拟了 @MockBean
。这会导致错误(下面的堆栈跟踪)。
Caused by: java.lang.IllegalStateException: Cache already created: 'cache'
at org.cache2k.core.CacheManagerImpl.newCache(CacheManagerImpl.java:174)
at org.cache2k.core.InternalCache2kBuilder.buildAsIs(InternalCache2kBuilder.java:239)
at org.cache2k.core.InternalCache2kBuilder.build(InternalCache2kBuilder.java:182)
at org.cache2k.core.Cache2kCoreProviderImpl.createCache(Cache2kCoreProviderImpl.java:215)
at org.cache2k.Cache2kBuilder.build(Cache2kBuilder.java:837)
at org.cache2k.extra.spring.SpringCache2kCacheManager.buildAndWrap(SpringCache2kCacheManager.java:205)
at org.cache2k.extra.spring.SpringCache2kCacheManager.lambda$addCache(SpringCache2kCacheManager.java:143)
at java.util.concurrent.ConcurrentHashMap.compute(ConcurrentHashMap.java:1853)
at org.cache2k.extra.spring.SpringCache2kCacheManager.addCache(SpringCache2kCacheManager.java:141)
at org.cache2k.extra.spring.SpringCache2kCacheManager.addCaches(SpringCache2kCacheManager.java:132)
at com.example.cachingdemo.CachingDemoApplication.springCacheManager(CachingDemoApplication.java:23)
at com.example.cachingdemo.CachingDemoApplication$$EnhancerBySpringCGLIB$dce99ca.CGLIB$springCacheManager[=15=](<generated>)
at com.example.cachingdemo.CachingDemoApplication$$EnhancerBySpringCGLIB$dce99ca$$FastClassBySpringCGLIB$$bbd240c0.invoke(<generated>)
at org.springframework.cglib.proxy.MethodProxy.invokeSuper(MethodProxy.java:244)
at org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor.intercept(ConfigurationClassEnhancer.java:363)
at com.example.cachingdemo.CachingDemoApplication$$EnhancerBySpringCGLIB$dce99ca.springCacheManager(<generated>)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:154)
... 52 more
如果删除 @MockBean
,两个测试都会通过。
如何在我的测试套件中避免这个错误?
您的第二个测试代表完全不同的 ApplicationContext
,因此测试框架将为其启动一个专用测试。如果 cache2k
是有状态的(例如为给定的类加载器共享 CacheManager
,如果它已经存在),第二个上下文将尝试创建一个新的 CacheManager
,而第一个仍然处于活动状态。
您需要将其中一个测试标记为脏(请参阅 @DirtiesContext
),这将关闭上下文并关闭 CacheManager
,或者您可以通过以下选项替换缓存基础结构不需要所有这些,请参阅 @AutoConfigureCache
.
如果 cache2k
的工作方式需要您弄脏上下文,我强烈建议您使用后面的选项来交换它。
由于我不想在测试中使用任何自定义行为,只是想消除此错误,因此解决方案是使用如下唯一名称创建 CacheManager:
@Bean
public CacheManager springCacheManager() {
SpringCache2kCacheManager cacheManager = new SpringCache2kCacheManager("spring-" + hashCode());
cacheManager.addCaches(b -> b.name("cache"));
return cacheManager;
}
我在Spring Dev Tools中使用cache2k时遇到了同样的错误,最终得到以下代码作为解决方案:
@Bean
public CacheManager cacheManager() {
SpringCache2kCacheManager cacheManager = new SpringCache2kCacheManager();
// To avoid the "Caused by: java.lang.IllegalStateException: Cache already created:"
// error when Spring DevTools is enabled and code reloaded
if (cacheManager.getCacheNames().stream()
.filter(name -> name.equals("cache"))
.count() == 0) {
cacheManager.addCaches(
b -> b.name("cache")
);
}
return cacheManager;
}