将@Component 从@Component 扫描中排除

我有一个组件,我想排除从一个 @ComponentScan在一个特定的 @Configuration:

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

否则,它似乎与我的项目中的其他类冲突。我不完全理解这个冲突,但是如果我注释掉 @Component注释,事情就会像我希望的那样运行。但是其他依赖于这个库的项目期望 Spring 管理这个类,所以我只想在我的项目中跳过它。

我试着用 @ComponentScan.Filter:

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

但似乎没有用。如果我尝试使用 FilterType.ASSIGNABLE_TYPE,我会得到一个奇怪的错误,无法加载一些看似随机的类:

由 java.io. FileNotFoundException 引起: 类路径资源[ junit/Framework/TestCase.class ]不能打开,因为它不存在

我还尝试使用 type=FilterType.CUSTOM如下:

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.CUSTOM, value=ExcludeFooFilter.class)})
public class MySpringConfiguration {}

但这似乎并不像我希望的那样,把这个部件排除在扫描之外。

怎么排除呢?

212780 次浏览

The configuration seem alright, except that you should use excludeFilters instead of excludes:

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

Another approach is to use new conditional annotations. Since plain Spring 4 you can use @Conditional annotation:

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

and define conditional logic for registering Foo component:

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

Conditional logic can be based on context, because you have access to bean factory. For Example when "Bar" component is not registered as bean:

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

With Spring Boot (should be used for EVERY new Spring project), you can use these conditional annotations:

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

You can avoid Condition class creation this way. Refer to Spring Boot docs for more detail.

Using explicit types in scan filters is ugly for me. I believe more elegant approach is to create own marker annotation:

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

Mark component that should be excluded with it:

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

And exclude this annotation from your component scan:

@ComponentScan(excludeFilters = @Filter(IgnoreDuringScan.class))
public class MySpringConfiguration {}

In case of excluding test component or test configuration, Spring Boot 1.4 introduced new testing annotations ABC0 and @TestConfiguration.

In case you need to define two or more excludeFilters criteria, you have to use the array.

For instances in this section of code I want to exclude all the classes in the org.xxx.yyy package and another specific class, MyClassToExclude

 @ComponentScan(
excludeFilters = {
@ComponentScan.Filter(type = FilterType.REGEX, pattern = "org.xxx.yyy.*"),
@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = MyClassToExclude.class) })

I had an issue when using @Configuration, @EnableAutoConfiguration and @ComponentScan while trying to exclude specific configuration classes, the thing is it didn't work!

Eventually I solved the problem by using @SpringBootApplication, which according to Spring documentation does the same functionality as the three above in one annotation.

Another Tip is to try first without refining your package scan (without the basePackages filter).

@SpringBootApplication(exclude= {Foo.class})
public class MySpringConfiguration {}

I needed to exclude an auditing @Aspect @Component from the app context but only for a few test classes. I ended up using @Profile("audit") on the aspect class; including the profile for normal operations but excluding it (don't put it in @ActiveProfiles) on the specific test classes.