when there are some classes which may not exist, how should I manage them in an XxxAutoConfiguration?

Tags: , ,



        <dependency>
            <groupId>com.example</groupId>
            <artifactId>A</artifactId> // it provide AFactory.class
            <version>1</version>
            <option>true</option>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>com.example</groupId>
            <artifactId>B</artifactId> // it provide BFactory.class
            <version>1</version>
            <option>true</option>
            <scope>provided</scope>
        </dependency>

In my starter project, I will write a XxxAutoConfiguration that uses AFactory or BFactory.

I’ve tried:

@Configuration
public class XxxAutoConfiguration {
    
    private AFactory aFactory;
    private BFactory bfactory;

    @Autowired(required = false)
    public void setaFactory(AFactory aFactory) {
        this.aFactory = aFactory;
    }

    @Autowired(required = false)
    public void setBfactory(BFactory bfactory) {
        this.bfactory = bfactory;
    }
    

    @Bean
    public Something something(){
        if(aFactory != null){
            return new Something(aFactory);
        }else if(bfactory != null){
            return new Something(bfactory);
        }else{
            throw new IllegalStateException();
        }
    }
}

but it doesn’t work.

I know I can write three AutoConfiguration respectively with @ConditionalOnBean(AFactory.class), @ConditionalOnBean(BFactory.class) and @ConditionalOnMissingBean(....) to solve the problem, but it’s far from elegant… do you have any good solution? Thanks a lot.

Answer

Writing code that uses a class that may not exist on the classpath at runtime is not good idea. Writing the code so it doesn’t cause NoClassDefFoundError is troublesome.

The standard Spring Boot way can e.g. be seen in the source code of DataSourceConfiguration.

In your case, you can do this:

abstract class XxxAutoConfiguration {

    @Configuration
    @ConditionalOnBean(AFactory.class)
    @ConditionalOnMissingBean(Something.class)
    @AutoConfigureOrder(1) // Try AFactory first
    static class ASomething {
        private AFactory aFactory;

        @Autowired
        void setaFactory(AFactory aFactory) {
            this.aFactory = aFactory;
        }

        @Bean
        Something something() {
            return new Something(this.aFactory);
        }
    }

    @Configuration
    @ConditionalOnBean(BFactory.class)
    @ConditionalOnMissingBean(Something.class)
    @AutoConfigureOrder(2) // Try BFactory second
    static class BSomething {
        @Bean
        Something something(BFactory bFactory) {
            return new Something(bFactory);
        }
    }

}

As you can see, you can do it using @Autowired or using a parameter, either way should work.

In case both AFactory and BFactory are present on the classpath, you can e.g. use @AutoConfigureOrder to specify which one wins, as shown here. There are other @ConditionalXxx annotations for more complex ways.

The XxxAutoConfiguration class is really just a pseudo-package for keeping it all together.



Source: stackoverflow