为什么spirng扫描到@compent会把对象注册到容器上
spring 扫描方式有多少种?
首先spring是可以通过xml方式和注解方式来扫描指定的包
1.xml形式:
applicationContext.xml
文件
<context:component-scan base-package="com.onion"></context:component-scan>
2.注解方式
@ComponentScan(basePackages = "com.onion")
源码解析
最后会触发:ComponentScanBeanDefinitionParser.parse
去解析xml , 如果想知道为什么会触发ComponentScanBeanDefinitionParser的小伙伴可以看: https://www.cnblogs.com/dabenxiang/p/11038914.html
public class ComponentScanBeanDefinitionParser implements BeanDefinitionParser {
private static final String BASE_PACKAGE_ATTRIBUTE = "base-package";
private static final String RESOURCE_PATTERN_ATTRIBUTE = "resource-pattern";
private static final String USE_DEFAULT_FILTERS_ATTRIBUTE = "use-default-filters";
private static final String ANNOTATION_CONFIG_ATTRIBUTE = "annotation-config";
private static final String NAME_GENERATOR_ATTRIBUTE = "name-generator";
private static final String SCOPE_RESOLVER_ATTRIBUTE = "scope-resolver";
private static final String SCOPED_PROXY_ATTRIBUTE = "scoped-proxy";
private static final String EXCLUDE_FILTER_ELEMENT = "exclude-filter";
private static final String INCLUDE_FILTER_ELEMENT = "include-filter";
private static final String FILTER_TYPE_ATTRIBUTE = "type";
private static final String FILTER_EXPRESSION_ATTRIBUTE = "expression";
//注释
public ComponentScanBeanDefinitionParser() {
}
@Nullable
public BeanDefinition parse(Element element, ParserContext parserContext) {
String basePackage = element.getAttribute("base-package");
basePackage = parserContext.getReaderContext().getEnvironment().resolvePlaceholders(basePackage);
String[] basePackages = StringUtils.tokenizeToStringArray(basePackage, ",;
");
ClassPathBeanDefinitionScanner scanner = this.configureScanner(parserContext, element);
Set<BeanDefinitionHolder> beanDefinitions = scanner.doScan(basePackages);
this.registerComponents(parserContext.getReaderContext(), beanDefinitions, element);
return null;
}
}
可以看到这里他是Set<BeanDefinitionHolder> beanDefinitions = scanner.doScan(basePackages);
,扫描包,并得到Set<BeanDefinitionHolder>
集合。
scanner.doScan的代码展示:
public class ClassPathBeanDefinitionScanner extends ClassPathScanningCandidateComponentProvider {
...
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
Assert.notEmpty(basePackages, "At least one base package must be specified");
Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet();
String[] var3 = basePackages;
int var4 = basePackages.length;
for(int var5 = 0; var5 < var4; ++var5) {
String basePackage = var3[var5];
Set<BeanDefinition> candidates = this.findCandidateComponents(basePackage);
...
}
return beanDefinitions;
}
}
public class ClassPathScanningCandidateComponentProvider implements EnvironmentCapable, ResourceLoaderAware {
...
public Set<BeanDefinition> findCandidateComponents(String basePackage) {
return this.componentsIndex != null && this.indexSupportsIncludeFilters() ? this.addCandidateComponentsFromIndex(this.componentsIndex, basePackage) :
//调用scanCandidateComponents
this.scanCandidateComponents(basePackage);
}
...
private Set<BeanDefinition> scanCandidateComponents(String basePackage) {
LinkedHashSet candidates = new LinkedHashSet();
String packageSearchPath = "classpath*:" + this.resolveBasePackage(basePackage) + '/' + this.resourcePattern;
//扫描包,得到相应的类信息
Resource[] resources = this.getResourcePatternResolver().getResources(packageSearchPath);
Resource[] var7 = resources;
int var8 = resources.length;
for(int var9 = 0; var9 < var8; ++var9) {
Resource resource = var7[var9];
if (resource.isReadable()) {
//得到类的元数据信息
MetadataReader metadataReader = this.getMetadataReaderFactory().getMetadataReader(resource);
//判断条件,如果满足加入到候选人处
if (this.isCandidateComponent(metadataReader)) {
ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
sbd.setSource(resource);
if (this.isCandidateComponent((AnnotatedBeanDefinition)sbd)) {
//加入到集合
candidates.add(sbd);
}
}
}
return candidates;
}
...
}
从代码中可以看到scanner.doScan
到 ClassPathScanningCandidateComponentProvider.findCandidateComponents(basePackage)
再到ClassPathScanningCandidateComponentProvider.scanCandidateComponents(basePackage)
。 注意: ClassPathBeanDefinitionScanner是ClassPathScanningCandidateComponentProvider的子类
this.scanCandidateComponents
前面部分用途就是扫描basepackage,并从中得到相应的类,并且把相应的类信息,转换成元数据MetadataReader,
然后再通过this.isCandidateComponent(metadataReader)
来判断这个类是否需要加入到容器里,
ClassPathScanningCandidateComponentProvider.isCandidateComponent
代码展示
public class ClassPathScanningCandidateComponentProvider implements EnvironmentCapable, ResourceLoaderAware {
//构造方法
public ClassPathScanningCandidateComponentProvider(boolean useDefaultFilters, Environment environment) {
if (useDefaultFilters) {
this.registerDefaultFilters();
}
}
//这里可以看到注入了@Component
protected void registerDefaultFilters() {
this.includeFilters.add(new AnnotationTypeFilter(Component.class));
}
//判断
protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {
Iterator var2 = this.excludeFilters.iterator();
TypeFilter tf;
do {
if (!var2.hasNext()) {
var2 = this.includeFilters.iterator(); //遍历includeFilters,然后跟metadataReader做比对
do {
if (!var2.hasNext()) {
return false;
}
tf = (TypeFilter)var2.next();
} while(!tf.match(metadataReader, this.getMetadataReaderFactory()));
return this.isConditionMatch(metadataReader);
}
tf = (TypeFilter)var2.next();
} while(!tf.match(metadataReader, this.getMetadataReaderFactory()));
return false;
}
}
从ClassPathScanningCandidateComponentProvider的构造方法再到registerDefaultFilters里可以看出,默认情况下,spring 会调用 this.includeFilters.add(new AnnotationTypeFilter(Component.class));
这个代码。而isCandidateComponent 则是遍历includeFilters里面的注解,再去跟传进来的类元数据做对比,如果类元数据存在该注解,返回true,就是需要这个类加入容器。
总结说明:这就是说明了只要加入了@Component注解的类就能注入到容器中,@Service , @Controller 等等都是继承了@Component,所以加上这些注解的类都可以注入到容器里。
那如果我们想自定义一个注解且不继承@Component, 怎么让带上这个自定义注解的类注入到容器里呢?
带上自定义注解,注入到容器
openFeign的代码示例
其实openFeign,也是做过这样的事情,只要我们仿造openFeign就可以达到这样的目的。
@SpringBootApplication
@EnableFeignClients
@EnableMyBeanAnno
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
}
@FeignClient(value = "spring-cloud-order-service")
public interface OpenFeignService {
@GetMapping("/orders")
public String getAllOrder();
}
@FeignClient
就是属于openFeign的自定义标签
@RestController
public class OpenFeignController {
@Autowired
private OpenFeignService openFeignService;
@GetMapping("/orders")
public String getAllOrder(){
return openFeignService.getAllOrder();
}
}
这段代码可以直接注入OpenFeignService,证明OpenFeignService已经注入到容器里。
源码解析
为何带上@FeignClient
的接口能注入到容器里:
首先看@EnableFeignClients
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import({FeignClientsRegistrar.class})
public @interface EnableFeignClients {
String[] value() default {};
String[] basePackages() default {};
}
根据@Import
的第三种用法,会加载FeignClientsRegistrar.registerBeanDefinitions
的方法,因为FeignClientsRegistrar 实现了 ImportBeanDefinitionRegistrar ,
提示:
@Import使用方法解析: https://www.cnblogs.com/yichunguo/p/12122598.html
FeignClientsRegistrar代码:
class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {
...
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
this.registerDefaultConfiguration(metadata, registry);
this.registerFeignClients(metadata, registry);
}
...
protected ClassPathScanningCandidateComponentProvider getScanner() {
return new ClassPathScanningCandidateComponentProvider(false, this.environment) {
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
boolean isCandidate = false;
if (beanDefinition.getMetadata().isIndependent() && !beanDefinition.getMetadata().isAnnotation()) {
isCandidate = true;
}
return isCandidate;
}
};
}
...
public void registerFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
ClassPathScanningCandidateComponentProvider scanner = this.getScanner();
scanner.setResourceLoader(this.resourceLoader);
//获取EnableFeignClients的属性
Map<String, Object> attrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName());
//构造AnnotationTypeFilter参数是FeignClient.class
AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(FeignClient.class);
Class<?>[] clients = attrs == null ? null : (Class[])((Class[])attrs.get("clients"));
Object basePackages;
if (clients != null && clients.length != 0) {
....
}
else {
//加入到includeFilter里面去
scanner.addIncludeFilter(annotationTypeFilter);
basePackages = this.getBasePackages(metadata);
}
Iterator var17 = ((Set)basePackages).iterator();
while(var17.hasNext()) {
String basePackage = (String)var17.next();
//扫描包。返回可以注入到容器中的BeanDefinition
Set<BeanDefinition> candidateComponents = scanner.findCandidateComponents(basePackage);
Iterator var21 = candidateComponents.iterator();
while(var21.hasNext()) {
BeanDefinition candidateComponent = (BeanDefinition)var21.next();
if (candidateComponent instanceof AnnotatedBeanDefinition) {
AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition)candidateComponent;
AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
Assert.isTrue(annotationMetadata.isInterface(), "@FeignClient can only be specified on an interface");
Map<String, Object> attributes = annotationMetadata.getAnnotationAttributes(FeignClient.class.getCanonicalName());
String name = this.getClientName(attributes);
this.registerClientConfiguration(registry, name, attributes.get("configuration"));
//注入到容器里
this.registerFeignClient(registry, annotationMetadata, attributes);
}
}
}
}
}
可以看到FeignClientsRegistrar.registerBeanDefinitions
调用 this.registerFeignClients
解析this.registerFeignClients
,
第一: 获取EnableFeignClients的属性,来确定要扫描的包是什么。
第二: 把FeignClient.class 加入到 scanner中
第三: scanner.findCandidateComponents(basePackage)。 返回加上了FeignClient的类信息的BeanDefinition
第四:把带上FeignClient注解的类注入到容器里
scanner.findCandidateComponents
就不做解析了。因为上面解析@compent已经解析过了
自制自定义注解
首先仿照@EnableFeignClients
, 新建一个@EnableMyBeanAnno
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import({MyImportBeanDefinitionRegistrar.class})
public @interface EnableMyBeanAnno {
String[] value() default {};
String[] basePackages() default {};
Class<?>[] basePackageClasses() default {};
Class<?>[] defaultConfiguration() default {};
Class<?>[] clients() default {};
}
仿照@FeignClientsRegistrar
, 新建一个@MyImportBeanDefinitionRegistrar
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {
private ResourceLoader resourceLoader;
private Environment environment;
public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) {
ClassPathScanningCandidateComponentProvider scanner = this.getScanner();
scanner.setResourceLoader(this.resourceLoader);
//加入MyBeanAnno.class到includeFilter中
AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(MyBeanAnno.class);
scanner.addIncludeFilter(annotationTypeFilter);
//这里我就直接指定要读的包了。不搞那么麻烦了
Set<BeanDefinition> candidateComponents = scanner.findCandidateComponents("com.onion.test");
for (BeanDefinition candidateComponent : candidateComponents) {
beanDefinitionRegistry.registerBeanDefinition(candidateComponent.getBeanClassName(),candidateComponent);
}
}
protected ClassPathScanningCandidateComponentProvider getScanner() {
return new ClassPathScanningCandidateComponentProvider(false, this.environment) {
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
boolean isCandidate = false;
if (beanDefinition.getMetadata().isIndependent() && !beanDefinition.getMetadata().isAnnotation()) {
isCandidate = true;
}
return isCandidate;
}
};
}
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
public void setEnvironment(Environment environment) {
this.environment = environment;
}
}
带上@MyBeanAnno
注解的类
@MyBeanAnno
public class MyBean {
}
测试类:
@SpringBootApplication
@EnableFeignClients
@EnableMyBeanAnno //带上了我们自定义的注解
public class App {
@Autowired
private StaticApp staticApp;
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
@Component
public class StaticApp implements ApplicationContextAware {
private ApplicationContext applicationContext;
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
MyBean bean = applicationContext.getBean(MyBean.class);
System.out.println(bean);
}
}
}
最后的结果: