浅谈Spring的 context:component-scan

学习自定义标签的实现,可以知道<context:component-scan/>标签由继承NamespaceHandlerSupport的ContextNamespaceHandler注册ComponentScanBeanDefinitionParser后进行解析,然后通过ReaderEventListener进行注册。


ComponentScanBeanDefinitionParser的解析步骤如下:


1-3 获取节点中的base-package,并进行处理(拆分)成一个String数组;

4 构建一个扫描器,获取节点的中的配置进行增强(use-default-filters默认为true ,会添加一个component相关的filer)

5 真正的扫包工作,通过asm读取class文件,最后返回符合条件的BeanDefinition 进行注册

接着简单分析下第5步


doscan主要做了两件事 

1.扫包;

2.注册;

现在只分析下扫包的逻辑:


1 拿到包名时,就可以通过classpath拿到对应的文件路径(诸如:D:\workCode\xxx\target\xxx-1.0.0-SNAPSHOT\WEB-INF\classes\com\xxx\xxx\controller)

2 有了路径就可以拿到一个包含class文件的 resource list

3 遍历list 然后通过asm从class文件中拿到MetadataReader(这里面水有点深。。然而我只会划水。),分析拿到的MetadataReader是否符合需要的条件

4 将符合条件的metadataReader 组装成beanDefinition返回


isCandidateComponent 会拿metadataReader的attributeMap和filters进行匹配,如果使用了@controller @service 等等注解 .class 文件中的attribute属性中会包含component相关的信息(这只是一个分析,目前没找到如何将这个attribute可视化)

end...

===================这是条分割线=====================

项目中遇到个业务,业务实现大致如下

step.1 扫包取类(根据注解)

step.2 反射出接口方法相应参数

step.3 参数入库,结合后台配置 组成相应对象,缓存至redis

step.4 当接口被调用时,通过aop根据注解切入。读redis,进行校验。

问题在于,拿到需求时,没去考虑实际意义(反正不管怎么后台配置,接口代码都要做调整)。直到开发过程中堵在可能存在循环依赖的参数结构时,才发现问题。。

虽然过程很坎坷,结果很悲催,但是这个过程还是要记个笔记的。。

第一步 扫包取类,仿造spring的扫包实现。

    public void parser(String basePackage) {
        String[] basePackages = resolvePlaceHolders(basePackage);
        ClassPathScanner scanner = configureAnnotationScanner();
        Set<BeanDefinition> beanDefinitions = scanner.doScan(basePackages);
        registerComponents(beanDefinitions);
    }
    protected String[] resolvePlaceHolders(String basepackage) {
        return StringUtils.tokenizeToStringArray(basepackage,
                ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);

    }
    private ClassPathScanner configureAnnotationScanner() {
        List<TypeFilter> includeFilters = new ArrayList<>();
        TypeFilter typeFilter = new AnnotationTypeFilter(ApiScanner.class);
        includeFilters.add(typeFilter);
        return new ClassPathScanner(includeFilters, null);
    }

先建个扫包器 ClassPathScanner。

public class ClassPathScanner {

    private ClassPathScanningCandidateComponentProvider provider;

    public ClassPathScanner(List<TypeFilter> includeFilters, List<TypeFilter> excludeFilters) {
        ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(false);
        if(CollectionUtils.isNotEmpty(includeFilters)){
            for (TypeFilter typeFilter : includeFilters) {
                provider.addIncludeFilter(typeFilter);
            }
        }
        if(CollectionUtils.isNotEmpty(excludeFilters)){
            for (TypeFilter typeFilter : excludeFilters) {
                provider.addExcludeFilter(typeFilter);
            }
        }
        this.provider = provider;
    }

    public Set<BeanDefinition> doScan(String... basePackages) {
        Assert.notEmpty(basePackages, "At least one base package must be specified");
        Set<BeanDefinition> beanDefinitions = new LinkedHashSet<>();
        for (String basePackage : basePackages) {
            Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
            beanDefinitions.addAll(candidates);
        }
        return beanDefinitions;
    }

    private Set<BeanDefinition> findCandidateComponents(String basePackage) {
        return this.provider.findCandidateComponents(basePackage);
    }
}
扫包器内部直接使用spring提供的ClassPathScanningCandidateComponentProvider,向其中加入一些过滤器TypeFilter,这里用的是个注解TypeFilter,最后调用ClassPathScanningCandidateComponentProvider的findCandidateComponents()方法,返回一个BeanDefinition类型的Set集合,findCandidateComponents内部实现与asm有关(水太深,,)。BeanDefinition可以理解为spring的一个标准的类的定义模板。有这个模板,可以拿到类的className,然后通过反射,根据注解拿对应的method,再去拿parameters。

然后又遇到反射时如何检查包装类型的问题。

public boolean isWrapClass(Class clz) {
        try {
            return ((Class) clz.getField("TYPE").get(null)).isPrimitive();
        } catch (Exception e) {
            return false;
        }
    }

又遇到检查list这种。。

	if (Collection.class.isAssignableFrom(parameterClass)) {
		Type type = parameter.getParameterizedType();
		String typeName = type.getTypeName();
		String nameTmp = typeName.substring(typeName.indexOf("<") + 1, typeName.indexOf(">"));
		logger.info("list getGenericInterfaces type is {}", nameTmp);
	}

先记到着把。。




猜你喜欢

转载自blog.csdn.net/boneix/article/details/72819916
今日推荐