深入SpringBoot源码(二)getSpringFactoriesInstances方法详解
SpringApplication的getSpringFactoriesInstances方法的第1行代码
在传入的ResourceLoader是null时,调用ClassUtils.getDefaultClassLoader()方法
public static ClassLoader getDefaultClassLoader() {
ClassLoader cl = null;
try {
cl = Thread.currentThread().getContextClassLoader();
}
catch (Throwable ex) {
}
if (cl == null) {
cl = ClassUtils.class.getClassLoader();
if (cl == null) {
try {
cl = ClassLoader.getSystemClassLoader();
}
catch (Throwable ex) {
}
}
}
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方法:
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());
}
}
}
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();
}
};
}
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];
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();
public abstract URL getURL();
public abstract URL getCodeSourceURL();
public abstract InputStream getInputStream() throws IOException;
public abstract int getContentLength() throws IOException;
private InputStream cis;
private synchronized InputStream cachedInputStream() throws IOException {
if (cis == null) {
cis = getInputStream();
}
return cis;
}
public byte[] getBytes() throws IOException {
byte[] b;
InputStream in = cachedInputStream();
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) {
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;
}
public ByteBuffer getByteBuffer() throws IOException {
InputStream in = cachedInputStream();
if (in instanceof ByteBuffered) {
return ((ByteBuffered)in).getByteBuffer();
}
return null;
}
public Manifest getManifest() throws IOException {
return null;
}
public java.security.cert.Certificate[] getCertificates() {
return null;
}
public CodeSigner[] getCodeSigners() {
return null;
}
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);
}
}
|