2、Spring之Bean生命周期~扫描
2、Spring之Bean生命周期~扫描
- spring 容器创建
- 扫描 doScan()
- findCandidateComponents()
- scanCandidateComponents()
- getResources()方法
- findPathMatchingResources()方法
- resolveScopeMetadata()方法
- generateBeanName()方法
- processCommonDefinitionAnnotations()方法
spring 容器创建
这是只是大致过一个spring容器的创建,具体详细的创建步骤并不会展开细说,只说和bean生命周期有关的部分;
创建spring容器有两种方式:
1、通过xml配置文件的方式
new ClassPathXmlApplicationContext("xxx.xml");
2、通过配置类的方式
new AnnotationConfigApplicationContext(xxx.class);
不管那种方式都需要指定spring容器的扫描路径,xml文件通过 标签设置扫描路径,配置类通过 @ComponentScan 注解设置扫描路径,spring容器在拿到扫描路径之后就会对配置的路径进行挨个扫描,如果符合bean的要求,就会把他封装成一个BeanDefinition。
扫描 doScan()
废话不多说,直接上源码
/**
* Perform a scan within the specified base packages,
* returning the registered bean definitions.
* This method does not register an annotation config processor
* but rather leaves this up to the caller.
* @param basePackages the packages to check for annotated classes
* @return set of beans registered if any for tooling registration purposes (never {@code null})
*/
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
Assert.notEmpty(basePackages, "At least one base package must be specified");
Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
for (String basePackage : basePackages) {
//通过 扫描路径 得到 BeanDefinition
Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
for (BeanDefinition candidate : candidates) {
// 解析@Scope注解的结果为ScopeMetadata 如果没有@Scope注解默认是singleton
ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
candidate.setScope(scopeMetadata.getScopeName());// 将scope注解中的值放到BeanDefinition中
/**
* 解析bean的名字
* */
String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
if (candidate instanceof AbstractBeanDefinition) {
// 给 beanDefinition 设置默认值
postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
}
if (candidate instanceof AnnotatedBeanDefinition) {
// 解析@Lazy、@Primary、@DependsOn、@Role、@Description
AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
}
// 检查Spring容器中是否已经存在该beanName
if (checkCandidate(beanName, candidate)) {
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
definitionHolder =
AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
beanDefinitions.add(definitionHolder);
// 注册
registerBeanDefinition(definitionHolder, this.registry);
}
}
}
return beanDefinitions;
}
通过上面注释,大致知道,这个方法的作用“在指定的基本封装内执行扫描,返回注册的bean定义”,我们走进这个方法一步一步看,因为传进来的包路径可能是数组,所以需要循环,逐步分析循环中的买个方法:
- 调用findCandidateComponents()方法,扫描calss文件,将class文件封装成BeanDefinition对象;
- 循环读取到的BeanDefinition对象;
- 调用AnnotationScopeMetadataResolver.resolveScopeMetadata()解析@Scope注解;
- 调用AnnotationBeanNameGenerator.generateBeanName()解析BeanName;
- 调用postProcessBeanDefinition()方法给BeanDefinition设置默认值。
- 调用AnnotationConfigUtils.processCommonDefinitionAnnotations()方法解析@Lazy、@Primary、@DependsOn、@Role、@Description注解,并赋值给BeanDefinition。
- 判断beanName是否在beanDefinitionMap中存在,不存在,放进beanDefinitionMap中,存在的话判断两个bean来源和beanDefinition对象是否一样,一样就不注册,不一样报错,说明两个不一样的bean,beanName冲突;
- 扫描完成之后会合并beanDefinition;
这是为大家提供一个spring Bean扫描的流程图;
findCandidateComponents()
findCandidateComponents方法详解
public Set<BeanDefinition> findCandidateComponents(String basePackage) {
if (this.componentsIndex != null && indexSupportsIncludeFilters()) {
//扫描所有配置文件
return addCandidateComponentsFromIndex(this.componentsIndex, basePackage);
} else {
// 扫描basePackage路径下的所有类 排除掉不符合要求的类 将java类封装到BeanDefinition中
return scanCandidateComponents(basePackage);
}
}
scanCandidateComponents()
scanCandidateComponents方法详解
private Set<BeanDefinition> scanCandidateComponents(String basePackage) {
Set<BeanDefinition> candidates = new LinkedHashSet<>();
try {
// 拼接扫描规则 classpath*:com/test/**/*.class com.test是配置的扫描路径
String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
resolveBasePackage(basePackage) + '/' + this.resourcePattern;
// 创建 PathMatchingResourcePatternResolver 对象
ResourcePatternResolver resourcePatternResolver = getResourcePatternResolver();
// 解析扫描路径,获取扫描路径下的文件 将文件封装到 Resource 对象中
Resource[] resources = resourcePatternResolver.getResources(packageSearchPath);
boolean traceEnabled = logger.isTraceEnabled();
boolean debugEnabled = logger.isDebugEnabled();
/**
* 循环内的主要逻辑就是排除掉所有不符合要求的 Resource对象(类),
* 比如:
* 包含在 excludeFilters中 没有包含在includeFilters
* 是否是抽象类 接口 如果是抽象类是否包含Lookup注解
* */
for (Resource resource : resources) {
if (traceEnabled) {
logger.trace("Scanning " + resource);
}
if (resource.isReadable()) {//判断当前文件可读 且 不是目录
try {
MetadataReaderFactory readerFactory = getMetadataReaderFactory();// 获取一个元数据读取工厂
MetadataReader metadataReader = readerFactory.getMetadataReader(resource);// 将resource 放到原数据缓存中
// excludeFilters、includeFilters判断
if (isCandidateComponent(metadataReader)) { // @Component-->includeFilters判断
ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
sbd.setSource(resource);
// 判断 是否是 抽象类、接口 如果是抽象类是否包含@Lookup注解
if (isCandidateComponent(sbd)) {
if (debugEnabled) {
logger.debug("Identified candidate component class: " + resource);
}
candidates.add(sbd);
} else {
if (debugEnabled) {
logger.debug("Ignored because not a concrete top-level class: " + resource);
}
}
} else {
if (traceEnabled) {
logger.trace("Ignored because not matching any filter: " + resource);
}
}
} catch (Throwable ex) {
throw new BeanDefinitionStoreException(
"Failed to read candidate component class: " + resource, ex);
}
} else {
if (traceEnabled) {
logger.trace("Ignored because not readable: " + resource);
}
}
}
} catch (IOException ex) {
throw new BeanDefinitionStoreException("I/O failure during classpath scanning", ex);
}
return candidates;
}
- 拼接扫描规则,将传进来的路径拼接成:classpath*:com/test/**/*.class
- 创建资源解析器ResourcePatternResolver,通过getResourcePatternResolver()方法可以看出是具体创建的是PathMatchingResourcePatternResolver实现类;
- 调用PathMatchingResourcePatternResolver.getResources()方法,将class文件封装成Resource对象;
- 拿到Resource对象数组后进入循环;
- 首先判断文件是否有读取权限,
- 判断excludeFilters和includeFilters
- 判断@Component注解和@Conditional注解
- 判断是否是抽象类、接口,如果是抽象类是否包含@Lookup注解;
- 所有判断都通过后将Resource对象封装成ScannedGenericBeanDefinition对象,放入BeanDefinition的Set集中(candidates);
- 加载完成,回到doScan()方法中
getResources()方法
getResources方法详解:
因为ResourcePatternResolver是一个接口,我们如果想要知道ResourcePatternResolver.getResources()具体走啦哪个实现类,有两个办法:
- debug 在源码中打断点;
- 找到创建ResourcePatternResolver对象的地方,看是用哪个实现类创建的对象;
在这里,我们可以通过getResourcePatternResolver方法很容易的看到是通过PathMatchingResourcePatternResolver类创建的,进入PathMatchingResourcePatternResolver.getResources()方法
/**
* @param locationPattern 通过classpath封装后的 class文件路径
*/
@Override
public Resource[] getResources(String locationPattern) throws IOException {
// 判断 class文件不是null
Assert.notNull(locationPattern, "Location pattern must not be null");
// startsWith 测试此字符串是否以指定的前缀开头。
// 检测 class文件路径是否已classpath 开头的
if (locationPattern.startsWith(CLASSPATH_ALL_URL_PREFIX)) {
// a class path resource (multiple resources for same name possible)
if (getPathMatcher().isPattern(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()))) {
// a class path resource pattern
//解析扫描路径,获取扫描路径下的文件 将文件封装到 Resource 对象中
return findPathMatchingResources(locationPattern);
} else {
// all class path resources with the given name
return findAllClassPathResources(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()));
}
} else {
// Generally only look for a pattern after a prefix here,
// and on Tomcat only after the "*/" separator for its "war:" protocol.
int prefixEnd = (locationPattern.startsWith("war:") ? locationPattern.indexOf("*/") + 1 :
locationPattern.indexOf(':') + 1);
if (getPathMatcher().isPattern(locationPattern.substring(prefixEnd))) {
// a file pattern
return findPathMatchingResources(locationPattern);
} else {
// a single resource with the given name
return new Resource[]{getResourceLoader().getResource(locationPattern)};
}
}
}
经过层层判断会进入findPathMatchingResources()方法
findPathMatchingResources()方法
重点:findPathMatchingResources()方法是真正去读取class文件,并将class文件封装成Resource对象,
/**
* 解析扫描路径,获取扫描路径下的文件 将文件封装到 Resource 对象中
*
* Find all resources that match the given location pattern via the
* Ant-style PathMatcher. Supports resources in jar files and zip files
* and in the file system.
*
* @param locationPattern the location pattern to match 截取classpath的class文件路径
* @return the result as Resource array
* @throws IOException in case of I/O errors
* @see #doFindPathMatchingJarResources
* @see #doFindPathMatchingFileResources
* @see org.springframework.util.PathMatcher
*/
protected Resource[] findPathMatchingResources(String locationPattern) throws IOException {
// 解析扫描路径
String rootDirPath = determineRootDir(locationPattern);
String subPattern = locationPattern.substring(rootDirPath.length());
//拿到扫描路径的绝对地址 和 类型文件
Resource[] rootDirResources = getResources(rootDirPath);
Set<Resource> result = new LinkedHashSet<>(16);
for (Resource rootDirResource : rootDirResources) {
rootDirResource = resolveRootDirResource(rootDirResource);
URL rootDirUrl = rootDirResource.getURL();//扫描路径的绝对地址
if (equinoxResolveMethod != null && rootDirUrl.getProtocol().startsWith("bundle")) {
URL resolvedUrl = (URL) ReflectionUtils.invokeMethod(equinoxResolveMethod, null, rootDirUrl);
if (resolvedUrl != null) {
rootDirUrl = resolvedUrl;
}
rootDirResource = new UrlResource(rootDirUrl);
}
//判断文件类型是否是 虚拟文件系统 VFS:虚拟文件系统
if (rootDirUrl.getProtocol().startsWith(ResourceUtils.URL_PROTOCOL_VFS)) {
result.addAll(VfsResourceMatchingDelegate.findMatchingResources(rootDirUrl, subPattern, getPathMatcher()));
}
// 检查文件是否是 "jar", "war, ""zip","vfszip","wsjar" 类型
else if (ResourceUtils.isJarURL(rootDirUrl) || isJarResource(rootDirResource)) {
result.addAll(doFindPathMatchingJarResources(rootDirResource, rootDirUrl, subPattern));
} else {
// 解析扫描路径,获取扫描路径下的文件 将文件封装到 Resource 对象中
result.addAll(doFindPathMatchingFileResources(rootDirResource, subPattern));
}
}
if (logger.isTraceEnabled()) {
logger.trace("Resolved location pattern [" + locationPattern + "] to resources " + result);
}
return result.toArray(new Resource[0]);
}
class文件读取完成,回到scanCandidateComponents()方法中
resolveScopeMetadata()方法
resolveScopeMetadata方法详解
/**
* 解析BeanDefinition中的@Scope注解
*
* 创建 ScopeMetadata时默认赋值SCOPE_SINGLETON
* 如果BeanDefinition上有@Scope注解,将@Scope注解中的值进行覆盖
*
* */
@Override
public ScopeMetadata resolveScopeMetadata(BeanDefinition definition) {
ScopeMetadata metadata = new ScopeMetadata();
if (definition instanceof AnnotatedBeanDefinition) {
AnnotatedBeanDefinition annDef = (AnnotatedBeanDefinition) definition;
AnnotationAttributes attributes = AnnotationConfigUtils.attributesFor(
annDef.getMetadata(), this.scopeAnnotationType);
if (attributes != null) {
metadata.setScopeName(attributes.getString("value"));
ScopedProxyMode proxyMode = attributes.getEnum("proxyMode");
if (proxyMode == ScopedProxyMode.DEFAULT) {
proxyMode = this.defaultProxyMode;
}
metadata.setScopedProxyMode(proxyMode);
}
}
return metadata;
}
@Scope注解解析完成,回到doScan()方法中
generateBeanName()方法
generateBeanName方法详解
/**
*
* 解析bean的名字
*
* 首先便利类上所有的注解 查看是否包含 @Component 或 包含@Component的注解(如:@RestController、@Controller、@Service等)
* 如果包含返回定义的beanName直接返回定义的beanName 空字符串会返回null取默认的BeanName
*
* 如果@Component注解中没有定义BeanName走
* buildDefaultBeanName方法 获取默认BeanName
*
* 如果类名第一个字符和第二个字符都是大写 直接返回首字母大写的BeanName 如:ABtest
*
* 否则将类名首字母小写返回 如:appConfig
*
* */
@Override
public String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) {
if (definition instanceof AnnotatedBeanDefinition) {
// 获取注解所指定的beanName
// 解析@Component注解 和 包含@Component的注解(如:@Controller)
String beanName = determineBeanNameFromAnnotation((AnnotatedBeanDefinition) definition);
if (StringUtils.hasText(beanName)) {
// Explicit bean name found.
return beanName;
}
}
// Fallback: generate a unique default bean name.
return buildDefaultBeanName(definition, registry);
}
BeanName解析完成,回到doScan()方法中
processCommonDefinitionAnnotations()方法
processCommonDefinitionAnnotations方法详解
// 处理@Lazy、@Primary、@DependsOn、@Role、@Description
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"));
}
}
解析完成,回到doScan()方法中