Spring源码情操陶冶-ComponentScanBeanDefinitionParser文件扫描解析器
承接前文Spring源码情操陶冶-自定义节点的解析,本文讲述spring通过
context:component-scan节点干了什么事
ComponentScanBeanDefinitionParser#私有属性
罗列下context:component-scan可填的基础属性
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";
ComponentScanBeanDefinitionParser#parse()-主方法
统一接口parse()方法,看下总体逻辑,代码如下
@Override
public BeanDefinition parse(Element element, ParserContext parserContext) {
//解析base-package属性值,扫描的包可以,;分隔
String[] basePackages = StringUtils.tokenizeToStringArray(element.getAttribute(BASE_PACKAGE_ATTRIBUTE), ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
// Actually scan for bean definitions and register them.
ClassPathBeanDefinitionScanner scanner = configureScanner(parserContext, element);
//通过ClassPathBeanDefinitionScanner扫描类来获取包名下的所有class并将他们注册到spring的bean工厂中
Set<BeanDefinitionHolder> beanDefinitions = scanner.doScan(basePackages);
//注册其他注解组件
registerComponents(parserContext.getReaderContext(), beanDefinitions, element);
return null;
}
我们关注下ComponentScanBeanDefinitionParser#configureScanner()创建扫描器操作和ClassPathBeanDefinitionScanner#doScan()扫描包方法
ComponentScanBeanDefinitionParser#configureScanner()-创建扫描器
观察下如何创建扫描器,以及相关的初始操作,代码奉上
protected ClassPathBeanDefinitionScanner configureScanner(ParserContext parserContext, Element element) {
XmlReaderContext readerContext = parserContext.getReaderContext();
//默认使用spring自带的注解过滤
boolean useDefaultFilters = true;
//解析`use-default-filters`,类型为boolean
if (element.hasAttribute(USE_DEFAULT_FILTERS_ATTRIBUTE)) {
useDefaultFilters = Boolean.valueOf(element.getAttribute(USE_DEFAULT_FILTERS_ATTRIBUTE));
}
// Delegate bean definition registration to scanner class.
//此处如果`use-default-filters`为true,则添加`@Component`、`@Service`、`@Controller`、`@Repository`、`@ManagedBean`、`@Named`添加到includeFilters的集合过滤
ClassPathBeanDefinitionScanner scanner = createScanner(readerContext, useDefaultFilters);
scanner.setResourceLoader(readerContext.getResourceLoader());
scanner.setEnvironment(parserContext.getDelegate().getEnvironment());
scanner.setBeanDefinitionDefaults(parserContext.getDelegate().getBeanDefinitionDefaults());
scanner.setAutowireCandidatePatterns(parserContext.getDelegate().getAutowireCandidatePatterns());
//设置`resource-pattern`属性,扫描资源的模式匹配,支持正则表达式
if (element.hasAttribute(RESOURCE_PATTERN_ATTRIBUTE)) {
scanner.setResourcePattern(element.getAttribute(RESOURCE_PATTERN_ATTRIBUTE));
}
try {
//解析name-generator属性 beanName生成器
parseBeanNameGenerator(element, scanner);
}
catch (Exception ex) {
readerContext.error(ex.getMessage(), readerContext.extractSource(element), ex.getCause());
}
try {
//解析scope-resolver属性和scoped-proxy属性,但两者只可存在其一
//后者值为targetClass:cglib代理、interfaces:JDK代理、no:不使用代理
parseScope(element, scanner);
}
catch (Exception ex) {
readerContext.error(ex.getMessage(), readerContext.extractSource(element), ex.getCause());
}
//解析子节点`context:include-filter`、`context:exclude-filter`主要用于对扫描class类的过滤
//例如<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller.RestController" />
parseTypeFilters(element, scanner, readerContext, parserContext);
return scanner;
}
此处只简单的罗列了如何创建一个文件扫描器以及相关的初始操作,具体的读者可自行去阅读分析
ClassPathBeanDefinitionScanner#doScan()-扫描操作
真实扫描base-package指定的目录并返回注册的所有beanDefinition,具体的扫描简析如下
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
//表明base-package属性是需要被指定的
Assert.notEmpty(basePackages, "At least one base package must be specified");
Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<BeanDefinitionHolder>();
for (String basePackage : basePackages) {
//对每个基础包都进行扫描寻找并且对基础包下的所有class都注册为BeanDefinition
/**
**
**并对得到的candidates集合进行过滤,此处便用到include-filters和exclude-filters
*/
Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
for (BeanDefinition candidate : candidates) {
//解析一个bean的scope属性,代表作用范围
//prototype->每次请求都创建新的对象 singleton->单例模式,处理多请求
ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
candidate.setScope(scopeMetadata.getScopeName());
//使用beanName生成器生成
String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
/**
**对注册的bean进行另外的赋值处理,比如默认属性的配置
*返回的candidate类型为ScannedGenericBeanDefinition,下面两者
*条件满足
*/
if (candidate instanceof AbstractBeanDefinition) {
//设置lazy-init/autowire-code默认属性,从spring配置的<beans>节点属性读取
postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
}
if (candidate instanceof AnnotatedBeanDefinition) {
//读取bean上的注解,比如`@Lazy`、`@Dependson`的值设置相应的属性
AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
}
//查看是否已注册
if (checkCandidate(beanName, candidate)) {
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
//默认采取cglib来做代理
definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
beanDefinitions.add(definitionHolder);
//注册bean信息到工厂中
registerBeanDefinition(definitionHolder, this.registry);
}
}
}
return beanDefinitions;
}
在这里我们只简单的看下其父类ClassPathScanningCandidateComponentProvider#findCandidateComponents获取包下的所有class资源文件并实例化为BeanDefinition对象
ClassPathScanningCandidateComponentProvider#findCandidateComponents()-找寻符合条件的资源文件
扫描包下的所有class文件并对其进行过滤,过滤的条件为includeFilters和excludeFilters集合。代码简析如下
public Set<BeanDefinition> findCandidateComponents(String basePackage) {
Set<BeanDefinition> candidates = new LinkedHashSet<BeanDefinition>();
try {
//值类似为classpath*:com/question/sky/**/*.class
String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +resolveBasePackage(basePackage) + "/" + this.resourcePattern;
//通过PathMatchingResourcePatternResolver来找寻资源
//常用的Resource为FileSystemResource
Resource[] resources = this.resourcePatternResolver.getResources(packageSearchPath);
boolean traceEnabled = logger.isTraceEnabled();
boolean debugEnabled = logger.isDebugEnabled();
for (Resource resource : resources) {
if (resource.isReadable()) {
try {
//生成MetadataReader对象->SimpleMetadataReader,内部包含AnnotationMetadataReadingVisitor注解访问处理类
MetadataReader metadataReader = this.metadataReaderFactory.getMetadataReader(resource);
//判断class是否不属于excludeFilters集合内但至少符合一个includeFilters集合
if (isCandidateComponent(metadataReader)) {
//包装为ScannedGenericBeanDefinition对象
ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
//保存文件资源
sbd.setResource(resource);
sbd.setSource(resource);
//判断class文件是否不为接口或者抽象类并且是独立的
if (isCandidateComponent(sbd)) {
//完成验证加入集合中
candidates.add(sbd);
}
}
}
catch (Throwable ex) {
throw new BeanDefinitionStoreException(
"Failed to read candidate component class: " + resource, ex);
}
}
}
}
catch (IOException ex) {
throw new BeanDefinitionStoreException("I/O failure during classpath scanning", ex);
}
return candidates;
}
对上面的代码解释作下补充,主要是验证beanDefinition的两个方法
- ClassPathScanningCandidateComponentProvider#isCandidateComponent(MetadataReader metadataReader)
对class类进行filter集合过滤
protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {
for (TypeFilter tf : this.excludeFilters) {
//满足excludeFilter集合中的一个便返回false,表示不对对应的beanDefinition注册
if (tf.match(metadataReader, this.metadataReaderFactory)) {
return false;
}
}
for (TypeFilter tf : this.includeFilters) {
//首先满足其中includeFilter集合中的一个
if (tf.match(metadataReader, this.metadataReaderFactory)) {
//判断对应的beanDifinition不存在@Conditional注解或者满足@Conditional中指定的条件,则返回true
//@Conditional注解的使用可自行查看相关资料
return isConditionMatch(metadataReader);
}
}
return false;
}
- ClassPathScanningCandidateComponentProvider#isCandidateComponent(AnnotatedBeanDefinition beanDefinition)
验证beanDefinition class类是否为具体类
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
//非抽象类、接口类并且有独立特性[它是一个顶级类还是一个嵌套类(静态内部类),可以独立于封闭类构造。]
return (beanDefinition.getMetadata().isConcrete() && beanDefinition.getMetadata().isIndependent());
}
ComponentScanBeanDefinitionParser#registerComponents-注册其他组件
在扫描包内的class文件注册为beanDefinition之后,ComponentScanBeanDefinitionParser还需要注册其他的组件,具体是什么可简单看下相关的源码
protected void registerComponents(
XmlReaderContext readerContext, Set<BeanDefinitionHolder> beanDefinitions, Element element) {
Object source = readerContext.extractSource(element);
//包装为CompositeComponentDefinition对象,内置多ComponentDefinition对象
CompositeComponentDefinition compositeDef = new CompositeComponentDefinition(element.getTagName(), source);
//将已注册的所有beanDefinitionHolder对象放到上述对象中
for (BeanDefinitionHolder beanDefHolder : beanDefinitions) {
compositeDef.addNestedComponent(new BeanComponentDefinition(beanDefHolder));
}
// Register annotation config processors, if necessary.
boolean annotationConfig = true;
//获取annotation-config的属性值,默认为true
if (element.hasAttribute(ANNOTATION_CONFIG_ATTRIBUTE)) {
annotationConfig = Boolean.valueOf(element.getAttribute(ANNOTATION_CONFIG_ATTRIBUTE));
}
if (annotationConfig) {
//注册多个BeanPostProcessor接口,具体什么可自行查看,返回的是包含BeanPostProcessor接口的beanDefinitionHolder对象集合
Set<BeanDefinitionHolder> processorDefinitions = AnnotationConfigUtils.registerAnnotationConfigProcessors(readerContext.getRegistry(), source);
//继续装入CompositeComponentDefinition对象
for (BeanDefinitionHolder processorDefinition : processorDefinitions) {
compositeDef.addNestedComponent(new BeanComponentDefinition(processorDefinition));
}
}
//此处为空
readerContext.fireComponentRegistered(compositeDef);
}
此处的目的主要是注册多个BeanPostProcessor接口实现类【供后续spring调用统一接口进行解析,比如>>>Spring源码情操陶冶-AbstractApplicationContext#invokeBeanFactoryPostProcessors可执行下述的@Configuration解析】具体的有
- ConfigurationClassPostProcessor解析
@Configuration注解类 - AutowiredAnnotationBeanPostProcessor解析
@Autowired/@Value注解 - RequiredAnnotationBeanPostProcessor解析
@Required注解 - CommonAnnotationBeanPostProcessor解析
@Resource注解 - PersistenceAnnotationBeanPostProcessor解析JPA注解,持久层
小结
context:component-scan节点的属性及其含义
- base-package 扫描的基础包名,必填项,也可指定多个包名,以
,;分隔- use-default-filters 默认为true,如果设置为false,则不启用
@Component及其相关注解- resource-pattern 自定义扫描的文件名,支持正则匹配,默认为
**/*.class- name-generator beanName生成器
- scope-resolver 指定bean的作用范围溶解器 与
scope-proxy分开使用- scope-proxy 与
scope-resolver分开使用,targetClass:cglib代理、interfaces:JDK代理、no:不使用代理 ,默认使用cglib代理- context:include-filter/context:exclude-filter 子节点,可有多个,表示可对beanDefinition上的注解过滤
具体的通过扫描base-package指定的包名来得到所有的class文件,请看>>>Spring源码情操陶冶-PathMatchingResourcePatternResolver路径资源匹配溶解器。注意:扫描一个类的时候,如果其内部有静态内部类也是会被扫描注册的
此
context:component-scan的指定表明默认可将base-package指定的包下的所有注解class比如@Service等注册为bean到bean工厂,方便后续的业务调用注册BeanFactoryPostProcessor/BeanPostProcessor接口实现类用于后续的bean实例化,比如
ConfigurationClassPostProcessor解析@Configuration注解类、AutowiredAnnotationBeanPostProcessor解析@Autowired/@Value注解、RequiredAnnotationBeanPostProcessor解析@Required注解、CommonAnnotationBeanPostProcessor解析@Resource注解、PersistenceAnnotationBeanPostProcessor解析JPA注解
Spring源码情操陶冶-ComponentScanBeanDefinitionParser文件扫描解析器的更多相关文章
- Spring源码情操陶冶-AnnotationConfigBeanDefinitionParser注解配置解析器
本文承接前文Spring源码情操陶冶-自定义节点的解析,分析spring中的context:annotation-config节点如何被解析 源码概览 对BeanDefinitionParser接口的 ...
- Spring源码情操陶冶-PropertyPlaceholderBeanDefinitionParser注解配置解析器
本文针对spring配置的context:property-placeholder作下简单的分析,承接前文Spring源码情操陶冶-自定义节点的解析 spring配置文件应用 <context: ...
- Spring源码情操陶冶-AOP之ConfigBeanDefinitionParser解析器
aop-Aspect Oriented Programming,面向切面编程.根据百度百科的解释,其通过预编译方式和运行期动态代理实现程序功能的一种技术.主要目的是为了程序间的解耦,常用于日志记录.事 ...
- Spring源码情操陶冶-自定义节点的解析
本文承接前文Spring源码情操陶冶-DefaultBeanDefinitionDocumentReader#parseBeanDefinitions,特开辟出一块新地来啃啃这块有意思的骨头 自定义节 ...
- SpringMVC源码情操陶冶-ResourcesBeanDefinitionParser静态资源解析器
解析mvc:resources节点,控制对静态资源的映射访问 查看官方注释 /** * {@link org.springframework.beans.factory.xml.BeanDefinit ...
- Spring源码情操陶冶-AOP之Advice通知类解析与使用
阅读本文请先稍微浏览下上篇文章Spring源码情操陶冶-AOP之ConfigBeanDefinitionParser解析器,本文则对aop模式的通知类作简单的分析 入口 根据前文讲解,我们知道通知类的 ...
- Spring源码情操陶冶#task:executor解析器
承接Spring源码情操陶冶-自定义节点的解析.线程池是jdk的一个很重要的概念,在很多的场景都会应用到,多用于处理多任务的并发处理,此处借由spring整合jdk的cocurrent包的方式来进行深 ...
- Spring源码情操陶冶-tx:advice解析器
承接Spring源码情操陶冶-自定义节点的解析.本节关于事务进行简单的解析 spring配置文件样例 简单的事务配置,对save/delete开头的方法加事务,get/find开头的设置为不加事务只读 ...
- Spring源码情操陶冶-AbstractApplicationContext#invokeBeanFactoryPostProcessors
阅读源码有利于陶冶情操,承接前文Spring源码情操陶冶-AbstractApplicationContext#postProcessBeanFactory 约定:web.xml中配置的context ...
随机推荐
- [bzoj3191] [JLOI2013]卡牌游戏
概率DP. 首先由题解可得>_<,胜出概率只与剩余人数.与庄家的相对位置有关. 所以设f[i][j]表示剩下i个人,从庄家开始第j个人的胜利概率... 根据卡牌一通乱搞即可... #inc ...
- jsp/servlet相关技术及知识
JSP页面的内容由两部分组成: 静态部分:标准的HTML标签.静态的页面内容, 动态部分:受Java程序控制的内容,这些都由java语言动态生成 简单的jsp页面代码: <%@ page lan ...
- 换行符 '\n' 和 回车符 '\r' 的区别?
顾名思义: 换行符就是另起一新行,光标在新行的开头: 回车符就是光标回到一旧行的开头:(即光标目前所在的行为旧行) ------------------------------------------ ...
- java 跳出多层循环
lableB: for(int i=0;i<10;i++){ lableA: for(int j=0;j<10;j++){ System.out.println(j); if(j==1){ ...
- 《SpringMVC从入门到放肆》五、SpringMVC配置式开发(处理器适配器)
上一篇我们大致讲解了处理器映射器的处理流程以及跟了一下源码的执行流程.今天我们来了解一下处理器适配器. 一.适配器模式 在阎宏博士的<JAVA与模式>一书中开头是这样描述适配器(Adapt ...
- .26-浅析webpack源码之事件流make(1)
compilation事件流中,依然只是针对细节步骤做事件流注入,代码流程如图: // apply => this-compilation // apply => compilation ...
- Linux终端连接Linux服务器
我们经常需要通过类UNIX下连接我们的Linux服务器.比如我的Mac下经常需要连接上Linux服务器.一般系统都提供了ssh支持,可以直接连接: 通过命令: ssh root@120.25.12.9 ...
- yourphp常用标签
方法/步骤 1 引入页面: 首页链接:{$site_url} 英文首页{$site_url}/en 面包屑导航: {:L(catpos)} {:L(home_font)} > 幻灯 ...
- ngRx 官方示例分析 - 1. 介绍
ngRx 的官方示例演示了在具体的场景中,如何使用 ngRx 管理应用的状态. 示例介绍 示例允许用户通过查询 google 的 book API 来查询图书,并保存自己的精选书籍列表. 菜单有两 ...
- 【问题解决】Eclipse中 ctrl+空格 content assist
改一下你的快捷键设置:window->perferences-->keys--->查找 content assist--->把这个地方改成你想要的就可以了.!