2、Spring之Bean生命周期~扫描

作者 : admin 本文共11880个字,预计阅读时间需要30分钟 发布时间: 2024-06-9 共3人阅读

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定义”,我们走进这个方法一步一步看,因为传进来的包路径可能是数组,所以需要循环,逐步分析循环中的买个方法:

  1. 调用findCandidateComponents()方法,扫描calss文件,将class文件封装成BeanDefinition对象;
  2. 循环读取到的BeanDefinition对象;
  3. 调用AnnotationScopeMetadataResolver.resolveScopeMetadata()解析@Scope注解;
  4. 调用AnnotationBeanNameGenerator.generateBeanName()解析BeanName;
  5. 调用postProcessBeanDefinition()方法给BeanDefinition设置默认值。
  6. 调用AnnotationConfigUtils.processCommonDefinitionAnnotations()方法解析@Lazy、@Primary、@DependsOn、@Role、@Description注解,并赋值给BeanDefinition。
  7. 判断beanName是否在beanDefinitionMap中存在,不存在,放进beanDefinitionMap中,存在的话判断两个bean来源和beanDefinition对象是否一样,一样就不注册,不一样报错,说明两个不一样的bean,beanName冲突;
  8. 扫描完成之后会合并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;
}
  1. 拼接扫描规则,将传进来的路径拼接成:classpath*:com/test/**/*.class
  2. 创建资源解析器ResourcePatternResolver,通过getResourcePatternResolver()方法可以看出是具体创建的是PathMatchingResourcePatternResolver实现类;
  3. 调用PathMatchingResourcePatternResolver.getResources()方法,将class文件封装成Resource对象;
  4. 拿到Resource对象数组后进入循环;
  5. 首先判断文件是否有读取权限,
  6. 判断excludeFilters和includeFilters
  7. 判断@Component注解和@Conditional注解
  8. 判断是否是抽象类、接口,如果是抽象类是否包含@Lookup注解;
  9. 所有判断都通过后将Resource对象封装成ScannedGenericBeanDefinition对象,放入BeanDefinition的Set集中(candidates);
  10. 加载完成,回到doScan()方法中

getResources()方法

  getResources方法详解:
  因为ResourcePatternResolver是一个接口,我们如果想要知道ResourcePatternResolver.getResources()具体走啦哪个实现类,有两个办法:

  1. debug 在源码中打断点;
  2. 找到创建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()方法中

本站无任何商业行为
个人在线分享 » 2、Spring之Bean生命周期~扫描
E-->