精华内容
下载资源
问答
  • 在解析(parse)时会调用doProcessConfigurationClass方法,其中就有@ComponentScans或@ComponentScan类型的处理。详细可以参见: SpringIoc源码(九)- ApplicationContext(五)- refresh...

    目录

    一、获取AnnotationAttributes属性并且解析

    二、doScan

    1)、轮训加载basePackage下的BeanDefinition

    1、scanCandidateComponents

    2、doFindPathMatchingFileResources

    3、isCandidateComponent(验证是否为Component)和初始为ScannedGenericBeanDefinition

    2)、解析Lazy、Primary、DependsOn、Role、Description等属性,设置到BeanDefinition中

    3)、验证BeanDefinition是否已经注册过

    4)、注册BeanDefinition

    三、轮训、递归解析其他注入类型

    四、总结


        前提梳理: Spring启动时会调用 AbstractApplicationContext的refresh方法,在invokeBeanFactoryPostProcessors时会处理(回调)BeanFactoryPostProcessor相关接口;其中比较特殊的BeanDefinitionRegistryPostProcessor类型也会被调用,其中就会回调ConfigurationClassPostProcessor的processConfigBeanDefinitions方法,则回初始化ConfigurationClassParser并且进行解析(parse方法)、验证(validate方法)、注册BeanDefinition(ConfigurationClassBeanDefinitionReader#loadBeanDefinitions方法)。在解析(parse)时会调用doProcessConfigurationClass方法,其中就有@ComponentScans或@ComponentScan类型的处理。详细可以参见:

    SpringIoc源码(九)- ApplicationContext(五)- refresh(invokeBeanFactoryPostProcessors和registerBeanPostProc)

    继续:ConfigurationClassParser的doProcessConfigurationClass方法:

    Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
            sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
    if (!componentScans.isEmpty() &&
            !this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
        for (AnnotationAttributes componentScan : componentScans) {
            // The config class is annotated with @ComponentScan -> perform the scan immediately
            Set<BeanDefinitionHolder> scannedBeanDefinitions =
                    this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
            // Check the set of scanned definitions for any further config classes and parse recursively if needed
            for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
                BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
                if (bdCand == null) {
                    bdCand = holder.getBeanDefinition();
                }
                if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
                    parse(bdCand.getBeanClassName(), holder.getBeanName());
                }
            }
        }
    }

    一、获取AnnotationAttributes属性并且解析

        ComponentScanAnnotationParser解析器进行AnnotationAttributes的解析。先初始化ClassPathBeanDefinitionScanner,先获取@ComponentScan注解的属性,添加到扫描器中,再进行扫描。

    public Set<BeanDefinitionHolder> parse(AnnotationAttributes componentScan, final String declaringClass) {
        ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(this.registry,
                componentScan.getBoolean("useDefaultFilters"), this.environment, this.resourceLoader);
    
        Class<? extends BeanNameGenerator> generatorClass = componentScan.getClass("nameGenerator");
        boolean useInheritedGenerator = (BeanNameGenerator.class == generatorClass);
        scanner.setBeanNameGenerator(useInheritedGenerator ? this.beanNameGenerator :
                BeanUtils.instantiateClass(generatorClass));
    
        ScopedProxyMode scopedProxyMode = componentScan.getEnum("scopedProxy");
        if (scopedProxyMode != ScopedProxyMode.DEFAULT) {
            scanner.setScopedProxyMode(scopedProxyMode);
        }
        else {
            Class<? extends ScopeMetadataResolver> resolverClass = componentScan.getClass("scopeResolver");
            scanner.setScopeMetadataResolver(BeanUtils.instantiateClass(resolverClass));
        }
    
        scanner.setResourcePattern(componentScan.getString("resourcePattern"));
    
        for (AnnotationAttributes filter : componentScan.getAnnotationArray("includeFilters")) {
            for (TypeFilter typeFilter : typeFiltersFor(filter)) {
                scanner.addIncludeFilter(typeFilter);
            }
        }
        for (AnnotationAttributes filter : componentScan.getAnnotationArray("excludeFilters")) {
            for (TypeFilter typeFilter : typeFiltersFor(filter)) {
                scanner.addExcludeFilter(typeFilter);
            }
        }
    
        boolean lazyInit = componentScan.getBoolean("lazyInit");
        if (lazyInit) {
            scanner.getBeanDefinitionDefaults().setLazyInit(true);
        }
    
        Set<String> basePackages = new LinkedHashSet<>();
        String[] basePackagesArray = componentScan.getStringArray("basePackages");
        for (String pkg : basePackagesArray) {
            String[] tokenized = StringUtils.tokenizeToStringArray(this.environment.resolvePlaceholders(pkg),
                    ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
            Collections.addAll(basePackages, tokenized);
        }
        for (Class<?> clazz : componentScan.getClassArray("basePackageClasses")) {
            basePackages.add(ClassUtils.getPackageName(clazz));
        }
        if (basePackages.isEmpty()) {
            basePackages.add(ClassUtils.getPackageName(declaringClass));
        }
    
        scanner.addExcludeFilter(new AbstractTypeHierarchyTraversingFilter(false, false) {
            @Override
            protected boolean matchClassName(String className) {
                return declaringClass.equals(className);
            }
        });
        return scanner.doScan(StringUtils.toStringArray(basePackages));
    }

        上面只是将注解属性设置到扫描器ClassPathBeanDefinitionScanner中,doScan的核心方法:

    二、doScan

    protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
        Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
        for (String basePackage : basePackages) {
            Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
            for (BeanDefinition candidate : candidates) {
                ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
                candidate.setScope(scopeMetadata.getScopeName());
                String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
                if (candidate instanceof AbstractBeanDefinition) {
                    postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
                }
                if (candidate instanceof AnnotatedBeanDefinition) {
                    AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
                }
                if (checkCandidate(beanName, candidate)) {
                    BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
                    definitionHolder =
                            AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
                    beanDefinitions.add(definitionHolder);
                    registerBeanDefinition(definitionHolder, this.registry);
                }
            }
        }
        return beanDefinitions;
    }

    1)、轮训加载basePackage下的BeanDefinition

    findCandidateComponents(basePackage);
    public Set<BeanDefinition> findCandidateComponents(String basePackage) {
        if (this.componentsIndex != null && indexSupportsIncludeFilters()) {
            return addCandidateComponentsFromIndex(this.componentsIndex, basePackage);
        }
        else {
            return scanCandidateComponents(basePackage);
        }
    }

        Spring5之后增加了@Index注解,addCandidateComponentsFromIndex解析,先不进行分析。

    1、scanCandidateComponents

    private Set<BeanDefinition> scanCandidateComponents(String basePackage) {
        Set<BeanDefinition> candidates = new LinkedHashSet<>();
        String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
                resolveBasePackage(basePackage) + '/' + this.resourcePattern;
        Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);
        for (Resource resource : resources) {
            if (resource.isReadable()) {
                MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);
                if (isCandidateComponent(metadataReader)) {
                    ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
                    sbd.setResource(resource);
                    sbd.setSource(resource);
                    if (isCandidateComponent(sbd)) {
                        // 省略部分
                        candidates.add(sbd);
                    }
                    // 省略部分
                }
            }
        }
        // 省略部分
        return candidates;
    }

        根据当前的basePackage,调用PathMatchingResourcePatternResolver的getResources方法;

    private ResourcePatternResolver getResourcePatternResolver() {
        if (this.resourcePatternResolver == null) {
            this.resourcePatternResolver = new PathMatchingResourcePatternResolver();
        }
        return this.resourcePatternResolver;
    }
    public Resource[] getResources(String locationPattern) throws IOException {
        Assert.notNull(locationPattern, "Location pattern must not be null");
        if (locationPattern.startsWith(CLASSPATH_ALL_URL_PREFIX)) {
            // a class path resource (multiple resources for same name possible)
            if (getPathMatcher().isPattern(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()))) {
                // a class path resource pattern
                return findPathMatchingResources(locationPattern);
            }
            else {
                // all class path resources with the given name
                return findAllClassPathResources(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()));
            }
        }
        else {
            // Generally only look for a pattern after a prefix here,
            // and on Tomcat only after the "*/" separator for its "war:" protocol.
            int prefixEnd = (locationPattern.startsWith("war:") ? locationPattern.indexOf("*/") + 1 :
                    locationPattern.indexOf(':') + 1);
            if (getPathMatcher().isPattern(locationPattern.substring(prefixEnd))) {
                // a file pattern
                return findPathMatchingResources(locationPattern);
            }
            else {
                // a single resource with the given name
                return new Resource[] {getResourceLoader().getResource(locationPattern)};
            }
        }
    }

        我们基本都会调用findPathMatchingResources方法,所以基于此分析:

    protected Resource[] findPathMatchingResources(String locationPattern) throws IOException {
        String rootDirPath = determineRootDir(locationPattern);
        String subPattern = locationPattern.substring(rootDirPath.length());
        Resource[] rootDirResources = getResources(rootDirPath);
        Set<Resource> result = new LinkedHashSet<>(16);
        for (Resource rootDirResource : rootDirResources) {
            rootDirResource = resolveRootDirResource(rootDirResource);
            URL rootDirUrl = rootDirResource.getURL();
            if (equinoxResolveMethod != null && rootDirUrl.getProtocol().startsWith("bundle")) {
                URL resolvedUrl = (URL) ReflectionUtils.invokeMethod(equinoxResolveMethod, null, rootDirUrl);
                if (resolvedUrl != null) {
                    rootDirUrl = resolvedUrl;
                }
                rootDirResource = new UrlResource(rootDirUrl);
            }
            if (rootDirUrl.getProtocol().startsWith(ResourceUtils.URL_PROTOCOL_VFS)) {
                result.addAll(VfsResourceMatchingDelegate.findMatchingResources(rootDirUrl, subPattern, getPathMatcher()));
            }
            else if (ResourceUtils.isJarURL(rootDirUrl) || isJarResource(rootDirResource)) {
                result.addAll(doFindPathMatchingJarResources(rootDirResource, rootDirUrl, subPattern));
            }
            else {
                result.addAll(doFindPathMatchingFileResources(rootDirResource, subPattern));
            }
        }
        if (logger.isTraceEnabled()) {
            logger.trace("Resolved location pattern [" + locationPattern + "] to resources " + result);
        }
        return result.toArray(new Resource[0]);
    }

        基本都是调用doFindPathMatchingFileResources方法,进行分析:

    2、doFindPathMatchingFileResources

    protected Set<Resource> doFindPathMatchingFileResources(Resource rootDirResource, String subPattern)
            throws IOException {
        File rootDir = rootDirResource.getFile().getAbsoluteFile();
        // 省略
        return doFindMatchingFileSystemResources(rootDir, subPattern);
    }

        或者根路径(File):

    protected Set<Resource> doFindMatchingFileSystemResources(File rootDir, String subPattern) throws IOException {
        if (logger.isTraceEnabled()) {
            logger.trace("Looking for matching resources in directory tree [" + rootDir.getPath() + "]");
        }
        Set<File> matchingFiles = retrieveMatchingFiles(rootDir, subPattern);
        Set<Resource> result = new LinkedHashSet<>(matchingFiles.size());
        for (File file : matchingFiles) {
            result.add(new FileSystemResource(file));
        }
        return result;
    }

        将根路径下的所以class文件加载为Spring的FileSystemResource,一个class文件就是一个Resource。

    3、isCandidateComponent(验证是否为Component)和初始为ScannedGenericBeanDefinition

    MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);
    if (isCandidateComponent(metadataReader)) {
        ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
        sbd.setResource(resource);
        sbd.setSource(resource);
        if (isCandidateComponent(sbd)) {
            candidates.add(sbd);
        }
    }

    再最外层根据isCandidateComponent方法,根据@ConponentScan定义的includeFilters和excludeFilters进行过滤,得到想要的BeanDefinition,初始化为ScannedGenericBeanDefinition类型。

     

    2)、解析Lazy、Primary、DependsOn、Role、Description等属性,设置到BeanDefinition中

    if (candidate instanceof AnnotatedBeanDefinition) {
        AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
    }
    public static void processCommonDefinitionAnnotations(AnnotatedBeanDefinition abd) {
        processCommonDefinitionAnnotations(abd, abd.getMetadata());
    }
    
    static void processCommonDefinitionAnnotations(AnnotatedBeanDefinition abd, AnnotatedTypeMetadata metadata) {
        AnnotationAttributes lazy = attributesFor(metadata, Lazy.class);
        if (lazy != null) {
            abd.setLazyInit(lazy.getBoolean("value"));
        }
        else if (abd.getMetadata() != metadata) {
            lazy = attributesFor(abd.getMetadata(), Lazy.class);
            if (lazy != null) {
                abd.setLazyInit(lazy.getBoolean("value"));
            }
        }
    
        if (metadata.isAnnotated(Primary.class.getName())) {
            abd.setPrimary(true);
        }
        AnnotationAttributes dependsOn = attributesFor(metadata, DependsOn.class);
        if (dependsOn != null) {
            abd.setDependsOn(dependsOn.getStringArray("value"));
        }
    
        AnnotationAttributes role = attributesFor(metadata, Role.class);
        if (role != null) {
            abd.setRole(role.getNumber("value").intValue());
        }
        AnnotationAttributes description = attributesFor(metadata, Description.class);
        if (description != null) {
            abd.setDescription(description.getString("value"));
        }
    }

        根据注解属性,设置到BeanDefinition中,比较简单,主要是在getBean时使用。

    3)、验证BeanDefinition是否已经注册过

    protected boolean checkCandidate(String beanName, BeanDefinition beanDefinition) throws IllegalStateException {
        if (!this.registry.containsBeanDefinition(beanName)) {
            return true;
        }
        BeanDefinition existingDef = this.registry.getBeanDefinition(beanName);
        BeanDefinition originatingDef = existingDef.getOriginatingBeanDefinition();
        if (originatingDef != null) {
            existingDef = originatingDef;
        }
        if (isCompatible(beanDefinition, existingDef)) {
            return false;
        }
        throw new ConflictingBeanDefinitionException("省略");
    }

        先从判断是否已经注册过,调用containsBeanDefinition方法判断,详细见SpringIoc源码(二)- BeanFactory(一)- 结构梳理(DefaultListableBeanFactory);

    4)、注册BeanDefinition

    protected void registerBeanDefinition(BeanDefinitionHolder definitionHolder, 
        BeanDefinitionRegistry registry) {
    
        BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, registry);
    }
    public static void registerBeanDefinition(
            BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
            throws BeanDefinitionStoreException {
    
        // Register bean definition under primary name.
        String beanName = definitionHolder.getBeanName();
        registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());
    
        // Register aliases for bean name, if any.
        String[] aliases = definitionHolder.getAliases();
        if (aliases != null) {
            for (String alias : aliases) {
                registry.registerAlias(beanName, alias);
            }
        }
    }

        比较清楚,之前也分析过。先将其注册到DefaultListableBeanFactorybeanDefinitionMap中,再查看是否有别名,轮训注册到的DefaultListableBeanFactory父类SimpleAliasRegistryaliasMap中。

     

    三、轮训、递归解析其他注入类型

    for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
        BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
        if (bdCand == null) {
            bdCand = holder.getBeanDefinition();
        }
        if (ConfigurationClassUtils.checkConfigurationClassCandidate(
            bdCand, this.metadataReaderFactory)) {
            parse(bdCand.getBeanClassName(), holder.getBeanName());
        }
    }

        在这之前已经将所有路径下的class文件解析为BeanDefinition并且注册到BeanFactory中;那么当前BeanDefinition可能还有各种情况,比如被ComponentSan扫描到的@Component上,有@Import、@ImportResource等情况,则需要使用ConfigurationClassUtils.checkConfigurationClassCandidate方法,检查是否还需要再进行解析(之前分析过该方法)。如果需要进行解析,再递归到ConfigurationClassParser的parse方法,详细参见:SpringIoc源码(十)- ApplicationContext(六)- refresh(ConfigurationClassPostProcessor上)

     

    四、总结

    1、先获取启动类型或者当前类上的@ComponentScans或ComponentScan注解信息,并将注解属性设置到扫描器(ClassPathBeanDefinitionScanner)中,进行解析。

    2、先根据注解的basePackages属性列表,轮训解析为Spring Resource数组(路径下的每个Class文件为一个Resource)。再轮训Resource数组,初始化为ScannedGenericBeanDefinition类型。

    3、解析Lazy、Primary、DependsOn、Role、Description等属性,设置到BeanDefinition中。

    4、检查是否添加到容器中,否则registerBeanDefinition(注册BeanDefinition)。

    5、对当前Bean再递归调用ConfigurationClassParser#parse方法,彻底解析各种情况类型(比如Component上有@Import等情况)。

    只等refresh的最后finishBeanFactoryInitialization时,将非懒加载的单利Bean全部getBean。

     

    展开全文
  • @ComponentScan注解原理

    2020-05-31 20:18:30
    @ComponentScan注解原理ComponentScan源码解析理解@Component1. 利用`@Repository`自定义一个注解2. 写一个测试类`NameRepository`,加上该注解3. 测试`NameRepository`类是否能被Spring容器加载探索源码1. 先看`@...

    ComponentScan源码解析

    下文源码分析版本Spring 5.2.5 release

    理解@Component

    @Component作为一种由Spring容器托管的通用组件,任何被@Component标注的组件均为组件扫描的对象。
    类似的组件比如@Repository@Service或者使用@Component注解作为自定义注释。

    //@see org.springframework.context.annotation.ClassPathBeanDefinitionScanner
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Indexed
    public @interface Component {
    
    	/**
    	 * The value may indicate a suggestion for a logical component name,
    	 * to be turned into a Spring bean in case of an autodetected component.
    	 * @return the suggested component name, if any (or empty String otherwise)
    	 */
    	String value() default "";
    
    }
    

    1. 利用@Repository自定义一个注解

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    //@Component
    @Repository
    public @interface StringRepository {
    
        String value() default "";
    }
    

    2. 写一个测试类NameRepository,加上该注解

    @StringRepository("chineseNameRepository")
    public class NameRepository {
    
        public List<String> findAll() {
            return Arrays.asList("张三", "李四", "王二麻子");
        }
    }
    

    3. 测试NameRepository类是否能被Spring容器加载

    创建一个测试类ComponentBootStrap,代码如下

    @Configuration
    @ComponentScan
    public class ComponentBootStrap {
        public static void main(String[] args) {
            AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ComponentBootStrap.class);
    //        context.register(ComponentBootStrap.class);
    //        context.refresh();
            NameRepository nameRepository = context.getBean(NameRepository.class);
            System.out.println("nameRepository.findAll() = " + nameRepository.findAll());
        }
    }
    

    小提示:类NameRepository与引导类ComponentBootStrap需放到一个包下,才能被@ComponentScan扫描加载。
    输出结果为:nameRepository.findAll() = [张三, 李四, 王二麻子],这说明了自定义注解类@StringRepository也有类似于@Component的功能。
    结论:只要你注解中包含了@Component,就能被Spring容器托管。因为@Component是元注解,也可以联合元注解去创造组合注解,比如@RestController就是由@Controller@ResponseBody组成的。
    官网beans-meta-annotations

    上面例子来源于小马哥的《Spring Boot编程思想》,NameRepository注解不是继承@ComponentScan,但是效果却是继承,借用小马哥对这种模式注解的定义为@ComponentScan的派生性。那下文就探索一下@ComponentScan派生性的原理。

    探索源码

    只要注解里面含有@Component,就会被Spring扫描,并注册。为什么会这么呢?是不是跟扫描方式有关系呢?
    Spring扫描bean的方式有两种,一种是自定义标签<context:component-scan base-package=“com.xxx”/>这里base-package值填写NameRepository的包名,一种是注解@ComponentScan,如果不填写value值,就默认扫描注解类的包下所有类。

    第一种自定义标签的方式这里暂时不讨论,以后会专门写一篇来解析。
    我们这里重点讲解注解@ComponentScan方式。

    1. 先看@ComponentScan源码

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE)
    @Documented
    @Repeatable(ComponentScans.class)
    public @interface ComponentScan {
    
    	@AliasFor("basePackages")
    	String[] value() default {};
    
    	@AliasFor("value")
    	String[] basePackages() default {};
    
    	Class<?>[] basePackageClasses() default {};
    
    	Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;
    
    	Class<? extends ScopeMetadataResolver> scopeResolver() default AnnotationScopeMetadataResolver.class;
    
    	ScopedProxyMode scopedProxy() default ScopedProxyMode.DEFAULT;
    
    	String resourcePattern() default ClassPathScanningCandidateComponentProvider.DEFAULT_RESOURCE_PATTERN;
    
        // 这里默认为true,说明使用默认的过滤器,下文会详细说这里
    	boolean useDefaultFilters() default true;
    
    	Filter[] includeFilters() default {};
    
    	Filter[] excludeFilters() default {};
    
    	boolean lazyInit() default false;
    
    	@Retention(RetentionPolicy.RUNTIME)
    	@Target({})
    	@interface Filter {
    
    		FilterType type() default FilterType.ANNOTATION;
    
    		@AliasFor("classes")
    		Class<?>[] value() default {};
    
    		@AliasFor("value")
    		Class<?>[] classes() default {};
    
    		String[] pattern() default {};
    
    	}
    }
    

    该注解里面有很多属性,我这里就不介绍,有兴趣的自行查阅资料。里面有个注解@AliasFor("value")经常出现,要是有读者有兴趣,我下次就单独写篇文章来介绍这个。说偏题了,来继续探索,Spring是在哪里进行扫描的呢?看到这里很多人都会比较困惑,我的方法一般是先利用idea的find usages,快捷键也就是CTRL+G,来搜索那个地方调用了该注解,如下


    然后在usage in .class里面去寻找有关系的,这里,我们在org.springframework.context.annotation.ConfigurationClassParser#doProcessConfigurationClass该方法里面找到了解析@ComponentScan的地方。但是猛的一看这个类,好家伙,里面不仅仅只解析我们谈论的@ComponentScan,还解析@Import,@Bean,@PropertySources,@ComponentScans,@ImportResource等,下次将一次性来讲解这个类。

    来就重点来看核心代码ConfigurationClassParser#doProcessConfigurationClass

    protected final SourceClass doProcessConfigurationClass(
    			ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter){
    			···
    		// Process any @ComponentScan annotations
    		// 获取ComponentScans,ComponentScan注解里面所有的属性
    		Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
    				sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
    		if (!componentScans.isEmpty() &&
    				!this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
    			for (AnnotationAttributes componentScan : componentScans) {
    				// The config class is annotated with @ComponentScan -> perform the scan immediately
    				// 核心代码parse:下面重点解析
    				Set<BeanDefinitionHolder> scannedBeanDefinitions =
    						this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
    				// Check the set of scanned definitions for any further config classes and parse recursively if needed
    				for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
    					BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
    					if (bdCand == null) {
    						bdCand = holder.getBeanDefinition();
    					}
    					if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
    						parse(bdCand.getBeanClassName(), holder.getBeanName());
    					}
    				}
    			}
    		}
    		···
    

    ComponentScanAnnotationParser#parse(AnnotationAttributes,String)

    	public Set<BeanDefinitionHolder> parse(AnnotationAttributes componentScan, final String declaringClass) {
    	    // 这里new一个scanner对象,因为@ComponentScan注解里面的useDefaultFilters默认为true,所以这里传入的值为true
    	    // 也就是在构造器中使用了默认的过滤器,下文会介绍
    		ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(this.registry,
    				componentScan.getBoolean("useDefaultFilters"), this.environment, this.resourceLoader);
    
    		Class<? extends BeanNameGenerator> generatorClass = componentScan.getClass("nameGenerator");
    		boolean useInheritedGenerator = (BeanNameGenerator.class == generatorClass);
    		scanner.setBeanNameGenerator(useInheritedGenerator ? this.beanNameGenerator :
    				BeanUtils.instantiateClass(generatorClass));
    
    		ScopedProxyMode scopedProxyMode = componentScan.getEnum("scopedProxy");
    		if (scopedProxyMode != ScopedProxyMode.DEFAULT) {
    			scanner.setScopedProxyMode(scopedProxyMode);
    		}
    		else {
    			Class<? extends ScopeMetadataResolver> resolverClass = componentScan.getClass("scopeResolver");
    			scanner.setScopeMetadataResolver(BeanUtils.instantiateClass(resolverClass));
    		}
    
    		scanner.setResourcePattern(componentScan.getString("resourcePattern"));
    
    		for (AnnotationAttributes filter : componentScan.getAnnotationArray("includeFilters")) {
    			for (TypeFilter typeFilter : typeFiltersFor(filter)) {
    				scanner.addIncludeFilter(typeFilter);
    			}
    		}
    		for (AnnotationAttributes filter : componentScan.getAnnotationArray("excludeFilters")) {
    			for (TypeFilter typeFilter : typeFiltersFor(filter)) {
    				scanner.addExcludeFilter(typeFilter);
    			}
    		}
    
    		boolean lazyInit = componentScan.getBoolean("lazyInit");
    		if (lazyInit) {
    			scanner.getBeanDefinitionDefaults().setLazyInit(true);
    		}
    
    		Set<String> basePackages = new LinkedHashSet<>();
    		String[] basePackagesArray = componentScan.getStringArray("basePackages");
    		for (String pkg : basePackagesArray) {
    			String[] tokenized = StringUtils.tokenizeToStringArray(this.environment.resolvePlaceholders(pkg),
    					ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
    			Collections.addAll(basePackages, tokenized);
    		}
    		for (Class<?> clazz : componentScan.getClassArray("basePackageClasses")) {
    			basePackages.add(ClassUtils.getPackageName(clazz));
    		}
    		if (basePackages.isEmpty()) {
    			basePackages.add(ClassUtils.getPackageName(declaringClass));
    		}
    
    		scanner.addExcludeFilter(new AbstractTypeHierarchyTraversingFilter(false, false) {
    			@Override
    			protected boolean matchClassName(String className) {
    				return declaringClass.equals(className);
    			}
    		});
    		// 上面都是将注解里面的属性赋值给scanner,然后解析方法委托给ClassPathBeanDefinitionScanner#doScan
    		return scanner.doScan(StringUtils.toStringArray(basePackages));
    	}
    

    org.springframework.context.annotation.ClassPathBeanDefinitionScanner#doScan

    protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
    	Assert.notEmpty(basePackages, "At least one base package must be specified");
    	Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
    	// 这里的basePackages就是之前的@ScanComponent所在的包名
    	for (String basePackage : basePackages) {
    	    // 找到候选组件
    		Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
    		for (BeanDefinition candidate : candidates) {
    			ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
    			candidate.setScope(scopeMetadata.getScopeName());
    			String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
    			if (candidate instanceof AbstractBeanDefinition) {
    				postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
    			}
    			if (candidate instanceof AnnotatedBeanDefinition) {
    				AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
    			}
    			if (checkCandidate(beanName, candidate)) {
    				BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
    				definitionHolder =
    						AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
    				beanDefinitions.add(definitionHolder);
    				// 将候选组件注册为BeanDefinition
    				registerBeanDefinition(definitionHolder, this.registry);
    			}
    		}
    	}
    	return beanDefinitions;
    }
    
    

    扫描候选组件的类路径
    ClassPathScanningCandidateComponentProvider#findCandidateComponents(String)

    public Set<BeanDefinition> findCandidateComponents(String basePackage) {
    	if (this.componentsIndex != null && indexSupportsIncludeFilters()) {
    		return addCandidateComponentsFromIndex(this.componentsIndex, basePackage);
    	}
    	else {
    		return scanCandidateComponents(basePackage);
    	}
    }
    
    private Set<BeanDefinition> scanCandidateComponents(String basePackage) {
    	Set<BeanDefinition> candidates = new LinkedHashSet<>();
    	try {
    	    // String CLASSPATH_ALL_URL_PREFIX = "classpath*:";
    	    // 假如这里的basePackage是com.example.learnspring,resolveBasePackage(basePackage)就把包名变成com/example/learnspring
    	    // resolveBasePackage(basePackage)的目的是将包名里面的"."换成"/",
    		String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
    				resolveBasePackage(basePackage) + '/' + "**/*.class";
    		// 根据传入的路径解析成Resource
    		Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);
    		···省去日志
    			if (resource.isReadable()) {
    				try {
    				    // 产生一个访问元数据的门面类MetadataReader
    					MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);
    					// 判断是否为候选组件
    					if (isCandidateComponent(metadataReader)) {
    						ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
    						sbd.setResource(resource);
    						sbd.setSource(resource);
    						if (isCandidateComponent(sbd)) {
    							candidates.add(sbd);
    						}
    						else {
    							if (debugEnabled) {
    								logger.debug("Ignored because not a concrete top-level class: " + resource);
    							}
    						}
    					}
    											}
    				}
    				catch (Throwable ex) {
    					···
    				}
    	return candidates;
    }
    

    这里先介绍一下MetadataReader,这是一个通过ASM访问类元数据的门面类,在这里的实现类是SimpleMetadataReader,元数据就是SimpleAnnotationMetadataReadingVisitor通过ASM产生的。

    final class SimpleMetadataReader implements MetadataReader {
    
    	private static final int PARSING_OPTIONS = ClassReader.SKIP_DEBUG
    			| ClassReader.SKIP_CODE | ClassReader.SKIP_FRAMES;
    
    	private final Resource resource;
    
        // 数据元信息,包括className,superClassName等等,
        // 如果还是不清楚,看下面类StringRepository数据元信息图
    	private final AnnotationMetadata annotationMetadata;
    
    	SimpleMetadataReader(Resource resource, @Nullable ClassLoader classLoader) throws IOException {
    	    // 通过SimpleAnnotationMetadataReadingVisitor来创建AnnotationMetadata,原理是ASM,对这里有兴趣的同学可以在这里打个断点,尽情调试
    		SimpleAnnotationMetadataReadingVisitor visitor = new SimpleAnnotationMetadataReadingVisitor(classLoader);
    		getClassReader(resource).accept(visitor, PARSING_OPTIONS);
    		this.resource = resource;
    		this.annotationMetadata = visitor.getMetadata();
    	}
    

    数据元信息
    再来判断是否是候选组件

    protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {
    	for (TypeFilter tf : this.excludeFilters) {
    		if (tf.match(metadataReader, getMetadataReaderFactory())) {
    			return false;
    		}
    	}
    	for (TypeFilter tf : this.includeFilters) {
    		if (tf.match(metadataReader, getMetadataReaderFactory())) {
    			return isConditionMatch(metadataReader);
    		}
    	}
    	return false;
    }
    

    我们的类StringRepository之前已经成功被Spring加载了,所有在这里肯定是返回true的,因此在这里打个断点进行调试。
    excludeFilters: 排除含有注解的元信息
    includeFilters:匹配含有注解的元信息
    这里发现includeFilters里面包含了两个AnnotationTypeFilter,分别包含ComponentManagedBean,这是因为在registerDefaultFilters方法里面注册了默认的过滤器。

    protected void registerDefaultFilters() {
    	this.includeFilters.add(new AnnotationTypeFilter(Component.class));
    	ClassLoader cl = ClassPathScanningCandidateComponentProvider.class.getClassLoader();
    	try {
    		this.includeFilters.add(new AnnotationTypeFilter(
    				((Class<? extends Annotation>) ClassUtils.forName("javax.annotation.ManagedBean", cl)), false));
    		logger.trace("JSR-250 'javax.annotation.ManagedBean' found and supported for component scanning");
    	}
    	catch (ClassNotFoundException ex) {
    		// JSR-250 1.1 API (as included in Java EE 6) not available - simply skip.
    	}
    	try {
    		this.includeFilters.add(new AnnotationTypeFilter(
    				((Class<? extends Annotation>) ClassUtils.forName("javax.inject.Named", cl)), false));
    		logger.trace("JSR-330 'javax.inject.Named' annotation found and supported for component scanning");
    	}
    	catch (ClassNotFoundException ex) {
    		// JSR-330 API not available - simply skip.
    	}
    }
    

    搜索一下哪里调用了registerDefaultFilters,发现ClassPathBeanDefinitionScanner#ClassPathBeanDefinitionScanner()

    	public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry, boolean useDefaultFilters,
    			Environment environment, @Nullable ResourceLoader resourceLoader) {
    
    		Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
    		this.registry = registry;
    
    		if (useDefaultFilters) {
    		    // 设置默认的过滤器
    			registerDefaultFilters();
    		}
    		setEnvironment(environment);
    		setResourceLoader(resourceLoader);
    	}
    

    最后发现在ClassPathBeanDefinitionScanner构造器中设置了默认的过滤器,也就是在ComponentScanAnnotationParser#parse方法的第一行,创建ClassPathBeanDefinitionScanner对象,与上文形成呼应。

    public Set<BeanDefinitionHolder> parse(AnnotationAttributes componentScan, final String declaringClass) {
    		ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(this.registry,
    				componentScan.getBoolean("useDefaultFilters"), this.environment, this.resourceLoader);
    

    绕来绕去,你可能有些晕了,让我们在整理一下,刚才介绍到判断是候选组件,也就是下面的match()方法返回true。从方法名猜测只要类中包含注解@Component或者@ManageBean就会匹配成功。下面继续深入源码分析。

    	protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {
    		for (TypeFilter tf : this.excludeFilters) {
    			if (tf.match(metadataReader, getMetadataReaderFactory())) {
    				return false;
    			}
    		}
    		for (TypeFilter tf : this.includeFilters) {
    		    // 核心判断逻辑
    			if (tf.match(metadataReader, getMetadataReaderFactory())) {
    			    // 判断元信息是否包含@Conditional,跟条件相关的,这里省略
    				return isConditionMatch(metadataReader);
    			}
    		}
    		return false;
    	}
    

    AbstractTypeHierarchyTraversingFilter#match(MetadataReader,MetadataReaderFactory)

    	public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory)
    			throws IOException {
            // 主要讲解这个方法,下面的逻辑有兴趣的同学可以自己研究
    		if (matchSelf(metadataReader)) {
    			return true;
    		}
            ······
    		return false;
    	}
    
    	protected boolean matchSelf(MetadataReader metadataReader) {
    	    // 获取元信息,这里是自定义注解@StringRepository信息
    		AnnotationMetadata metadata = metadataReader.getAnnotationMetadata();
    		// 下面的this.annotationType.getName()是传入的org.springframework.stereotype.Component
    		return metadata.hasAnnotation(this.annotationType.getName()) ||
    				(this.considerMetaAnnotations && metadata.hasMetaAnnotation(this.annotationType.getName()));
    	}
    

    AnnotationMetadata#hasMetaAnnotation

    // 判断底层注解是否包含metaAnnotationName
    default boolean hasMetaAnnotation(String metaAnnotationName) {
    	return getAnnotations().get(metaAnnotationName,
    			MergedAnnotation::isMetaPresent).isPresent();
    }
    

    到了这里, 就已经很清晰了,只有两个方法,一个是metadata.hasAnnotation(this.annotationType.getName()),这是判断类上是否含有给定的注解,
    还有个方法就是 metadata.hasMetaAnnotation(this.annotationType.getName()),这是判断底层class是否含有给定的注解,而@StringRepository的底层确实包含了@Component,所以这里方法返回true。

    如果有地方有疑惑或者写的有不好,可以评论或者通过邮箱联系我creazycoder@sina.com

    展开全文
  • 主要介绍了springboot @ComponentScan注解原理解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
  • @ComponentScan就是根据配置的扫描路径,把符合扫描规则的类装配到spring容器中。如:https://blog.51cto.com/1754966750/1947226 注意:详细了解注解的原理看源码。 (1)、backPackages和value用于指定扫描包的...

    @ComponentScan就是根据配置的扫描路径,把符合扫描规则的类装配到spring容器中。如:https://blog.51cto.com/1754966750/1947226

    注意:详细了解注解的原理看源码。

    (1)、backPackages和value用于指定扫描包的路径。

    (2)、basePackages:用于指定某个类的包的路径进行扫描。

    (3)、nameGenerator:bean的名称生成器。

    (4)、useDefaulterFilters:是否开启对@Componest,@Repository,@Service,@Controller这些类的检测,这里需要注意的是@Repository注解的含义,并且很多的时候都不会用到?因为这四种注解没有什么本质的区别,作用都是声明作用,取不同的名字只是为了区分不同的功能,

     为什么我们不用@Respository来注解接口,但是照样可以注入到这个接口的实现类,可以利用@AutoWried.

    (5)、includeFilters: 包含的过滤条件

           FilterType.ANNOTATION:按照注解过滤

           FilterType.ASSIGNABLE_TYPE:按照给定的类型

           FilterType.ASPECTJ:使用ASPECTJ表达式

           FilterType.REGEX:正则

           FilterType.CUSTOM:自定义规则

           excludeFilters: 排除的过滤条件,用法和includeFilters一样

    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.TYPE})
    @Documented
    @Repeatable(ComponentScans.class)
    public @interface ComponentScan {
        @AliasFor("basePackages")
        String[] value() default {};
     
        @AliasFor("value")
        String[] basePackages() default {};
     
        Class<?>[] basePackageClasses() default {};
     
        Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;
     
        Class<? extends ScopeMetadataResolver> scopeResolver() default AnnotationScopeMetadataResolver.class;
     
        ScopedProxyMode scopedProxy() default ScopedProxyMode.DEFAULT;
     
        String resourcePattern() default "**/*.class";
        boolean useDefaultFilters() default true;
        ComponentScan.Filter[] includeFilters() default {};
        ComponentScan.Filter[] excludeFilters() default {};
        boolean lazyInit() default false;
        @Retention(RetentionPolicy.RUNTIME)
        @Target({})
        public @interface Filter {
            FilterType type() default FilterType.ANNOTATION;
            @AliasFor("classes")
            Class<?>[] value() default {};
            @AliasFor("value")
            Class<?>[] classes() default {};
            String[] pattern() default {};
        }
    }

    转载于:https://my.oschina.net/u/4034553/blog/3040836

    展开全文
  • 概述2.@ComponentScan3.源码分析 1.概述 Spring是如何通过注解的形式将Bean注入到Spring容器当中的呢? 答案就在@ComponentScan注解上,该注解告诉Spring要去哪里去寻找Bean。 通过这篇博客将学习到如何正确使用@...

    1.概述

    Spring是如何通过注解的形式将Bean注入到Spring容器当中的呢?

    答案就在@ComponentScan注解上,该注解告诉Spring要去哪里去寻找Bean。

    通过这篇博客将学习到如何正确使用@ComponentScan,并分析其底层是如何实现。

    2.@ComponentScan

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE)
    @Documented
    @Repeatable(ComponentScans.class)//可重复注解
    public @interface ComponentScan {
    
       @AliasFor("basePackages")
       String[] value() default {};//基础包名,等同于basePackages
    
       @AliasFor("value")
       String[] basePackages() default {};//基础包名,value
    
       Class<?>[] basePackageClasses() default {};//扫描的类,会扫描该类所在包及其子包的组件。
    
       Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;//注册为BeanName生成策略 默认BeanNameGenerator,用于给扫描到的Bean生成BeanName
    
       Class<? extends ScopeMetadataResolver> scopeResolver() default AnnotationScopeMetadataResolver.class;//用于解析bean的scope的属性的解析器,默认是AnnotationScopeMetadataResolver
    
       ScopedProxyMode scopedProxy() default ScopedProxyMode.DEFAULT;//scoped-proxy 用来配置代理方式 // no(默认值):如果有接口就使用JDK代理,如果没有接口就使用CGLib代理 interfaces: 接口代理(JDK代理) targetClass:类代理(CGLib代理)
    
       String resourcePattern() default ClassPathScanningCandidateComponentProvider.DEFAULT_RESOURCE_PATTERN;//配置要扫描的资源的正则表达式的,默认是"**/*.class",即配置类包下的所有class文件。
      
       boolean useDefaultFilters() default true;//useDefaultFilters默认是true,扫描带有@Component ro @Repository ro @Service ro @Controller 的组件
    
       Filter[] includeFilters() default {};//包含过滤器
    
       Filter[] excludeFilters() default {};//排除过滤器
    
       boolean lazyInit() default false;//是否是懒加载
    
       @Retention(RetentionPolicy.RUNTIME)
       @Target({})
       @interface Filter {//过滤器注解
    
          FilterType type() default FilterType.ANNOTATION;//过滤判断类型
    
          @AliasFor("classes")
          Class<?>[] value() default {};//要过滤的类,等同于classes
    
          @AliasFor("value")
          Class<?>[] classes() default {};//要过滤的类,等同于value
    
          String[] pattern() default {};// 正则化匹配过滤
    
       }
    
    }
    

    该注解是一个@Repeatable注解,表明是可以重复使用的,例如
    在这里插入图片描述
    或者使用@ComponentScans注解,该注解仅有一个属性就是@ComponentScan注解的数组,用法如下:在这里插入图片描述
    然而当我debug调试的时候,竟然发现上面这样的做法是无法扫描指定包下的类的。

    这是为什么呢?

    Spring启动的时候,会执行ConfigurationClassPostProcessor的postProcessBeanDefinitionRegistry方法,其中就会遍历已有的BeanDefinitionNames,此时appConfig就是其中之一,会判断它是否是一个配置类
    在这里插入图片描述
    如果是配置类的话,则会加入到configCandidates集合当中。

    判断的方法是ConfigurationClassUtils.checkConfigurationClassCandidate
    在这里插入图片描述
    此时没有加@Configuration注解,所以config为null,那么就会判断是否是一个半配置类。

    调用isConfigurationCandidate方法

    半配置类的标准是是否有这4个注解中的一种
    在这里插入图片描述
    当使用2个及以上的@ComponentScan注解的时候,或者使用@ComponentScans注解的时候,此时的metadata如下:
    在这里插入图片描述
    注解的类型不是这4个其中一个。

    最后就是判断是否有注解@Bean的注解方法。很显然此时的AppConfig并没有,所以返回false。说明它不是一个配置类。

    那么configCandidates则为空,直接返回,不会进行之后的扫描逻辑了。
    在这里插入图片描述
    所以当使用2个及以上的@ComponentScan注解,或者使用@ComponentScans的使用需要再加上@Configuration注解表明他是一个配置类,这样才会进行下面的扫描逻辑。

    其中的属性使用都比较简单,相对复杂一点的是includeFilters和excludeFilters,下面举例说明一下。

    包结构如下所示:
    在这里插入图片描述
    其中A,B,C,D类的代码如下:

    @Controller
    public class A {
    }
    @Component
    public class B {
    }
    @Service
    public class C {
    }
    @Repository
    public class D {
    }
    

    启动类如下:

    @ComponentScan(basePackages = "hdu.gongsenlin6")
    public class AppConfig {
       public static void main(String[] args) throws IOException {
          AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
          System.in.read();
       }
    

    使用默认的过滤器,扫描hdu.gongsenlin6包下的类,此时容器中内容如下所示:
    在这里插入图片描述
    4个都被扫描到了,将启动类的代码更改一下,使用excludeFilters和includeFilters。

    注意此时useDefaultFilters设置为false,不使用它默认的。
    在这里插入图片描述
    excludeFilters排除拥有注解@Service的类,includeFilters包含拥有注解@Repository或者注解@Controller的类。此时容器的内容如下:
    在这里插入图片描述
    只有a和d被注入到Spring容器当中了。

    这两个过滤器的使用还是比较简单的,一是指定过滤的类型FilterType,然后根据类型来指定过滤的条件value。

    FilterType过滤类型有如下几种:
    在这里插入图片描述
    最后一种自定义过滤器,要实现TypeFilter接口,编写match方法。
    在这里插入图片描述

    3.源码分析

    这一小节将分析源码,探究根据@ComponentScan进行扫描的底层是如何实现的。

    在上一节中,ConfigurationClassPostProcessor中的processConfigBeanDefinitions方法,第一步是找到配置类,也就是找到了AppConfig这个类。

    然后会构造一个配置类解析器ConfigurationClassParser,来对AppConfig进行解析。
    在这里插入图片描述
    下面重点来看看parse方法是如何解析AppConfig。
    在这里插入图片描述
    遍历配置类候选集合,此时只有AppConfig。

    拿到bean的定义,判断该bean属于AnnotatedBeanDefinition。

    所以会进入第一个if的parse方法。
    在这里插入图片描述
    根据注解元数据和beanName封装成ConfigurationClass,调用processConfigurationClass
    在这里插入图片描述
    第一个if,判断是否跳过的条件,也就是condition相关的信息,@Conditional注解,在之后的博客再做分析。

    此时明显是是没有加条件的,所以这里不会跳过。之后判断缓存中是否已有当前准备解析的配置类,显然也是没有的,只有在处理了之后,才会放入该缓存当中。

    调用asSourceClass,递归地处理配置类及其超类层次结构,得到类元信息,也就是这个类的相关信息都在sourceClass当中,sourceClass如下:
    在这里插入图片描述
    之后调用doProcessConfigurationClass方法,do开头的方法往往都是真正做大事的方法。

    该方法很长,大致意思就是根据不同的注解的类型,来调用不同的处理逻辑。

    本文关注点放在@ComponentScan注解上,我们来分析红色框框的部分。
    在这里插入图片描述
    调用AnnotationConfigUtils的静态方法attributesForRepeatable,获取@ComponentScan注解的属性。

    得到的结果如下:
    在这里插入图片描述
    之后就是一个for循环,遍历componentScans,此时仅有一个componentScan,使用componentScanParser解析器来解析componentScan这个对象。

    调用componentScanParser的parse方法。
    在这里插入图片描述
    虽然很长,但是逻辑还是比较简单的,初始化ClassPathBeanDefinitionScanner扫描器

    根据componentScan的属性,设置扫描器的属性。

    最后调用扫描器的doScan方法执行真正的扫描工作。
    在这里插入图片描述
    遍历扫描包,调用findCandidateComponents方法根据基础包路径来找到候选的Bean。

    之后就是遍历扫描到的候选Bean,给他们设置作用域,生成BeanName等一系列的操作。然后检查BeanName是否冲突,添加到beanDefinitions集合当中,调用registerBeanDefinition注册Bean,将Bean的定义注册到Spring容器当中。

    完成扫描,方法返回到上面红色框框的部分。
    在这里插入图片描述
    遍历扫描到的所有Bean,判断是否是配置类,如果是的话,那么就会递归的调用parse去解析它。完结撒花~

    展开全文
  • Spring组件注册注解之@ComponentScan,@ComponentScans

    万次阅读 多人点赞 2019-01-05 21:35:26
    目录 1. 说明 ...5. @ComponentScan原理分析 6. @ComponentScans 1. 说明 与ComponentScan注解相对应的XML配置就是&lt;context:component-scan/&gt;, 根据指定的配置自动扫描pac...
  • @ComponentScan的作用 @Component注解及其衍生注解@RestController、@Controller @Service和@Repository都是组件注册注解。@ComponentScan注解主要是从约定的扫描路径中,识别标注了组件注册注解的类,并且把这些类...
  • 2. @ComponentScan注解作用 二、实例分析 1. excludeFilters=Filter[ ] 2. includeFilters=filter[ ] 3. FilterType.CUSTOM 三、源码追踪 案例已上传GitHub,欢迎star:...
  • @ComponentScan

    千次阅读 2018-06-29 18:00:11
    作用 ... 要创建Bean的class文件集合形成扫描路径,传入@ComponentScan,然后由ComponentScanAnnotationParser或者ClassPathBeanDefinitionScanner对扫描路径下的class文件扫描解析,如下图所示: ...
  • @SpringBootApplication @...@ComponentScan(basePackages = { "cn..." }) @MapperScan("cn...mapper") public class SpringApplication { public static void main(S...
  • <?xml version="1.0" encoding="UTF-8"?>
  • 一、核心注解 @SpringBootApplication:组合注解,包括以下注解。 @SpringBootConfiguration: 配置注解,底层其实也是@Configuration注解,只不过在SpringBoot工程中更推荐使用@...@ComponentScan: 扫描
  • Spring内扫描指定包下的类(@ComponentScan原理) 容器外获取Spring容器内的Bean实例 HttpServletRequest封装类,支持RequestBody的多次读取
  • 首先声明该系列不适合零基本的程序员浏览! 总体系列流程总体分析 Spring注解驱动开发: 容器: AnnotationConfigapplicationContext 组件添加 ...扩展原理: BeanFactoryPostProcesor Be...
  • 一 springboot启动原理及相关流程概览 二 springboot的启动类入口 三 单单是SpringBootApplication接口用到了这些注解  1)@Configuration注解  2)@ComponentScan注解 3)@EnableAutoConfiguration ...
  • springboot 原理

    2019-10-17 18:25:16
    spring boot 为什么加了@SpringbootApplication注解就是启动类? 因为@Springboot... 1.@ComponentScan 2.@SpringBootConfiguration 3.@EnableAutoConfiguration @ComponentScan @ComponentScan注解等同...
  • SpringBoot——自动装配原理(干货)

    万次阅读 多人点赞 2020-10-31 16:26:39
    SpringBoot——自动装配原理(干货) 在编写SpringBoot项目时,@SpringBootApplication是最常见的注解了,我们可以看一下源代码: 这里面包含了@SpringBootConfiguration,@EnableAutoConfiguration,@...
  • Spring Boot - 自动配置实现原理

    千次阅读 多人点赞 2021-05-16 11:54:26
    文章目录Pre@SpringBootApplication 注解@ComponentScan 注解@SpringBootConfiguration 注解@EnableAutoConfiguration 注解@AutoConfigurationPackage@Import@Import(AutoConfigurationImportSelector.class) ...
  • SpringBoot自动装配原理

    千次阅读 2020-03-23 22:27:47
    SpringBoot自动装配原理 ... @SpringBootApplication1.1 @SpringBootConfiguration1.2 @ComponentScan1.3 @EnableAutoConfiguration1.3.1 @AutoConfigurationPackage1.3.2 AutoConfigurationImportSele...
  • 一、启动注解 @SpringBootApplication @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @SpringBootConfiguration ...@ComponentScan(excludeFilters={...
  • SpringBoot原理分析

    2019-12-13 18:21:02
    1 @SpringBootApplication启动原理 @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @SpringBootConfiguration @EnableAutoConfiguration @ComponentScan(excludeFilters = ...
  • 了解@SpringBootApplication4.1.1.@SpringBootConfiguration4.1.2.@EnableAutoConfiguration4.1.3.@ComponentScan4.2.默认配置原理4.2.1默认配置类4.2.2.默认配置属性4.3.总结 4.自动配置原理 使用SpringBoot之后,...
  • springboot运行原理

    2019-10-09 01:40:48
    一、@SpringBootApplication主要集合了@ComponentScan、@SpringBootConfiguration、@EnableAutoConfiguration 1、@ComponentScan:告诉Spring从哪里找到bean 2、@SpringBootConfiguration :Spring Boot的配置类...
  • Springboot-自动配置原理 @SpringBootApplication相当于是@SpringBootConfiguration+@EnableAutoConfiguration+@ComponentScan 1、@SpringBootConfiguration @Configuration //代表当前是一个配置类 2、@...
  • 用法与最佳实践 在@Configuration注解的配置类中添加,用于为该应用添加SpringMVC的功能,即添加之后可以在项目中,可以使用@RequestMapping,@Controller等注解来定义请求处理与...@ComponentScan(basePackageCla...
  • SpringBoot自动配置原理

    2021-01-29 15:57:19
    SpringBoot自动配置原理解析 要说自动配置的原理,首先...@ComponentScan(...) @SpringBootConfiguration里面主要是有@Configuration注解 @Configuration //作用是声明配置类 public @interface SpringBootConfigurati

空空如也

空空如也

1 2 3 4 5 ... 16
收藏数 305
精华内容 122
关键字:

componentscan原理