프로그래밍 방식으로 스프링 부트 애플리케이션 시작하기

게시 됨: 2022-03-11

이 기사에서는 다른 Java 프로그램에서 Spring Boot 애플리케이션을 시작하는 방법을 보여줍니다. Spring Boot 애플리케이션은 일반적으로 단일 실행 가능한 JAR 아카이브로 빌드됩니다. 여기에는 중첩된 JAR로 패키지된 모든 종속성이 포함됩니다.

마찬가지로 Spring Boot 프로젝트는 일반적으로 모든 더러운 작업을 수행하는 제공된 maven 플러그인에 의해 실행 가능한 JAR 파일로 빌드됩니다. 그 결과 다른 사람과 쉽게 공유하고 서버에 배포하는 등의 작업을 수행할 수 있는 편리한 단일 JAR 파일이 생성됩니다.

Spring Boot 애플리케이션을 시작하는 것은 java -jar mySpringProg.jar 를 입력하는 것만큼 쉽고 애플리케이션은 콘솔에 멋진 형식의 정보 메시지를 인쇄합니다.

그러나 Spring Boot 개발자가 사람의 개입 없이 다른 Java 프로그램에서 애플리케이션을 실행하려면 어떻게 해야 할까요?

중첩된 JAR의 작동 방식

모든 종속성이 있는 Java 프로그램을 실행 가능한 단일 JAR 파일로 압축하려면 JAR 파일이기도 한 종속성을 제공해야 하며 어떻게든 최종 실행 가능한 JAR 파일 내부에 저장해야 합니다.

"음영"은 하나의 옵션입니다. 종속성 음영은 응용 프로그램(프로젝트) 자체 코드와 함께 번들로 제공되는 복사본을 만들기 위해 종속성을 포함하고 이름을 변경하고, 클래스를 재배치하고, 영향을 받는 바이트 코드 및 리소스를 다시 작성하는 프로세스입니다.

음영 처리를 통해 사용자는 종속성에서 모든 클래스와 리소스의 압축을 풀고 실행 가능한 JAR 파일로 다시 압축할 수 있습니다. 이것은 간단한 시나리오에서 작동할 수 있지만 두 종속성이 정확히 동일한 이름과 경로를 가진 동일한 리소스 파일 또는 클래스를 포함하는 경우 중복되어 프로그램이 작동하지 않을 수 있습니다.

Spring Boot는 다른 접근 방식을 취하고 실행 가능한 JAR 내부에 종속 JAR을 중첩 JAR로 묶습니다.

 example.jar | +-META-INF | +-MANIFEST.MF +-org | +-springframework | +-boot | +-loader | +-<spring boot loader classes> +-BOOT-INF +-classes | +-mycompany | +-project | +-YourClasses.class +-lib +-dependency1.jar +-dependency2.jar

JAR 아카이브는 표준 Java 실행 가능한 JAR 파일로 구성됩니다. Spring Boot 로더 클래스는 org/springframework/boot/loader 경로에 있고 사용자 클래스와 종속성은 BOOT-INF/classesBOOT-INF/lib 에 있습니다.

참고: Spring을 처음 사용하는 경우 Top 10 Spring Framework Mistakes 기사를 참조할 수도 있습니다 .

일반적인 Spring Boot JAR 파일에는 세 가지 유형의 항목이 있습니다.

  • 프로젝트 수업
  • 중첩된 JAR 라이브러리
  • 스프링 부트 로더 클래스

Spring Boot Classloader는 먼저 클래스 경로에 JAR 라이브러리를 설정한 다음 프로젝트 클래스를 설정하므로 IDE(Eclipse, IntelliJ)에서 실행하는 것과 콘솔에서 실행하는 것 사이에 약간의 차이가 있습니다.

클래스 재정의 및 클래스 로더에 대한 추가 정보는 이 기사를 참조하십시오.

스프링 부트 애플리케이션 시작

명령줄이나 셸에서 수동으로 Spring Boot 애플리케이션을 시작하는 것은 다음을 입력하는 것만큼 쉽습니다.

 java -jar example.jar

그러나 다른 Java 프로그램에서 프로그래밍 방식으로 Spring Boot 애플리케이션을 시작하려면 더 많은 노력이 필요합니다. org/springframework/boot/loader/*.class 코드를 로드하고 약간의 Java 리플렉션을 사용하여 JarFileArchive , JarLauncher 를 인스턴스화하고 launch(String[]) 메소드를 호출해야 합니다.

다음 섹션에서 이것이 어떻게 수행되는지 자세히 살펴보겠습니다.

스프링 부트 로더 클래스 로드

이미 지적했듯이 Spring Boot JAR 파일은 모든 JAR 아카이브와 같습니다. org/springframework/boot/loader/*.class 항목을 로드하고, Class 객체를 생성하고, 나중에 Spring Boot 애플리케이션을 실행하는 데 사용할 수 있습니다.

 import java.net.URLClassLoader; import java.util.Enumeration; import java.util.HashMap; import java.util.Map; import java.util.jar.JarEntry; import java.util.jar.JarFile; . . . public static void loadJar(final String pathToJar) throws IOException . . . { // Class name to Class object mapping. final Map<String, Class<?>> classMap = new HashMap<>(); final JarFile jarFile = new JarFile(pathToJar); final Enumeration<JarEntry> jarEntryEnum = jarFile.entries(); final URL[] urls = { new URL("jar:file:" + pathToJar + "!/") }; final URLClassLoader urlClassLoader = URLClassLoader.newInstance(urls);

여기서 우리는 classMap 이 각각의 패키지 이름에 매핑된 Class 객체를 보유하는 것을 볼 수 있습니다. 예를 들어 String 값 org.springframework.boot.loader.JarLauncherJarLauncher.class 객체에 매핑될 것입니다.

 while (jarEntryEnum.hasMoreElements()) { final JarEntry jarEntry = jarEntryEnum.nextElement(); if (jarEntry.getName().startsWith("org/springframework/boot") && jarEntry.getName().endsWith(".class") == true) { int endIndex = jarEntryName.lastIndexOf(".class"); className = jarEntryName.substring(0, endIndex).replace('/', '.'); try { final Class<?> loadedClass = urlClassLoader.loadClass(className); result.put(loadedClass.getName(), loadedClass); } catch (final ClassNotFoundException ex) { } } } jarFile.close();

while 루프 의 최종 결과는 Spring Boot 로더 클래스 객체로 채워진 맵입니다.

실제 실행 자동화

로드가 끝나면 자동 실행을 완료하고 이를 사용하여 실제로 앱을 시작할 수 있습니다.

Java 리플렉션을 사용하면 로드된 클래스에서 객체를 생성할 수 있으며 이는 튜토리얼 컨텍스트에서 매우 유용합니다.

첫 번째 단계는 JarFileArchive 객체를 만드는 것입니다.

 // Create JarFileArchive(File) object, needed for JarLauncher. final Class<?> jarFileArchiveClass = result.get("org.springframework.boot.loader.archive.JarFileArchive"); final Constructor<?> jarFileArchiveConstructor = jarFileArchiveClass.getConstructor(File.class); final Object jarFileArchive = jarFileArchiveConstructor.newInstance(new File(pathToJar));

JarFileArchive 객체의 생성자는 File(String) 객체를 인수로 취하므로 반드시 제공해야 합니다.

다음 단계는 생성자에 Archive 가 필요한 JarLauncher 객체를 생성하는 것입니다.

 final Class<?> archiveClass = result.get("org.springframework.boot.loader.archive.Archive"); // Create JarLauncher object using JarLauncher(Archive) constructor. final Constructor<?> jarLauncherConstructor = mainClass.getDeclaredConstructor(archiveClass); jarLauncherConstructor.setAccessible(true); final Object jarLauncher = jarLauncherConstructor.newInstance(jarFileArchive);

혼동을 피하기 위해 Archive 는 실제로 인터페이스이고 JarFileArchive 는 구현 중 하나입니다.

프로세스의 마지막 단계는 새로 생성된 jarLauncher 객체에서 launch(String[]) 메서드를 호출하는 것입니다. 이것은 비교적 간단하며 몇 줄의 코드만 있으면 됩니다.

 // Invoke JarLauncher#launch(String[]) method. final Class<?> launcherClass = result.get("org.springframework.boot.loader.Launcher"); final Method launchMethod = launcherClass.getDeclaredMethod("launch", String[].class); launchMethod.setAccessible(true); launchMethod.invoke(jarLauncher, new Object[]{new String[0]});

invoke(jarLauncer, new Object[]{new String[0]}) 메서드는 마침내 Spring Boot 애플리케이션을 시작합니다. 메인 스레드는 여기서 멈추고 Spring Boot 애플리케이션이 종료될 때까지 기다립니다.

스프링 부트 클래스 로더에 대한 한마디

Spring Boot JAR 파일을 조사하면 다음 구조가 나타납니다.

 +--- mySpringApp1-0.0.1-SNAPSHOT.jar +--- META-INF +--- BOOT-INF | +--- classes # 1 - project classes | | | | | +--- com.example.mySpringApp1 | | \--- SpringBootLoaderApplication.class | | | +--- lib # 2 - nested jar libraries | +--- javax.annotation-api-1.3.1 | +--- spring-boot-2.0.0.M7.jar | \--- (...) | +--- org.springframework.boot.loader # 3 - Spring Boot loader classes +--- JarLauncher.class +--- LaunchedURLClassLoader.class \--- (...)

세 가지 유형의 항목에 유의하십시오.

  • 프로젝트 수업
  • 중첩된 JAR 라이브러리
  • 스프링 부트 로더 클래스

프로젝트 클래스( BOOT-INF/classes )와 중첩된 JAR( BOOT-INF/lib ) 모두 동일한 클래스 로더 LaunchedURLClassLoader 에 의해 처리됩니다. 이 로더는 Spring Boot JAR 애플리케이션의 루트에 있습니다.

LaunchedURLClassLoader 는 IDE와 다른 라이브러리 콘텐츠( BOOT-INF/lib ) 다음에 클래스 콘텐츠( BOOT-INF/classes )를 로드합니다. 예를 들어, Eclipse는 먼저 클래스 콘텐츠를 클래스 경로에 배치한 다음 라이브러리(종속성)에 배치합니다.

LaunchedURLClassLoader 는 클래스 로딩에 사용될 URL 세트로 생성되는 java.net.URLClassLoader 를 확장합니다. URL은 JAR 아카이브 또는 클래스 폴더와 같은 위치를 가리킬 수 있습니다. 클래스 로딩을 수행할 때 URL로 지정된 모든 리소스는 URL이 제공된 순서대로 순회되며 검색된 클래스를 포함하는 첫 번째 리소스가 사용됩니다.

마무리

고전적인 Java 응용 프로그램은 모든 종속성을 classpath 인수에 열거해야 하므로 시작 절차가 다소 복잡하고 복잡합니다.

대조적으로 Spring Boot 애플리케이션은 명령줄에서 시작하기 쉽고 간편합니다. 그들은 모든 종속성을 관리하며 최종 사용자는 세부 사항에 대해 걱정할 필요가 없습니다.

그러나 다른 Java 프로그램에서 Spring Boot 애플리케이션을 시작하면 Spring Boot의 로더 클래스를 로드하고 JarFileArchiveJarLauncher 와 같은 특수 객체를 생성한 다음 Java 리플렉션을 사용하여 launch 메소드를 호출해야 하므로 절차가 더 복잡해집니다.

결론 : Spring Boot는 내부에서 많은 사소한 작업을 처리할 수 있으므로 개발자가 시간을 확보하고 새로운 기능 생성, 테스트 등과 같은 보다 유용한 작업에 집중할 수 있습니다.