spring的小Demo
top.itkaoti.test01.TestBean
@Component()
public class TestBean {
}
top.itkaoti.test01.test.TestBean01
@Component
public class TestBean01 {
}
top.itkaoti.test01.SpringTest01
public static void main(String[] args) {
String pacakge = SpringTest01.class.getPackage().getName();
AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(pacakge);
Object bean = annotationConfigApplicationContext.getBean("testBean");
System.out.println(bean.getClass());
Object bean1 = annotationConfigApplicationContext.getBean("testBean01");
System.out.println(bean1.getClass());
}
运行
class top.itkaoti.test01.TestBean
class top.itkaoti.test01.test.TestBean01
我们写了一个spring基于注解的加载方式, 只需要简单的设置一下,spring就能帮我们实现类的加载, 那么spring是如何找到这些包下的类呢?
源码跟踪
--1, SpringTest01.java-->
new AnnotationConfigApplicationContext(pacakge);
--2, AnnotationConfigApplicationContext.java-->
public AnnotationConfigApplicationContext(String... basePackages) {
this();
// 包扫描(点开)
scan(basePackages);
refresh();
}
--3, AnnotationConfigApplicationContext.java-->
public void scan(String... basePackages) {
Assert.notEmpty(basePackages, "At least one base package must be specified");
// 扫描(点开)
this.scanner.scan(basePackages);
}
--4, ClassPathBeanDefinitionScanner.java-->
public int scan(String... basePackages) {
int beanCountAtScanStart = this.registry.getBeanDefinitionCount();
// 一般spring中do开头的方法就是真正干活的(点开)
doScan(basePackages);
// Register annotation config processors, if necessary.
if (this.includeAnnotationConfig) {
AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
}
return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);
}
--5, ClassPathBeanDefinitionScanner.java-->
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) {
// 暂时不关注
...
}
}
return beanDefinitions;
}
--6, ClassPathScanningCandidateComponentProvider.java-->
public Set<BeanDefinition> findCandidateComponents(String basePackage) {
if (this.componentsIndex != null && indexSupportsIncludeFilters()) {
return addCandidateComponentsFromIndex(this.componentsIndex, basePackage);
}
else {
// (点开)
return scanCandidateComponents(basePackage);
}
}
--7, ClassPathScanningCandidateComponentProvider.java-->
private Set<BeanDefinition> scanCandidateComponents(String basePackage) {
Set<BeanDefinition> candidates = new LinkedHashSet<>();
try {
String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
resolveBasePackage(basePackage) + '/' + this.resourcePattern;
// 使用了资源解析器解析(点开)
Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);
}
...
}
--8, PathMatchingResourcePatternResolver.java-->
public Resource[] getResources(String locationPattern) throws IOException {
Assert.notNull(locationPattern, "Location pattern must not be null");
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(点开)
return findPathMatchingResources(locationPattern);
}
else {
...
}
}
else {
...
}
}
--9, PathMatchingResourcePatternResolver.java-->
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) {
...
// 扫描VFS格式文件(不太懂, 不用关注)
if (rootDirUrl.getProtocol().startsWith(ResourceUtils.URL_PROTOCOL_VFS)) {
result.addAll(VfsResourceMatchingDelegate.findMatchingResources(rootDirUrl, subPattern, getPathMatcher()));
}
// 扫描jar包中的class
else if (ResourceUtils.isJarURL(rootDirUrl) || isJarResource(rootDirResource)) {
result.addAll(doFindPathMatchingJarResources(rootDirResource, rootDirUrl, subPattern));
}
else {
// 扫描我们写的class文件(点开)
result.addAll(doFindPathMatchingFileResources(rootDirResource, subPattern));
}
}
return result.toArray(new Resource[0]);
}
--10, PathMatchingResourcePatternResolver.java-->
protected Set<Resource> doFindPathMatchingFileResources(Resource rootDirResource, String subPattern)
throws IOException {
File rootDir;
rootDir = rootDirResource.getFile().getAbsoluteFile();
...
// (点开)
return doFindMatchingFileSystemResources(rootDir, subPattern);
}
--11, PathMatchingResourcePatternResolver.java-->
protected Set<Resource> doFindMatchingFileSystemResources(File rootDir, String subPattern) throws IOException {
// (点开)
Set<File> matchingFiles = retrieveMatchingFiles(rootDir, subPattern);
Set<Resource> result = new LinkedHashSet<>(matchingFiles.size());
for (File file : matchingFiles) {
result.add(new FileSystemResource(file));
}
return result;
}
--12, PathMatchingResourcePatternResolver.java-->
protected Set<File> retrieveMatchingFiles(File rootDir, String pattern) throws IOException {
...
String fullPattern = StringUtils.replace(rootDir.getAbsolutePath(), File.separator, "/");
if (!pattern.startsWith("/")) {
fullPattern += "/";
}
fullPattern = fullPattern + StringUtils.replace(pattern, File.separator, "/");
Set<File> result = new LinkedHashSet<>(8);
// do开头的(点开)
doRetrieveMatchingFiles(fullPattern, rootDir, result);
return result;
}
--13, PathMatchingResourcePatternResolver.java-->
protected void doRetrieveMatchingFiles(String fullPattern, File dir, Set<File> result) throws IOException {
for (File content : listDirectory(dir)) {
String currPath = StringUtils.replace(content.getAbsolutePath(), File.separator, "/");
if (content.isDirectory() && getPathMatcher().matchStart(fullPattern, currPath + "/")) {
// 如果是目录的话
if (!content.canRead()) {
...
}
else {
// 递归调用自己, 获取所有目录下的文件
doRetrieveMatchingFiles(fullPattern, content, result);
}
}
// 判断符合的类
if (getPathMatcher().match(fullPattern, currPath)) {
result.add(content);
}
}
}
如果扫描jar包
// 上面的9
--9, PathMatchingResourcePatternResolver.java-->
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) {
...
// 扫描VFS格式文件(不太懂, 不用关注)
if (rootDirUrl.getProtocol().startsWith(ResourceUtils.URL_PROTOCOL_VFS)) {
result.addAll(VfsResourceMatchingDelegate.findMatchingResources(rootDirUrl, subPattern, getPathMatcher()));
}
// 扫描jar包中的class(点开)
else if (ResourceUtils.isJarURL(rootDirUrl) || isJarResource(rootDirResource)) {
result.addAll(doFindPathMatchingJarResources(rootDirResource, rootDirUrl, subPattern));
}
else {
// 扫描我们写的class文件
result.addAll(doFindPathMatchingFileResources(rootDirResource, subPattern));
}
}
return result.toArray(new Resource[0]);
}
--10, -->
protected Set<Resource> doFindPathMatchingJarResources(Resource rootDirResource, URL rootDirURL, String subPattern)
throws IOException {
URLConnection con = rootDirURL.openConnection();
JarFile jarFile;
String jarFileUrl;
String rootEntryPath;
boolean closeJarFile;
if (con instanceof JarURLConnection) {
// Should usually be the case for traditional JAR files.
JarURLConnection jarCon = (JarURLConnection) con;
ResourceUtils.useCachesIfNecessary(jarCon);
jarFile = jarCon.getJarFile();
jarFileUrl = jarCon.getJarFileURL().toExternalForm();
JarEntry jarEntry = jarCon.getJarEntry();
rootEntryPath = (jarEntry != null ? jarEntry.getName() : "");
closeJarFile = !jarCon.getUseCaches();
}
else {
...
}
...
Set<Resource> result = new LinkedHashSet<>(8);
for (Enumeration<JarEntry> entries = jarFile.entries(); entries.hasMoreElements();) {
JarEntry entry = entries.nextElement();
String entryPath = entry.getName();
if (entryPath.startsWith(rootEntryPath)) {
String relativePath = entryPath.substring(rootEntryPath.length());
if (getPathMatcher().match(subPattern, relativePath)) {
result.add(rootDirResource.createRelative(relativePath));
}
}
}
return result;
}
我们发现spring真的太复杂了, 但是我们要关注重点, 是spring如何找到包下的class, 主要以自定义的和jar包中的类。那我们就写一个简单的包扫描程序
包扫描
/**
* 自定义简单的包扫描工具类, 实现jar包和class文件扫描
*/
public class PackageScanner {
public static Set<Class> scan(String pkg, ClassLoader classLoader) {
if(classLoader == null){
classLoader = PackageScanner.class.getClassLoader();
}
LinkedHashSet<Class> classes = new LinkedHashSet<>();
try {
Enumeration<URL> resources = classLoader.getResources(pkg.replaceAll("\\.", "/"));
while (resources.hasMoreElements()) {
URL url = resources.nextElement();
String protocol = url.getProtocol();
if ("jar".equals(protocol) || "war".equals(protocol) || "zip".equals(protocol)) {
// 加载jar包
loadJarClass(classes, url, classLoader);
} else if ("file".equals(protocol)) {
// 加载文件
loadFileClass(classes, new File(url.getFile()), classLoader);
}
}
} catch (IOException e) {
e.printStackTrace();
}
return classes;
}
// 加载jar
private static void loadJarClass(LinkedHashSet<Class> classes, URL url, ClassLoader classLoader) {
try {
URLConnection urlConnection = url.openConnection();
if(urlConnection instanceof JarURLConnection){
JarFile jarFile = ((JarURLConnection) urlConnection).getJarFile();
Enumeration<JarEntry> entries = jarFile.entries();
while(entries.hasMoreElements()){
JarEntry jarEntry = entries.nextElement();
String name = jarEntry.getName();
if(name.endsWith(".class")){
String fullClassName = name.replace(".class", "").replaceAll("/", ".");
Class aClass = loaderClass(fullClassName, classLoader);
if(aClass != null){
classes.add(aClass);
}
}
}
jarFile.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
// 加载文件
private static void loadFileClass(LinkedHashSet<Class> classes, File file, ClassLoader classLoader) {
if (file.isDirectory()) {
File[] files = file.listFiles();
for (File f : files) {
loadFileClass(classes, f, classLoader);
}
return;
}
String absolutePath = file.getAbsolutePath();
if(!absolutePath.endsWith(".class")){
// 过滤非class文件
return;
}
String file1 = new File(classLoader.getResource("").getFile()).getAbsolutePath()+"\\";
String fullClassName = absolutePath.replace(file1, "").replaceAll("\\\\", ".").replace(".class", "");
Class aClass = loaderClass(fullClassName, classLoader);
if(aClass != null){
classes.add(aClass);
}
}
/*
* 加载class
*/
private static Class loaderClass(String fullClassName, ClassLoader classLoader){
try {
Class<?> aClass = classLoader.loadClass(fullClassName);
return aClass;
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return null;
}
}
总结
spring的包扫描重点就是自己写的和jar包中的类的扫描。
spring的源码查看, 我们一定要把握重点, 那就是
- 略过细节, 把握整体, 整体了解之后, 再把整体拆分开来看细节。
千万不要一步一步debug, 尤其在不了解源码的情况下, 因为你会发现特别多的调用,特别多的类, 你并不知道这个调用是否需要点进去查看其是否有用, 一旦你点进去, 多调几个方法, 就会把你给绕晕了。小逗比在写这篇文章的时候因为看过spring源码的文章, 大概知道spring的整体, 扫包加载为beanDefinition, beanFactory容器加载Bean等等, 在多跑几遍, 大概能知道其调用。
- 如果你知道某一个关键点, 直接在其位置打断点
因为如果我们一旦知道某个关键位置的话, 我们直接打断点, 我们就能看到起调用栈信息, 也就能大概知道其调用流程。