Spec Class — spring-boot Architecture
Architecture documentation for the Spec class in OnBeanCondition.java from the spring-boot codebase.
Entity Profile
Relationship Graph
Source Code
core/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnBeanCondition.java lines 561–800
private static class Spec<A extends Annotation> {
private final ConditionContext context;
private final Class<? extends Annotation> annotationType;
private final Set<String> names;
private final Set<BeanType> types;
private final Set<String> annotations;
private final Set<BeanType> ignoredTypes;
private final Set<BeanType> parameterizedContainers;
private final @Nullable SearchStrategy strategy;
Spec(ConditionContext context, AnnotatedTypeMetadata metadata, MergedAnnotations annotations,
Class<A> annotationType) {
MultiValueMap<String, @Nullable Object> attributes = annotations.stream(annotationType)
.filter(MergedAnnotationPredicates.unique(MergedAnnotation::getMetaTypes))
.collect(MergedAnnotationCollectors.toMultiValueMap(Adapt.CLASS_TO_STRING));
MergedAnnotation<A> annotation = annotations.get(annotationType);
this.context = context;
this.annotationType = annotationType;
this.names = extract(attributes, "name");
this.annotations = extract(attributes, "annotation");
this.ignoredTypes = resolveWhenPossible(extract(attributes, "ignored", "ignoredType"));
this.parameterizedContainers = resolveWhenPossible(extract(attributes, "parameterizedContainer"));
this.strategy = annotation.getValue("search", SearchStrategy.class).orElse(null);
Set<BeanType> types = resolveWhenPossible(extractTypes(attributes));
BeanTypeDeductionException deductionException = null;
if (types.isEmpty() && this.names.isEmpty() && this.annotations.isEmpty()) {
try {
types = deducedBeanType(context, metadata);
}
catch (BeanTypeDeductionException ex) {
deductionException = ex;
}
}
this.types = types;
validate(deductionException);
}
protected Set<String> extractTypes(@Nullable MultiValueMap<String, @Nullable Object> attributes) {
return extract(attributes, "value", "type");
}
private Set<String> extract(@Nullable MultiValueMap<String, @Nullable Object> attributes,
String... attributeNames) {
if (CollectionUtils.isEmpty(attributes)) {
return Collections.emptySet();
}
Set<String> result = new LinkedHashSet<>();
for (String attributeName : attributeNames) {
List<@Nullable Object> values = attributes.getOrDefault(attributeName, Collections.emptyList());
for (Object value : values) {
if (value instanceof String[] stringArray) {
merge(result, stringArray);
}
else if (value instanceof String string) {
merge(result, string);
}
}
}
return result.isEmpty() ? Collections.emptySet() : result;
}
private void merge(Set<String> result, String... additional) {
Collections.addAll(result, additional);
}
private Set<BeanType> resolveWhenPossible(Set<String> classNames) {
if (classNames.isEmpty()) {
return Collections.emptySet();
}
Set<BeanType> resolved = new LinkedHashSet<>(classNames.size());
for (String className : classNames) {
try {
Class<?> type = resolve(className, this.context.getClassLoader());
resolved.add(new BeanType(className, ResolvableType.forRawClass(type)));
}
catch (ClassNotFoundException | NoClassDefFoundError ex) {
resolved.add(new BeanType(className, ResolvableType.NONE));
}
}
return resolved;
}
protected void validate(@Nullable BeanTypeDeductionException ex) {
if (!hasAtLeastOneElement(getTypes(), getNames(), getAnnotations())) {
String message = getAnnotationName() + " did not specify a bean using type, name or annotation";
if (ex == null) {
throw new IllegalStateException(message);
}
throw new IllegalStateException(message + " and the attempt to deduce the bean's type failed", ex);
}
}
private boolean hasAtLeastOneElement(Set<?>... sets) {
for (Set<?> set : sets) {
if (!set.isEmpty()) {
return true;
}
}
return false;
}
protected final String getAnnotationName() {
return "@" + ClassUtils.getShortName(this.annotationType);
}
private Set<BeanType> deducedBeanType(ConditionContext context, AnnotatedTypeMetadata metadata) {
if (metadata instanceof MethodMetadata && metadata.isAnnotated(Bean.class.getName())) {
return deducedBeanTypeForBeanMethod(context, (MethodMetadata) metadata);
}
return Collections.emptySet();
}
private Set<BeanType> deducedBeanTypeForBeanMethod(ConditionContext context, MethodMetadata metadata) {
try {
return Set.of(getReturnType(context, metadata));
}
catch (Throwable ex) {
throw new BeanTypeDeductionException(metadata.getDeclaringClassName(), metadata.getMethodName(), ex);
}
}
private BeanType getReturnType(ConditionContext context, MethodMetadata metadata)
throws ClassNotFoundException, LinkageError {
// Safe to load at this point since we are in the REGISTER_BEAN phase
ClassLoader classLoader = context.getClassLoader();
ResolvableType returnType = getMethodReturnType(metadata, classLoader);
if (isParameterizedContainer(returnType.resolve())) {
returnType = returnType.getGeneric();
}
return new BeanType(returnType.toString(), returnType);
}
private boolean isParameterizedContainer(@Nullable Class<?> type) {
return (type != null) && this.parameterizedContainers.stream()
.map((beanType) -> beanType.resolvableType().resolve(type))
.anyMatch((container) -> container != null && container.isAssignableFrom(type));
}
private ResolvableType getMethodReturnType(MethodMetadata metadata, @Nullable ClassLoader classLoader)
throws ClassNotFoundException, LinkageError {
Class<?> declaringClass = resolve(metadata.getDeclaringClassName(), classLoader);
Method beanMethod = findBeanMethod(declaringClass, metadata.getMethodName());
return ResolvableType.forMethodReturnType(beanMethod);
}
private Method findBeanMethod(Class<?> declaringClass, String methodName) {
Method method = ReflectionUtils.findMethod(declaringClass, methodName);
if (isBeanMethod(method)) {
return method;
}
Method[] candidates = ReflectionUtils.getAllDeclaredMethods(declaringClass);
for (Method candidate : candidates) {
if (candidate.getName().equals(methodName) && isBeanMethod(candidate)) {
return candidate;
}
}
throw new IllegalStateException("Unable to find bean method " + methodName);
}
@Contract("null -> false")
private boolean isBeanMethod(@Nullable Method method) {
return method != null && MergedAnnotations.from(method, MergedAnnotations.SearchStrategy.TYPE_HIERARCHY)
.isPresent(Bean.class);
}
private SearchStrategy getStrategy() {
return (this.strategy != null) ? this.strategy : SearchStrategy.ALL;
}
Set<BeanType> getTypes() {
return this.types;
}
private ConditionContext getContext() {
return this.context;
}
private Set<String> getNames() {
return this.names;
}
private Set<String> getAnnotations() {
return this.annotations;
}
private Set<BeanType> getIgnoredTypes() {
return this.ignoredTypes;
}
private Set<BeanType> getParameterizedContainers() {
return this.parameterizedContainers;
}
private Builder message() {
return ConditionMessage.forCondition(this.annotationType, this);
}
private Builder message(ConditionMessage message) {
return message.andCondition(this.annotationType, this);
}
@Override
public String toString() {
boolean hasNames = !this.names.isEmpty();
boolean hasTypes = !this.types.isEmpty();
boolean hasIgnoredTypes = !this.ignoredTypes.isEmpty();
StringBuilder string = new StringBuilder();
string.append("(");
if (hasNames) {
string.append("names: ");
string.append(StringUtils.collectionToCommaDelimitedString(this.names));
string.append(hasTypes ? " " : "; ");
}
if (hasTypes) {
string.append("types: ");
string.append(StringUtils.collectionToCommaDelimitedString(this.types));
string.append(hasIgnoredTypes ? " " : "; ");
}
if (hasIgnoredTypes) {
string.append("ignored: ");
string.append(StringUtils.collectionToCommaDelimitedString(this.ignoredTypes));
string.append("; ");
}
if (this.strategy != null) {
string.append("SearchStrategy: ");
string.append(this.strategy.toString().toLowerCase(Locale.ENGLISH));
}
string.append(")");
return string.toString();
}
}
Domain
Source
Analyze Your Own Codebase
Get architecture documentation, dependency graphs, and domain analysis for your codebase in minutes.
Try Supermodel Free