excluir @Component de @ComponentScan

90

Tenho um componente que desejo excluir de um @ComponentScanem particular @Configuration:

@Component("foo") class Foo {
...
}

Caso contrário, parece colidir com alguma outra classe em meu projeto. Não entendo totalmente a colisão, mas se eu comentar a @Componentanotação, as coisas funcionam como eu quero. Mas outros projetos que dependem desta biblioteca esperam que esta classe seja gerenciada pelo Spring, então eu quero pular apenas no meu projeto.

Tentei usar @ComponentScan.Filter:

@Configuration 
@EnableSpringConfigured
@ComponentScan(basePackages = {"com.example"}, excludeFilters={
  @ComponentScan.Filter(type=FilterType.ASSIGNABLE_TYPE, value=Foo.class)})
public class MySpringConfiguration {}

mas não parece funcionar. Se eu tentar usar FilterType.ASSIGNABLE_TYPE, recebo um erro estranho sobre não ser capaz de carregar alguma classe aparentemente aleatória:

Causado por: java.io.FileNotFoundException: recurso de caminho de classe [junit / framework / TestCase.class] não pode ser aberto porque não existe

Eu também tentei usar type=FilterType.CUSTOMo seguinte:

class ExcludeFooFilter implements TypeFilter {
    @Override
    public boolean match(MetadataReader metadataReader,
            MetadataReaderFactory metadataReaderFactory) throws IOException {
        return metadataReader.getClass() == Foo.class;
    }
}

@Configuration @EnableSpringConfigured
@ComponentScan(basePackages = {"com.example"}, excludeFilters={
  @ComponentScan.Filter(type=FilterType.ASSIGNABLE_TYPE, value=Foo.class)})
public class MySpringConfiguration {}

Mas isso não parece excluir o componente da varredura como eu desejo.

Como faço para excluí-lo?

Ykaganovich
fonte

Respostas:

108

A configuração parece boa, exceto que você deve usar em excludeFiltersvez de excludes:

@Configuration @EnableSpringConfigured
@ComponentScan(basePackages = {"com.example"}, excludeFilters={
  @ComponentScan.Filter(type=FilterType.ASSIGNABLE_TYPE, value=Foo.class)})
public class MySpringConfiguration {}
Sergi Almar
fonte
desculpe, foi um erro de recortar e colar. Estou usando excludeFilters. Vou dar uma olhada, o erro que isso me causa é realmente bizarro ...
ykaganovich
52

Usar tipos explícitos em filtros de verificação é feio para mim. Acredito que uma abordagem mais elegante é criar a própria anotação do marcador:

@Retention(RetentionPolicy.RUNTIME)
public @interface IgnoreDuringScan {
}

Marque o componente que deve ser excluído com ele:

@Component("foo") 
@IgnoreDuringScan
class Foo {
    ...
}

E exclua esta anotação de sua verificação de componente:

@ComponentScan(excludeFilters = @Filter(IgnoreDuringScan.class))
public class MySpringConfiguration {}
Luboskrnac
fonte
13
Essa é uma ideia inteligente para exclusão universal, embora não ajude se você quiser excluir um componente de apenas um subconjunto de contextos de aplicativo em um projeto. Na verdade, para excluí-lo universalmente, pode-se simplesmente remover o @Component, mas não acho que seja isso que a pergunta está perguntando
Kirby
2
isso não funcionará se você tiver outra anotação de varredura de componente em algum lugar que não tenha o mesmo filtro
Bashar Ali Labadi
@Bashar Ali Labadi, não é esse tipo de ponto desta construção? Se você deseja excluí-lo de todas as varreduras de componentes, provavelmente não deveria ser um componente Spring.
luboskrnac
30

Outra abordagem é usar novas anotações condicionais. Desde o Spring 4 simples, você pode usar a anotação @Conditional:

@Component("foo")
@Conditional(FooCondition.class)
class Foo {
    ...
}

e definir a lógica condicional para registrar o componente Foo:

public class FooCondition implements Condition{
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        // return [your conditional logic]
    }     
}

A lógica condicional pode ser baseada no contexto, porque você tem acesso ao bean factory. Por exemplo, quando o componente "Bar" não está registrado como bean:

    return !context.getBeanFactory().containsBean(Bar.class.getSimpleName());

Com o Spring Boot (deve ser usado para CADA novo projeto Spring), você pode usar estas anotações condicionais:

  • @ConditionalOnBean
  • @ConditionalOnClass
  • @ConditionalOnExpression
  • @ConditionalOnJava
  • @ConditionalOnMissingBean
  • @ConditionalOnMissingClass
  • @ConditionalOnNotWebApplication
  • @ConditionalOnProperty
  • @ConditionalOnResource
  • @ConditionalOnWebApplication

Você pode evitar a criação de classes de Condição dessa maneira. Consulte a documentação do Spring Boot para obter mais detalhes.

Luboskrnac
fonte
1
+1 para as condicionais, esta é uma maneira muito mais limpa para mim do que usar filtros. Eu nunca fiz os filtros funcionarem tão consistentemente quanto o carregamento condicional de grãos
wonderergoat77
13

Caso você precise definir dois ou mais critérios excludeFilters , você deve usar a matriz.

Por exemplo, nesta seção de código, desejo excluir todas as classes do pacote org.xxx.yyy e outra classe específica, MyClassToExclude

 @ComponentScan(            
        excludeFilters = {
                @ComponentScan.Filter(type = FilterType.REGEX, pattern = "org.xxx.yyy.*"),
                @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = MyClassToExclude.class) })
Enrico Giurin
fonte
eu sugeriria ASPECTJ como tipo de filtro, porque este regex também corresponderia a "org.xxx.yyyy.z"
wutzebaer
9

Eu tive um problema ao usar @Configuration , @EnableAutoConfiguration e @ComponentScan ao tentar excluir classes de configuração específicas, o que não funcionou!

Eventualmente resolvi o problema usando @SpringBootApplication , que, de acordo com a documentação do Spring, tem a mesma funcionalidade dos três acima em uma anotação.

Outra dica é tentar primeiro sem refinar sua verificação de pacote (sem o filtro basePackages).

@SpringBootApplication(exclude= {Foo.class})
public class MySpringConfiguration {}
dorônia
fonte
1
Eu tentei fazer isso, mas mostrando um erro como "As seguintes classes não puderam ser excluídas porque não são classes de configuração automática". excludeFilters com @ComponentScan está funcionando bem.
AzarEJ
1

No caso de excluir o componente de teste ou configuração de teste, Spring Boot 1.4 introduziu novas anotações de teste @TestComponente@TestConfiguration .

Luboskrnac
fonte
0

Eu precisava excluir um @Aspect @Component de auditoria do contexto do aplicativo, mas apenas para algumas classes de teste. Acabei usando @Profile ("audit") na classe de aspecto; incluindo o perfil para operações normais, mas excluindo-o (não o coloque em @ActiveProfiles) nas classes de teste específicas.

Miguel Pereira
fonte