前言
深入理解Spring源码分为7小节,本小节为Spring源码第一小节,各个小节目录如下。
1. 扫描过程
- bean创建过程
- 容器扩展
- AOP源码分析
- 事务源码分析
- Spring JDBC源码分析
- Spring常用设计模式
扫描过程
扫描过程这个叫法可能不太合适,但是在源码中,有个很重要的方法叫scan,他是第一步要执行的,或者也可以叫做信息收集过程,因为在这个过程中,主要做的就是从xml配置文件或者类路径中收集Spring所要管理的对象。
scan是AnnotationConfigApplicationContext中才有的,xml方式可能以后也不太常见了,所以本章以及以后的文章中,都是使用AnnotationConfigApplicationContext,不论是使用哪种方式,第一步要做的都是解析、收集必要的信息。
比如你写的@Configuration类,需要让Spring管理,那么Spring启动过程第一步就是从若干个类中,找到需要被管理的类,把这些类,封装在一个BeanDefinition对象,这个对象,就是我们俗称的bean定义,比如WebConfig类上标有@Configuration,那么这个类的信息会被封装成BeanDefinition,这里面包含这个类是不是懒加载、初始化方法等信息。
BeanDefinition是个接口,他有三个实现,RootBeanDefinition、ChildBeanDefinition、GenericBeanDefinition。
那这些信息一定会有一个集合保存着,而这个集合所在的类,也一定也很重要。
这个类就是DefaultListableBeanFactory,可以说是一个核心类,其中有个字段beanDefinitionMap,就保存这一堆BeanDefinition,那么就需要一个方法,来对这个集合增加、删除,分别叫registerBeanDefinition,removeBeanDefinition。
所以第一部分,其实就是围绕这registerBeanDefinition展开,而我们需要知道的是Spring怎么找到带有@Configuration、@Component等注解的类的,以及如何封装成BeanDefinition,并调用registerBeanDefinition进行保存的。
DefaultListableBeanFactory类中还有其他重要字段,在其他章节介绍。
ClassPathBeanDefinitionScanner
扫描哪些类中具有这些注解的工作也是一个不小的任务,所以,Spring单独使用ClassPathBeanDefinitionScanner进行处理,首先看下他类上的注释。
A bean definition scanner that detects bean candidates on the classpath,
registering corresponding bean definitions with a given registry ({@code BeanFactory}
* {@link org.springframework.stereotype.Component @Component},
* {@link org.springframework.stereotype.Repository @Repository},
* {@link org.springframework.stereotype.Service @Service}, or
* {@link org.springframework.stereotype.Controller @Controller} stereotype.
意思就是bean定义扫描器,在类路径上检测是否有以下注解,如果存在,就在给定的BeanFactory中注册相应的BeanDefinition,这里就又牵扯出BeanFactory,他是个很重要的接口,DefaultListableBeanFactory就实现了他,这个接口定义了获取bean以及bean的各种属性。
直接看ClassPathBeanDefinitionScanner#scan方法,非常简单。
public int scan(String... basePackages) {
//开始时候的bean 定义的数量
int beanCountAtScanStart = this.registry.getBeanDefinitionCount();
//要开始做实事了,进行扫描
doScan(basePackages);
//添加一些后置处理器,后面说
if (this.includeAnnotationConfig) {
AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
}
//返回新增了多少个
return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);
}
Spring中所有do开头的方法,都是做实事的方法,所以必须看doScan().
doScan
Spring会扫描目标包和他的子包,doScan方法接受多个包名,通常这里只传递项目的根路径,这个方法其中最重要的还是findCandidateComponents,他会找到符合这个包路径的所有类,从中挑选出需要被管理的。
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
Assert.notEmpty(basePackages, "At least one base package must be specified");
/**
* 保存扫描到的BeanDefinition
*/
Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
/**
* 遍历目标包名
*/
for (String basePackage : basePackages) {
/**
* 根据包名找到带有指定注解的类的所有BeanDefinition对象
*/
Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
/**
* 遍历BeanDefinition
*/
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);
}
/**
* 根据其他注解设置BeanDefinition对应字段值
*/
if (candidate instanceof AnnotatedBeanDefinition) {
AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
}
/**
* 验证这个bean,比如是否存在
*/
if (checkCandidate(beanName, candidate)) {
/**
* 使用BeanDefinitionHolder包装BeanDefinition
*/
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
definitionHolder =
AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
beanDefinitions.add(definitionHolder);
/**
* 向容器注册这个BeanDefinition
*/
registerBeanDefinition(definitionHolder, this.registry);
}
}
}
return beanDefinitions;
}
findCandidateComponents->scanCandidateComponents
findCandidateComponents中做实事的还是scanCandidateComponents,他首先会根据传递的包名,拼接成完整的通配符路径,比如包名是com.xxx,那么最后完整的通配路径就是classpath*:com/xxx/**/*.class,这个通配路径中也包含子包。
private Set<BeanDefinition> scanCandidateComponents(String basePackage) {
Set<BeanDefinition> candidates = new LinkedHashSet<>();
try {
String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
resolveBasePackage(basePackage) + '/' + this.resourcePattern;
/**
* 扫描出符合指定通配符的所有class
*/
Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);
/**
* 过滤不需要的class
*/
for (Resource resource : resources) {
if (traceEnabled) {
logger.trace("Scanning " + resource);
}
try {
/**
* 真正读取的Class的Metadata信息的是SimpleMetadataReader,但是默认实现是CachingMetadataReaderFactory,只不过在他的基础
* 上增加了缓存功能
*/
MetadataReaderFactory metadataReaderFactory = getMetadataReaderFactory();
MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(resource);
/**
* 如果这个类标有Component注解或者他的子类,那么进行BeanDefinition注册
*/
if (isCandidateComponent(metadataReader)) {
ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
sbd.setSource(resource);
if (isCandidateComponent(sbd)) {
candidates.add(sbd);
}
}
} catch (FileNotFoundException ex) {
} 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;
}
这里重要的还是下面这句,他会收集所有符合这个通配路径的Resource对象,Resource中包含这具体这个类的位置,如果项目中依赖其他jar文件,并且这个jar中同样有符合这个路径的类,也会收集,所以这个结果通常都比较多,但如果你指定的扫描路径越详细,这里也就越少,处理的速度也更快。
Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);
接下来就是判断这个类上存在不存在指定注解,这里是比较复杂的,因为要解析class文件判断类上是不是标有注解,普通的做法可能是使用ClassLoader加载成Class对象,之后在通过getDeclaredAnnotations之类的方法提取注解,但是在样效率不高,更高的方法是暴力按照class格式规范去解析,但是这就增加了难度,需要对class文件格式非常熟悉。
所以这部分是由ClassReader完成的,他的核心代码也不需要看,也看不懂,他就是按照class规范,前几个字节是什么意思,后几个字节是什么意思的约定,一点点的读取,可以取出访问权限、父类名、注解等信息,最终返回成MetadataReader对象,MetadataReader就包含这这个类上有哪些注解。
MetadataReaderFactory metadataReaderFactory = getMetadataReaderFactory();
MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(resource);
而下面这段,则是判断有没有指定注解。
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;
}
includeFilters字段里保存的就是一些注解列表,在默认的时候里面含有Component、ManagedBean、Named这三个,所以,类上只要标有这三个注解,就可以让Spring管理,而@Service这些注解上都又标有Component注解,简单可以理解成Service继承Component,所以,标有@Service也可以让Spring管理,同样我们自定义一个注解,注解上加入@Component,同样可以。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Component
public @interface TestAnnnotation {
}
最后这个方法返回所有带有这些注解的类,在回到doScan,剩下的就是遍历上一步返回的结果,注册到容器里,除此之外,还有下面这段代码,首先这个条件是成立的,因为在扫描过程中,返回的是ScannedGenericBeanDefinition,而他是继承AnnotatedBeanDefinition
if (candidate instanceof AnnotatedBeanDefinition) {
AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
}
最后会进入这段代码,他的作用是提取其余辅助注解,设置信息到BeanDefinition中,比如获取Lazy注解,如果存在,对BeanDefinition设置他的value值,Lazy注解是懒加载,就是当这个类用到的时候才实例化,否在不,Spring默认都是不进行懒加载的。
还有其余注解如Primary用来设置当有众多相同的Bean中,优先使用@Primary注解的Bean。
所以下面代码,只是根据其他注解的值填充到BeanDefinition,这样BeanDefinition就完善了。
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"));
}
}
注册
最后会封装成BeanDefinitionHolder,这个对象里面含有BeanDefinition,调用registerBeanDefinition添加到容器中,容器就是DefaultListableBeanFactory。
registerBeanDefinition(definitionHolder, this.registry);
到这里就结束了,这里就会调用开头我们说的registerBeanDefinition方法,位于DefaultListableBeanFactory,把BeanDefinitionHolder对象添加到beanDefinitionMap中,这个过程中还要处理别名。
系统其余BeanDefinition
除了我们自己的,Spring还有他自己的需要注册,位于scan下的这一句。
if (this.includeAnnotationConfig) {
AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
}
但是在AnnotationConfigApplicationContext的构造方法执行阶段,同样也调用了这句。所以在内部也判断了如果对应的BeanDefinition不存在,才注册,这里Spring添加的BeanDefinition非常重要,比如ConfigurationClassPostProcessor,这是后续的事情了,总之这个过程就是收集所有的BeanDefinition对象,这些对象以后会进行实例化,完成依赖注入。
但是这还不算结束,还有其他Bean在这个过程中是收集不到的,比如类中还有方法标有@Bean,这需要实例化所在类才可以收集到,另外还可以通过扩展接口收集,这部分在下章说。
本章结束。