본문 바로가기
Develop/Spring+JPA

[Java] 스프링 부트를 제거해서 생긴 일

by 연로그 2023. 2. 5.
반응형

😄 개요

 

깃허브 프로필에 블로그 최신 포스트를 업데이트 시켜주는 간단한 토이 프로젝트를 만들었다. (현재 이 프로젝트는 최소한의 기능만 만들고 업데이트를 중단하고 있다. 나중에 회사 적응이 끝나고 시간적으로 여유가 난다면.. 다시 개선할 예정이다.) 처음에는 Spring Boot를 이용하다가 최대한 간소화 시키기 위해 제거했다. 그 과정 중에서 겪은 오류에 대해서 정리해보려 한다. 자바만으로도 당연히 되겠지, 라고 생각했던 기능 중 알고보니 스프링/스프링 부트가 담당했던 기능! 을 기억해두기 위한 포스팅이다. 

 

 

👿 jar 파일에서 클래스를 못 찾는다?

 

GitHub Action 스크립트를 작성할 때 jar 파일을 생성하고, 이 jar 파일을 실행하는 형식으로 만들었다. Spring을 적용했을 때는 무사히 동작했지만, Spring을 제거하고 나니 동작하지 않기 시작했다. 주요 원인은 크게 두 가지가 있었는데 각각의 케이스에 대해서 설명해보겠다.

 

👻 Could not find or load main class

Spring을 사용할 때는 메인 클래스가 아래와 같은 코드가 작성되어있었다. Spring Application을 실행할 때 메인 클래스를 PostingboxApplication 클래스로 지정해두었다.

@SpringBootApplication
public class PostingboxApplication {
    public static void main(String[] args) {
        SpringApplication.run(PostingboxApplication.class, args);
    }
}

 

하지만 스프링을 제거하면서 위의 코드는 제거되었다. jar를 실행할 때 지정된 main 클래스가 없으니까 위와 같은 오류가 발생했던 것이다. 이는 build.gradle에 메인 클래스를 직접 지정함으로써 해결했다.

jar {
    manifest {
        attributes 'Main-Class': '메인클래스 경로'
    }
}

 

 

👻 NoClassDefFoundError

외부 라이브러리를 이용하는 로직이 있었다. 분명 로컬에서 실행해볼 때는 제대로 클래스를 찾았지만 jar를 이용해 실행할 때는 클래스를 못 찾겠다며 잘 돌아가지 않았다. 정확한 원인이 설명된 공식 문서를 찾지는 못했으나 Spring Docs에서 유사한 내용은 찾았다. jar를 생성할 때 외부 라이브러리는 포함되지 않는 것이라고 추측했다.

 

Java does not provide a standard way to load nested jar files (jar files that are themselves contained within a jar). This can be problematic if you are looking to distribute a self-contained application.

 

따라서 외부 라이브러리를 포함해 jar 파일을 만드는 방법을 찾아봤다. 조금 막막해서 ChatGPT의 도움을 받았는데 shadowJar라는 플러그인을 추천받았다. 하지만 정상적으로 동작하지 않거나, deprecate된 메서드이거나, Gradle 6.x 이상 버전에서는 더이상 지원하지 않는 코드까지 호출했다. 많은 예제들을 찾아봤지만 오래된 자료들 뿐이었고 제대로 동작하지 않아 다른 방법을 찾아보았다.

 

직접 구글링해서 찾은 방법은 fatJar이라는 태스크를 만드는 일이다. 

task fatJar(type: Jar) {
    // main 클래스 지정
    manifest {
        attributes "Main-Class": "com.github.postingbox.PostingboxApplication"
    }
   
    // jar 파일에서 중복된 파일은 제외한다
    duplicatesStrategy = DuplicatesStrategy.EXCLUDE
    
    // runtimeClasspath에서 디렉토리와 zip 파일들을 수집하여 jar 파일로 만들 수 있도록 설정
    from { configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) } }
    with jar
}

 

이제 jar파일을 생성할 때 아래와 같은 명령어를 이용하면 외부 라이브러리까지 다 포함된! 뚱뚱한 자르 파일을 만들 수 있다. 

./gradlew fatJar

 

Spring에서는 의존성에 대해서 자동적으로 관리해주는듯 하다. (참고: Spring Docs)

Spring Boot auto-configuration attempts to automatically configure your Spring application based on the jar dependencies that you have added. For example, if HSQLDB is on your classpath, and you have not manually configured any database connection beans, then Spring Boot auto-configures an in-memory database.

 

 

😈 환경 변수를 불러올 수 없다?

 

GitHub REST API를 실행할 때 access token을 이용하지 않으면 Bad Credendtial이 발생한다. 이 access token 값은 아주 중요한 값이라 깃허브에 그대로 올리면 보안적으로 매우 위험한 일이다. 이를 위해 GitHub Secrets를 이용했다.

 

GitHub Secrets에 ACCESS_TOKEN 이라는 이름으로 access token 값을 등록했다고 가정해보자. GitHub Action에서는 아래처럼 ${{ secrets.시크릿에_등록한_이름 }} 를 통해 해당 값을 이용할 수 있다. 이를 이용해서 access token 값을 jar가 실행되는 환경에 해당 값을 환경 변수로 설정할 수 있게 된다.

jobs:
  build:
    runs-on: ubuntu-latest
    env:
      ACCESS_TOKEN: ${{ secrets.ACCESS_TOKEN }}

 

그리고 properties 또는 yml 파일에서 환경 변수를 불러오기 위해 아래와 같이 설정하면? ❌ 안 된다. ❌

access-token: ${ACCESS_TOKEN}

 

  • 원래 의도: ACCESS_TOKEN 이라는 이름으로 환경 변수 값을 불러오기
  • 실제 동작: "${ACCESS_TOKEN}"이라는 값이 그대로 들어감

 

순수 Java는 properties에서 ${환경변수}를 지원하지 않는다. ${ }를 포함한 문자열이 그대로 들어갈 뿐이다. 대신 아래와 같은 코드를 통해 환경 변수 값을 그대로 가져올 수 있다.

System.getenv("ACCESS_TOKEN");

 

스프링 공식 문서에서도 아래와 같이 환경 변수에 대한 내용이 짧막하게 나마 있다.

Spring Boot supports setting a prefix for environment properties. This is useful if the system environment is shared by multiple Spring Boot applications with different configuration requirements. The prefix for system environment properties can be set directly on SpringApplication.
반응형