学习自定义标签的实现,可以知道<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); }
先记到着把。。