Spring Framework 中的 @ComponentScan 注解扫描的配置类分为两种:一种是完全(Full)的配置类,另外一种是简化(Lite)的配置类。

  1. 完全(Full)的配置类:@Configuration 注解修饰的类。

    1
    2
    3
    public static boolean isFullConfigurationCandidate(AnnotationMetadata metadata) {
    return metadata.isAnnotated(Configuration.class.getName());
    }
  2. 简化(Lite)的配置类:@Component、 @ComponentScan、 @Import、 @ImportResource 注解修饰的类以及 @Bean注解修饰的方法。

    1
    2
    3
    4
    5
    6
    static {
    candidateIndicators.add(Component.class.getName());
    candidateIndicators.add(ComponentScan.class.getName());
    candidateIndicators.add(Import.class.getName());
    candidateIndicators.add(ImportResource.class.getName());
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    public static boolean isLiteConfigurationCandidate(AnnotationMetadata metadata) {
    // Do not consider an interface or an annotation...
    if (metadata.isInterface()) {
    return false;
    }

    // Any of the typical annotations found?
    for (String indicator : candidateIndicators) {
    if (metadata.isAnnotated(indicator)) {
    return true;
    }
    }

    // Finally, let's look for @Bean methods...
    try {
    return metadata.hasAnnotatedMethods(Bean.class.getName());
    }
    catch (Throwable ex) {
    if (logger.isDebugEnabled()) {
    logger.debug("Failed to introspect @Bean methods on class [" + metadata.getClassName() + "]: " + ex);
    }
    return false;
    }
    }

@ComponentScan 直接使用

创建一个 Spring 配置类,直接添加 @ComponentScan 注解。该注解默认会扫描该类所在包下的所有配置类,相当于之前的 <context:component-scan/>

1
2
3
4
5
6
7
8
9
10
package cn.worstone.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan
public class SpringConfig {

}

创建一个 测试类,使用 AnnotationConfigApplicationContext 对象加载配置类,使用 getBeanDefinitionNames() 方法获取已经注册到容器中的 bean 的名称。

1
2
3
4
5
6
7
8
9
10
11
12
13
import cn.worstone.config.SpringConfig;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class AnnotationTest {

public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
for (String beanName : context.getBeanDefinitionNames()) {
System.out.println("bean name: " + beanName);
}
}
}

运行结果:

1
2
3
4
5
6
bean name: org.springframework.context.annotation.internalConfigurationAnnotationProcessor
bean name: org.springframework.context.annotation.internalAutowiredAnnotationProcessor
bean name: org.springframework.context.annotation.internalCommonAnnotationProcessor
bean name: org.springframework.context.event.internalEventListenerProcessor
bean name: org.springframework.context.event.internalEventListenerFactory
bean name: springConfig

可以看到,除了 Spring 本身注册的一些 bean 以外,SpringConfig 这个类也注册到容器中了。如果这个配置类使用了 @Import 注解导入了其他的配置类,那么导入的类也会被注册到容器中。

@ComponentScan 指定扫描位置

指定要扫描的位置,需要使用 @ComponentScan 的 valule 属性来配置。创建了一个 dao包,并在包下创建了 UserDao 类。在类上添加 @Repository 注解,说明该类是一个 Component。

1
2
3
4
5
6
7
8
package cn.worstone.dao;

import org.springframework.stereotype.Repository;

@Repository
public class UserDao {

}

修改配置类 SpringConfig,在 @ComponentScan 注解中指定扫描的位置:

1
2
3
4
5
6
7
8
9
10
package cn.worstone.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan(basePackages = "cn.worstone.dao")
public class SpringConfig {

}

运行结果:

1
2
3
4
5
6
7
bean name: org.springframework.context.annotation.internalConfigurationAnnotationProcessor
bean name: org.springframework.context.annotation.internalAutowiredAnnotationProcessor
bean name: org.springframework.context.annotation.internalCommonAnnotationProcessor
bean name: org.springframework.context.event.internalEventListenerProcessor
bean name: org.springframework.context.event.internalEventListenerFactory
bean name: springConfig
bean name: userDao

可以看到,UserDao 已经注册到容器中了。

@ComponentScan 设置 excludeFilters 和 includeFilters 使用

使用 excludeFilters 来按照规则排除某些位置的扫描,使用 includeFilters 来按照规则只扫描某些位置。

excludeFilters 的参数是一个 Filter[] 数组,然后指定 FilterType 的类型为 ANNOTATION,也就是通过注解来过滤,最后的 value 则是 Service 注解类。配置之后,在 Spring 扫描的时候,就会跳过 cn.worstone 路径下所有被 @Service 注解修饰的类。includeFilters 的参数与 excludeFilters 参数相同。

1
2
3
4
5
6
7
8
9
10
11
12
package cn.worstone.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.stereotype.Service;

@Configuration
@ComponentScan(basePackages = "cn.worstone", excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION, value = {Service.class})})
public class SpringConfig {

}

创建一个 service 的包,并在包下创建一个 UserService 类,在类上添加 @Service 注解。

1
2
3
4
5
6
7
8
package cn.worstone.service;

import org.springframework.stereotype.Service;

@Service
public class UserService {

}

修改 SpringConfig 配置类,设置 includeFilters:

1
2
3
4
5
6
7
8
9
10
11
12
package cn.worstone.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.stereotype.Service;

@Configuration
@ComponentScan(basePackages = "cn.worstone", includeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION, value = {Service.class})})
public class SpringConfig {

}

运行结果:

1
2
3
4
5
6
7
8
bean name: org.springframework.context.annotation.internalConfigurationAnnotationProcessor
bean name: org.springframework.context.annotation.internalAutowiredAnnotationProcessor
bean name: org.springframework.context.annotation.internalCommonAnnotationProcessor
bean name: org.springframework.context.event.internalEventListenerProcessor
bean name: org.springframework.context.event.internalEventListenerFactory
bean name: springConfig
bean name: userDao
bean name: userService

根据配置过滤规则,应该只有 @Service 注解修饰的类才会被注册到容器中,那么,为什么 @Repository 注解修饰的类也被注册到容器中了呢?这里涉及 @ComponentScan useDefaultFilters 属性的用法,该属性默认值为 true,也就是说 Spring 默认会自动发现被 @Component、 @Repository、 @Service 以及 @Controller 注解修饰的类,并注册到容器中。想要达到只包含某些位置的扫描结果,就必须将这个默认行为给禁用掉(在 @ComponentScan 中将 useDefaultFilters 属性设置为 false 即可)。

一定要慎重使用这个属性,否则无法扫描到 @Component、 @Repository、 @Service 以及 @Controller 注解修饰的类。

1
2
3
4
5
6
7
<xsd:attribute name="use-default-filters" type="xsd:boolean" default="true">
<xsd:annotation>
<xsd:documentation>
<![CDATA[Indicates whether automatic detection of classes annotated with @Component, @Repository, @Service, or @Controller should be enabled. Default is "true".]]>
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
1
2
3
4
5
6
7
8
9
10
11
12
package cn.worstone.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.stereotype.Service;

@Configuration
@ComponentScan(basePackages = "cn.worstone", includeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION, value = {Service.class})}, useDefaultFilters = false)
public class SpringConfig {

}

运行结果:

1
2
3
4
5
6
7
bean name: org.springframework.context.annotation.internalConfigurationAnnotationProcessor
bean name: org.springframework.context.annotation.internalAutowiredAnnotationProcessor
bean name: org.springframework.context.annotation.internalCommonAnnotationProcessor
bean name: org.springframework.context.event.internalEventListenerProcessor
bean name: org.springframework.context.event.internalEventListenerFactory
bean name: springConfig
bean name: userService

@ComponentScan 重复标注使用

以这种方式使用,必须在配置类中添加 @Configuration 注解,否则无效。

  1. 如果使用 Java 8 及以上版本,则可以直接添加多个 @ComponentScan 注解来添加多个扫描规则。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    package cn.worstone.config;

    import org.springframework.context.annotation.ComponentScan;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.FilterType;

    @Configuration
    @ComponentScan(basePackages = "cn.worstone.dao")
    @ComponentScan(basePackages = "cn.worstone.service")
    public class SpringConfig {

    }
  2. 使用 @ComponentScans 注解来添加多个 @ComponentScan,从而添加多个扫描规则。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    package cn.worstone.config;

    import org.springframework.context.annotation.*;

    @Configuration
    @ComponentScans({@ComponentScan(basePackages = "cn.worstone.dao"), @ComponentScan(basePackages = "cn.worstone.service")})
    public class SpringConfig {

    }

@ComponentScan 自定义过滤规则使用

前面使用过的 @Filter 注解,里面的 type 属性是一个 FilterType 的枚举类型。使用 CUSTOM 类型,就可以自定义过滤类,从而达到自定义过滤规则的目的。

1
2
3
4
5
6
7
public enum FilterType {
ANNOTATION,
ASSIGNABLE_TYPE,
ASPECTJ,
REGEX,
CUSTOM
}

首先创建一个实现 TypeFilter 接口的 ServiceFilter 类,并实现其 match 方法。

这里简单对扫描到的类名进行判断,如果类名包含 ”Service“ 的就符合条件,也就会注入到容器中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package cn.worstone.filter;

import org.springframework.core.io.Resource;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.ClassMetadata;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.core.type.filter.TypeFilter;

import java.io.IOException;

public class ServiceFilter implements TypeFilter {

@Override
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
// 获取当前扫描到的类的注解元数据
AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();
// 获取当前扫描到的类的元数据
ClassMetadata classMetadata = metadataReader.getClassMetadata();
// 获取当前扫描到的类的资源信息
Resource resource = metadataReader.getResource();

if (classMetadata.getClassName().contains("Service")) {
return true;
}
return false;
}
}

修改配置类 SpringConfig,修改过滤类型为 CUSTOM,并设置 value 属性为 ServiceFilter.class。

1
2
3
4
5
6
7
8
9
10
11
12
package cn.worstone.config;

import cn.worstone.filter.ServiceFilter;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;

@Configuration
@ComponentScan(basePackages = "cn.worstone", includeFilters = {@ComponentScan.Filter(type = FilterType.CUSTOM, value = {ServiceFilter.class})}, useDefaultFilters = false)
public class SpringConfig {

}

运行结果:

1
2
3
4
5
6
7
8
bean name: org.springframework.context.annotation.internalConfigurationAnnotationProcessor
bean name: org.springframework.context.annotation.internalAutowiredAnnotationProcessor
bean name: org.springframework.context.annotation.internalCommonAnnotationProcessor
bean name: org.springframework.context.event.internalEventListenerProcessor
bean name: org.springframework.context.event.internalEventListenerFactory
bean name: springConfig
bean name: serviceFilter
bean name: userService

因为 cn.worstone.filter.ServiceFilter 以及 cn.worstone.service.UserService 两个类都符合条件,所以都被注入到了容器中。

参考资料:

__END__

Live For Code
文章作者:Live For Code
文章出处Spring Framework 中的 @ComponentScan 注解
作者签名:简单地活着, 肆意又精彩.
关于主题Hexo - Live For Code
版权声明:文章除特别声明外,均采用 BY-NC-SA 许可协议,转载请注明出处