赞
踩
PathMatchingResourcePatternResolver
URL协议处理,获取绝对路径
/** * Find all class location resources with the given path via the ClassLoader. * Called by {@link #findAllClassPathResources(String)}. * @param path the absolute path within the classpath (never a leading slash) * @return a mutable Set of matching Resource instances * @since 4.1.1 */ // path = classpath*:com/sec/mdm/manage/ protected Set<Resource> doFindAllClassPathResources(String path) throws IOException { Set<Resource> result = new LinkedHashSet<>(16); ClassLoader cl = getClassLoader(); Enumeration<URL> resourceUrls = (cl != null ? cl.getResources(path) : ClassLoader.getSystemResources(path)); while (resourceUrls.hasMoreElements()) { URL url = resourceUrls.nextElement(); result.add(convertClassLoaderURL(url)); } if (!StringUtils.hasLength(path)) { // The above result is likely to be incomplete, i.e. only containing file system references. // We need to have pointers to each of the jar files on the classpath as well... addAllClassLoaderJarRoots(cl, result); } return result; }
对包进行格式化处理,最终执行到PathMatchingResourcePatternResolver listDirectory()方法,获取绝对路径下target的文件
protected File[] listDirectory(File dir) {
File[] files = dir.listFiles();
if (files == null) {
if (logger.isInfoEnabled()) {
logger.info("Could not retrieve contents of directory [" + dir.getAbsolutePath() + "]");
}
return new File[0];
}
Arrays.sort(files, Comparator.comparing(File::getName));
return files;
}
private PathMatcher pathMatcher = new AntPathMatcher();
// /Users/xielianjun/005-code/demo/target/classes/com/meituan/sec/mdm/manage/**/*.class
// /Users/xielianjun/005-code/demo/target/classes/com/meituan/sec/mdm/manage/ItsecDynamicDataSourceConfig.class
if (getPathMatcher().match(fullPattern, currPath)) {
result.add(content);
}
Set<File> result = new LinkedHashSet<>(8);
for (File file : matchingFiles) {
result.add(new FileSystemResource(file));
}
ClassReader读取字节流resource.getInputStream(),然后字节流处理获取className,组装成元数据信息MetadataReader
URL url = new URL("https://start.spring.io/"); // https
URL ftpurl = new URL("ftp://ftp.baidu.com"); // ftp
URL jarurl = new URL("jar://jar.baidu.com"); // jar 协议
URL wetchat = new URL("webchat://..."); // 微信协议
URL dubboURL = new URL("dubbo://..."); // dubbo 协议
URL classpathURL = new URL("classpath:/"); /// classpath
jdk源码包包含各种协议处理的handler
sun.net.www.protocol.file.Handler
文本协议的 Handlersun.net.www.protocol.http.Handler
HTTP URl 的handlersun.net.www.protocol.https.Handler
https 的sun.net.www.protocol.war.Handler
war URl 的handlersun.net.www.protocol.jar.Handler
jar URl 的handlersun.net.www.protocol.ftp.Handler
HTTP URl 的handler通过URL中的构造方法调用ge tURLStreamHandler传入协议反射获取该协议处理器
/** * Returns the Stream Handler. * @param protocol the protocol to use */ static URLStreamHandler getURLStreamHandler(String protocol) { // 省略。。。。 while (handler == null && packagePrefixIter.hasMoreTokens()) { String packagePrefix = packagePrefixIter.nextToken().trim(); try { String clsName = packagePrefix + "." + protocol + ".Handler"; Class<?> cls = null; try { cls = Class.forName(clsName); } catch (ClassNotFoundException e) { ClassLoader cl = ClassLoader.getSystemClassLoader(); if (cl != null) { cls = cl.loadClass(clsName); } } if (cls != null) { handler = (URLStreamHandler)cls.newInstance(); } } catch (Exception e) { // any number of exceptions can get thrown here } } } // 省略。。。。 return handler; }
org.springframework.core.io.Resource
/** * Interface for a resource descriptor that abstracts from the actual * type of underlying resource, such as a file or class path resource. * * <p>An InputStream can be opened for every resource if it exists in * physical form, but a URL or File handle can just be returned for * certain resources. The actual behavior is implementation-specific. * * @author Juergen Hoeller * @since 28.12.2003 * @see #getInputStream() * @see #getURL() * @see #getURI() * @see #getFile() * @see WritableResource * @see ContextResource * @see UrlResource * @see FileUrlResource * @see FileSystemResource * @see ClassPathResource * @see ByteArrayResource * @see InputStreamResource */ public interface Resource extends InputStreamSource { boolean exists(); default boolean isReadable() { return 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(getInputStream()); } long contentLength() throws IOException; long lastModified() throws IOException; Resource createRelative(String relativePath) throws IOException; @Nullable String getFilename(); String getDescription(); }
Spring的Resource和 URL 非常像,也就是封装的比较好而已。
public static void main(String[] args) throws IOException {
// Resource
// FileSystemResource
// ClassPathResource
ResourceLoader resourceLoader = new DefaultResourceLoader();
Resource resource = resourceLoader
.getResource("classpath:/application.properties");
InputStream inputStream = resource.getInputStream();
String content = StreamUtils.copyToString(inputStream, Charset.forName("UTF-8"));
System.out.println(content);
}
META-INF主文件MANIFEST.MF启动类定义为JarLauncher
Manifest-Version: 1.0 Spring-Boot-Classpath-Index: BOOT-INF/classpath.idx Implementation-Title: demo Implementation-Version: 0.0.1-SNAPSHOT Spring-Boot-Layers-Index: BOOT-INF/layers.idx Start-Class: com.meituan.sec.mdm.manage.DemoApplication Spring-Boot-Classes: BOOT-INF/classes/ Spring-Boot-Lib: BOOT-INF/lib/ Build-Jdk-Spec: 1.8 Spring-Boot-Version: 2.4.3 Created-By: Maven Jar Plugin 3.2.0 Main-Class: org.springframework.boot.loader.JarLauncher
A.java 和 B.java放在同一包下,再各种情况下,A如何寻找B,默认是在Eclipse编辑环境下
URL url = A.class.getProtectionDomain().getCodeSource()
.getLocation();System.out.println(url);
结果(未打成JAR时):
file:/C:/Documents and Settings/Administrator.MICROSOF-B715C3/workspace/T/bin/
结果(打成JAR时)
file:/C:/Documents and Settings/Administrator.MICROSOF-B715C3/%e6%a1%8c%e9%9d%a2/1.jar
那么如何加载这个jar中的类呢
JarFile file = new JarFile(url.getFile());
提示:走读的时候时不时结合概览中的时序图,可能好些。
JarLauncher 的 main 方法:
public static void main(String[] args) { // 构造JarLauncher,然后调用它的launch方法 new JarLauncher().launch(args);}
JarLauncher 被构造的时候会调用父类 ExecutableArchiveLauncher 的构造方法。
ExecutableArchiveLauncher 的构造方法内部会去构造 Archive,这里构造了 JarFileArchive。构造 JarFileArchive 的过程中还会构造很多东西,比如 JarFile,Entry …
public abstract class ExecutableArchiveLauncher extends Launcher { private final Archive archive; // 构造器会初始化代表 fat jar 的 Archive public ExecutableArchiveLauncher() { this.archive = createArchive(); } // 由父类 Launcher 实现 protected final Archive createArchive() throws Exception { ProtectionDomain protectionDomain = getClass().getProtectionDomain(); CodeSource codeSource = protectionDomain.getCodeSource(); URI location = (codeSource == null ? null : codeSource.getLocation().toURI()); String path = (location == null ? null : location.getSchemeSpecificPart()); if (path == null) { throw new IllegalStateException("Unable to determine code source archive"); } File root = new File(path); if (!root.exists()) { throw new IllegalStateException( "Unable to determine code source archive from " + root); } // 最终会 new 一个 Arichive,内部生产的 JarFile-->这个逼对FatJar资源加载非常重要 return (root.isDirectory() ? new ExplodedArchive(root) : new JarFileArchive(root)); } @Override protected List<Archive> getClassPathArchives() throws Exception { List<Archive> archives = new ArrayList<Archive>( // 获取内部所有有的 Arichive this.archive.getNestedArchives(new EntryFilter() { @Override public boolean matches(Entry entry) { return isNestedArchive(entry); } })); // 空实现,没用 postProcessClassPathArchives(archives); return archives; } }
JarLauncher 的 launch 方法:
protected void launch(String[] args) { try { // 在系统属性中设置注册了自定义的URL协议处理器:org.springframework.boot.loader.jar.Handler。 // 初始化URL的时候,如果URL中没有指定处理器,会去系统属性中查询 JarFile.registerUrlProtocolHandler(); // getClassPathArchives方法会去找lib目录下对应的第三方依赖JarFileArchive,同时也会找项目自身的JarFileArchive // 根据getClassPathArchives得到的JarFileArchive集合去创建类加载器ClassLoader。这里会构造一个LaunchedURLClassLoader类加载器,这个类加载器继承URLClassLoader,并使用这些JarFileArchive集合的URL构造成URLClassPath // 多说两句句, // 1.URLClassPath这个属性很重要,自定义ClassLoader,findClass就靠它了! // 2.可以关注一下构造LaunchedURLClassLoader时,archive.getUrl方法,这里就涉及到自定义URL协议处理器了,JarFile等。毕竟实现jar in jar功能靠他们这些小罗罗。 ClassLoader classLoader = createClassLoader(getClassPathArchives()); // getMainClass方法会去项目自身的Archive中的Manifest中找出key为Start-Class的类 // 调用重载方法launch launch(args, getMainClass(), classLoader); } catch (Exception ex) { ex.printStackTrace(); System.exit(1); } } // Archive的getMainClass方法,不过由ExecutableArchiveLauncher实现 // 这里会找出Start-Class标识的com.example.jarlauncher.JarlauncherApplication这个类 public String getMainClass() throws Exception { Manifest manifest = getManifest(); String mainClass = null; if (manifest != null) { mainClass = manifest.getMainAttributes().getValue("Start-Class"); } if (mainClass == null) { throw new IllegalStateException( "No 'Start-Class' manifest entry specified in " + this); } return mainClass; } // launch重载方法 protected void launch(String[] args, String mainClass, ClassLoader classLoader) throws Exception { // 设置 LaunchedURLClassLoader 为线程上下文加载器 Thread.currentThread().setContextClassLoader(classLoader); // 创建一个MainMethodRunner 并运行 createMainMethodRunner(mainClass, args, classLoader).run(); }
MainMethodRunner 的 run 方法:
public void run() throws Exception {
// 使用线程上下文类加载器加载主类
Class<?> mainClass = Thread.currentThread().getContextClassLoader()
.loadClass(this.mainClassName);
// 反射执行,至此咱们的应用程序就启动起来啦,good,启动流程走读结束,开心!可以跟面试官扯些了
Method mainMethod = mainClass.getDeclaredMethod("main", String[].class);
mainMethod.invoke(null, new Object[] { this.args });
}
Start-Class 的 main 方法调用之后,内部会构造 Spring 容器,启动内置 Servlet 容器等过程
看看传说中的 LaunchedURLClassLoader 有什么神奇的
// 自定义类加载器会执行获取lib路径进行加载,关注后边代码getUrls,进行遍历加载,就是加载此些jar包
protected boolean isSearchCandidate(Entry entry) {
return entry.getName().startsWith("BOOT-INF/");
}
LaunchedURLClassLoader 重写了 loadClass 方法,走读一下
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { Handler.setUseFastConnectionExceptions(true); try { try { // 在调用 findClass 之前定义 package,确保嵌套JAR清单与包相关联 definePackageIfNecessary(name); } catch (IllegalArgumentException ex) { if (getPackage(name) == null) { throw new AssertionError("Package " + name + " has already been " + "defined but it could not be found"); } } // 调用 父类 loadClass 走正常的加载委派流程 return super.loadClass(name, resolve); } finally { Handler.setUseFastConnectionExceptions(false); } }
基本就是普通的双亲委派过程。
而且 LaunchedURLClassLoader 使用的 findClass 是从父类 URLClassLoader 继承的。
最终 loadClass 会走到 LaunchedURLClassLoader 的父类 URLClassLoader#findClass
protected Class<?> findClass(final String name) throws ClassNotFoundException { final Class<?> result; try { result = AccessController.doPrivileged( new PrivilegedExceptionAction<Class<?>>() { public Class<?> run() throws ClassNotFoundException { // 把类名解析成路径并加上.class后缀 String path = name.replace('.', '/').concat(".class"); // 基于之前得到的第三方jar包依赖以及自己的jar包得到URL数组,进行遍历找出对应类名的资源 // 比如path是org/springframework/boot/loader/JarLauncher.class,它在jar:file:/Users/Format/Develop/gitrepository/springboot-analysis/springboot-executable-jar/target/executable-jar-1.0-SNAPSHOT.jar!/lib/spring-boot-loader-1.3.5.RELEASE.jar!/中被找出 // 那么找出的资源对应的URL为jar:file:/Users/Format/Develop/gitrepository/springboot-analysis/springboot-executable-jar/target/executable-jar-1.0-SNAPSHOT.jar!/lib/spring-boot-loader-1.3.5.RELEASE.jar!/org/springframework/boot/loader/JarLauncher.class // 加载fatjar class的关键部分!!! Resource res = ucp.getResource(path, false); if (res != null) { // 找到了资源 try { return defineClass(name, res); } catch (IOException e) { throw new ClassNotFoundException(name, e); } } else { throw new ClassNotFoundException(name); } } }, acc); } catch (java.security.PrivilegedActionException pae) { throw (ClassNotFoundException) pae.getException(); } if (result == null) { throw new ClassNotFoundException(name); } return result; }
<!-- Spring Boot loader -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-loader</artifactId>
</dependency>
1、Idea配置以 Jar 应用的方式启动
2、配置 Jar 路径,然后 Apply
3、找到启动类 JarLauncher,打上断点,debug 方式启动
参考
https://xie.infoq.cn/article/765f324659d44a5e1eae1ee0c
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。