赞
踩
在上一篇文章我们分析了一下基于注解的IOC启动流程的第一种方式,根据指定的BeanClass启动,这篇文章我们分析另外一种方式,扫描一个包路径来启动。
我这里还是使用 AnnotationConfigApplicationContext 写一个简单的IOC案例
第一步:创建一个类
package cn.xx
//通过扫描方式注册Bean
@Component
public class OtherBean {
}
第二步:使用 AnnotationConfigApplicationContext 扫描一个包
public class MyBeanTest {
@Test
public void testMyBean(){
//通过容器工厂:AnnotationConfigApplicationContext 加载一个包中的Bean
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext("cn.xxx");
//通过容器工厂获取bean
OtherBean bean= applicationContext.getBean(OtherBean .class);
//打印bean
System.out.println(bean);
}
}
与之前不一样的是,这次的OtherBean这个类上面贴了 @Component
注解,对于AnnotationConfigApplicationContext
我们传入了该所在的包,我们希望它可以自动扫描到该注解所在的类,然后自动注册到Spring容器中。
我们直接定位到这个构造器中,
/** * Create a new AnnotationConfigApplicationContext, scanning for bean definitions * in the given packages and automatically refreshing the context. * @param basePackages the packages to check for annotated classes */ //创建一个新的 AnnotationConfigApplicationContext,扫描给定包中的 bean 定义并自动刷新上下文。 public AnnotationConfigApplicationContext(String... basePackages) { //创建 AnnotatedBeanDefinitionReader Bean注册器 和 ClassPathBeanDefinitionScanner Bean扫描器 this(); //扫描给定的包 scan(basePackages); //刷新容器 refresh(); } public void scan(String... basePackages) { Assert.notEmpty(basePackages, "At least one base package must be specified"); this.scanner.scan(basePackages); }
这三个方法和上篇文章里面说到的差不多,只是register(annotatedClasses)
根据class注册Bean 方法变成了 scan(basePackages);
扫描一个包下的Bean.该方法使用了 ClassPathBeanDefinitionScanner
来扫描, 跟一下scan方法
//在指定的基本包内执行扫描。
public int scan(String... basePackages) {
int beanCountAtScanStart = this.registry.getBeanDefinitionCount();
doScan(basePackages);
// Register annotation config processors, if necessary.
if (this.includeAnnotationConfig) {
AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
}
return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);
}
这里返回了注册的Bean的个数,通过doScan方法实现包的扫描和Bean的注册
protected Set<BeanDefinitionHolder> doScan(String... basePackages) { Assert.notEmpty(basePackages, "At least one base package must be specified"); //用来装注册的Bean Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>(); //循环扫描多个包 for (String basePackage : basePackages) { //获取符合条件的Bean,调用 ClassPathScanningCandidateComponentProvider Set<BeanDefinition> candidates = findCandidateComponents(basePackage); //循环处理扫描到的BeanDefinition for (BeanDefinition candidate : candidates) { //解析Scope元注解 ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate); //把scope属性设置到BeanDefinition中,默认单利 candidate.setScope(scopeMetadata.getScopeName()); //生成Bean的名字 String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry); if (candidate instanceof AbstractBeanDefinition) { //设置Bean的自动注入装配属性等 postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName); } if (candidate instanceof AnnotatedBeanDefinition) { //解析Bean的通用注解,lazy,parimary等 AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate); } //判断容器中是否已经包含该Bean,不兼容就报错 if (checkCandidate(beanName, candidate)) { //把BeanDefinition封装成BeanDefinitionHolder BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName); //根据Bean的作用域,生成代理 , 默认不会产生 definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry); beanDefinitions.add(definitionHolder); //走 BeanDefinitionReaderUtils.registerBeanDefinition 方法注册Bean registerBeanDefinition(definitionHolder, this.registry); } } } //返回注册的所有Bean return beanDefinitions; }
该逻辑大概做了这些事情
bean的解析和注册流程和上一篇文章都一样,只是增加了包的扫描过程,我们跟一下ClassPathScanningCandidateComponentProvider#scanCandidateComponents方法
private Set<BeanDefinition> scanCandidateComponents(String basePackage) { Set<BeanDefinition> candidates = new LinkedHashSet<>(); try { //拼接扫描路径: classpath/包路径/*.class ,扫描classpath下的所有class文件 String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + resolveBasePackage(basePackage) + '/' + this.resourcePattern; //使用 ResourcePatternResolver 把 class 字节码文件转成Resource[] Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath); boolean traceEnabled = logger.isTraceEnabled(); boolean debugEnabled = logger.isDebugEnabled(); for (Resource resource : resources) { if (traceEnabled) { logger.trace("Scanning " + resource); } //resource可读 if (resource.isReadable()) { try { //获取元数据解析器 MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource); //是否是满足条件的Bean, //确定给定的类是否不匹配任何排除过滤器excludeFilters,并且匹配至少一个包含过滤器 if (isCandidateComponent(metadataReader)) { //把满足条件的bean封装成ScannedGenericBeanDefinition对象,装到set中返回 ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader); sbd.setSource(resource); if (isCandidateComponent(sbd)) { if (debugEnabled) { logger.debug("Identified candidate component class: " + resource); } candidates.add(sbd); } ...省略... } } catch (IOException ex) { throw new BeanDefinitionStoreException("I/O failure during classpath scanning", ex); } return candidates; } //根据MetadataReader判断Bean是否满足条件 protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException { for (TypeFilter tf : this.excludeFilters) { //是否被排除 if (tf.match(metadataReader, getMetadataReaderFactory())) { return false; } } for (TypeFilter tf : this.includeFilters) { //是否包含在includeFilters if (tf.match(metadataReader, getMetadataReaderFactory())) { return isConditionMatch(metadataReader); } } return false; }
该方法中大概做了如下事情
最后来做个小结,AnnotationConfigApplicationContext(package) 指定包扫描路径的方式和直接指定类名的方式多了一个包的扫描过程,Bean的解析和注册流程都是一样的。
IOC的启动流程就到这里把,喜欢的话请给个好评哦,你的肯定是我最大的动力!!!
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。