Search code examples
javaspringspring-bootcomponent-scan

Spring ComponentScan package-structure


My application contains the following classes among others:
SpringMainApplication:

@SpringBootApplication
@ComponentScan(basePackages = {"com.foo"})
class com.foo.appl.SpringMainApplication {
... some code...
}

An interface that should be used to autowire a field:

interface com.foo.bar.ClassToAutowire {
}

And another class that uses said interface for a field:

@Component
class com.foo.appl.pack.ImplementationClass {

@Autowired
ClassToAutowire autoClass;

@Scheduled(fixedRate = 60000)
public void startStuff() {
  // do something...
  }

}

But the field won't autowire:

Field autoClass in com.foo.appl.pack.ImplementationClass required a bean of type 'com.foo.bar.ClassToAutowire' that could not be found.

Action:

Consider defining a bean of type 'com.foo.bar.ClassToAutowire' in your configuration.

I guess Spring doesn't like my package-structure?

com.foo.bar.ClassToAutowire
com.foo.appl.SpringMainApplication
com.foo.appl.pack.ImplementationClass

Does the @SpringBootApplication have to be in the root package and all components must be in subpackages? If so, how do I solve my "problem", because the ClassToAutowire comes from an imported JAR.

When changing the basePackge to com.foo.bar the application starts, but then the scheduled method won't run.

Thanks


Solution

  • When using Spring Boot it by default does component scanning. This component-scanning is done starting in the same package as the @SpringBootApplication annotated class is in. In your case that is com.foo.appl however this does not cover com.foo.bar.

    The best practice is to put your @SpringBootApplication annotated class in the most top-level package you can find, in your case that would be com.foo. This will scan all packages beneath it and will include the proper components.

    You could also add @ComponentScan("com.foo") to your @SpringBootApplication annotated class to let it start scanning at a different package or (@SpringBootApplication(basePackage="com.foo").

    If there aren't anymore components in the dependency jar you could also add a @Bean method to create an instance of the desired class.

    @Bean
    public ClassToAutowire classToAutowire() {
        return new ClassToAutowire();
    }
    

    The drawback of the second approach is that when using things like Spring Data or JPA you will also manually have to configure those (adding things like @EnableJpaRepositories and @EntityScan). This will grow when using/adding different frameworks, this isn't the case when you put the class in a top-level package as all packages are considered.