Node.js

node.js 환경에서 java 코드를 사용하는 방법 (npm java)

왈왈디 2024. 8. 17. 00:11
728x90

Node.js에서 Java를 실행해야 하는 상황 발생

node.js 서버를 운영하다 보면

자바 코드를 사용하고 싶은 (혹은 사용해야만 하는) 경우가 생기기도 한다.

(특히 자바 공화국이라고도 불리는 이 나라에서는 더더욱...)

 

최근 업무 중 KISA 한국 인터넷 진흥원에서 제공하는 암호화 방식을 사용해야 했는데,

KISA에서 제공하는 소스 코드와 매뉴얼이 자바로만 작성되어 있었다.

 

자바 코드를 모두 node.js 코드로 변환하는 것도 옵션이었다.

 

하지만 함께 사용해야 했던 또 다른 암호화 방식은

사기업이 제공하여 소스 코드를 제공하지 않은 채

jar 파일만(여러개의 자바클래스 파일과 메타데이터, 리소스(텍스트, 이미지 등)를 하나의 파일로 모아서 배포하기 위한 패키지 파일)

제공되었기 때문에

node.js 코드로 변환이 불가능하여

이 김에 node.js 에서 java 코드를 실행할 수 있는 방법을 강구하게 됐다.

(사정 상 AWS 람다에서 java로 구현된 함수를 사용하는 것도 불가능했다.)

 

npm java 모듈

놀랍게도 node.js 모듈 중 java 라는 모듈이 있다.

node.js 코드 상에서 java 코드를 호출하고 실행할 수 있게 해준다. (npm java 모듈 공식 문서)

 

사용 방법

step 1.

node.js 엔진이 자바 코드를 실행 시킬 수 있는 것은 아니기 때문에

자바를 실행시킬 jvm이 필요하다.

실행할 서버에 jdk를 설치해 준다.

 

step 2.

node.js 프로젝트에 java 패키지를 설치한다.

npm install java

 

step 3.

실행하고 싶은 자바 코드들을 패키지로 만들어

jar 파일로 만든다.

 

jar 파일을 생성할 때 사용한 jdk 버전과

서버에 설치된 jdk 버전이 동일하지 않으면 에러가 발생할 수 있다.

나는 8 버전으로 통일하여 사용했다.

 

step 4.

만들어진 jar 파일들을 node.js 프로젝트에 올린다.

나는 src에 jars 디렉토리를 생성하여 jar 파일들을 저장했다.

src
├── main.ts
├── app.module.ts
├── components
├── jars
│   ├── Kisa-encryption.jar
│   └── ...
└── ...

 

 

step 5.

이제 필요한 곳에서 사용하면 된다.

가장 중요한 코드는 아래 3줄이다.

const java = require('java');

java.classpath.push('src/jars/Kisa-encryption.jar');

const encryptionUtil = java.import('EncryptionUtil');

 

우선 java 패키지를 require문으로 import 해준다.

 

그리고 java.classpath.push 메서드 인자로 jar 파일 주소를 넘긴다. 상대 주소도 가능하다.

java.classpath는 JVM 생성에 전달할 경로 또는 jar의 배열로, 

메서드를 호출하기 전에 반드시 java.classpath에 push 해주어야 한다.

[classpath]
java.classpath*
Array of paths or jars to pass to the creation of the JVM.
All items must be added to the classpath before calling any other node-java methods.

Example
java.classpath.push('commons.io.jar');
java.classpath.push('src');​

 

필요한 jar 파일들의 경로를 classpath에 push했으면

java.import 메서드 인자로 사용할 클래스 이름을 입력한다.

인자로 입력한 클래스는 당연히 jar 파일 중 하나에 포함되어 있어야 한다.

 

그럼 java.import 메서드가 node.js에서 실행될 수 있는 클래스 인스턴스를 반환한다.

변수에 할당하여 사용하면 끝이다.

 

사용 예시

NestJS 환경에서 사용하는 예시이다.

// eslint-disable-next-line @typescript-eslint/no-var-requires
const java = require('java');

interface KisaSeed {
  encrypt(key: string, message: string): string;
}

@Injectable()
export class UserService {
  private readonly KISA_SEED: KisaSeed;

  constructor() {
    java.classpath.push('src/jars/Kisa-encryption.jar');
    this.KISA_SEED = java.import('EncryptionUtil');
    
    console.log('KISA_SEED', this.KISA_SEED);
    const encryptedKey = this.KISA_SEED.encrypt('key', 'message');
  }
}

 

NestJS 기본 eslint 설정에 의해 require문으로 import할 수 없어

// eslint-disable-next-line @typescript-eslint/no-var-requires 를 사용해 해당 라인만 eslint를 무력화 시켜줬다.

 

import 하는 java 클래스에는 type이 지정되어 있지 않기 때문에

호출부에서 type을 정의하여 지정해주었다.

 

console.log로 찍어본 KISA_SEED 인스턴스는 아래와 같다.

KISA_SEED [Function: javaClassConstructorProxy] {
  class: nodeJava_java_lang_Class {},
  decryptSync: [Function: bound callStaticMethodSync],
  decrypt: [Function: bound callStaticMethod],
  encryptSync: [Function: bound callStaticMethodSync],
  encrypt: [Function: bound callStaticMethod],
  mainSync: [Function: bound callStaticMethodSync],
  main: [Function: bound callStaticMethod]
}

 

이 중 사용하고 싶은 메서드를 사용하면 된다.

encrypt 메서드를 사용하여 암호화해주었다.

 

위 예시에서는 호출부에서 java.classpath.push를 해주었으나,

필요한 jar 파일을 모두 한 번에 push 해주고

호출부에서는 클래스를 import하여 사용하는 방식도 가능하다.

src
├── main.ts
├── java.ts
├── app.module.ts
├── components
├── jars
│   ├── Kisa-encryption.jar
│   └── ...
└── ...

 

main.ts 와 같은 경로에 java.ts를 생성해준다.

// src/java.ts
// eslint-disable-next-line @typescript-eslint/no-var-requires
const java = require('java');

export function initJVM() {
  java.classpath.push('jars/OkCert3-java1.5-2.3.2.jar');
  java.classpath.push('jars/xecuredb-0.0.2.jar');
  java.classpath.push('jars/Kisa-encryption.jar');
}

 

main.ts에서 app initialize 할 때 initJVM을 실행시킨다.

// src/main.ts
import { NestFactory } from '@nestjs/core';
import { initJVM } from './java';
import { WalwalAppModule } from './walwal-app.module';

async function bootstrap() {
  initJVM();

  const app = await NestFactory.create(WalwalAppModule);
  ...

 

부가 기능

이외에도 async option 설정 

여러가지 옵션들을 활용할 수 있으니 필요에 따라 공식 문서를 살펴보자.

object 생명 주기 등에 대해서도 설명해주고 있다.

 

기타

npm java 패키지 설치할 때 gyp 관련 에러가 발생하며

install에 실패하는 경우가 많은데

 

업무용 맥에서 진행했을 때는 해당 에러가 발생하고

개인용 맥에서는 발생하지 않았고

aws ec2 linux 서버에서도 발생하지 않았다.

 

다시 발생한다면 해결 방법을 정리해보려고 했는데,

에러를 재현해야 가능할 것 같다.

728x90