스프링이 외부 jar를 불러오는 방법

Byung Seon Kang·2023년 10월 15일
2

스프링

목록 보기
2/2

서론

  • Spring security같은 것들 dependency로 설정해주면 어떤 설정 안해줘도 바로 실행을 한다.
  • 그래서 Spring과 jar가 어떻게 연결되는지 이해가 안되어 알아보기로 했다.
    • 도대체 gradle이나 maven에서 가져온 jar들을 어떤 과정으로 연결해서 실행시키는가?

설명

  • Jar 파일을 압축해제해보자
    • 아래와같은 폴더 구조를 가지고 있다

      .
      ├── BOOT-INF
      │   ├── classes
      │   │   ├── application-dev.yml
      │   │   ├── application-local.yml
      │   │   ├── application-test.yml
      │   │   ├── application.yml
      │   │   ├── com
      │   │   │   └── example
      │   │   ├── data.sql
      │   │   ├── log4j2-dev.xml
      │   │   └── log4j2-local.xml
      │   ├── classpath.idx
      │   ├── layers.idx
      │   └── lib
      │       ├── HikariCP-5.0.1.jar
      │       ├── angus-activation-2.0.1.jar
      │       ├── spring-data-commons-3.1.3.jar
      │       ├── spring-data-jpa-3.1.3.jar
      │       ├── spring-data-keyvalue-3.1.3.jar
      				...
      │       ├── spring-web-6.0.11.jar
      │       ├── spring-webmvc-6.0.11.jar
      │       └── webjars-locator-core-0.52.jar
      ├── META-INF
      │   └── MANIFEST.MF
      ├── jartest-0.0.1-SNAPSHOT-plain.jar
      ├── jartest-0.0.1-SNAPSHOT.jar
      └── org
          └── springframework
              └── boot
                  └── loader

      BOOT-INF

      • libs
        • maven, gradle 등에서 설정한 dependency들 담겨있는 폴더
        • 각각은 jar파일로 되어있다.
      • BOOT-INF/classes
        • 유저가 작성한 spring application class

      META-INF

      • jar을 설명해주는 문서

순서

  1. META-INF/MANIFEST.MF를 읽는다

    Manifest-Version: 1.0
    Main-Class: org.springframework.boot.loader.JarLauncher
    Start-Class: com.example.jartest.JarTestApplication
    Spring-Boot-Version: 3.1.3
    Spring-Boot-Classes: BOOT-INF/classes/
    Spring-Boot-Lib: BOOT-INF/lib/
    Spring-Boot-Classpath-Index: BOOT-INF/classpath.idx
    Spring-Boot-Layers-Index: BOOT-INF/layers.idx
    Build-Jdk-Spec: 17
    Implementation-Title: jartest
    Implementation-Version: 0.0.1-SNAPSHOT
  2. JarLauncher 실행한다.

    1. 메인 메서드 실행

      public static void main(String[] args) throws Exception {
      		new JarLauncher().launch(args);
      	}
    2. JarLauncher.launch()

      protected void launch(String[] args) throws Exception {
      		if (!isExploded()) {
      			Handlers.register();
      		}
      		try {
      			ClassLoader classLoader = createClassLoader(getClassPathUrls()); //여기서 BOOT-INF주소 가져옴
      			String jarMode = System.getProperty("jarmode");
      			String mainClassName = hasLength(jarMode) ? JAR_MODE_RUNNER_CLASS_NAME : getMainClass();
      			launch(classLoader, mainClassName, args);
      		}
      		catch (UncheckedIOException ex) {
      			throw ex.getCause();
      		}
      	}
    3. LaunchedClassLoader로 jar들 불러온다(extends JarUrlClassLoader)

      • 위에서 전달한/BOOT-INF/lib를 기반으로 ClassLoader 생성하고 Jar들 불러온다
    4. Launcherlaunch() 사용

      /**
      	 * Launch the application. This method is the initial entry point that should be
      	 * called by a subclass {@code public static void main(String[] args)} method.
      	 * @param args the incoming arguments
      	 * @throws Exception if the application fails to launch
      	 */
      protected void launch(String[] args) throws Exception {
      		if (!isExploded()) {
      			Handlers.register();
      		}
      		try {
      			ClassLoader classLoader = createClassLoader(getClassPathUrls());
      			String jarMode = System.getProperty("jarmode");
      			String mainClassName = hasLength(jarMode) ? JAR_MODE_RUNNER_CLASS_NAME : getMainClass();
      			launch(classLoader, mainClassName, args);
      		}
      		catch (UncheckedIOException ex) {
      			throw ex.getCause();
      		}
      	}
      • getMainClass 내부에서 호출하는것을 보자
        • PropertiesLauncher 에서 override 된 getMainClass를 호출
          @Override
          	protected String getMainClass() throws Exception {
          		String mainClass = getProperty(MAIN, "Start-Class");
          		if (mainClass == null) {
          			throw new IllegalStateException("No '%s' or 'Start-Class' specified".formatted(MAIN));
          		}
          		return mainClass;
          	}
        • Start-Class 어디서 많이 보지 않았었나?
        • MANIFEST.MF 에 우리가 만들어놓은 스프링 애플리케이션의 시작점이 적혀있다!
      • 결론적으로 Launcher 로 필요한 jar들 모두 읽어들이고나서 시작함을 확인

참고

  • 공식문서를 한번 보자
    myapp.jar
    +-------------------+-------------------------+
    | /BOOT-INF/classes | /BOOT-INF/lib/mylib.jar |
    |+-----------------+||+-----------+----------+|
    ||     A.class      |||  B.class  |  C.class ||
    |+-----------------+||+-----------+----------+|
    +-------------------+-------------------------+
     ^                    ^           ^
     0063                 3452        3980
    위의 예시는 myapp.jar 내부의 /BOOT-INF/classes 가 0063 포지션에 있음을 알려준다. B.classmyapp.jar 의 3452 포지션. 그리고 C.class는 3980에 있다.
  • 요런식으로 JarFile 의 구현체인 NestedJarFile 로 load해온다.
  • 처음 load되고 나면 각 JarEntry 의 위치는 바깥 jar의 물리 주소 offset으로 매핑된다.

Armed with this information, we can load specific nested entries by seeking to the appropriate part of the outer jar. We do not need to unpack the archive, and we do not need to read all entry data into memory.

  • jar의 전체 data를 가져올 필요도 없고, jar를 압축해제 할 필요도 없음. 그냥 필요한 부분 jar 자체에서 뽑아옴

공식문서

The Executable Jar Format

참고자료

Understanding the JAR Manifest File | Baeldung

공식 github

https://github.com/swagger-api/swagger-core.git

profile
왜 필요한지 질문하기

0개의 댓글