Home / Class/ Spec Class — spring-boot Architecture

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

Analyze Your Own Codebase

Get architecture documentation, dependency graphs, and domain analysis for your codebase in minutes.

Try Supermodel Free