当前位置:   article > 正文

Springboot IDEA启动分析_idea springboot2 启动 分析工具

idea springboot2 启动 分析工具

链路分析

核心加载类PathMatchingResourcePatternResolver

PathMatchingResourcePatternResolver

IDEA启动分析

idea启动后对包进行格式化处理

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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

获取路径下的File[]文件

对包进行格式化处理,最终执行到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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

然后进行antPathMatcher匹配class文件

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);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

返回Set集合

Set<File> result = new LinkedHashSet<>(8);
  • 1

包装成FileSystemResource

for (File file : matchingFiles) {
			result.add(new FileSystemResource(file));
}
  • 1
  • 2
  • 3

循环Resource包装成MetadataReader

ClassReader读取字节流resource.getInputStream(),然后字节流处理获取className,组装成元数据信息MetadataReader

jar -jar命令启动

Java资源管理URL

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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

jdk源码包包含各种协议处理的handler

  • URLStreamhandler
    • sun.net.www.protocol.file.Handler 文本协议的 Handler
    • sun.net.www.protocol.http.Handler HTTP URl 的handler
    • sun.net.www.protocol.https.Handler https 的
    • sun.net.www.protocol.war.Handler war URl 的handler
    • sun.net.www.protocol.jar.Handler jar URl 的handler
    • sun.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;

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36

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();

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53

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);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

启动类JarLauncher

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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
不同环境获取文件路径URL

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 的执行过程

提示:走读的时候时不时结合概览中的时序图,可能好些。

JarLauncher 的 main 方法:

public static void main(String[] args) {    // 构造JarLauncher,然后调用它的launch方法    new JarLauncher().launch(args);}
  • 1

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;
	}

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41

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();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44

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 });
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

Start-Class 的 main 方法调用之后,内部会构造 Spring 容器,启动内置 Servlet 容器等过程

关于自定义的类加载器

看看传说中的 LaunchedURLClassLoader 有什么神奇的

// 自定义类加载器会执行获取lib路径进行加载,关注后边代码getUrls,进行遍历加载,就是加载此些jar包
protected boolean isSearchCandidate(Entry entry) {
    return entry.getName().startsWith("BOOT-INF/");
}
  • 1
  • 2
  • 3
  • 4

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);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

基本就是普通的双亲委派过程。

而且 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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34

jar启动调试

<!-- Spring Boot loader		-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-loader</artifactId>
</dependency>
  • 1
  • 2
  • 3
  • 4
  • 5

1、Idea配置以 Jar 应用的方式启动

img

2、配置 Jar 路径,然后 Apply

img

3、找到启动类 JarLauncher,打上断点,debug 方式启动

img

参考

https://xie.infoq.cn/article/765f324659d44a5e1eae1ee0c

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/从前慢现在也慢/article/detail/66387
推荐阅读
相关标签
  

闽ICP备14008679号