JavaRush /Java Blog /Random-KO /Java에서 JNDI 사용
Анзор Кармов
레벨 31
Санкт-Петербург

Java에서 JNDI 사용

Random-KO 그룹에 게시되었습니다
안녕하세요! 오늘은 JNDI를 소개하겠습니다. 그것이 무엇인지, 왜 필요한지, 어떻게 작동하는지, 어떻게 작업할 수 있는지 알아봅시다. 그런 다음 Spring Boot 단위 테스트를 작성하고 그 내부에서 바로 이 JNDI를 사용해 보겠습니다. Java에서 JNDI 사용 - 1

소개. 이름 지정 및 디렉토리 서비스

JNDI에 대해 알아보기 전에 이름 지정 및 디렉토리 서비스가 무엇인지 이해해 보겠습니다. 이러한 서비스의 가장 확실한 예는 PC, 노트북 또는 스마트폰의 파일 시스템입니다. 파일 시스템은 (이상하게도) 파일을 관리합니다. 이러한 시스템의 파일은 트리 구조로 그룹화됩니다. 각 파일에는 고유한 전체 이름이 있습니다(예: C:\windows\notepad.exe). 참고: 전체 파일 이름은 일부 루트 지점(C 드라이브)에서 파일 자체(notepad.exe)까지의 경로입니다. 이러한 체인의 중간 노드는 디렉터리(Windows 디렉터리)입니다. 디렉터리 내부의 파일에는 속성이 있습니다. 예를 들어 "숨김", "읽기 전용" 등입니다. 파일 시스템과 같은 간단한 것에 대한 자세한 설명은 이름 지정 및 디렉터리 서비스의 정의를 더 잘 이해하는 데 도움이 됩니다. 따라서 이름 및 디렉터리 서비스는 많은 이름과 많은 개체의 매핑을 관리하는 시스템입니다. 파일 시스템에서는 객체를 숨기는 파일 이름, 즉 다양한 형식의 파일 자체와 상호 작용합니다. 명명 및 디렉터리 서비스에서 명명된 개체는 트리 구조로 구성됩니다. 그리고 디렉터리 개체에는 속성이 있습니다. 이름 및 디렉터리 서비스의 또 다른 예는 DNS(Domain Name System)입니다. 이 시스템은 사람이 읽을 수 있는 도메인 이름(예: https://javarush.com/)과 컴퓨터가 읽을 수 있는 IP 주소(예: 18.196.51.113) 간의 매핑을 관리합니다. DNS 및 파일 시스템 외에도 다음과 같은 다른 서비스가 많이 있습니다.

JNDI

JNDI(Java Naming and Directory Interface)는 이름 지정 및 디렉토리 서비스에 액세스하기 위한 Java API입니다. JNDI는 Java 프로그램이 다양한 이름 지정 및 디렉토리 서비스와 상호 작용할 수 있도록 통일된 메커니즘을 제공하는 API입니다. 내부적으로 JNDI와 특정 서비스 간의 통합은 SPI(서비스 공급자 인터페이스)를 사용하여 수행됩니다. SPI를 사용하면 다양한 이름 지정 및 디렉토리 서비스를 투명하게 연결할 수 있으므로 Java 애플리케이션이 JNDI API를 사용하여 연결된 서비스에 액세스할 수 있습니다. 아래 그림은 JNDI 아키텍처를 보여줍니다. Java에서 JNDI 사용 - 2

출처: Oracle Java 자습서

JNDI. 간단한 단어의 의미

주요 질문은 JNDI가 왜 필요한가입니다. JNDI는 이 객체에 바인딩된 객체 이름으로 Java 코드의 객체 "등록"에서 Java 객체를 얻을 수 있도록 필요합니다. 반복되는 단어가 많아 혼동을 주지 않도록 위의 진술을 다음과 같은 몇 가지로 나누어 보겠습니다.
  1. 궁극적으로 우리는 Java 객체를 얻어야 합니다.
  2. 우리는 일부 레지스트리에서 이 개체를 가져올 것입니다.
  3. 이 레지스트리에는 여러 개체가 있습니다.
  4. 이 레지스트리의 각 개체에는 고유한 이름이 있습니다.
  5. 레지스트리에서 개체를 가져오려면 요청에 이름을 전달해야 합니다. 마치 “이런 저런 이름으로 갖고 있는 것을 나에게 주세요.”라고 말하는 것과 같습니다.
  6. 우리는 레지스트리에서 이름으로 개체를 읽을 수 있을 뿐만 아니라 특정 이름으로 이 레지스트리에 개체를 저장할 수도 있습니다(어쨌든 이름은 거기에 있습니다).
따라서 우리는 일종의 레지스트리, 개체 저장소 또는 JNDI 트리를 갖게 됩니다. 다음으로, 예를 들어 JNDI의 의미를 이해해 보겠습니다. 대부분의 경우 JNDI가 엔터프라이즈 개발에 사용된다는 점은 주목할 가치가 있습니다. 그리고 이러한 애플리케이션은 일부 애플리케이션 서버 내에서 작동합니다. 이 서버는 일부 Java EE 애플리케이션 서버이거나 Tomcat과 같은 서블릿 컨테이너 또는 기타 컨테이너일 수 있습니다. 객체 레지스트리 자체, 즉 JNDI 트리는 일반적으로 이 애플리케이션 서버 내에 위치합니다. 후자가 항상 필요한 것은 아니지만(이러한 트리를 로컬에 가질 수 있음) 가장 일반적입니다. JNDI 트리는 이름과 함께 개체를 "레지스트리에 저장"하는 특별한 사람(시스템 관리자 또는 DevOps 전문가)이 관리할 수 있습니다. 애플리케이션과 JNDI 트리가 동일한 컨테이너 내에 공존하면 이러한 레지스트리에 저장된 모든 Java 객체에 쉽게 액세스할 수 있습니다. 더욱이, 레지스트리와 애플리케이션은 서로 다른 컨테이너, 심지어는 서로 다른 물리적 시스템에 위치할 수도 있습니다. JNDI를 사용하면 원격으로 Java 객체에 액세스할 수 있습니다. 전형적인 경우. Java EE 서버 관리자는 데이터베이스 연결에 필요한 정보를 저장하는 객체를 레지스트리에 배치합니다. 따라서 데이터베이스 작업을 위해 JNDI 트리에서 필요한 개체를 요청하고 작업하면 됩니다. 매우 편안합니다. 편리함은 또한 기업 개발에 다양한 환경이 있다는 사실에도 있습니다. 프로덕션 서버가 있고, 테스트 서버가 있습니다(그리고 테스트 서버가 2개 이상인 경우도 많습니다). 그런 다음 JNDI 내부의 각 서버에 데이터베이스 연결을 위한 개체를 배치하고 이 개체를 애플리케이션 내에서 사용하면 한 서버(테스트, 릴리스)에서 다른 서버로 애플리케이션을 배포할 때 아무것도 변경할 필요가 없습니다. 어디에서나 데이터베이스에 액세스할 수 있습니다. 물론 이 예는 다소 단순화되어 있지만 JNDI가 필요한 이유를 더 잘 이해하는 데 도움이 되기를 바랍니다. 다음으로 몇 가지 공격 요소를 포함하여 Java의 JNDI에 대해 더 자세히 알아 보겠습니다.

JNDI API

JNDI는 Java SE 플랫폼 내에서 제공됩니다. JNDI를 사용하려면 JNDI 클래스와 이름 지정 및 디렉토리 서비스에 액세스하기 위한 하나 이상의 서비스 제공자를 가져와야 합니다. JDK에는 다음 서비스에 대한 서비스 제공자가 포함되어 있습니다.
  • LDAP(Lightweight Directory Access Protocol);
  • CORBA(공통 객체 요청 브로커 아키텍처);
  • COS(Common Object Services) 이름 서비스.
  • Java 원격 메소드 호출(RMI) 레지스트리;
  • 도메인 이름 서비스(DNS).
JNDI API 코드는 여러 패키지로 구분됩니다.
  • javax.이름 지정;
  • javax.naming.directory;
  • javax.naming.ldap;
  • javax.naming.event;
  • javax.naming.spi.
주요 JNDI 기능을 포함하는 이름과 컨텍스트라는 두 가지 인터페이스로 JNDI 소개를 시작하겠습니다.

인터페이스 이름

Name 인터페이스를 사용하면 구성 요소 이름과 JNDI 이름 지정 구문을 제어할 수 있습니다. JNDI에서는 모든 이름 및 디렉터리 작업이 컨텍스트를 기준으로 수행됩니다. 절대적인 뿌리는 없습니다. 따라서 JNDI는 이름 지정 및 디렉터리 작업의 시작점을 제공하는 InitialContext를 정의합니다. 초기 컨텍스트에 액세스하면 개체 및 기타 컨텍스트를 검색하는 데 사용할 수 있습니다.
Name objectName = new CompositeName("java:comp/env/jdbc");
위의 코드에서 우리는 일부 개체가 위치한 이름을 정의했습니다(찾을 수는 없지만 우리는 그것을 믿고 있습니다). 우리의 최종 목표는 이 객체에 대한 참조를 얻어 프로그램에서 사용하는 것입니다. 따라서 이름은 슬래시로 구분된 여러 부분(또는 토큰)으로 구성됩니다. 이러한 토큰을 컨텍스트라고 합니다. 맨 처음은 단순히 컨텍스트이고, 이후의 모든 컨텍스트는 하위 컨텍스트(이하 하위 컨텍스트라고 함)입니다. 컨텍스트를 디렉터리나 디렉터리 또는 일반 폴더와 유사하게 생각하면 이해하기가 더 쉽습니다. 루트 컨텍스트는 루트 폴더입니다. 하위 컨텍스트는 하위 폴더입니다. 다음 코드를 실행하면 특정 이름의 모든 구성요소(컨텍스트 및 하위 컨텍스트)를 볼 수 있습니다.
Enumeration<String> elements = objectName.getAll();
while(elements.hasMoreElements()) {
  System.out.println(elements.nextElement());
}
출력은 다음과 같습니다.

java:comp
env
jdbc
출력에는 이름의 토큰이 슬래시로 서로 구분되어 있음이 표시됩니다(그러나 이에 대해 언급했습니다). 각 이름 토큰에는 자체 색인이 있습니다. 토큰 인덱싱은 0에서 시작합니다. 루트 컨텍스트의 인덱스는 0이고, 다음 컨텍스트의 인덱스는 1, 다음 컨텍스트의 인덱스는 2입니다. 인덱스를 통해 하위 컨텍스트 이름을 얻을 수 있습니다.
System.out.println(objectName.get(1)); // -> env
추가 토큰을 추가할 수도 있습니다(끝이나 인덱스의 특정 위치에).
objectName.add("sub-context"); // Добавит sub-context в конец
objectName.add(0, "context"); // Добавит context в налачо
전체 메소드 목록은 공식 문서에서 찾을 수 있습니다 .

인터페이스 컨텍스트

이 인터페이스에는 컨텍스트 초기화를 위한 상수 세트뿐만 아니라 컨텍스트 생성 및 삭제, 객체를 이름에 바인딩, 객체 검색 및 검색을 위한 메소드 세트가 포함되어 있습니다. 이 인터페이스를 사용하여 수행되는 일부 작업을 살펴보겠습니다. 가장 일반적인 작업은 이름으로 개체를 검색하는 것입니다. 이는 다음 방법을 사용하여 수행됩니다.
  • Object lookup(String name)
  • Object lookup(Name name)
객체를 이름에 바인딩하는 것은 다음 메소드를 사용하여 수행됩니다 bind.
  • void bind(Name name, Object obj)
  • void bind(String name, Object obj)
두 가지 방법 모두 이름 name을 개체에 바인딩합니다. Object 바인딩의 반대 작업(이름에서 개체 바인딩 해제)은 다음 메서드를 사용하여 수행됩니다 unbind.
  • void unbind(Name name)
  • void unbind(String name)
전체 방법 목록은 공식 문서 웹사이트 에서 확인할 수 있습니다 .

초기컨텍스트

InitialContextJNDI 트리의 루트 요소를 나타내고 Context. 특정 노드를 기준으로 JNDI 트리 내에서 이름으로 객체를 검색해야 합니다. 트리의 루트 노드는 그러한 노드 역할을 할 수 있습니다 InitialContext. JNDI의 일반적인 사용 사례는 다음과 같습니다.
  • 얻다 InitialContext.
  • InitialContextJNDI 트리에서 이름으로 객체를 검색하는 데 사용됩니다 .
그것을 얻는 방법에는 여러 가지가 있습니다 InitialContext. 이는 모두 Java 프로그램이 위치한 환경에 따라 다릅니다. 예를 들어, Java 프로그램과 JNDI 트리가 동일한 애플리케이션 서버 내에서 실행 중인 경우 다음을 InitialContext얻는 것은 매우 간단합니다.
InitialContext context = new InitialContext();
그렇지 않은 경우 컨텍스트를 파악하는 것이 조금 더 어려워집니다. 때로는 컨텍스트를 초기화하기 위해 환경 속성 목록을 전달해야 하는 경우도 있습니다.
Hashtable env = new Hashtable();
env.put(Context.INITIAL_CONTEXT_FACTORY,
    "com.sun.jndi.fscontext.RefFSContextFactory");

Context ctx = new InitialContext(env);
위의 예는 컨텍스트를 초기화하는 가능한 방법 중 하나를 보여 주며 다른 의미론적 부하를 전달하지 않습니다. 코드를 자세히 살펴볼 필요는 없습니다.

SpringBoot 단위 테스트 내에서 JNDI를 사용하는 예

위에서 우리는 JNDI가 네이밍 및 디렉토리 서비스와 상호 작용하려면 SPI(서비스 제공자 인터페이스)가 필요하며 이를 통해 Java와 네이밍 서비스 간의 통합이 수행될 것이라고 말했습니다. 표준 JDK에는 여러 가지 다른 SPI(위에 나열됨)가 포함되어 있으며 각 SPI는 데모 목적으로는 거의 관심이 없습니다. 컨테이너 내부에서 JNDI 및 Java 애플리케이션을 발생시키는 것은 다소 흥미롭습니다. 그러나 이 기사의 작성자는 게으른 사람이므로 JNDI의 작동 방식을 보여주기 위해 저항이 가장 적은 경로를 선택했습니다. 즉, SpringBoot 애플리케이션 단위 테스트 내에서 JNDI를 실행하고 Spring Framework의 작은 해킹을 사용하여 JNDI 컨텍스트에 액세스하는 것입니다. 따라서 우리의 계획은 다음과 같습니다.
  • 빈 Spring Boot 프로젝트를 작성해 보겠습니다.
  • 이 프로젝트 내에 단위 테스트를 만들어 보겠습니다.
  • 테스트 내에서 JNDI 작업을 시연해 보겠습니다.
    • 컨텍스트에 액세스합니다.
    • JNDI의 특정 이름으로 일부 객체를 바인딩(바인딩)합니다.
    • 이름으로 객체를 가져옵니다(조회).
    • 객체가 null이 아닌지 확인해 보겠습니다.
순서대로 시작합시다. File->New->Project... Java에서 JNDI 사용 - 3 다음으로 Spring 초기화 항목을 선택합니다 . Java에서 JNDI 사용 - 4프로젝트에 대한 메타데이터를 입력합니다. Java에서 JNDI 사용 - 5그런 다음 필요한 Spring Framework 구성 요소를 선택합니다. 일부 DataSource 객체를 바인딩할 것이므로 데이터베이스와 작동하려면 구성 요소가 필요합니다.
  • JDBC API;
  • H2 D데이터베이스.
Java에서 JNDI 사용 - 6파일 시스템의 위치를 ​​결정해 보겠습니다. Java에서 JNDI 사용 - 7그러면 프로젝트가 생성됩니다. 실제로 하나의 단위 테스트가 자동으로 생성되었으며, 이를 데모 목적으로 사용할 것입니다. 다음은 우리에게 필요한 프로젝트 구조와 테스트입니다. Java에서 JNDI 사용 - 8contextLoads 테스트 내에서 코드 작성을 시작해 보겠습니다. 위에서 논의한 Spring의 작은 해킹은 클래스입니다 SimpleNamingContextBuilder. 이 클래스는 단위 테스트나 독립형 애플리케이션 내부에서 JNDI를 쉽게 올릴 수 있도록 설계되었습니다. 컨텍스트를 가져오는 코드를 작성해 보겠습니다.
final SimpleNamingContextBuilder simpleNamingContextBuilder
       = new SimpleNamingContextBuilder();
simpleNamingContextBuilder.activate();

final InitialContext context = new InitialContext();
처음 두 줄의 코드를 사용하면 나중에 JNDI 컨텍스트를 쉽게 초기화할 수 있습니다. 이것이 없으면 InitialContext인스턴스를 생성할 때 예외가 발생합니다: javax.naming.NoInitialContextException. 부인 성명. 클래스는 SimpleNamingContextBuilder더 이상 사용되지 않는 클래스입니다. 그리고 이 예제는 JNDI로 작업하는 방법을 보여주기 위한 것입니다. 이는 단위 테스트 내에서 JNDI를 사용하는 모범 사례가 아닙니다. 이는 컨텍스트를 구축하고 JNDI에서 객체 바인딩 및 검색을 시연하기 위한 버팀목이라고 할 수 있습니다. 컨텍스트를 수신하면 컨텍스트에서 객체를 추출하거나 컨텍스트에서 객체를 검색할 수 있습니다. JNDI에는 아직 객체가 없으므로 거기에 무언가를 넣는 것이 논리적입니다. 예를 들어, DriverManagerDataSource:
context.bind("java:comp/env/jdbc/datasource", new DriverManagerDataSource("jdbc:h2:mem:mydb"));
DriverManagerDataSource이 줄에서는 클래스 객체를 name 에 바인딩했습니다 java:comp/env/jdbc/datasource. 다음으로 이름으로 컨텍스트에서 개체를 가져올 수 있습니다. 컨텍스트에 다른 객체가 없기 때문에 방금 넣은 객체를 얻을 수밖에 없습니다 =(
final DataSource ds = (DataSource) context.lookup("java:comp/env/jdbc/datasource");
이제 DataSource에 연결이 있는지 확인해 보겠습니다(연결, 연결 또는 연결은 데이터베이스와 작동하도록 설계된 Java 클래스입니다).
assert ds.getConnection() != null;
System.out.println(ds.getConnection());
모든 작업을 올바르게 수행했다면 출력은 다음과 같습니다.

conn1: url=jdbc:h2:mem:mydb user=
일부 코드 줄에서는 예외가 발생할 수 있다는 점은 주목할 가치가 있습니다. 다음 줄이 표시됩니다 javax.naming.NamingException.
  • simpleNamingContextBuilder.activate()
  • new InitialContext()
  • context.bind(...)
  • context.lookup(...)
그리고 클래스로 작업할 때 DataSource이 클래스가 던져질 수 있습니다 java.sql.SQLException. try-catch이와 관련하여 블록 내에서 코드를 실행 하거나 예외를 던질 수 있다는 것을 테스트 단위의 서명에 표시 해야 합니다 . 테스트 클래스의 전체 코드는 다음과 같습니다.
@SpringBootTest
class JndiExampleApplicationTests {

    @Test
    void contextLoads() {
        try {
            final SimpleNamingContextBuilder simpleNamingContextBuilder
                    = new SimpleNamingContextBuilder();
            simpleNamingContextBuilder.activate();

            final InitialContext context = new InitialContext();

            context.bind("java:comp/env/jdbc/datasource", new DriverManagerDataSource("jdbc:h2:mem:mydb"));

            final DataSource ds = (DataSource) context.lookup("java:comp/env/jdbc/datasource");

            assert ds.getConnection() != null;
            System.out.println(ds.getConnection());

        } catch (SQLException | NamingException e) {
            e.printStackTrace();
        }
    }
}
테스트를 실행한 후 다음 로그를 볼 수 있습니다.

o.s.m.jndi.SimpleNamingContextBuilder    : Activating simple JNDI environment
o.s.mock.jndi.SimpleNamingContext        : Static JNDI binding: [java:comp/env/jdbc/datasource] = [org.springframework.jdbc.datasource.DriverManagerDataSource@4925f4f5]
conn1: url=jdbc:h2:mem:mydb user=

결론

오늘은 JNDI에 대해 살펴보았습니다. 우리는 이름 지정 및 디렉토리 서비스가 무엇인지, 그리고 JNDI가 Java 프로그램의 다양한 서비스와 균일하게 상호 작용할 수 있게 해주는 Java API라는 것을 배웠습니다. 즉, JNDI의 도움으로 JNDI 트리에 객체를 특정 이름으로 기록하고 동일한 객체를 이름으로 받을 수 있습니다. 보너스 작업으로 JNDI 작동 방식에 대한 예제를 실행할 수 있습니다. 다른 개체를 컨텍스트에 바인딩한 다음 이 개체를 이름으로 읽습니다.
코멘트
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION