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

Use of @ConditionalOnMissingClass and @ConditionalOnMissingBean on @Configuration classes may cause java.lang.ArrayStoreException: sun.reflect.annotation.TypeNotPresentExceptionProxy #1065

Closed
wilkinsona opened this issue Jun 10, 2014 · 17 comments

Comments

@wilkinsona
Copy link
Member

@Configuration classes are turned into beans and stored in the application context. Spring will sometimes query the class of every bean to see if it has been annotated with a particular annotation, for example to find @Controllers or @Aspects. This is typically done with AnnotationUtils.findAnnotation(…) which will fail with a very cryptic java.lang.ArrayStoreException: sun.reflect.annotation.TypeNotPresentExceptionProxy if any of the annotations it encounters refer to classes that cannot be loaded. This will always be the case with @ConditionalOnMissingClass: the only way for the bean that represents the configuration to be in the application context is if the class that it refers to is missing. It may be the case for @ConditionalOnMissingBean.

To avoid problems with @ConditionalOnMissingBean it should either be used on @Bean methods, or be protected with an @ConditionalOnClass annotation. Flyway's auto-configuration is a good example of the latter:

@Configuration
@ConditionalOnClass(Flyway.class)
@ConditionalOnBean(DataSource.class)
@ConditionalOnExpression("${flyway.enabled:true}")
@AutoConfigureAfter(DataSourceAutoConfiguration.class)
public class FlywayAutoConfiguration {

    @Configuration
    @ConditionalOnMissingBean(Flyway.class)
    @EnableConfigurationProperties(FlywayProperties.class)
    @Import(FlywayJpaDependencyConfiguration.class)
    public static class FlywayConfiguration {

Note the @ConditionalOnClass(Flyway.class) on FlywayAutoConfiguration that makes it safe to use @ConditionalOnMissingBean(Flyway.class) on the nested FlywayConfiguration

To avoid potential problems with @ConditionalOnMissingClass I think it has to either be on an @Bean method or, if it's used on a class, then the class must be referred to by name, i.e. as a String, rather than as a Class. At the time of writing, SocialWebAutoConfiguration.AnonymousUserIdSourceConfig is the only class-level user of @ConditionalOnMissingClass where the class is referenced directly, rather than by name.

@wilkinsona wilkinsona added this to the 1.1.1 milestone Jun 10, 2014
@wilkinsona wilkinsona added the bug label Jun 10, 2014
@philwebb
Copy link
Member

It should be possible to use @ConditionalOnMissingClass and refer to a class by type. The annotation should be read using ASM so it doesn't matter if the class is not actually on the classpath. I'm sure that this used to work.

@wilkinsona
Copy link
Member Author

It's Spring's reading of the annotation that causes the problem. It uses AnnotationUtils.findAnnotation in at least two places where it looks at every bean in the bean factory:

@dsyer
Copy link
Member

dsyer commented Jun 11, 2014

We deprecated the value() attribute and fixed the Social autoconfig in 1.1.0. So I don't think there's anything left to do here.

@dsyer dsyer closed this as completed Jun 11, 2014
@snicoll
Copy link
Member

snicoll commented Jun 12, 2014

While working on #1013 I had the same issue with ConditionalOnClass

You can reproduce by using the boot fork available in this branch alongside this stupid app

You can switch the @ConditionalOnClass to use the name attribute instead of the value attribute in DataSourceInfoProvidersConfiguration and the app will work as expected.

Could it be related to the way this configuration class is added? that is

@ConditionalOnBean(DataSource.class)
@AutoConfigureAfter(DataSourceAutoConfiguration.class)
@Import(DataSourceInfoProvidersConfiguration.class)
public class MetricDataSourceAutoConfiguration { ... }

@snicoll snicoll reopened this Jun 12, 2014
@philwebb philwebb added this to the 1.1.2 milestone Jun 12, 2014
@wilkinsona
Copy link
Member Author

This is worse than I thought. Methods on a configuration class annotated with either @ConditionalOnClass or @ConditionalOnMissingClass are also a problem. BeanAnnotationHelper.isBeanAnnotated calls AnnotationUtils.findAnnotation(method, Bean.class). If the method has an annotation that references an unloadable class it blows up.

To be safe, if you use @ConditionalOnClass on a configuration class method you either have to use a String rather than a Class or the annotation should be moved up onto the class at which point using a Class is safe.

@philwebb
Copy link
Member

@wilkinsona Has a Spring Framework bug been raised for this yet?

@dsyer
Copy link
Member

dsyer commented Jun 12, 2014

I don't think it is a bug in Spring, or at least not an urgent one. It's the same as @ConditionalOnMissingClass only the rule for usage is different (and we shouldn't deprecate this one): you can use @ConditionalOnClass freely on types, but not on methods. The reason not to change Spring urgently is that if Spring is defensive, someone else will load the class and inspect all the annotations anyway (JBoss or Tomcat or Hibernate or whoever).

@wilkinsona
Copy link
Member Author

I'm inclined to agree with Dave. I'll open an issue against Spring so that it can be discussed. I'm also going to open an issue against the JDK to see if they can make the exception that's thrown more helpful.

It's worth noting that we also need to be careful with @ConditionalOnBean

@wilkinsona
Copy link
Member Author

I've opened SPR-11874

@snicoll
Copy link
Member

snicoll commented Jun 13, 2014

Alright. Do we agree I should stick with my current setup and use the "name" attribute instead? I don't see any other way to prevent the bean to be created if the class is not available.

@dsyer
Copy link
Member

dsyer commented Jun 13, 2014

That isn't optimal in my opinion. It's better to use @ConditionalOnClass(value=...) but you have to do that at the type level (even if that means creating a class that only has one @Bean).

@philwebb philwebb modified the milestones: 1.1.3, 1.1.2, 1.2.0 Jun 20, 2014
@markfisher markfisher modified the milestones: 1.1.4, 1.2.0 Jul 3, 2014
@philwebb philwebb removed this from the 1.1.4 milestone Jul 3, 2014
@behboud
Copy link

behboud commented Jul 24, 2014

Hi. Not sure if it relates to this or I should open a new issue: For our corporation it is not possible to use spring boot. We have to deploy using a deployment tool to Glassfish 3.1.2. But it fails. I describe everything here:
http://stackoverflow.com/questions/24799958/deploy-jar-to-war-guide-on-glassfish

Will there be a workaround for this?

@wilkinsona
Copy link
Member Author

I don't think there's anything Spring Boot can do about your problem with GlassFish.. I agree with Dave's comment on StackOverflow: it's a bug in GlassFish. Judging by the log output in your SO post, GlassFish's annotation scanning is very brittle as it appears to rely on loading a class to scan its annotations. Using ASM and examining the byte code directly is one more robust approach. Perhaps things have improved in GlassFish 4?

@dsyer
Copy link
Member

dsyer commented Jul 24, 2014

IIRC Glassfish 4 is a different CDI standard and I believe that standard allows for an app to opt out of classpath scanning for certain packages (e.g. org.springframework.boot). Probably Glassfish 3 does as well (but as a vendor extension). We'd be interested to know if you find that feature and report back.

@behboud
Copy link

behboud commented Jul 25, 2014

seems I can't disable the CDI of Glassfish 3.1.2. Foud out that CDI 1.1 would indeed allow such an operation: https://blogs.oracle.com/theaquarium/entry/default_cdi_enablement_in_java

I tried to deploy this beans.xml file:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://xmlns.jcp.org/xml/ns/javaee"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
       http://xmlns.jcp.org/xml/ns/javaee/beans_1_1.xsd"
       bean-discovery-mode="none">

        <scan>
                <exclude name="org.springframework.boot.*" />
        </scan>
</beans>

but still same error. I guess GF3 is using CDI 1.0.
https://docs.jboss.org/cdi/spec/1.0/html_single/

Glassfish 4 is not an alternative for us. Oracle won't commercially support GF4. So we will move away from GF3 anyhow. But not in the near future.
Thanks

@dsyer
Copy link
Member

dsyer commented Jul 25, 2014

That's pretty much what I thought. IIRC JBoss AS has the equivalent of beans.xml as a vendor extension in its CDI1.0 container (I forget which that is), so I thought maybe Glassfish had something similar.

@philwebb
Copy link
Member

With SPR-11874 being fixed and the GlassFish issue outside of our control I'm going to close this one.

wilkinsona added a commit that referenced this issue Mar 18, 2015
Using a Class reference can cause reflection problems at runtime (see
gh-1065).

Closes gh-2674
wilkinsona added a commit that referenced this issue Mar 18, 2015
Using a Class reference can cause reflection problems at runtime (see
gh-1065).

Closes gh-2674
snicoll pushed a commit to snicoll/spring-boot that referenced this issue Mar 20, 2015
Using a Class reference can cause reflection problems at runtime (see
spring-projectsgh-1065).

Closes spring-projectsgh-2674
@wilkinsona wilkinsona removed status: waiting-for-feedback We need additional information before we can continue labels Dec 6, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants