なぜSpringBootのjarは直接実行できるのですか?

12079 ワード

過去のヒット記事:
1,《往期精选优秀博文都在这里了!》2、早くSpring Bootに埋め込み監視カメラを作ってあげましょう.3、驚いた.Spring Bootがこんなにメモリを消費するなんて!4、Spring Boot注釈大全、ワンタッチコレクション!5、MySQLのLimit性能が悪い?本当に使えないの?5、キャッシュ破壊防止?ブロンフィルターを使ってください!ソース|https://urlify.cn/uQvIna
SpringBootは、プログラムを実行可能なjarパッケージにパッケージ化するためのプラグインspring-boot-maven-pluginを提供しています.pomファイルにこのプラグインを追加すればいいです.

    
        
            org.springframework.boot
            spring-boot-maven-plugin
        
    


生成されたexecutable-jar-1.0-SNAPSHOTをパッケージ化する.JAr内部の構造は以下の通りです.
├── META-INF
│   ├── MANIFEST.MF
│   └── maven
│       └── spring.study
│           └── executable-jar
│               ├── pom.properties
│               └── pom.xml
├── lib
│   ├── aopalliance-1.0.jar
│   ├── classmate-1.1.0.jar
│   ├── spring-boot-1.3.5.RELEASE.jar
│   ├── spring-boot-autoconfigure-1.3.5.RELEASE.jar
│   ├── ...
├── org
│   └── springframework
│       └── boot
│           └── loader
│               ├── ExecutableArchiveLauncher$1.class
│               ├── ...
└── spring
    └── study
        └── executablejar
            └── ExecutableJarApplication.class

jarパッケージを直接実行することでプログラムを起動できます.java -jar executable-jar-1.0-SNAPSHOT.jar
パッケージ化fat jarの内部には4つのファイルタイプがあります.
  • META-INFフォルダ:MANIFEST.MF jarパケットを記述するための情報
  • libディレクトリ:springbootなどのサードパーティ依存jarパケットを配置するjarパケット
  • spring boot loader関連コード
  • モジュール自体のコード
  • MANIFEST.MFファイルの内容:
    Manifest-Version: 1.0
    Implementation-Title: executable-jar
    Implementation-Version: 1.0-SNAPSHOT
    Archiver-Version: Plexus Archiver
    Built-By: Format
    Start-Class: spring.study.executablejar.ExecutableJarApplication
    Implementation-Vendor-Id: spring.study
    Spring-Boot-Version: 1.3.5.RELEASE
    Created-By: Apache Maven 3.2.3
    Build-Jdk: 1.8.0_20
    Implementation-Vendor: Pivotal Software, Inc.
    Main-Class: org.springframework.boot.loader.JarLauncher
    

    Main-Classはorgですspringframework.boot.loader.JarLauncherは、Java-jarを使用してjarパッケージを実行すると、SpringApplicationではなくJarLauncherのmainメソッドを呼び出します.
    ではJarLauncherというクラスの役割は何ですか?
    これはSpringBoot内部で提供されるツールSpring Boot Loaderが提供するApplicationクラスを実行するためのツールクラスです(fat jar内部にspring loader関連のコードがあるのは、ここで使用されているからです).Spring Boot Loaderに相当し、SpringBootパッケージを実行するためのjarの標準を提供します.
    Spring Boot Loader抽象的なクラス
    抽象クラスLauncher:アプリケーションを起動するための各種Launcherの基礎抽象クラス.Archiveとの併用現在、JarLauncher、WarLauncher、PropertiesLauncherの3つの実装があります.
    Archive:アーカイブファイルの基礎抽象クラス.JarFileArchiveはjarパッケージファイルの抽象です.getUrlがこのArchiveに対応するURLを返すなど、いくつかの方法を提供しています.getManifestメソッドは、Manifestデータなどを取得します.ExplodedArchiveはファイルディレクトリの抽象です
    JarFile:jarパッケージのパッケージで、JarFileArchiveごとに1つのJarFileに対応します.JarFileが構築されると内部構造が解析され、jarパッケージ内の各ファイルやフォルダが取得され、これらのファイルやフォルダはEntryにカプセル化され、JarFileArchiveにも格納されます.Entryがjarの場合、JarFileArchiveとして解析されます.
    例えば、JarFileArchiveに対応するURLは次のとおりです.
    jar:file:/Users/format/Develop/gitrepository/springboot-analysis/springboot-executable-jar/target/executable-jar-1.0-SNAPSHOT.jar!/
    

    対応するJarFileは次のとおりです.
    /Users/format/Develop/gitrepository/springboot-analysis/springboot-executable-jar/target/executable-jar-1.0-SNAPSHOT.jar
    

    このJarFileにはEntryがたくさんあります.例えば、
    META-INF/
    META-INF/MANIFEST.MF
    spring/
    spring/study/
    ....
    spring/study/executablejar/ExecutableJarApplication.class
    lib/spring-boot-starter-1.3.5.RELEASE.jar
    lib/spring-boot-1.3.5.RELEASE.jar
    ...
    

    JarFileArchiveの内部にあるjarに依存するURL(SpringBootはorg.springframework.boot.loader.jar.Handlerプロセッサを使用してこれらのURLを処理します):
    jar:file:/Users/Format/Develop/gitrepository/springboot-analysis/springboot-executable-jar/target/executable-jar-1.0-SNAPSHOT.jar!/lib/spring-boot-starter-web-1.3.5.RELEASE.jar!/
    
    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
    

    jarパッケージにjarが含まれているか、jarパッケージにjarパッケージのclassファイルが含まれている場合に使用されます!/区切る、この方式はorgのみである.springframework.boot.loader.jar.SpringBoot内部に拡張されたURLプロトコルであるHandlerは処理できます.
    JarLauncherの実行手順
    JarLauncherのmainメソッド:
    public static void main(String[] args) {
        //   JarLauncher,      launch  。         
        new JarLauncher().launch(args);
    }  
    

    JarLauncherが構築されると親ExecutableArchiveLauncherの構築方法が呼び出されます.
    ExecutableArchiveLauncherの構造方法内部はArchiveを構築し,ここではJarFileArchiveを構築する.JarFileArchiveを構築する過程で、JarFile、Entryなど多くのものを構築します.
    JarLauncher launch  :
    protected void launch(String[] args) {
      try {
        //                URL   :org.springframework.boot.loader.jar.Handler。  URL        ,         
        JarFile.registerUrlProtocolHandler();
        // getClassPathArchives      lib           JarFileArchive,         JarFileArchive
        //   getClassPathArchives   JarFileArchive         ClassLoader。       LaunchedURLClassLoader    ,        URLClassLoader,     JarFileArchive   URL   URLClassPath
        // LaunchedURLClassLoader                JarLauncher     
        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  
    //      spring.study.executablejar.ExecutableJarApplication   
    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 {
          //     MainMethodRunner,  args Start-Class    
      Runnable runner = createMainMethodRunner(mainClass, args, classLoader);
          //      
      Thread runnerThread = new Thread(runner);
          //             ,    
      runnerThread.setContextClassLoader(classLoader);
      runnerThread.setName(Thread.currentThread().getName());
      runnerThread.start();
    }
    

    MainMethodRunnerのrunメソッド:
    @Override
    public void run() {
      try {
        //   Start-Class     
        Class> mainClass = Thread.currentThread().getContextClassLoader()
            .loadClass(this.mainClassName);
        //   main  
        Method mainMethod = mainClass.getDeclaredMethod("main", String[].class);
        //   main     ,    
        if (mainMethod == null) {
          throw new IllegalStateException(
              this.mainClassName + " does not have a main method");
        }
        //   
        mainMethod.invoke(null, new Object[] { this.args });
      }
      catch (Exception ex) {
        UncaughtExceptionHandler handler = Thread.currentThread()
            .getUncaughtExceptionHandler();
        if (handler != null) {
          handler.uncaughtException(Thread.currentThread(), ex);
        }
        throw new RuntimeException(ex);
      }
    }
    

    Start-Classのmainメソッド呼び出しの後、内部にSpringコンテナが構築され、内蔵サーブレットコンテナなどのプロセスが開始されます.これらの過程はすでに分析した.
    カスタムクラスローダについてLaunchedURLClassLoader
    LaunchedURLClassLoaderはloadClassメソッドを書き換えました.つまり、デフォルトのクラスロード方式を変更しました(クラスがロードされているかどうかを見てみましょう.この部分は変更されません.その後、本当にクラスをロードするルールが変更されました.親ローダから直接ロードするのではありません).LaunchedURLClassLoaderは、独自のクラス・ロード・ルールを定義します.
    private Class> doLoadClass(String name) throws ClassNotFoundException {
    
      // 1) Try the root class loader
      try {
        if (this.rootClassLoader != null) {
          return this.rootClassLoader.loadClass(name);
        }
      }
      catch (Exception ex) {
        // Ignore and continue
      }
    
      // 2) Try to find locally
      try {
        findPackage(name);
        Class> cls = findClass(name);
        return cls;
      }
      catch (Exception ex) {
        // Ignore and continue
      }
    
      // 3) Use standard loading
      return super.loadClass(name, false);
    }
    

    ロード規則:
  • ルート・クラス・ローダが存在する場合、そのロード・メソッドが呼び出されます.ルートクラスロードはExtClassLoader
  • です
  • LaunchedURLClassLoader自身を呼び出すfindClassメソッド、すなわちURLClassLoaderのfindClassメソッド
  • 親クラスのloadClassメソッドを呼び出します.つまり、デフォルトのクラスロード順序(BootstrapClassLoaderから下を探します)
  • を実行します.
    LaunchedURLClassLoader自身のfindClassメソッド:
    protected Class> findClass(final String name)
         throws ClassNotFoundException
    {
        try {
            return AccessController.doPrivileged(
                new PrivilegedExceptionAction>() {
                    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
                        Resource res = ucp.getResource(path, false);
                        if (res != null) { //      
                            try {
                                return defineClass(name, res);
                            } catch (IOException e) {
                                throw new ClassNotFoundException(name, e);
                            }
                        } else { //            ClassNotFoundException  
                            throw new ClassNotFoundException(name);
                        }
                    }
                }, acc);
        } catch (java.security.PrivilegedActionException pae) {
            throw (ClassNotFoundException) pae.getException();
        }
    }
    

    次はLaunchedURLClassLoaderのテストです.
    //   org.springframework.boot.loader.jar.Handler URL     
    JarFile.registerUrlProtocolHandler();
    //   LaunchedURLClassLoader    ,     2 URL,    jar     spring-boot-loader spring-boot,   "!/"   ,  org.springframework.boot.loader.jar.Handler     
    LaunchedURLClassLoader classLoader = new LaunchedURLClassLoader(
            new URL[] {
                    new 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!/")
                    , new URL("jar:file:/Users/Format/Develop/gitrepository/springboot-analysis/springboot-executable-jar/target/executable-jar-1.0-SNAPSHOT.jar!/lib/spring-boot-1.3.5.RELEASE.jar!/")
            },
            LaunchedURLClassLoaderTest.class.getClassLoader());
    
    //    
    //  2                (URLClassLoader findClass  )
    classLoader.loadClass("org.springframework.boot.loader.JarLauncher");
    classLoader.loadClass("org.springframework.boot.SpringApplication");
    //               ApplicationClassLoader    
    classLoader.loadClass("org.springframework.boot.autoconfigure.web.DispatcherServletAutoConfiguration");
    

    Spring Boot Loaderの役割
    SpringBootは実行可能なjarパッケージに独自のルールを定義している.例えば、サードパーティがjarパッケージに依存する/libディレクトリの下で、jarパッケージのURLパスはカスタムルールを使用し、このルールはorgを使用する必要がある.springframework.boot.loader.jar.Handlerプロセッサ処理.そのMain-ClassはJarLauncherを使用し、warパッケージの場合はWarLauncherを使用して実行します.これらのLauncherの内部には、別のスレッドがあり、カスタムSpringApplicationクラスが起動します.
    これらの特性はspring-boot-maven-pluginプラグインによってパッケージ化されます.
          :
    1,《          !           !》
    2,      ?       !
    3,Java 14    !     !
    
    4,start.aliyun.com    !    Spring Cloud  
    5,      B “    ”,        ?
    6 ,    41 SpringBoot   ,     !
    7 ,    CPU 100%?        !
    8 ,   ,                  !
    
    9,6       ,       !
    10 ,            ,     Bug!