雰囲気で挙動を理解していたのをちゃんと見たのでメモ
@Nullable public Object doResolveDependency(DependencyDescriptor descriptor, @Nullable String beanName, @Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException { ... // Step 3b: direct bean matches, possibly direct beans of type Collection / Map Map<String, Object> matchingBeans = findAutowireCandidates(beanName, type, descriptor); // ここで型に対して一致するbeanの一覧を取得する ... // Step 4: determine single candidate if (matchingBeans.size() > 1) { autowiredBeanName = determineAutowireCandidate(matchingBeans, descriptor); // 複数のbeanが当てはまる場合はどれを使うかを決定する if (autowiredBeanName == null) { if (isRequired(descriptor) || !indicatesArrayCollectionOrMap(type)) { // Raise exception if no clear match found for required injection point return descriptor.resolveNotUnique(descriptor.getResolvableType(), matchingBeans); } else { // In case of an optional Collection/Map, silently ignore a non-unique case: // possibly it was meant to be an empty collection of multiple regular beans // (before 4.3 in particular when we didn't even look for collection beans). return null; } } instanceCandidate = matchingBeans.get(autowiredBeanName); } ... }
protected String determineAutowireCandidate(Map<String, Object> candidates, DependencyDescriptor descriptor) { Class<?> requiredType = descriptor.getDependencyType(); String primaryCandidate = determinePrimaryCandidate(candidates, requiredType); // @Primaryがないかを優先して探す if (primaryCandidate != null) { return primaryCandidate; // 見つかったら返す } String priorityCandidate = determineHighestPriorityCandidate(candidates, requiredType); // @Priorityで指定した中で最も優先度の高い(値の低い)ものを探す if (priorityCandidate != null) { return priorityCandidate; // 見つかったら返す } // Fallback: pick directly registered dependency or qualified bean name match for (Map.Entry<String, Object> entry : candidates.entrySet()) { String candidateName = entry.getKey(); Object beanInstance = entry.getValue(); if ((beanInstance != null && this.resolvableDependencies.containsValue(beanInstance)) || matchesBeanName(candidateName, descriptor.getDependencyName())) { return candidateName; // Autowireする際の変数名が一致するものが見つかったら返す } } return null; }
@Priority
でInjectするbeanを操作しようとした際に、↓は一見 @Priority
が評価されそうに見えるが、
実際は評価されず exampleA
と exampleB
どちらを使えば良いかわからずに終了する
import jakarta.annotation.Priority import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration import org.springframework.core.Ordered interface MarkerExample data class ExampleImpl1(val value: String): MarkerExample data class ExampleImpl2(val value: String): MarkerExample @Configuration class ExampleConfig { @Bean @Priority(Ordered.LOWEST_PRECEDENCE) fun exampleA(): MarkerExample { return ExampleImpl1( value = "ExampleA", ) } @Bean @Priority(Ordered.HIGHEST_PRECEDENCE) fun exampleB(): MarkerExample { return ExampleImpl2( value = "ExampleB", ) } @Bean fun exampleC(example: MarkerExample): String { return "ExampleC" } }
逆に↓は動く(Bで解決される)
import jakarta.annotation.Priority import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration import org.springframework.core.Ordered interface MarkerExample @Priority(Ordered.LOWEST_PRECEDENCE) data class ExampleImpl1(val value: String): MarkerExample @Priority(Ordered.HIGHEST_PRECEDENCE) data class ExampleImpl2(val value: String): MarkerExample @Configuration class ExampleConfig { @Bean fun exampleA(): MarkerExample { return ExampleImpl1( value = "ExampleA", ) } @Bean fun exampleB(): MarkerExample { return ExampleImpl2( value = "ExampleB", ) } @Bean fun exampleC(example: MarkerExample): String { return "ExampleC" } }
こういったパターンはほぼ @Primary
があれば足りるし使うことはないが、
思っていた挙動と違ったので意外だった