Cosas que quizás no sepa sobre el principio de inicio de jar en SpringBoot

¡Acostúmbrate a escribir juntos! Este es el sexto día de mi participación en el "Nuggets Daily New Plan · April Update Challenge", haz clic para ver los detalles del evento .

prefacio

Este artículo necesita comprender Classloader, URL y SpringBoot como base.

Creo que ha visto el directorio de formato jar empaquetado por el complemento SpringBoot, pero hoy hablaremos de otra cosa, es decir, cómo SpringBoot carga las clases en el directorio /BOOT-INF/lib/.

En el formato jar estándar, los jars no se pueden anidar ni cargar. Ya lo mencionamos en el artículo anterior. Si su aplicación necesita empaquetarse con otros jars, hay dos soluciones. Una es descomprimir estos jars junto con usted. Los jars principales están juntos, pero la estructura de directorios está dispersa. El segundo es personalizar el Classloader. SpringBoot adopta el segundo método.

El siguiente paso es estudiar el Classloader utilizado para cargar estas clases en SpringBoot, pero después de ingresar el código fuente, encontrará que hay un LaunchedURLClassLoader, lo primero que carga es la clase de inicio de nuestro proyecto y todas las clases posteriores. también son cargados por él.

Pero el problema es que el código fuente principal no está en LaunchedURLClassLoader, es inútil mirarlo, en realidad se puede completar sin LaunchedURLClassLoader, y el código fuente principal está en URLClassLoader.

URLClassLoader sabe que carga la clase de acuerdo con la URL mirando el nombre. Dale una dirección URL del jar y puede cargar la clase en él sin intervención adicional, como la siguiente.

fun main() {
    var classloader = URLClassLoader(arrayOf(URL("file:/home/HouXinLin/project/java/XiaoAi/build/libs/XiaoAi-1.0-SNAPSHOT.jar")))
    println(classloader.loadClass("com.hxl.xiaoai.anno.Action"))
}
复制代码

Entonces, ¿cómo representar la dirección del jar en el jar? Podrías pensar así.

file:/home/HouXinLin/project/java/XiaoAi/build/libs/XiaoAi-1.0-SNAPSHOT.jar/demo.jar
复制代码

Este no es el enfoque correcto. El método anterior no puede cumplir con los requisitos que esperamos, pero hay un protocolo jar en Java, que puede representar un recurso en el jar. La escritura completa es así.

jar:file:/home/HouXinLin/project/java/XiaoAi/build/libs/XiaoAi-1.0-SNAPSHOT.jar!/demo.jar
复制代码

Esta forma de escribir puede leer directamente cualquier recurso de ruta en el jar y convertirlo a InputStream, que es la forma en que el propio Java nos proporciona.

但问题就是URLClassLoader也不识别这样的路径,在说回来,SpringBoot给LaunchedURLClassLoader传递的就是这样的路径,比如你的项目main.jar依赖一个demo.jar,最终SpringBoot插件会把他放入/BOOT-INF/lib/下,在构建出下面这个URL地址,传递给LaunchedURLClassLoader,神奇的是你会发现这货居然能加载,不报错,而你试的时候永远看到的是ClassNotFoundException。

jar:file:/home/main.jar!/BOOT-INF/lib/demo.jar
复制代码

解密

问题就出在URL上,看过他源码的人知道他构造方式有一个URLStreamHandler参数,这个参数用来自定义解析地址中的资源,在需要InputStream的时候根据你的需求自行转换。而SpringBoot中就是利用了这一特点。

但是首先得看URLClassLoader是怎么加载Class的,才能明白自定义URLStreamHandler的作用。

进入他重写的findClass,做的事情也不多,就是使用URLClassPath尝试获取要加载的class的Resource对象,URLClassPath是URLClassLoader构造方法中初始化的,参数也是URL集合,用来给定一个路径,返回一个Resource对象。

但是这个URLClassPath是不能读取jar中jar中的class的,他读取的方法是通过URL的openConnection方法尝试返回一个URLConnection,如果有,则不为空,而谁来返回URLConnection呢?就是SpringBoot中自定义的URLStreamHandler,看URL的openConnection源码,可以看到直接调用的是URLStreamHandler。

public URLConnection openConnection() throws java.io.IOException {
    return handler.openConnection(this);
}
复制代码

La clase de implementación de URLStreamHandler es org.springframework.boot.loader.jar.Handlerque intenta devolver org.springframework.boot.loader.jar.JarURLConnection. Tenga en cuenta que también hay un JarURLConnection en Java. SpringBoot tiene el mismo nombre que él. Esta clase es para leer la clase en el jar en el jar, y el getInputStream reescrito devuelve el flujo de entrada de esta clase.

En este punto, se puede devolver URLClassPath. El siguiente es el objeto que devuelve. Tenga en cuenta que hay un método getBytes en Resource, que se utiliza para devolver la matriz de bytes en este flujo de entrada.

return new Resource() {
   public String getName() {
       return var1;
   }
   public URL getURL() {
       return var3;
   }
   public URL getCodeSourceURL() {
       return Loader.this.base;
   }
   public InputStream getInputStream() throws IOException {
       return var4.getInputStream();
   }
   public int getContentLength() throws IOException {
       return var4.getContentLength();
   }
};
复制代码

Luego ingresará el siguiente método de URLClassLoader. Tenga en cuenta que este método no es exclusivo de Classloader, sino propio de URLClassLoader. Se usa para devolver Clase de Recurso, por lo que está conectado con el resultado anterior.

private Class<?> defineClass(String name, Resource res)
复制代码

código de prueba

Tenga en cuenta que la biblioteca org.springframework.boot.loader ya no está en ninguna dependencia en SpringBoot de forma predeterminada y debe agregarse por sí misma. La dirección de maven es la siguiente

implementation("org.springframework.boot:spring-boot-loader:2.6.1")
复制代码
fun testSpring() {
    val springJarFile =
        org.springframework.boot.loader.jar.JarFile(File("/home/HouXinLin/project/java/blog/build/libs/OneBlog-0.0.1-SNAPSHOT.jar"))
        
    val url: URL = springJarFile.getNestedJarFile(springJarFile.getJarEntry("BOOT-INF/lib/freemarker-2.3.31.jar")).url
   
   println(URLClassPath(arrayOf(url)).getResource("freemarker/cache/AndMatcher.class"))
   
    var classloader = URLClassLoader(arrayOf(url))
    println(classloader.loadClass("freemarker.cache.AndMatcher"))
}
复制代码

Supongo que te gusta

Origin juejin.im/post/7084532763625259038
Recomendado
Clasificación