1. Overview
The @Import annotation is the primary mechanism to import @Bean definitions typically contained in @Configuration classes. Although it is mainly used to import configuration classes, its usage isn't limited to that. In this tutorial, we'll examine different examples to import @Bean definitions contained in @Configuration, @Component, and ImportSelector classes.
2. Sample Application
Let's start with the sample application.
We have the Counter class:
public class Counter {
private int current = 0;
public void count() {
System.out.println(current++);
}
}
Then the ImpressionService class uses Counter:
public class ImpressionService {
private final Counter counter;
public ImpressionService(Counter counter) {
this.counter = counter;
}
public void countImpression(){
counter.count();
}
}
3. Use @Import with @Configuration
We'll first look at how we can import @Configuration classes using @Import. @Configuration classes provide bean definitions through either @Bean methods or component scanning:
@Configuration
public class CounterConfiguration {
@Bean
public Counter counter() {
return new Counter();
}
}
Here, we have the CounterConfiguration class. It's providing a bean definition for Counter.
When another @Configuration class imports CounterConfiguration, the Counter bean becomes available to it:
@Configuration
@Import(CounterConfiguration.class)
public class MainConfiguration {
@Bean
public ImpressionService impressionService(Counter counter) {
return new ImpressionService(counter);
}
}
In this example, we have the MainConfiguration class. Note that the impressionService method declares Counter as a method parameter. This is valid because MainConfiguration imports the CounterConfiguration class where Counter is exposed as a bean.
4. Use @Import with @Component
Although we generally import @Configuration classes, it is also valid to import @Component classes using @Import. Remember that @Component classes can also provide bean definitions using the lite @Bean methods:
For this purpose, we'll change our ImpressionService class a bit:
@Component
public class ImpressionService {
private final Counter counter;
public ImpressionService(Counter counter) {
this.counter = counter;
}
...
@Bean
public static Counter counter() {
return new Counter();
}
}
In this modified version of ImpressionService, we're defining a Counter bean using the counter method. Notice that we're declaring the @Bean method as static so that Spring can create the bean without needing to initialize ImpressionService.
Then another @Configuration class can import the ImpressionService class:
@Configuration
@Import(ImpressionService.class)
public class MainConfiguration {
}
When MainConfiguration imports ImpressionService, it loads two bean definitions, ImpressionService and Counter.
5. Use @Import with ImportSelector
Next, we'll look at how we can use ImportSelector to control the import process in a fine-grained manner. For example, we can use ImportSelector to select one @Configuration class over another according to some criteria:
Firstly, we'll refactor a bit and add the environment information to Counter:
public class Counter {
private final String environment;
private int current = 0;
public Counter(String environment) {
this.environment = environment;
}
public void count() {
System.out.println(environment + ": " + current++);
}
}
Then we'll write two @Configuration classes for local and prod:
@Configuration
public class LocalCounterConfiguration {
@Bean
public Counter counter() {
return new Counter("local");
}
}
@Configuration
public class ProdCounterConfiguration {
@Bean
public Counter counter() {
return new Counter("prod");
}
}
Now that we have two configurations, we want to use LocalCounterConfiguration only on the local environment:
public class CounterImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
if (isOnLocal()) {
return new String[]{LocalCounterConfiguration.class.getName()};
}
return new String[]{ProdCounterConfiguration.class.getName()};
}
private boolean isOnLocal() {
// return after environment check...
}
}
Here, the CounterImportSelector class returns either LocalConfiguration or ProdConfiguration according to the environment.
Similar to the previous examples, we'll import our ImportSelector implementation using the @Import annotation:
@Configuration
@Import(CounterImportSelector.class)
public class MainConfiguration {
@Bean
public ImpressionService impressionService(Counter counter) {
return new ImpressionService(counter);
}
}
6. Meta-Annotation with @Import
Lastly, we'll examine how we can create a meta-annotation using @Import. The newly created annotation serves similar to @Import but tells more about the intention. Spring itself makes use of these meta-annotations in the format of @EnableSomething. For example, Spring provides the @EnableAsync annotation:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(AsyncConfigurationSelector.class)
public @interface EnableAsync {
...
}
Similar to this annotation, we'll create a new one to import CounterConfiguration:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(CounterConfiguration.class)
public @interface EnableCounter {
}
Here, we have the @EnableCounter annotation following the EnableSomething naming format. Note that we're meta-annotating it with @Import(CounterConfiguration.class).
Then another configuration uses it in place of @Import:
@Configuration
@EnableCounter
public class MainConfiguration {
...
}
7. Summary
In this tutorial, we've examined different usages of the @Import annotation. We started with importing the configuration classes. Then we showed that we can also import @Component classes and ImportSelector implementations. Lastly, we detailed how we can create a meta-annotation.
As always, the source code for all examples in this tutorial is available on Github.