• 为什么spirng扫描到@compent会把对象注册到容器上


    为什么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.doScanClassPathScanningCandidateComponentProvider.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);
            }
    
    
        }
    
    
    }
    

    最后的结果:

  • 相关阅读:
    Ubuntu 各版本代号简介
    Ubuntu如何同步网络时间
    Ubuntu下修改DNS重启也能用的方法
    provider networks和self-service networks
    openstack 架构
    系统故障排除
    系统日志管理
    系统引导
    网络管理
    Linux下开启FTP服务
  • 原文地址:https://www.cnblogs.com/dabenxiang/p/13616491.html
Copyright © 2020-2023  润新知