Uso e Extensão da Anotação @Import no Spring Framework

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 {
}

Tags: Spring Spring Framework java ImportSelector ImportBeanDefinitionRegistrar

Publicado em 7-5 05:38