IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> 深入SpringBoot源码(二)getSpringFactoriesInstances方法详解 -> 正文阅读

[Java知识库]深入SpringBoot源码(二)getSpringFactoriesInstances方法详解

SpringApplication的getSpringFactoriesInstances方法的第1行代码

在这里插入图片描述
在这里插入图片描述
在传入的ResourceLoader是null时,调用ClassUtils.getDefaultClassLoader()方法

	public static ClassLoader getDefaultClassLoader() {
		ClassLoader cl = null;
		try {
			cl = Thread.currentThread().getContextClassLoader();
		}
		catch (Throwable ex) {
			// Cannot access thread context ClassLoader - falling back...
		}
		if (cl == null) {
			// No thread context class loader -> use class loader of this class.
			cl = ClassUtils.class.getClassLoader();
			if (cl == null) {
				// getClassLoader() returning null indicates the bootstrap ClassLoader
				try {
					cl = ClassLoader.getSystemClassLoader();
				}
				catch (Throwable ex) {
					// Cannot access system ClassLoader - oh well, maybe the caller can live with null...
				}
			}
		}
		return cl;
	}

在cl = Thread.currentThread().getContextClassLoader()这一步即可满足返回条件
在这里插入图片描述

SpringApplication的getSpringFactoriesInstances方法的第2行代码

在这里插入图片描述
SpringFactoriesLoader(org.springframework.core.io.support.SpringFactoriesLoader)是用于框架内部使用的通用工厂加载机制。SpringFactoriesLoader从“META-INF/spring.factories”文件加载和实例化给定类型的工厂,这些文件可能存在于类路径中的多个 JAR 文件中。spring.factories文件必须是Properties格式,其中key是接口或抽象类的完全限定名称,value是逗号分隔的实现类名称列表。例如:example.MyService = example.MyServiceImpl1, example.MyServiceImpl2(example.MyService是接口的名称, MyServiceImpl1和MyServiceImpl2是两个实现)
SpringFactoriesLoader的成员字段:

	public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";

	private static final Log logger = LogFactory.getLog(SpringFactoriesLoader.class);

	static final Map<ClassLoader, Map<String, List<String>>> cache = new ConcurrentReferenceHashMap<>();

SpringFactoriesLoader的loadFactoryNames方法

SpringFactoriesLoader的loadFactoryNames方法:

	/**
	 * 使用给定的类加载器从"META-INF/spring.factories"加载给定类型的工厂实现的完全限定类名。
	 * 从 Spring Framework 5.3 开始,如果为给定的工厂类型多次发现特定的实现类名称,则将忽略重复项。
	 * @param factoryType 代表工厂的接口或抽象类
	 * @param classLoader 用于加载资源的 ClassLoader;可以为null以使用默认值
	 * @throws IllegalArgumentException 如果加载工厂名称时发生错误
	 */
	public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
		ClassLoader classLoaderToUse = classLoader;
		if (classLoaderToUse == null) {
			classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
		}
		String factoryTypeName = factoryType.getName();
		return loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
	}

SpringApplication的getSpringFactoriesInstances方法的第2行代码调用SpringFactoriesLoader的loadFactoryNames方法传入的factoryType、classLoader参数值如下:
在这里插入图片描述
SpringFactoriesLoader的loadFactoryNames方法最终调用loadSpringFactories方法:
在这里插入图片描述
SpringFactoriesLoader的loadSpringFactories方法:

	private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
		Map<String, List<String>> result = cache.get(classLoader);
		if (result != null) {
			return result;
		}

		result = new HashMap<>();
		try {
			Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
			while (urls.hasMoreElements()) {
				URL url = urls.nextElement();
				UrlResource resource = new UrlResource(url);
				Properties properties = PropertiesLoaderUtils.loadProperties(resource);
				for (Map.Entry<?, ?> entry : properties.entrySet()) {
					String factoryTypeName = ((String) entry.getKey()).trim();
					String[] factoryImplementationNames =
							StringUtils.commaDelimitedListToStringArray((String) entry.getValue());
					for (String factoryImplementationName : factoryImplementationNames) {
						result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>())
								.add(factoryImplementationName.trim());
					}
				}
			}

			// Replace all lists with unmodifiable lists containing unique elements
			result.replaceAll((factoryType, implementations) -> implementations.stream().distinct()
					.collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList)));
			cache.put(classLoader, result);
		}
		catch (IOException ex) {
			throw new IllegalArgumentException("Unable to load factories from location [" +
					FACTORIES_RESOURCE_LOCATION + "]", ex);
		}
		return result;
	}

在这里插入图片描述
SpringApplication的getSpringFactoriesInstances方法在调用SpringFactoriesLoader的loadSpringFactories方法时传入了classLoader,让SpringFactoriesLoader把该classLoader作为key在其成员字段cache(一个ConcurrentReferenceHashMap,key类型是ClassLoader,value是一个Map<String,List<String>>,value的key是factoryTypeName,value的value是factoryImplementationNames)中取出对应的value。由于这是首次调用,所以取出的value为null,需要对该key映射的value创建并赋值:
在这里插入图片描述

ClassLoader的getResources方法

Enumeration urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
这里的classLoader类型是AppClassLoader:
在这里插入图片描述
AppClassLoader类本身是没有getResources(String)方法的,使用IDEA查看AppClassLoader类的结构视图(勾选Show Inherited选项便可查看该类继承的所有方法)可以发现调用AppClassLoader的getResources(String)方法是调用ClassLoader的getResources(String)方法
在这里插入图片描述
AppClassLoader的parent为ExtClassLoader,将调用ExtClassLoader的getResources(String)方法
在这里插入图片描述
调用ExtClassLoader的getResources(String)方法也是调用ClassLoader的getResources(String)方法
在这里插入图片描述
ExtClassLoader的parent为null,将调用getBoostrapResources(String)方法
在这里插入图片描述
getBoostrapResources方法将从Java虚拟机的内置类加载器中查找资源(此时的调用链路为SpringApplication.getSpringFactoriesInstances --> SpringFactoriesLoader.loadFactoryNames --> SpringFactoriesLoader.loadSpringFactories --> AppClassLoader.getResources --> ExtClassLoader.getResources --> ClassLoader.getBootstrapResources)

    private static Enumeration<URL> getBootstrapResources(String name)
        throws IOException
    {
        final Enumeration<Resource> e =
            getBootstrapClassPath().getResources(name);
        return new Enumeration<URL> () {
            public URL nextElement() {
                return e.nextElement().getURL();
            }
            public boolean hasMoreElements() {
                return e.hasMoreElements();
            }
        };
    }
	
	//返回用于查找系统资源的 URLClassPath。
    static URLClassPath getBootstrapClassPath() {
        return sun.misc.Launcher.getBootstrapClassPath();
    }

通过ClassLoader的getBootstrapClassPath()方法获得用于查找系统资源的URLClassPath。

sun.misc.Launcher.getBootstrapClassPath()

在这里插入图片描述
sun.misc.Launcher.getBootstrapClassPath()方法返回的是sun.misc.Launcher的静态内部类BootClassPathHolder的成员字段URLClassPath bcp:

    private static class BootClassPathHolder {
        static final URLClassPath bcp;
        static {
            URL[] urls;
            if (bootClassPath != null) {
                urls = AccessController.doPrivileged(
                    new PrivilegedAction<URL[]>() {
                        public URL[] run() {
                            File[] classPath = getClassPath(bootClassPath);
                            int len = classPath.length;
                            Set<File> seenDirs = new HashSet<File>();
                            for (int i = 0; i < len; i++) {
                                File curEntry = classPath[i];
                                // Negative test used to properly handle
                                // nonexistent jars on boot class path
                                if (!curEntry.isDirectory()) {
                                    curEntry = curEntry.getParentFile();
                                }
                                if (curEntry != null && seenDirs.add(curEntry)) {
                                    MetaIndex.registerDirectory(curEntry);
                                }
                            }
                            return pathToURLs(classPath);
                        }
                    }
                );
            } else {
                urls = new URL[0];
            }
            bcp = new URLClassPath(urls, factory, null);
            bcp.initLookupCache(null);
        }
    }

bootClassPath作为Launcher的成员静态字段,初始值取key为"sun.boot.class.path"的value。
在这里插入图片描述
在BootClassPathHolder静态代码块执行时,bootClassPath已经有值了(我使用的是Zulu JDK)
在这里插入图片描述
执行bcp = new URLClassPath(urls, factory, null),URLClassPath(URL[] urls,
URLStreamHandlerFactory factory,
AccessControlContext acc)方法的三个参数含义:urls – 用于搜索类和资源的目录和 JAR 文件 URL、factory – 创建新 URL 时使用的 URLStreamHandlerFactory、acc – 加载类和资源时使用的上下文,可以为 null。

URLClassPath的getResources方法

Resource(这是sun.misc.Resource 不是org.springframework.core.io.Resource)用于表示已从类路径加载的资源

public abstract class Resource {
    /**
     * 返回资源的名称。
     */
    public abstract String getName();

    /**
     * 返回资源的URL。
     */
    public abstract URL getURL();

    /**
     * 返回资源的CodeSource URL。
     */
    public abstract URL getCodeSourceURL();

    /**
     * 返回用于读取资源数据的InputStream。
     */
    public abstract InputStream getInputStream() throws IOException;

    /**
     * 返回资源数据的长度,如果未知则返回-1。
     */
    public abstract int getContentLength() throws IOException;

    private InputStream cis;

    /* 在getByteBuffer之后调用getBytes时缓存结果。 */
    private synchronized InputStream cachedInputStream() throws IOException {
        if (cis == null) {
            cis = getInputStream();
        }
        return cis;
    }

    /**
     * 以字节数组的形式返回资源数据。
     */
    public byte[] getBytes() throws IOException {
        byte[] b;
        // 在内容长度之前获取流,以便FileNotFoundException可以向上传播而不会被过早捕获
        InputStream in = cachedInputStream();

        // 此代码已被混淆以防止中断。
        // 即使线程在加载资源时被中断,IO也不应该中止,因此必须小心重试,
        // 只有在重试导致其他IO异常时才会失败。

        boolean isInterrupted = Thread.interrupted();
        int len;
        for (;;) {
            try {
                len = getContentLength();
                break;
            } catch (InterruptedIOException iioe) {
                Thread.interrupted();
                isInterrupted = true;
            }
        }

        try {
            b = new byte[0];
            if (len == -1) len = Integer.MAX_VALUE;
            int pos = 0;
            while (pos < len) {
                int bytesToRead;
                if (pos >= b.length) { // Only expand when there's no room
                    bytesToRead = Math.min(len - pos, b.length + 1024);
                    if (b.length < pos + bytesToRead) {
                        b = Arrays.copyOf(b, pos + bytesToRead);
                    }
                } else {
                    bytesToRead = b.length - pos;
                }
                int cc = 0;
                try {
                    cc = in.read(b, pos, bytesToRead);
                } catch (InterruptedIOException iioe) {
                    Thread.interrupted();
                    isInterrupted = true;
                }
                if (cc < 0) {
                    if (len != Integer.MAX_VALUE) {
                        throw new EOFException("Detect premature EOF");
                    } else {
                        if (b.length != pos) {
                            b = Arrays.copyOf(b, pos);
                        }
                        break;
                    }
                }
                pos += cc;
            }
        } finally {
            try {
                in.close();
            } catch (InterruptedIOException iioe) {
                isInterrupted = true;
            } catch (IOException ignore) {}

            if (isInterrupted) {
                Thread.currentThread().interrupt();
            }
        }
        return b;
    }

    /**
     * 将 Resource 数据作为 ByteBuffer 返回,但前提是输入流是在 ByteBuffer 之上实现的。
     * 否则返回null。
     */
    public ByteBuffer getByteBuffer() throws IOException {
        InputStream in = cachedInputStream();
        if (in instanceof ByteBuffered) {
            return ((ByteBuffered)in).getByteBuffer();
        }
        return null;
    }

    /**
     * 返回资源的清单,如果没有则返回 null。
     */
    public Manifest getManifest() throws IOException {
        return null;
    }

    /**
     * 返回资源的证书,如果没有则返回 null。
     */
    public java.security.cert.Certificate[] getCertificates() {
        return null;
    }

    /**
     * 返回资源的代码签名者,如果没有,则返回 null。
     */
    public CodeSigner[] getCodeSigners() {
        return null;
    }

    /**
     * 在数据检索期间返回非致命读取错误(如果有)。例如,读取 JAR 条目时出现 CRC 错误。
     */
    public Exception getDataError() {
        return null;
    }
}

Resource的层次性关系(子类型)
在这里插入图片描述
URLClassPath的getResources方法用于查找URL搜索路径上具有给定名称的所有资源并返回资源对象的枚举。

    public Enumeration<Resource> getResources(final String name) {
        return getResources(name, true);
    }
    
    public Enumeration<Resource> getResources(final String name,
                                    final boolean check) {
        return new Enumeration<Resource>() {
            private int index = 0;
            private int[] cache = getLookupCache(name);
            private Resource res = null;

            private boolean next() {
                if (res != null) {
                    return true;
                } else {
                    Loader loader;
                    while ((loader = getNextLoader(cache, index++)) != null) {
                        res = loader.getResource(name, check);
                        if (res != null) {
                            return true;
                        }
                    }
                    return false;
                }
            }

            public boolean hasMoreElements() {
                return next();
            }

            public Resource nextElement() {
                if (!next()) {
                    throw new NoSuchElementException();
                }
                Resource r = res;
                res = null;
                return r;
            }
        };
    }

注意,URLClassPath的getResources方法返回的是一个匿名Enumeration<Resource>对象,需要外部调用其hasMoreElements()方法或者nextElement()方法去触发next()方法进行核心操作。
在这里插入图片描述
ClassLoader的getBootstrapResources方法返回的是一个匿名的Enumeration<URL>对象,实质上通过调用Enumeration<Resource>对象的hasMoreElements()方法或者nextElement()方法去触发其next()方法。

回到ExtClassLoader.getResources(调用链路为SpringApplication.getSpringFactoriesInstances --> SpringFactoriesLoader.loadFactoryNames --> SpringFactoriesLoader.loadSpringFactories --> AppClassLoader.getResources --> ExtClassLoader.getResources
此时tmp内容如下:
在这里插入图片描述
接着调用findResources方法
在这里插入图片描述
在ExtClassLoader中调用findResources方法是调用URLClassLoader的findResources方法
在这里插入图片描述
URLClassLoader的findResources(String)方法返回一个 URL 枚举,表示 URL 搜索路径上具有指定名称的所有资源:

    public Enumeration<URL> findResources(final String name)
        throws IOException
    {
        final Enumeration<URL> e = ucp.findResources(name, true);

        return new Enumeration<URL>() {
            private URL url = null;

            private boolean next() {
                if (url != null) {
                    return true;
                }
                do {
                    URL u = AccessController.doPrivileged(
                        new PrivilegedAction<URL>() {
                            public URL run() {
                                if (!e.hasMoreElements())
                                    return null;
                                return e.nextElement();
                            }
                        }, acc);
                    if (u == null)
                        break;
                    url = ucp.checkURL(u);
                } while (url == null);
                return url != null;
            }

            public URL nextElement() {
                if (!next()) {
                    throw new NoSuchElementException();
                }
                URL u = url;
                url = null;
                return u;
            }

            public boolean hasMoreElements() {
                return next();
            }
        };
    }

此时ExtClassLoader的getResources执行完毕
在这里插入图片描述
回到AppClassLoader的getResources方法,tmp[0]的内容刚好是ExtClassLoader的getResources执行的结果
在这里插入图片描述
在AppClassLoader中调用findResources方法也是调用URLClassLoader的findResources方法
在这里插入图片描述
回到SpringFactoriesLoader的loadSpringFactories方法中,此时的urls内容如下:
在这里插入图片描述
urls.hasMoreElements()方法调用的是urls.next()方法,urls.next()方法将会while循环遍历enums,第一次循环遍历到的是刚才AppClassLoader的getResources方法中的tmp[0],也就是ExtClassLoader的getResources执行的结果,并且调用其hasMoreElements()方法。最终将会调用到URLClassPath的getResources方法所创建出来的匿名Enumeration<Resource>对象的next方法(后续的遍历操作也是同样的方式,会最终调用到匿名Enumeration对象的next方法):
在这里插入图片描述
接着SpringFactoriesLoader通过PropertiesLoaderUtils.loadProperties(resource)加载资源属性,并且对factoryTypeName和factoryImplementationNames的映射进行缓存
在这里插入图片描述
回到SpringFactoriesLoader的loadFactoryNames方法
在这里插入图片描述
由于我的项目引入的JAR包中spring.factories文件没有key是org.springframework.boot.BootstrapRegistryInitializer的键值对(见本专栏的第一章),而在SpringApplication的构造方法调用链达到SpringFactoriesLoader的loadFactoryNames方法时传入的factoryType是org.springframework.boot.BootstrapRegistryInitializer,所以这里返回的会是Collections.emptyList()。
getSpringFactoriesInstances方法第二行代码执行后获得的Set集合是空的。
在这里插入图片描述

SpringApplication的getSpringFactoriesInstances方法的第3行代码

在这里插入图片描述
createSpringFactoriesInstances方法用于创建Spring工厂实例(spring.factories的value的类对象)

	private <T> List<T> createSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes,
			ClassLoader classLoader, Object[] args, Set<String> names) {
		List<T> instances = new ArrayList<>(names.size());
		for (String name : names) {
			try {
				Class<?> instanceClass = ClassUtils.forName(name, classLoader);
				Assert.isAssignable(type, instanceClass);
				Constructor<?> constructor = instanceClass.getDeclaredConstructor(parameterTypes);
				T instance = (T) BeanUtils.instantiateClass(constructor, args);
				instances.add(instance);
			}
			catch (Throwable ex) {
				throw new IllegalArgumentException("Cannot instantiate " + type + " : " + name, ex);
			}
		}
		return instances;
	}

还是由于我的项目引入的JAR包中spring.factories文件没有key是org.springframework.boot.BootstrapRegistryInitializer的键值对,所以这里也就没有创建相关实例了
在这里插入图片描述

SpringApplication的getSpringFactoriesInstances方法的第4行代码

在这里插入图片描述
AnnotationAwareOrderComparator是OrderComparator的扩展,它支持 Spring 的org.springframework.core.Ordered接口以及@Order和@Priority注释, Ordered实例提供的顺序值覆盖静态定义的注释值(如果有)。
AnnotationAwareOrderComparator的sort方法使用默认AnnotationAwareOrderComparator对给定列表进行排序。

	public static void sort(List<?> list) {
		if (list.size() > 1) {
			list.sort(INSTANCE);
		}
	}
  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2022-04-14 23:36:51  更:2022-04-14 23:38:32 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 4:51:42-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码