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

ConditionalOnClass not working for Bean methods on Java 8 #27846

Closed
filiphr opened this issue Sep 1, 2021 · 6 comments
Closed

ConditionalOnClass not working for Bean methods on Java 8 #27846

filiphr opened this issue Sep 1, 2021 · 6 comments
Assignees
Labels
type: documentation A documentation update
Milestone

Comments

@filiphr
Copy link
Contributor

filiphr commented Sep 1, 2021

When I have the following auto configuration

@Configuration
public class DemoAutoConfiguration {

    @Bean
    @ConditionalOnClass(DemoCommandLineRunner.class)
    public CommandLineRunner demoCommandLineRunner() {
        return new DemoCommandLineRunner();
    }
}

My application cannot start on Java 8 due to

java.lang.ArrayStoreException: sun.reflect.annotation.TypeNotPresentExceptionProxy

On Java 11 this problem is not there. The return type is not the same as the one in ConditionalOnClass.

This is clearly some kind of difference in the way annotations are loaded in Java 8 vs Java 11.
I am creating this here in case there is something that could be done in Spring Boot or Spring Core that would fix this behaviour between Java 8 and Java 11.

I have also created a minimal project that can be used to see this problem. The project is located here

@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged label Sep 1, 2021
@mbhave
Copy link
Contributor

mbhave commented Sep 1, 2021

The javadoc for @ConditionalOnClass calls out being extra careful when placing it on a @Bean method. See also Andy's comment in a previous issue. I think based on that there is nothing that we can do in Spring Boot. Leaving the issue open to get the team's thoughts.

@filiphr
Copy link
Contributor Author

filiphr commented Sep 1, 2021

Thanks for the link to the other issue @mbhave. I didn't find that one.

I've read the Javadoc and I thought that it is more linked towards the return type being available when the class is loaded. However, in this case the return type is available. The class can be safely loaded. The problem is then the Spring Core AnnotationsScanner.

The full cause is

Caused by: java.lang.ArrayStoreException: sun.reflect.annotation.TypeNotPresentExceptionProxy
	at sun.reflect.annotation.AnnotationParser.parseClassArray(AnnotationParser.java:724) ~[na:1.8.0_292]
	at sun.reflect.annotation.AnnotationParser.parseArray(AnnotationParser.java:531) ~[na:1.8.0_292]
	at sun.reflect.annotation.AnnotationParser.parseMemberValue(AnnotationParser.java:355) ~[na:1.8.0_292]
	at sun.reflect.annotation.AnnotationParser.parseAnnotation2(AnnotationParser.java:286) ~[na:1.8.0_292]
	at sun.reflect.annotation.AnnotationParser.parseAnnotations2(AnnotationParser.java:120) ~[na:1.8.0_292]
	at sun.reflect.annotation.AnnotationParser.parseAnnotations(AnnotationParser.java:72) ~[na:1.8.0_292]
	at java.lang.reflect.Executable.declaredAnnotations(Executable.java:602) ~[na:1.8.0_292]
	at java.lang.reflect.Executable.declaredAnnotations(Executable.java:600) ~[na:1.8.0_292]
	at java.lang.reflect.Executable.getDeclaredAnnotations(Executable.java:588) ~[na:1.8.0_292]
	at java.lang.reflect.Method.getDeclaredAnnotations(Method.java:630) ~[na:1.8.0_292]
	at org.springframework.core.annotation.AnnotationsScanner.getDeclaredAnnotations(AnnotationsScanner.java:454) ~[spring-core-5.3.9.jar:5.3.9]
	at org.springframework.core.annotation.AnnotationsScanner.isKnownEmpty(AnnotationsScanner.java:492) ~[spring-core-5.3.9.jar:5.3.9]
	at org.springframework.core.annotation.TypeMappedAnnotations.from(TypeMappedAnnotations.java:251) ~[spring-core-5.3.9.jar:5.3.9]
	at org.springframework.core.annotation.MergedAnnotations.from(MergedAnnotations.java:351) ~[spring-core-5.3.9.jar:5.3.9]
	at org.springframework.core.annotation.MergedAnnotations.from(MergedAnnotations.java:330) ~[spring-core-5.3.9.jar:5.3.9]
	at org.springframework.core.annotation.AnnotatedElementUtils.findAnnotations(AnnotatedElementUtils.java:764) ~[spring-core-5.3.9.jar:5.3.9]
	at org.springframework.core.annotation.AnnotatedElementUtils.hasAnnotation(AnnotatedElementUtils.java:531) ~[spring-core-5.3.9.jar:5.3.9]
	at org.springframework.context.annotation.BeanAnnotationHelper.isBeanAnnotated(BeanAnnotationHelper.java:41) ~[spring-context-5.3.9.jar:5.3.9]
	at org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor.isMatch(ConfigurationClassEnhancer.java:407) ~[spring-context-5.3.9.jar:5.3.9]
	at org.springframework.context.annotation.ConfigurationClassEnhancer$ConditionalCallbackFilter.accept(ConfigurationClassEnhancer.java:192) ~[spring-context-5.3.9.jar:5.3.9]
	at org.springframework.cglib.proxy.Enhancer.emitMethods(Enhancer.java:1217) ~[spring-core-5.3.9.jar:5.3.9]
	at org.springframework.cglib.proxy.Enhancer.generateClass(Enhancer.java:726) ~[spring-core-5.3.9.jar:5.3.9]
	at org.springframework.cglib.transform.TransformingClassGenerator.generateClass(TransformingClassGenerator.java:33) ~[spring-core-5.3.9.jar:5.3.9]
	at org.springframework.cglib.core.DefaultGeneratorStrategy.generate(DefaultGeneratorStrategy.java:25) ~[spring-core-5.3.9.jar:5.3.9]
	at org.springframework.cglib.core.ClassLoaderAwareGeneratorStrategy.generate(ClassLoaderAwareGeneratorStrategy.java:57) ~[spring-core-5.3.9.jar:5.3.9]
	at org.springframework.cglib.core.AbstractClassGenerator.generate(AbstractClassGenerator.java:358) ~[spring-core-5.3.9.jar:5.3.9]
	at org.springframework.cglib.proxy.Enhancer.generate(Enhancer.java:585) ~[spring-core-5.3.9.jar:5.3.9]
	at org.springframework.cglib.core.AbstractClassGenerator$ClassLoaderData$3.apply(AbstractClassGenerator.java:110) ~[spring-core-5.3.9.jar:5.3.9]
	at org.springframework.cglib.core.AbstractClassGenerator$ClassLoaderData$3.apply(AbstractClassGenerator.java:108) ~[spring-core-5.3.9.jar:5.3.9]
	at org.springframework.cglib.core.internal.LoadingCache$2.call(LoadingCache.java:54) ~[spring-core-5.3.9.jar:5.3.9]
	at java.util.concurrent.FutureTask.run(FutureTask.java:266) ~[na:1.8.0_292]
	at org.springframework.cglib.core.internal.LoadingCache.createEntry(LoadingCache.java:61) ~[spring-core-5.3.9.jar:5.3.9]
	at org.springframework.cglib.core.internal.LoadingCache.get(LoadingCache.java:34) ~[spring-core-5.3.9.jar:5.3.9]
	at org.springframework.cglib.core.AbstractClassGenerator$ClassLoaderData.get(AbstractClassGenerator.java:134) ~[spring-core-5.3.9.jar:5.3.9]
	at org.springframework.cglib.core.AbstractClassGenerator.create(AbstractClassGenerator.java:319) ~[spring-core-5.3.9.jar:5.3.9]
	at org.springframework.cglib.proxy.Enhancer.createHelper(Enhancer.java:572) ~[spring-core-5.3.9.jar:5.3.9]
	at org.springframework.cglib.proxy.Enhancer.createClass(Enhancer.java:419) ~[spring-core-5.3.9.jar:5.3.9]
	at org.springframework.context.annotation.ConfigurationClassEnhancer.createClass(ConfigurationClassEnhancer.java:137) ~[spring-context-5.3.9.jar:5.3.9]
	at org.springframework.context.annotation.ConfigurationClassEnhancer.enhance(ConfigurationClassEnhancer.java:109) ~[spring-context-5.3.9.jar:5.3.9]
	at org.springframework.context.annotation.ConfigurationClassPostProcessor.enhanceConfigurationClasses(ConfigurationClassPostProcessor.java:447) ~[spring-context-5.3.9.jar:5.3.9]
	at org.springframework.context.annotation.ConfigurationClassPostProcessor.postProcessBeanFactory(ConfigurationClassPostProcessor.java:268) ~[spring-context-5.3.9.jar:5.3.9]
	at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:325) ~[spring-context-5.3.9.jar:5.3.9]
	at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:147) ~[spring-context-5.3.9.jar:5.3.9]
	at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:746) ~[spring-context-5.3.9.jar:5.3.9]
	at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:564) ~[spring-context-5.3.9.jar:5.3.9]
	at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:754) ~[spring-boot-2.5.4.jar:2.5.4]
	at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:434) ~[spring-boot-2.5.4.jar:2.5.4]
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:338) ~[spring-boot-2.5.4.jar:2.5.4]
	at org.springframework.boot.test.context.SpringBootContextLoader.loadContext(SpringBootContextLoader.java:123) ~[spring-boot-test-2.5.4.jar:2.5.4]
	at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContextInternal(DefaultCacheAwareContextLoaderDelegate.java:99) ~[spring-test-5.3.9.jar:5.3.9]
	at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:124) ~[spring-test-5.3.9.jar:5.3.9]
	... 68 common frames omitted

Most likely there is not much you can do, perhaps the only thing would be to update the Javadoc to mention that @ConditionalClass shouldn't really be used on @Bean methods when you want your application to run on Java 8.

@mbhave
Copy link
Contributor

mbhave commented Sep 3, 2021

Thanks for the suggestion. I think we should update the javadoc.

@mhalbritter
Copy link
Contributor

mhalbritter commented Nov 23, 2022

The JavaDoc states

Extra care is required when placed on {@code @Bean} methods, consider isolating the condition in a separate {@code Configuration} class, in particular if the return type of the method matches the {@link #value target of the condition}.

Should we reword it to

Extra care is required when placed on {@code @Bean} methods, consider isolating the condition in a separate {@code Configuration} class.

?

@wilkinsona
Copy link
Member

wilkinsona commented Nov 23, 2022

Thanks for looking at this, @mhalbritter. I think we should probably go a bit further than that. The previous paragraph reads:

A Class value can be safely specified on @configuration classes as the annotation metadata is parsed by using ASM before the class is loaded. If a class reference cannot be used then a name String attribute can be used.

I think we should replace the "Note" with something that builds on that and states that value should not be used when annotating a method and that only name should be used in that case. It's slightly overly restrictive, but I think that's better than getting into the subtleties of the different behaviors of Java 8 and 11. The javadoc for the value and name attributes could also be updated along those lines.

@mhalbritter
Copy link
Contributor

Great, I will do this. We only need to do this for 2.6.x and 2.7.x, as 3.x has Java 17 as a baseline where this limitation no longer applies.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type: documentation A documentation update
Projects
None yet
Development

No branches or pull requests

5 participants