|
21 | 21 | import java.util.Arrays;
|
22 | 22 | import java.util.Collection;
|
23 | 23 | import java.util.Collections;
|
| 24 | +import java.util.HashMap; |
| 25 | +import java.util.HashSet; |
24 | 26 | import java.util.LinkedHashSet;
|
25 | 27 | import java.util.List;
|
| 28 | +import java.util.Map; |
26 | 29 | import java.util.Set;
|
27 | 30 |
|
28 | 31 | import org.springframework.beans.factory.BeanFactory;
|
@@ -76,80 +79,163 @@ public ConditionOutcome getMatchOutcome(ConditionContext context,
|
76 | 79 | if (metadata.isAnnotated(ConditionalOnBean.class.getName())) {
|
77 | 80 | BeanSearchSpec spec = new BeanSearchSpec(context, metadata,
|
78 | 81 | ConditionalOnBean.class);
|
79 |
| - List<String> matching = getMatchingBeans(context, spec); |
80 |
| - if (matching.isEmpty()) { |
81 |
| - return ConditionOutcome.noMatch( |
82 |
| - ConditionMessage.forCondition(ConditionalOnBean.class, spec) |
83 |
| - .didNotFind("any beans").atAll()); |
| 82 | + MatchResult matchResult = getMatchingBeans(context, spec); |
| 83 | + if (!matchResult.isAllMatched()) { |
| 84 | + String reason = createOnBeanNoMatchReason(matchResult); |
| 85 | + return ConditionOutcome.noMatch(ConditionMessage |
| 86 | + .forCondition(ConditionalOnBean.class, spec).because(reason)); |
84 | 87 | }
|
85 | 88 | matchMessage = matchMessage.andCondition(ConditionalOnBean.class, spec)
|
86 |
| - .found("bean", "beans").items(Style.QUOTE, matching); |
| 89 | + .found("bean", "beans") |
| 90 | + .items(Style.QUOTE, matchResult.getNamesOfAllMatches()); |
87 | 91 | }
|
88 | 92 | if (metadata.isAnnotated(ConditionalOnSingleCandidate.class.getName())) {
|
89 | 93 | BeanSearchSpec spec = new SingleCandidateBeanSearchSpec(context, metadata,
|
90 | 94 | ConditionalOnSingleCandidate.class);
|
91 |
| - List<String> matching = getMatchingBeans(context, spec); |
92 |
| - if (matching.isEmpty()) { |
| 95 | + MatchResult matchResult = getMatchingBeans(context, spec); |
| 96 | + if (!matchResult.isAllMatched()) { |
93 | 97 | return ConditionOutcome.noMatch(ConditionMessage
|
94 | 98 | .forCondition(ConditionalOnSingleCandidate.class, spec)
|
95 | 99 | .didNotFind("any beans").atAll());
|
96 | 100 | }
|
97 |
| - else if (!hasSingleAutowireCandidate(context.getBeanFactory(), matching, |
| 101 | + else if (!hasSingleAutowireCandidate(context.getBeanFactory(), |
| 102 | + matchResult.getNamesOfAllMatches(), |
98 | 103 | spec.getStrategy() == SearchStrategy.ALL)) {
|
99 | 104 | return ConditionOutcome.noMatch(ConditionMessage
|
100 | 105 | .forCondition(ConditionalOnSingleCandidate.class, spec)
|
101 | 106 | .didNotFind("a primary bean from beans")
|
102 |
| - .items(Style.QUOTE, matching)); |
| 107 | + .items(Style.QUOTE, matchResult.getNamesOfAllMatches())); |
103 | 108 | }
|
104 | 109 | matchMessage = matchMessage
|
105 | 110 | .andCondition(ConditionalOnSingleCandidate.class, spec)
|
106 |
| - .found("a primary bean from beans").items(Style.QUOTE, matching); |
| 111 | + .found("a primary bean from beans") |
| 112 | + .items(Style.QUOTE, matchResult.namesOfAllMatches); |
107 | 113 | }
|
108 | 114 | if (metadata.isAnnotated(ConditionalOnMissingBean.class.getName())) {
|
109 | 115 | BeanSearchSpec spec = new BeanSearchSpec(context, metadata,
|
110 | 116 | ConditionalOnMissingBean.class);
|
111 |
| - List<String> matching = getMatchingBeans(context, spec); |
112 |
| - if (!matching.isEmpty()) { |
| 117 | + MatchResult matchResult = getMatchingBeans(context, spec); |
| 118 | + if (matchResult.isAnyMatched()) { |
| 119 | + String reason = createOnMissingBeanNoMatchReason(matchResult); |
113 | 120 | return ConditionOutcome.noMatch(ConditionMessage
|
114 | 121 | .forCondition(ConditionalOnMissingBean.class, spec)
|
115 |
| - .found("bean", "beans").items(Style.QUOTE, matching)); |
| 122 | + .because(reason)); |
116 | 123 | }
|
117 | 124 | matchMessage = matchMessage.andCondition(ConditionalOnMissingBean.class, spec)
|
118 | 125 | .didNotFind("any beans").atAll();
|
119 | 126 | }
|
120 | 127 | return ConditionOutcome.match(matchMessage);
|
121 | 128 | }
|
122 | 129 |
|
123 |
| - private List<String> getMatchingBeans(ConditionContext context, |
124 |
| - BeanSearchSpec beans) { |
| 130 | + private String createOnBeanNoMatchReason(MatchResult matchResult) { |
| 131 | + StringBuilder reason = new StringBuilder(); |
| 132 | + appendMessageForNoMatches(reason, matchResult.unmatchedAnnotations, |
| 133 | + "annotated with"); |
| 134 | + appendMessageForNoMatches(reason, matchResult.unmatchedTypes, "of type"); |
| 135 | + appendMessageForNoMatches(reason, matchResult.unmatchedNames, "named"); |
| 136 | + return reason.toString(); |
| 137 | + } |
| 138 | + |
| 139 | + private void appendMessageForNoMatches(StringBuilder reason, |
| 140 | + Collection<String> unmatched, String description) { |
| 141 | + if (!unmatched.isEmpty()) { |
| 142 | + if (reason.length() > 0) { |
| 143 | + reason.append(" and "); |
| 144 | + } |
| 145 | + reason.append("did not find any beans "); |
| 146 | + reason.append(description); |
| 147 | + reason.append(" "); |
| 148 | + reason.append(StringUtils.collectionToDelimitedString(unmatched, ", ")); |
| 149 | + } |
| 150 | + } |
| 151 | + |
| 152 | + private String createOnMissingBeanNoMatchReason(MatchResult matchResult) { |
| 153 | + StringBuilder reason = new StringBuilder(); |
| 154 | + appendMessageForMatches(reason, matchResult.matchedAnnotations, "annotated with"); |
| 155 | + appendMessageForMatches(reason, matchResult.matchedTypes, "of type"); |
| 156 | + if (!matchResult.matchedNames.isEmpty()) { |
| 157 | + if (reason.length() > 0) { |
| 158 | + reason.append(" and "); |
| 159 | + } |
| 160 | + reason.append("found beans named "); |
| 161 | + reason.append(StringUtils |
| 162 | + .collectionToDelimitedString(matchResult.matchedNames, ", ")); |
| 163 | + } |
| 164 | + return reason.toString(); |
| 165 | + } |
| 166 | + |
| 167 | + private void appendMessageForMatches(StringBuilder reason, |
| 168 | + Map<String, Collection<String>> matches, String description) { |
| 169 | + if (!matches.isEmpty()) { |
| 170 | + for (Map.Entry<String, Collection<String>> match : matches.entrySet()) { |
| 171 | + if (reason.length() > 0) { |
| 172 | + reason.append(" and "); |
| 173 | + } |
| 174 | + reason.append("found beans "); |
| 175 | + reason.append(description); |
| 176 | + reason.append("'"); |
| 177 | + reason.append(match.getKey()); |
| 178 | + reason.append("'"); |
| 179 | + reason.append( |
| 180 | + StringUtils.collectionToDelimitedString(match.getValue(), ", ")); |
| 181 | + } |
| 182 | + } |
| 183 | + } |
| 184 | + |
| 185 | + private MatchResult getMatchingBeans(ConditionContext context, BeanSearchSpec beans) { |
125 | 186 | ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
|
126 | 187 | if (beans.getStrategy() == SearchStrategy.ANCESTORS) {
|
127 | 188 | BeanFactory parent = beanFactory.getParentBeanFactory();
|
128 | 189 | Assert.isInstanceOf(ConfigurableListableBeanFactory.class, parent,
|
129 | 190 | "Unable to use SearchStrategy.PARENTS");
|
130 | 191 | beanFactory = (ConfigurableListableBeanFactory) parent;
|
131 | 192 | }
|
132 |
| - if (beanFactory == null) { |
133 |
| - return Collections.emptyList(); |
134 |
| - } |
135 |
| - List<String> beanNames = new ArrayList<String>(); |
| 193 | + MatchResult matchResult = new MatchResult(); |
136 | 194 | boolean considerHierarchy = beans.getStrategy() != SearchStrategy.CURRENT;
|
| 195 | + List<String> beansIgnoredByType = getNamesOfBeansIgnoredByType( |
| 196 | + beans.getIgnoredTypes(), beanFactory, context, considerHierarchy); |
137 | 197 | for (String type : beans.getTypes()) {
|
138 |
| - beanNames.addAll(getBeanNamesForType(beanFactory, type, |
139 |
| - context.getClassLoader(), considerHierarchy)); |
140 |
| - } |
141 |
| - for (String ignoredType : beans.getIgnoredTypes()) { |
142 |
| - beanNames.removeAll(getBeanNamesForType(beanFactory, ignoredType, |
143 |
| - context.getClassLoader(), considerHierarchy)); |
| 198 | + Collection<String> typeMatches = getBeanNamesForType(beanFactory, type, |
| 199 | + context.getClassLoader(), considerHierarchy); |
| 200 | + typeMatches.removeAll(beansIgnoredByType); |
| 201 | + if (typeMatches.isEmpty()) { |
| 202 | + matchResult.recordUnmatchedType(type); |
| 203 | + } |
| 204 | + else { |
| 205 | + matchResult.recordMatchedType(type, typeMatches); |
| 206 | + } |
144 | 207 | }
|
145 | 208 | for (String annotation : beans.getAnnotations()) {
|
146 |
| - beanNames.addAll(Arrays.asList(getBeanNamesForAnnotation(beanFactory, |
147 |
| - annotation, context.getClassLoader(), considerHierarchy))); |
| 209 | + List<String> annotationMatches = Arrays |
| 210 | + .asList(getBeanNamesForAnnotation(beanFactory, annotation, |
| 211 | + context.getClassLoader(), considerHierarchy)); |
| 212 | + annotationMatches.removeAll(beansIgnoredByType); |
| 213 | + if (annotationMatches.isEmpty()) { |
| 214 | + matchResult.recordUnmatchedAnnotation(annotation); |
| 215 | + } |
| 216 | + else { |
| 217 | + matchResult.recordMatchedAnnotation(annotation, annotationMatches); |
| 218 | + } |
148 | 219 | }
|
149 | 220 | for (String beanName : beans.getNames()) {
|
150 |
| - if (containsBean(beanFactory, beanName, considerHierarchy)) { |
151 |
| - beanNames.add(beanName); |
| 221 | + if (!beansIgnoredByType.contains(beanName) |
| 222 | + && containsBean(beanFactory, beanName, considerHierarchy)) { |
| 223 | + matchResult.recordMatchedName(beanName); |
152 | 224 | }
|
| 225 | + else { |
| 226 | + matchResult.recordUnmatchedName(beanName); |
| 227 | + } |
| 228 | + } |
| 229 | + return matchResult; |
| 230 | + } |
| 231 | + |
| 232 | + private List<String> getNamesOfBeansIgnoredByType(List<String> ignoredTypes, |
| 233 | + ListableBeanFactory beanFactory, ConditionContext context, |
| 234 | + boolean considerHierarchy) { |
| 235 | + List<String> beanNames = new ArrayList<String>(); |
| 236 | + for (String ignoredType : ignoredTypes) { |
| 237 | + beanNames.addAll(getBeanNamesForType(beanFactory, ignoredType, |
| 238 | + context.getClassLoader(), considerHierarchy)); |
153 | 239 | }
|
154 | 240 | return beanNames;
|
155 | 241 | }
|
@@ -227,15 +313,15 @@ private String[] getBeanNamesForAnnotation(
|
227 | 313 | }
|
228 | 314 |
|
229 | 315 | private boolean hasSingleAutowireCandidate(
|
230 |
| - ConfigurableListableBeanFactory beanFactory, List<String> beanNames, |
| 316 | + ConfigurableListableBeanFactory beanFactory, Set<String> beanNames, |
231 | 317 | boolean considerHierarchy) {
|
232 | 318 | return (beanNames.size() == 1
|
233 | 319 | || getPrimaryBeans(beanFactory, beanNames, considerHierarchy)
|
234 | 320 | .size() == 1);
|
235 | 321 | }
|
236 | 322 |
|
237 | 323 | private List<String> getPrimaryBeans(ConfigurableListableBeanFactory beanFactory,
|
238 |
| - List<String> beanNames, boolean considerHierarchy) { |
| 324 | + Set<String> beanNames, boolean considerHierarchy) { |
239 | 325 | List<String> primaryBeans = new ArrayList<String>();
|
240 | 326 | for (String beanName : beanNames) {
|
241 | 327 | BeanDefinition beanDefinition = findBeanDefinition(beanFactory, beanName,
|
@@ -439,4 +525,64 @@ private BeanTypeDeductionException(String className, String beanMethodName,
|
439 | 525 |
|
440 | 526 | }
|
441 | 527 |
|
| 528 | + static final class MatchResult { |
| 529 | + |
| 530 | + private final Map<String, Collection<String>> matchedAnnotations = new HashMap<String, Collection<String>>(); |
| 531 | + |
| 532 | + private final List<String> matchedNames = new ArrayList<String>(); |
| 533 | + |
| 534 | + private final Map<String, Collection<String>> matchedTypes = new HashMap<String, Collection<String>>(); |
| 535 | + |
| 536 | + private final List<String> unmatchedAnnotations = new ArrayList<String>(); |
| 537 | + |
| 538 | + private final List<String> unmatchedNames = new ArrayList<String>(); |
| 539 | + |
| 540 | + private final List<String> unmatchedTypes = new ArrayList<String>(); |
| 541 | + |
| 542 | + private final Set<String> namesOfAllMatches = new HashSet<String>(); |
| 543 | + |
| 544 | + private void recordMatchedName(String name) { |
| 545 | + this.matchedNames.add(name); |
| 546 | + this.namesOfAllMatches.add(name); |
| 547 | + } |
| 548 | + |
| 549 | + private void recordUnmatchedName(String name) { |
| 550 | + this.unmatchedNames.add(name); |
| 551 | + } |
| 552 | + |
| 553 | + private void recordMatchedAnnotation(String annotation, |
| 554 | + Collection<String> matchingNames) { |
| 555 | + this.matchedAnnotations.put(annotation, matchingNames); |
| 556 | + this.namesOfAllMatches.addAll(matchingNames); |
| 557 | + } |
| 558 | + |
| 559 | + private void recordUnmatchedAnnotation(String annotation) { |
| 560 | + this.unmatchedAnnotations.add(annotation); |
| 561 | + } |
| 562 | + |
| 563 | + private void recordMatchedType(String type, Collection<String> matchingNames) { |
| 564 | + this.matchedTypes.put(type, matchingNames); |
| 565 | + this.namesOfAllMatches.addAll(matchingNames); |
| 566 | + } |
| 567 | + |
| 568 | + private void recordUnmatchedType(String type) { |
| 569 | + this.unmatchedTypes.add(type); |
| 570 | + } |
| 571 | + |
| 572 | + private boolean isAllMatched() { |
| 573 | + return this.unmatchedAnnotations.isEmpty() && this.unmatchedNames.isEmpty() |
| 574 | + && this.unmatchedTypes.isEmpty(); |
| 575 | + } |
| 576 | + |
| 577 | + private boolean isAnyMatched() { |
| 578 | + return (!this.matchedAnnotations.isEmpty()) || (!this.matchedNames.isEmpty()) |
| 579 | + || (!this.matchedTypes.isEmpty()); |
| 580 | + } |
| 581 | + |
| 582 | + private Set<String> getNamesOfAllMatches() { |
| 583 | + return this.namesOfAllMatches; |
| 584 | + } |
| 585 | + |
| 586 | + } |
| 587 | + |
442 | 588 | }
|
0 commit comments