Skip to content

Commit ed538ea

Browse files
committed
NoSuchCacheManagerFailureAnalyzer - analyzer for 'CacheManager' when 'CacheManager' is not found/unique.
1 parent b64feb4 commit ed538ea

File tree

5 files changed

+294
-17
lines changed

5 files changed

+294
-17
lines changed

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/CacheAutoConfiguration.java

Lines changed: 66 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616

1717
package org.springframework.boot.autoconfigure.cache;
1818

19+
import java.util.Map;
20+
import java.util.Set;
1921
import java.util.stream.Collectors;
2022

2123
import org.springframework.beans.factory.InitializingBean;
@@ -42,7 +44,8 @@
4244
import org.springframework.core.type.AnnotationMetadata;
4345
import org.springframework.orm.jpa.AbstractEntityManagerFactoryBean;
4446
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
45-
import org.springframework.util.Assert;
47+
import org.springframework.util.CollectionUtils;
48+
import org.springframework.util.StringUtils;
4649

4750
/**
4851
* {@link EnableAutoConfiguration Auto-configuration} for the cache abstraction. Creates a
@@ -74,8 +77,9 @@ public CacheManagerCustomizers cacheManagerCustomizers(
7477

7578
@Bean
7679
public CacheManagerValidator cacheAutoConfigurationValidator(
77-
CacheProperties cacheProperties, ObjectProvider<CacheManager> cacheManager) {
78-
return new CacheManagerValidator(cacheProperties, cacheManager);
80+
CacheProperties cacheProperties,
81+
ObjectProvider<Map<String, CacheManager>> cacheManagers) {
82+
return new CacheManagerValidator(cacheProperties, cacheManagers);
7983
}
8084

8185
@Configuration
@@ -91,27 +95,29 @@ public CacheManagerJpaDependencyConfiguration() {
9195
}
9296

9397
/**
94-
* Bean used to validate that a CacheManager exists and provide a more meaningful
95-
* exception.
98+
* Bean used to validate that a CacheManager exists and it unique.
9699
*/
97100
static class CacheManagerValidator implements InitializingBean {
98101

99102
private final CacheProperties cacheProperties;
100103

101-
private final ObjectProvider<CacheManager> cacheManager;
104+
private final Map<String, CacheManager> cacheManagers;
102105

103106
CacheManagerValidator(CacheProperties cacheProperties,
104-
ObjectProvider<CacheManager> cacheManager) {
107+
ObjectProvider<Map<String, CacheManager>> cacheManagers) {
105108
this.cacheProperties = cacheProperties;
106-
this.cacheManager = cacheManager;
109+
this.cacheManagers = cacheManagers.getIfAvailable();
107110
}
108111

109112
@Override
110113
public void afterPropertiesSet() {
111-
Assert.notNull(this.cacheManager.getIfAvailable(),
112-
() -> "No cache manager could "
113-
+ "be auto-configured, check your configuration (caching "
114-
+ "type is '" + this.cacheProperties.getType() + "')");
114+
if (CollectionUtils.isEmpty(this.cacheManagers)) {
115+
throw new NoSuchCacheManagerException(this.cacheProperties);
116+
}
117+
if (this.cacheManagers.size() > 1) {
118+
throw new NoUniqueCacheManagerException(this.cacheManagers.keySet(),
119+
this.cacheProperties);
120+
}
115121
}
116122

117123
}
@@ -133,4 +139,52 @@ public String[] selectImports(AnnotationMetadata importingClassMetadata) {
133139

134140
}
135141

142+
/**
143+
* Exception thrown when {@link org.springframework.cache.CacheManager} implementation
144+
* are not specified.
145+
*/
146+
static class NoSuchCacheManagerException extends RuntimeException {
147+
148+
private final CacheProperties properties;
149+
150+
NoSuchCacheManagerException(CacheProperties properties) {
151+
super(String.format("No qualifying bean of type '%s' available",
152+
CacheManager.class.getName()));
153+
this.properties = properties;
154+
}
155+
156+
NoSuchCacheManagerException(String message, CacheProperties properties) {
157+
super(message);
158+
this.properties = properties;
159+
}
160+
161+
CacheProperties getProperties() {
162+
return this.properties;
163+
}
164+
165+
}
166+
167+
/**
168+
* Exception thrown when multiple {@link org.springframework.cache.CacheManager}
169+
* implementations are available with no way to know which implementation should be
170+
* used.
171+
*/
172+
static class NoUniqueCacheManagerException extends NoSuchCacheManagerException {
173+
174+
private final Set<String> beanNames;
175+
176+
NoUniqueCacheManagerException(Set<String> beanNames, CacheProperties properties) {
177+
super(String.format(
178+
"expected single matching bean of type '%s' but found " + "%d: %s",
179+
CacheManager.class.getName(), beanNames.size(),
180+
StringUtils.collectionToCommaDelimitedString(beanNames)), properties);
181+
this.beanNames = beanNames;
182+
}
183+
184+
Set<String> getBeanNames() {
185+
return this.beanNames;
186+
}
187+
188+
}
189+
136190
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
/*
2+
* Copyright 2012-2018 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.autoconfigure.cache;
18+
19+
import java.util.Collection;
20+
21+
import org.springframework.beans.BeansException;
22+
import org.springframework.beans.factory.BeanFactory;
23+
import org.springframework.boot.autoconfigure.diagnostics.analyzer.NoSuchBeanDefinitionFailureAnalyzerSupport;
24+
import org.springframework.boot.diagnostics.AbstractFailureAnalyzer;
25+
import org.springframework.boot.diagnostics.FailureAnalysis;
26+
import org.springframework.boot.diagnostics.analyzer.NoUniqueBeanDefinitionFailureAnalyzerSupport;
27+
import org.springframework.cache.CacheManager;
28+
29+
/**
30+
* An {@link AbstractFailureAnalyzer} for
31+
* {@link CacheAutoConfiguration.NoSuchCacheManagerException}.
32+
*
33+
* @author Dmytro Nosan
34+
*/
35+
class NoSuchCacheManagerFailureAnalyzer extends
36+
NoSuchBeanDefinitionFailureAnalyzerSupport<CacheAutoConfiguration.NoSuchCacheManagerException> {
37+
38+
private final NoUniqueCacheManagerFailureAnalyzer analyzer = new NoUniqueCacheManagerFailureAnalyzer();
39+
40+
@Override
41+
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
42+
super.setBeanFactory(beanFactory);
43+
this.analyzer.setBeanFactory(beanFactory);
44+
}
45+
46+
@Override
47+
public FailureAnalysis analyze(Throwable failure) {
48+
FailureAnalysis analyze = this.analyzer.analyze(failure);
49+
if (analyze != null) {
50+
return analyze;
51+
}
52+
return super.analyze(failure);
53+
}
54+
55+
@Override
56+
protected BeanMetadata getBeanMetadata(Throwable rootFailure,
57+
CacheAutoConfiguration.NoSuchCacheManagerException cause) {
58+
return new BeanMetadata(CacheManager.class);
59+
}
60+
61+
private static final class NoUniqueCacheManagerFailureAnalyzer extends
62+
NoUniqueBeanDefinitionFailureAnalyzerSupport<CacheAutoConfiguration.NoUniqueCacheManagerException> {
63+
64+
@Override
65+
protected Collection<String> getBeanNames(Throwable rootFailure,
66+
CacheAutoConfiguration.NoUniqueCacheManagerException cause) {
67+
return cause.getBeanNames();
68+
}
69+
70+
}
71+
72+
}

spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,8 @@ org.springframework.boot.diagnostics.FailureAnalyzer=\
143143
org.springframework.boot.autoconfigure.diagnostics.analyzer.NoSuchBeanDefinitionFailureAnalyzer,\
144144
org.springframework.boot.autoconfigure.jdbc.DataSourceBeanCreationFailureAnalyzer,\
145145
org.springframework.boot.autoconfigure.jdbc.HikariDriverConfigurationFailureAnalyzer,\
146-
org.springframework.boot.autoconfigure.session.NonUniqueSessionRepositoryFailureAnalyzer
146+
org.springframework.boot.autoconfigure.session.NonUniqueSessionRepositoryFailureAnalyzer,\
147+
org.springframework.boot.autoconfigure.cache.NoSuchCacheManagerFailureAnalyzer
147148

148149
# Template availability providers
149150
org.springframework.boot.autoconfigure.template.TemplateAvailabilityProvider=\

spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/cache/CacheAutoConfigurationTests.java

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,16 @@ public void simpleCacheExplicitWithCacheNames() {
171171
});
172172
}
173173

174+
@Test
175+
public void multiplyCacheManagers() {
176+
this.contextRunner.withUserConfiguration(DefaultCacheConfiguration.class)
177+
.withConfiguration(AutoConfigurations.of(CacheManagerConfiguration.class))
178+
.withPropertyValues("spring.cache.type=simple")
179+
.run((context) -> assertThat(context).getFailure()
180+
.isInstanceOf(BeanCreationException.class).hasMessageContaining(
181+
"expected single matching bean of type 'org.springframework.cache.CacheManager' but found 2: cacheManager,customCacheManager"));
182+
}
183+
174184
@Test
175185
public void genericCacheWithCaches() {
176186
this.contextRunner.withUserConfiguration(GenericCacheConfiguration.class)
@@ -191,8 +201,8 @@ public void genericCacheExplicit() {
191201
.withPropertyValues("spring.cache.type=generic")
192202
.run((context) -> assertThat(context).getFailure()
193203
.isInstanceOf(BeanCreationException.class)
194-
.hasMessageContaining("No cache manager could be auto-configured")
195-
.hasMessageContaining("GENERIC"));
204+
.hasMessageContaining(" No qualifying bean of type 'org"
205+
+ ".springframework.cache.CacheManager'"));
196206
}
197207

198208
@Test
@@ -362,8 +372,8 @@ public void jCacheCacheNoProviderExplicit() {
362372
.withPropertyValues("spring.cache.type=jcache")
363373
.run((context) -> assertThat(context).getFailure()
364374
.isInstanceOf(BeanCreationException.class)
365-
.hasMessageContaining("No cache manager could be auto-configured")
366-
.hasMessageContaining("JCACHE"));
375+
.hasMessageContaining(" No qualifying bean of type 'org"
376+
+ ".springframework.cache.CacheManager'"));
367377
}
368378

369379
@Test
@@ -811,6 +821,16 @@ static class DefaultCacheAndCustomizersConfiguration {
811821

812822
}
813823

824+
@Configuration
825+
static class CacheManagerConfiguration {
826+
827+
@Bean
828+
public CacheManager customCacheManager() {
829+
return new SimpleCacheManager();
830+
}
831+
832+
}
833+
814834
@Configuration
815835
@EnableCaching
816836
static class GenericCacheConfiguration {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
/*
2+
* Copyright 2012-2018 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.autoconfigure.cache;
18+
19+
import org.junit.Test;
20+
21+
import org.springframework.beans.factory.BeanCreationException;
22+
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
23+
import org.springframework.boot.diagnostics.FailureAnalysis;
24+
import org.springframework.boot.diagnostics.LoggingFailureAnalysisReporter;
25+
import org.springframework.boot.test.util.TestPropertyValues;
26+
import org.springframework.cache.CacheManager;
27+
import org.springframework.cache.annotation.EnableCaching;
28+
import org.springframework.cache.concurrent.ConcurrentMapCacheManager;
29+
import org.springframework.cache.support.SimpleCacheManager;
30+
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
31+
import org.springframework.context.annotation.Bean;
32+
import org.springframework.context.annotation.Configuration;
33+
import org.springframework.context.annotation.Import;
34+
35+
import static org.assertj.core.api.Assertions.assertThat;
36+
37+
/**
38+
* Tests for {@link NoSuchCacheManagerFailureAnalyzer}.
39+
*
40+
* @author Dmytro Nosan
41+
*/
42+
public class NoSuchCacheManagerFailureAnalyzerTests {
43+
44+
private final NoSuchCacheManagerFailureAnalyzer analyzer = new NoSuchCacheManagerFailureAnalyzer();
45+
46+
@Test
47+
public void failureAnalysisForNoSuchCacheManager() {
48+
FailureAnalysis analysis = analyzeFailure(createFailure(
49+
DefaultCacheAutoConfiguration.class, "spring.cache.type=generic"));
50+
assertThat(analysis.getAction())
51+
.contains("Consider revisiting the entries above or defining "
52+
+ "a bean of type 'org.springframework.cache.CacheManager' in your configuration.");
53+
assertThat(analysis.getDescription()).contains(
54+
"A component required a bean of type 'org.springframework.cache.CacheManager' that could not be found.")
55+
.contains(
56+
"did not find any beans of type org.springframework.cache.Cache");
57+
}
58+
59+
@Test
60+
public void failureAnalysisForNoUniqueCacheManager() {
61+
FailureAnalysis analysis = analyzeFailure(
62+
createFailure(DefaultSeveralCacheManagerConfiguration.class,
63+
"spring.cache.type=simple"));
64+
65+
assertThat(analysis.getDescription())
66+
.contains("A component required a single bean, but 2 were found")
67+
.contains(String.format("'cacheManager' of type '%s'",
68+
ConcurrentMapCacheManager.class.getName()))
69+
.contains(String.format("'simpleCacheManager' of type '%s'",
70+
SimpleCacheManager.class.getName()));
71+
72+
assertThat(analysis.getAction()).contains(
73+
"Consider marking one of the beans as @Primary, updating the consumer "
74+
+ "to accept multiple beans, or using @Qualifier to identify the bean that should be consumed");
75+
76+
}
77+
78+
private BeanCreationException createFailure(Class<?> config, String... environment) {
79+
try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext()) {
80+
this.analyzer.setBeanFactory(context.getBeanFactory());
81+
TestPropertyValues.of(environment).applyTo(context);
82+
context.register(config);
83+
context.refresh();
84+
return null;
85+
}
86+
catch (BeanCreationException ex) {
87+
return ex;
88+
}
89+
}
90+
91+
private FailureAnalysis analyzeFailure(Exception failure) {
92+
FailureAnalysis analysis = this.analyzer.analyze(failure);
93+
if (analysis != null) {
94+
new LoggingFailureAnalysisReporter().report(analysis);
95+
}
96+
return analysis;
97+
}
98+
99+
@EnableCaching
100+
@Configuration
101+
static class DefaultCacheConfiguration {
102+
103+
}
104+
105+
@Configuration
106+
static class CacheManagerConfiguration {
107+
108+
@Bean
109+
public CacheManager simpleCacheManager() {
110+
return new SimpleCacheManager();
111+
}
112+
113+
}
114+
115+
@Configuration
116+
@ImportAutoConfiguration(CacheAutoConfiguration.class)
117+
@Import(DefaultCacheConfiguration.class)
118+
static class DefaultCacheAutoConfiguration {
119+
120+
}
121+
122+
@Configuration
123+
@Import(DefaultCacheConfiguration.class)
124+
@ImportAutoConfiguration({ CacheAutoConfiguration.class,
125+
CacheManagerConfiguration.class })
126+
static class DefaultSeveralCacheManagerConfiguration {
127+
128+
}
129+
130+
}

0 commit comments

Comments
 (0)