锁定这个方法,Spring会在ClassPathScanningCandidateComponentProvider类的这个方法中进行扫描
private Set<BeanDefinition> scanCandidateComponents(String basePackage) {
LinkedHashSet candidates = new LinkedHashSet();
try {
//这是一个处理路径的步骤替换点.为/,后缀添加his.resourcePattern: "**/*.class"
String packageSearchPath = "classpath*:" + this.resolveBasePackage(basePackage) + '/' + this.resourcePattern;
//这里获取所有的类
Resource[] resources = this.getResourcePatternResolver().getResources(packageSearchPath);
boolean traceEnabled = this.logger.isTraceEnabled();
boolean debugEnabled = this.logger.isDebugEnabled();
Resource[] var7 = resources;
int var8 = resources.length;
//这里Spring对所有的资源进行过滤,读取文件,然后读取该类的接口,父类,父类的接口,验证有没有包含@Component注解(被作为了接口)
for(int var9 = 0; var9 < var8; ++var9) {
Resource resource = var7[var9];
if (traceEnabled) {
this.logger.trace("Scanning " + resource);
}
if (resource.isReadable()) {
try {
MetadataReader metadataReader = this.getMetadataReaderFactory().getMetadataReader(resource);
if (this.isCandidateComponent(metadataReader)) {
ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
sbd.setResource(resource);
sbd.setSource(resource);
if (this.isCandidateComponent((AnnotatedBeanDefinition)sbd)) {
if (debugEnabled) {
this.logger.debug("Identified candidate component class: " + resource);
}
candidates.add(sbd);
} else if (debugEnabled) {
this.logger.debug("Ignored because not a concrete top-level class: " + resource);
}
} else if (traceEnabled) {
this.logger.trace("Ignored because not matching any filter: " + resource);
}
} catch (Throwable var13) {
throw new BeanDefinitionStoreException("Failed to read candidate component class: " + resource, var13);
}
} else if (traceEnabled) {
this.logger.trace("Ignored because not readable: " + resource);
}
}
return candidates;
} catch (IOException var14) {
throw new BeanDefinitionStoreException("I/O failure during classpath scanning", var14);
}
}
可以看到当前的的目录变成了/分隔的**/*.class结尾的字符串
Resource[] resources = this.getResourcePatternResolver().getResources(packageSearchPath);
Resource是Spring定义的一种表示资源的类型,如下
package org.springframework.core.io;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.net.URL;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import org.springframework.lang.Nullable;
public interface Resource extends InputStreamSource {
boolean exists();
default boolean isReadable() {
return this.exists();
}
default boolean isOpen() {
return false;
}
default boolean isFile() {
return false;
}
URL getURL() throws IOException;
URI getURI() throws IOException;
File getFile() throws IOException;
default ReadableByteChannel readableChannel() throws IOException {
return Channels.newChannel(this.getInputStream());
}
long contentLength() throws IOException;
long lastModified() throws IOException;
Resource createRelative(String var1) throws IOException;
@Nullable
String getFilename();
String getDescription();
}
public Resource[] getResources(String locationPattern) throws IOException {
return this.resourcePatternResolver.getResources(locationPattern);
}
public Resource[] getResources(String locationPattern) throws IOException {
//一个检查是否为空的警报类
Assert.notNull(locationPattern, "Location pattern must not be null");
//因为我们是classpath开头的,所以会进入这里
if (locationPattern.startsWith("classpath*:")) {
return this.getPathMatcher().isPattern(locationPattern.substring("classpath*:".length())) ? this.findPathMatchingResources(locationPattern) : this.findAllClassPathResources(locationPattern.substring("classpath*:".length()));
} else {
int prefixEnd = locationPattern.startsWith("war:") ? locationPattern.indexOf("*/") + 1 : locationPattern.indexOf(58) + 1;
return this.getPathMatcher().isPattern(locationPattern.substring(prefixEnd)) ? this.findPathMatchingResources(locationPattern) : new Resource[]{this.getResourceLoader().getResource(locationPattern)};
}
}
this.getPathMatcher()这里是获取了Spring的一个工具类,是一个路径匹配的工具类,
isPattern方法是为了检查该路径是否为一个path路径,如下,包含下面几种字符就被认为是path,为了安全,这样是应该的
public boolean isPattern(@Nullable String path) {
if (path == null) {
return false;
} else {
boolean uriVar = false;
for(int i = 0; i < path.length(); ++i) {
char c = path.charAt(i);
if (c == '*' || c == '?') {
return true;
}
if (c == '{') {
uriVar = true;
} else if (c == '}' && uriVar) {
return true;
}
}
return false;
}
}
执行this.findPathMatchingResources(locationPattern)方法
protected Resource[] findPathMatchingResources(String locationPattern) throws IOException {
//对路径进行处理,保证路径的正常
String rootDirPath = this.determineRootDir(locationPattern);
//取一下结尾的类型信息,其实就是**/*.class
String subPattern = locationPattern.substring(rootDirPath.length());
//获取资源根目录,
//URL [file:/E:/你的项目文件夹/target/classes/com/hhcat/hhcat/],
//涉及的不是很复杂,想了解自己debug一下就行
Resource[] rootDirResources = this.getResources(rootDirPath);
Set<Resource> result = new LinkedHashSet(16);
Resource[] var6 = rootDirResources;
int var7 = rootDirResources.length;
for(int var8 = 0; var8 < var7; ++var8) {
Resource rootDirResource = var6[var8];
Resource rootDirResource = this.resolveRootDirResource(rootDirResource);
URL rootDirUrl = ((Resource)rootDirResource).getURL();
if (equinoxResolveMethod != null && rootDirUrl.getProtocol().startsWith("bundle")) {
URL resolvedUrl = (URL)ReflectionUtils.invokeMethod(equinoxResolveMethod, (Object)null, new Object[]{rootDirUrl});
if (resolvedUrl != null) {
rootDirUrl = resolvedUrl;
}
rootDirResource = new UrlResource(rootDirUrl);
}
//一连串判断,是为了查看是什么类型的文件我们是file开头的
if (rootDirUrl.getProtocol().startsWith("vfs")) {
result.addAll(PathMatchingResourcePatternResolver.VfsResourceMatchingDelegate.findMatchingResources(rootDirUrl, subPattern, this.getPathMatcher()));
}
//走了这里,这里是实际上的扫描包,使用了File类,扫描目录下所有的以之前那个**/*.class格式的文件,然后加入在result中,完成了扫描
else if (!ResourceUtils.isJarURL(rootDirUrl) && !this.isJarResource((Resource)rootDirResource)) {
result.addAll(this.doFindPathMatchingFileResources((Resource)rootDirResource, subPattern));
} else {
result.addAll(this.doFindPathMatchingJarResources((Resource)rootDirResource, rootDirUrl, subPattern));
}
}
if (logger.isTraceEnabled()) {
logger.trace("Resolved location pattern [" + locationPattern + "] to resources " + result);
}
return (Resource[])result.toArray(new Resource[0]);
}
把过滤后的类返回,就完成了扫描。