O Processamento de @Import
A anotação @Import é analisada durante a fase de processamento das classes de configuração, executada pelo ConfigurationClassPostProcessor. Como essa classe implementa BeanDefinitionRegistryPostProcessor, seu método postProcessBeanDefinitionRegistry é invocado no momento apropriado da inicialização do contexto.
Durante esse processamento, o Spring percorre as classes marcadas com @Configuration e identifica as anotações @Import. O conteúdo do atributo value é avaliado recursivamente, e o comportamento varia de acordo com o tipo de classe importada.
Tipos Suportados por @Import
O atributo value de @Import aceita três tipos principais de classes:
- Classes comuns, tratadas como configurações adicionais
- Implementações de
ImportSelector - Implementações de
ImportBeanDefinitionRegistrar
1. ImportSelector
Implementações de ImportSelector permitem selecionar nomes de classes a serem registradas com base na metadados da classe que possui @Import. Esse mecanismo é útil para importações condicionais.
public interface ImportSelector {
String[] selectImports(AnnotationMetadata importingClassMetadata);
@Nullable
default Predicate<string> getExclusionFilter() {
return null;
}
}</string>
Se o seletor também implementar DeferredImportSelector, seu processamento será adiado até que todas as outras classes de configuração sejam carregadas, o que é essencial para auto-configurações em projetos como o Spring Boot.
2. ImportBeanDefinitionRegistrar
Essa interface permite registrar definições de beans programaticamente no registro do container, oferecendo controle total sobre o nome, escopo e outras propriedades do bean.
public interface ImportBeanDefinitionRegistrar {
default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
BeanDefinitionRegistry registry,
BeanNameGenerator importBeanNameGenerator) {
registerBeanDefinitions(importingClassMetadata, registry);
}
default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
BeanDefinitionRegistry registry) {
}
}
3. Classes Comuns
Quando a classe importada não implementa nenhuma das interfaces acima, ela é processdaa como uma classe de configuração comum, permitindo que seus métodos @Bean e outras anotações sejam avaliados.
Exemplos Práticos
Importando uma Classe Utilitária
Suponha uma classe utilitária que acessa o ApplicationContext. Para torná-la disponível de forma elegante, cria-se uma anotação customizada:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(AppContextHelper.class)
public @interface EnableAppContextHelper {
}
public class AppContextHelper implements ApplicationContextAware {
private static ApplicationContext context;
@Override
public void setApplicationContext(ApplicationContext ctx) throws BeansException {
context = ctx;
}
public static ApplicationContext getContext() {
return context;
}
}
Com isso, basta adicionar @EnableAppContextHelper na classe principal para que a utilitária seja registrada.
Import Condicional com ImportSelector
É possível criar uma anotação que, com base em um atributo, seleciona qual implementação será regsitrada.
public enum PersonType {
STUDENT,
TEACHER
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(PersonSelector.class)
public @interface EnablePersonService {
PersonType type() default PersonType.TEACHER;
}
public class PersonSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata metadata) {
PersonType type = metadata.getAnnotations()
.get(EnablePersonService.class)
.getEnum("type", PersonType.class);
return switch (type) {
case STUDENT -> new String[]{StudentManager.class.getName()};
case TEACHER -> new String[]{TeacherManager.class.getName()};
};
}
}
Dessa forma, a aplicação pode escolher a implementação desejada sem alterar a estrutura de classes:
@Configuration
@EnablePersonService(PersonType.STUDENT)
public class AppConfiguration {
}
Registro Dinâmico com ImportBeanDefinitionRegistrar
Esse cenário é útil quando se deseja escanear um pacote e registrar apenas classes anotadas com uma anotação específica.
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(CustomComponentRegistrar.class)
public @interface EnableCustomScan {
String[] basePackages();
Class extends Annotation>[] markerAnnotations();
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CustomBean {
}
public class CustomComponentRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
MergedAnnotation<enablecustomscan> ann = metadata.getAnnotations().get(EnableCustomScan.class);
String[] packages = ann.getStringArray("basePackages");
Class extends Annotation>[] markers = ann.getClassArray("markerAnnotations");
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(registry, false);
for (Class extends Annotation> marker : markers) {
scanner.addIncludeFilter(new AnnotationTypeFilter(marker));
}
scanner.scan(packages);
}
}</enablecustomscan>
Ao utilizar a anotação customizada, o Spring registra automaticamente as classes anotadas com @CustomBean dentro dos pacotes informados:
@Configuration
@EnableCustomScan(
basePackages = "com.exemplo.projeto.servicos",
markerAnnotations = CustomBean.class)
public class ProjectConfiguration {
}