Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Skip runtime hint registration for validation constraint with missing dependencies #33949

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
import org.springframework.beans.factory.aot.BeanRegistrationAotContribution;
import org.springframework.beans.factory.aot.BeanRegistrationAotProcessor;
import org.springframework.beans.factory.aot.BeanRegistrationCode;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.RegisteredBean;
import org.springframework.core.KotlinDetector;
import org.springframework.core.ResolvableType;
Expand Down Expand Up @@ -98,7 +99,7 @@ private static Validator getValidatorIfAvailable() {

@Nullable
public static BeanRegistrationAotContribution processAheadOfTime(RegisteredBean registeredBean) {
if (validator == null) {
if (validator == null || BeanDefinition.ROLE_INFRASTRUCTURE == registeredBean.getMergedBeanDefinition().getRole()) {
return null;
}

Expand Down Expand Up @@ -127,18 +128,19 @@ private static void processAheadOfTime(Class<?> clazz, Set<Class<?>> visitedClas
try {
descriptor = validator.getConstraintsForClass(clazz);
}
catch (RuntimeException ex) {
catch (RuntimeException | LinkageError ex) {
String className = clazz.getName();
if (KotlinDetector.isKotlinType(clazz) && ex instanceof ArrayIndexOutOfBoundsException) {
// See https://hibernate.atlassian.net/browse/HV-1796 and https://youtrack.jetbrains.com/issue/KT-40857
logger.warn("Skipping validation constraint hint inference for class " + clazz +
logger.warn("Skipping validation constraint hint inference for class " + className +
" due to an ArrayIndexOutOfBoundsException at validator level");
}
else if (ex instanceof TypeNotPresentException) {
logger.debug("Skipping validation constraint hint inference for class " +
clazz + " due to a TypeNotPresentException at validator level: " + ex.getMessage());
className + " due to a TypeNotPresentException at validator level: " + ex.getMessage());
}
else {
logger.warn("Skipping validation constraint hint inference for class " + clazz, ex);
logger.warn("Skipping validation constraint hint inference for class " + className, ex);
}
return;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,10 @@
import org.springframework.aot.hint.predicate.RuntimeHintsPredicates;
import org.springframework.aot.test.generate.TestGenerationContext;
import org.springframework.beans.factory.aot.BeanRegistrationAotContribution;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.RegisteredBean;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.core.OverridingClassLoader;
import org.springframework.lang.Nullable;

import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
Expand All @@ -55,6 +56,9 @@
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.springframework.beans.factory.config.BeanDefinition.ROLE_APPLICATION;
import static org.springframework.beans.factory.config.BeanDefinition.ROLE_INFRASTRUCTURE;
import static org.springframework.beans.factory.support.BeanDefinitionBuilder.rootBeanDefinition;

/**
* Tests for {@link BeanValidationBeanRegistrationAotProcessor}.
Expand Down Expand Up @@ -134,17 +138,36 @@ void shouldProcessRecursiveGenericsWithoutInfiniteRecursion(Class<?> beanClass)
.withMemberCategory(MemberCategory.DECLARED_FIELDS)).accepts(this.generationContext.getRuntimeHints());
}

@Test // gh-33940
void shouldSkipConstraintWithMissingDependency() throws Exception {
FilteringClassLoader classLoader = new FilteringClassLoader(getClass().getClassLoader());
Class<?> beanClass = classLoader.loadClass(ConstraintWithMissingDependency.class.getName());
process(beanClass);
assertThat(this.generationContext.getRuntimeHints().reflection().typeHints()).isEmpty();
}

@Test // gh-33940
void shouldSkipInfrastructureBean() {
process(EmptyClass.class, ROLE_INFRASTRUCTURE);
assertThat(this.generationContext.getRuntimeHints().reflection().typeHints()).isEmpty();
}

private void process(Class<?> beanClass) {
BeanRegistrationAotContribution contribution = createContribution(beanClass);
process(beanClass, ROLE_APPLICATION);
}

private void process(Class<?> beanClass, int role) {
BeanRegistrationAotContribution contribution = createContribution(beanClass, role);
if (contribution != null) {
contribution.applyTo(this.generationContext, mock());
}
}

@Nullable
private BeanRegistrationAotContribution createContribution(Class<?> beanClass) {
private BeanRegistrationAotContribution createContribution(Class<?> beanClass, int role) {
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
beanFactory.registerBeanDefinition(beanClass.getName(), new RootBeanDefinition(beanClass));
BeanDefinition beanDefinition = rootBeanDefinition(beanClass).setRole(role).getBeanDefinition();
beanFactory.registerBeanDefinition(beanClass.getName(), beanDefinition);
return this.processor.processAheadOfTime(RegisteredBean.of(beanFactory, beanClass.getName()));
}

Expand Down Expand Up @@ -269,4 +292,31 @@ static class BeanWithRecursiveOptional {
Optional<BeanWithRecursiveOptional> optional;
}

static class ConstraintWithMissingDependency {

private final Filtered filtered = new Filtered();
}

static class Filtered {}

static class FilteringClassLoader extends OverridingClassLoader {

FilteringClassLoader(ClassLoader parent) {
super(parent);
}

@Override
protected boolean isEligibleForOverriding(String className) {
return className.startsWith(BeanValidationBeanRegistrationAotProcessorTests.class.getName());
}

@Override
protected Class<?> loadClassForOverriding(String name) throws ClassNotFoundException {
if (name.contains("Filtered")) {
throw new NoClassDefFoundError(name);
}
return super.loadClassForOverriding(name);
}
}

}