JNDI (Java Naming and Directory Interface)

JNDI (Java Naming and Directory Interface)

JNDI는 Java 애플리케이션에서 네이밍 및 디렉터리 서비스에 접근하기 위한 표준 API입니다.

JNDI 개요

JNDI란?

JNDI(Java Naming and Directory Interface)는 Java 애플리케이션이 다양한 네이밍 및 디렉터리 서비스를 일관된 방식으로 접근할 수 있게 해주는 API입니다.

┌─────────────────────────────────────────────────────────────────┐
│                      Java Application                            │
├─────────────────────────────────────────────────────────────────┤
│                         JNDI API                                 │
│   ┌─────────────────────────────────────────────────────────┐   │
│   │  Context, InitialContext, DirContext, Attributes, ...  │   │
│   └─────────────────────────────────────────────────────────┘   │
├─────────────────────────────────────────────────────────────────┤
│                    JNDI SPI (Service Provider Interface)         │
├──────────┬──────────┬──────────┬──────────┬────────────────────┤
│   LDAP   │   DNS    │   RMI    │  CORBA   │   File System      │
│ Provider │ Provider │ Provider │ Provider │     Provider       │
└──────────┴──────────┴──────────┴──────────┴────────────────────┘
          │          │          │          │          │
          ▼          ▼          ▼          ▼          ▼
     ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐
     │  LDAP   │ │   DNS   │ │   RMI   │ │  CORBA  │ │  File   │
     │ Server  │ │ Server  │ │Registry │ │  ORB    │ │ System  │
     └─────────┘ └─────────┘ └─────────┘ └─────────┘ └─────────┘

주요 특징

특징설명
표준화된 API다양한 네이밍 서비스를 동일한 방식으로 접근
플러그인 아키텍처SPI를 통해 다양한 서비스 프로바이더 지원
위치 투명성리소스의 물리적 위치와 무관하게 접근
디커플링애플리케이션과 리소스 설정의 분리

사용 사례

┌─────────────────────────────────────────────────────────────────┐
│                     JNDI 활용 영역                               │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│  ┌─────────────────┐  ┌─────────────────┐  ┌─────────────────┐ │
│  │   DataSource    │  │      EJB        │  │      JMS        │ │
│  │   데이터베이스    │  │   엔터프라이즈    │  │   메시징 서비스  │ │
│  │   연결 풀        │  │   자바 빈        │  │                 │ │
│  └─────────────────┘  └─────────────────┘  └─────────────────┘ │
│                                                                  │
│  ┌─────────────────┐  ┌─────────────────┐  ┌─────────────────┐ │
│  │     LDAP        │  │      DNS        │  │   환경 변수      │ │
│  │   디렉터리 서비스 │  │   도메인 조회    │  │   설정 값        │ │
│  └─────────────────┘  └─────────────────┘  └─────────────────┘ │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

핵심 개념

네이밍 서비스 (Naming Service)

이름과 객체를 연결(바인딩)하고, 이름으로 객체를 찾는(조회) 서비스입니다.

┌─────────────────────────────────────────────────────────────────┐
│                    Naming Service 개념                           │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│   이름 (Name)              객체 (Object)                         │
│   ─────────────           ─────────────                         │
│   "jdbc/myDB"      ──▶    DataSource 객체                       │
│   "ejb/UserBean"   ──▶    EJB Home 객체                         │
│   "jms/Queue"      ──▶    JMS Queue 객체                        │
│                                                                  │
│   바인딩 (Binding): 이름과 객체를 연결                            │
│   조회 (Lookup): 이름으로 객체를 검색                             │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

디렉터리 서비스 (Directory Service)

네이밍 서비스를 확장하여 객체에 속성(Attributes)을 추가로 저장하고 검색할 수 있습니다.

┌─────────────────────────────────────────────────────────────────┐
│                   Directory Service 개념                         │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│   LDAP Entry (DN: cn=John,ou=Users,dc=example,dc=com)           │
│   ┌─────────────────────────────────────────────────────────┐   │
│   │  속성 (Attributes)                                      │   │
│   │  ─────────────────                                      │   │
│   │  cn: John                                               │   │
│   │  mail: john@example.com                                 │   │
│   │  telephoneNumber: 02-1234-5678                          │   │
│   │  department: Engineering                                │   │
│   └─────────────────────────────────────────────────────────┘   │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

컨텍스트 (Context)

JNDI에서 컨텍스트는 바인딩들의 집합을 나타내며, 파일 시스템의 디렉터리와 유사합니다.

┌─────────────────────────────────────────────────────────────────┐
│                    Context 계층 구조                             │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│   InitialContext (루트)                                          │
│   │                                                              │
│   ├── java:comp/env (컴포넌트 환경)                              │
│   │   │                                                          │
│   │   ├── jdbc                                                   │
│   │   │   ├── myDB          → DataSource                        │
│   │   │   └── oracleDB      → DataSource                        │
│   │   │                                                          │
│   │   ├── jms                                                    │
│   │   │   ├── connectionFactory → ConnectionFactory             │
│   │   │   └── myQueue           → Queue                         │
│   │   │                                                          │
│   │   └── ejb                                                    │
│   │       └── UserService   → EJB Reference                     │
│   │                                                              │
│   └── java:global (글로벌 네임스페이스)                          │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

JNDI 아키텍처

API와 SPI

┌─────────────────────────────────────────────────────────────────┐
│                    JNDI Architecture                             │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│   ┌─────────────────────────────────────────────────────────┐   │
│   │                    Application                          │   │
│   └─────────────────────────┬───────────────────────────────┘   │
│                             │                                    │
│                             ▼                                    │
│   ┌─────────────────────────────────────────────────────────┐   │
│   │              JNDI API (javax.naming.*)                  │   │
│   │  ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────────┐   │   │
│   │  │ Context │ │  Name   │ │Binding  │ │ Reference   │   │   │
│   │  └─────────┘ └─────────┘ └─────────┘ └─────────────┘   │   │
│   └─────────────────────────┬───────────────────────────────┘   │
│                             │                                    │
│                             ▼                                    │
│   ┌─────────────────────────────────────────────────────────┐   │
│   │                   Naming Manager                        │   │
│   └─────────────────────────┬───────────────────────────────┘   │
│                             │                                    │
│                             ▼                                    │
│   ┌─────────────────────────────────────────────────────────┐   │
│   │           JNDI SPI (javax.naming.spi.*)                 │   │
│   └─────────────────────────┬───────────────────────────────┘   │
│                             │                                    │
│           ┌─────────────────┼─────────────────┐                 │
│           ▼                 ▼                 ▼                 │
│   ┌─────────────┐   ┌─────────────┐   ┌─────────────┐          │
│   │LDAP Provider│   │ DNS Provider│   │ RMI Provider│          │
│   └─────────────┘   └─────────────┘   └─────────────┘          │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

주요 패키지

패키지설명
javax.naming네이밍 서비스 접근을 위한 클래스와 인터페이스
javax.naming.directory디렉터리 서비스 접근을 위한 확장
javax.naming.event이벤트 알림 지원
javax.naming.ldapLDAP v3 확장 기능 지원
javax.naming.spi서비스 프로바이더 인터페이스

기본 사용법

InitialContext 생성

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import java.util.Hashtable;

public class JndiBasicExample {

    public static void main(String[] args) {
        try {
            // 1. 기본 InitialContext (애플리케이션 서버 환경)
            Context ctx = new InitialContext();

            // 2. 환경 속성을 사용한 InitialContext
            Hashtable<String, String> env = new Hashtable<>();
            env.put(Context.INITIAL_CONTEXT_FACTORY,
                "com.sun.jndi.ldap.LdapCtxFactory");
            env.put(Context.PROVIDER_URL,
                "ldap://localhost:389");

            Context ldapCtx = new InitialContext(env);

            // 3. 시스템 프로퍼티 사용
            // -Djava.naming.factory.initial=...
            // -Djava.naming.provider.url=...

        } catch (NamingException e) {
            e.printStackTrace();
        }
    }
}

환경 속성

속성상수설명
java.naming.factory.initialContext.INITIAL_CONTEXT_FACTORY초기 컨텍스트 팩토리 클래스
java.naming.provider.urlContext.PROVIDER_URL서비스 프로바이더 URL
java.naming.security.authenticationContext.SECURITY_AUTHENTICATION인증 방식
java.naming.security.principalContext.SECURITY_PRINCIPAL사용자 이름 (DN)
java.naming.security.credentialsContext.SECURITY_CREDENTIALS비밀번호

주요 연산

바인딩과 조회

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;

public class JndiOperationsExample {

    public void basicOperations() throws NamingException {
        Context ctx = new InitialContext();

        // 1. bind: 이름에 객체 바인딩 (이미 존재하면 예외)
        MyService service = new MyService();
        ctx.bind("myService", service);

        // 2. rebind: 이름에 객체 바인딩 (이미 존재하면 교체)
        ctx.rebind("myService", new MyService());

        // 3. lookup: 이름으로 객체 조회
        Object obj = ctx.lookup("myService");
        MyService lookedUp = (MyService) obj;

        // 4. unbind: 바인딩 제거
        ctx.unbind("myService");

        // 5. rename: 이름 변경
        ctx.rename("oldName", "newName");

        // 6. list: 컨텍스트의 바인딩 목록 조회
        javax.naming.NamingEnumeration<javax.naming.NameClassPair> list =
            ctx.list("");
        while (list.hasMore()) {
            javax.naming.NameClassPair pair = list.next();
            System.out.println(pair.getName() + " : " + pair.getClassName());
        }

        // 7. listBindings: 바인딩과 객체 함께 조회
        javax.naming.NamingEnumeration<javax.naming.Binding> bindings =
            ctx.listBindings("");
        while (bindings.hasMore()) {
            javax.naming.Binding binding = bindings.next();
            System.out.println(binding.getName() + " = " + binding.getObject());
        }

        // 리소스 해제
        ctx.close();
    }
}

서브컨텍스트 생성

public class SubContextExample {

    public void createSubContext() throws NamingException {
        Context ctx = new InitialContext();

        // 서브컨텍스트 생성
        Context subCtx = ctx.createSubcontext("myApp");

        // 서브컨텍스트에 바인딩
        subCtx.bind("config", new Configuration());

        // 전체 경로로 조회
        Object obj = ctx.lookup("myApp/config");

        // 서브컨텍스트 삭제 (비어있어야 함)
        ctx.destroySubcontext("myApp");
    }
}

DataSource JNDI 설정

Tomcat에서 DataSource 설정

context.xml (META-INF/ 또는 conf/)

<?xml version="1.0" encoding="UTF-8"?>
<Context>
    <Resource name="jdbc/myDB"
              auth="Container"
              type="javax.sql.DataSource"
              driverClassName="com.mysql.cj.jdbc.Driver"
              url="jdbc:mysql://localhost:3306/mydb"
              username="root"
              password="password"
              maxTotal="20"
              maxIdle="10"
              maxWaitMillis="10000"
              validationQuery="SELECT 1"
              testOnBorrow="true"/>
</Context>

web.xml에서 리소스 참조

<web-app>
    <resource-ref>
        <description>MySQL DataSource</description>
        <res-ref-name>jdbc/myDB</res-ref-name>
        <res-type>javax.sql.DataSource</res-type>
        <res-auth>Container</res-auth>
    </resource-ref>
</web-app>

Java 코드에서 사용

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.sql.DataSource;
import java.sql.Connection;

public class DataSourceLookupExample {

    public Connection getConnection() throws Exception {
        // 1. InitialContext 생성
        Context initCtx = new InitialContext();

        // 2. 환경 네이밍 컨텍스트 조회
        Context envCtx = (Context) initCtx.lookup("java:comp/env");

        // 3. DataSource 조회
        DataSource ds = (DataSource) envCtx.lookup("jdbc/myDB");

        // 또는 한 번에 조회
        // DataSource ds = (DataSource) initCtx.lookup("java:comp/env/jdbc/myDB");

        // 4. Connection 획득
        return ds.getConnection();
    }

    public void useConnection() {
        try (Connection conn = getConnection()) {
            // 데이터베이스 작업 수행
            System.out.println("Connected: " + conn.getCatalog());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

Spring에서 JNDI DataSource 사용

application.properties

# JNDI DataSource 사용
spring.datasource.jndi-name=java:comp/env/jdbc/myDB

Java Config 방식

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jndi.JndiObjectFactoryBean;

import javax.naming.NamingException;
import javax.sql.DataSource;

@Configuration
public class JndiDataSourceConfig {

    @Bean
    public DataSource dataSource() throws NamingException {
        JndiObjectFactoryBean jndiFactory = new JndiObjectFactoryBean();
        jndiFactory.setJndiName("java:comp/env/jdbc/myDB");
        jndiFactory.setProxyInterface(DataSource.class);
        jndiFactory.setLookupOnStartup(true);
        jndiFactory.afterPropertiesSet();
        return (DataSource) jndiFactory.getObject();
    }
}

XML Config 방식

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:jee="http://www.springframework.org/schema/jee">

    <jee:jndi-lookup id="dataSource"
                     jndi-name="java:comp/env/jdbc/myDB"
                     expected-type="javax.sql.DataSource"/>

</beans>

Spring Boot 내장 Tomcat에서 JNDI 설정

import org.apache.catalina.Context;
import org.apache.catalina.startup.Tomcat;
import org.apache.tomcat.util.descriptor.web.ContextResource;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.embedded.tomcat.TomcatWebServer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class TomcatJndiConfig {

    @Bean
    public TomcatServletWebServerFactory tomcatFactory() {
        return new TomcatServletWebServerFactory() {
            @Override
            protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) {
                tomcat.enableNaming();  // JNDI 활성화
                return super.getTomcatWebServer(tomcat);
            }

            @Override
            protected void postProcessContext(Context context) {
                // DataSource 리소스 정의
                ContextResource resource = new ContextResource();
                resource.setName("jdbc/myDB");
                resource.setType(javax.sql.DataSource.class.getName());
                resource.setProperty("driverClassName", "com.mysql.cj.jdbc.Driver");
                resource.setProperty("url", "jdbc:mysql://localhost:3306/mydb");
                resource.setProperty("username", "root");
                resource.setProperty("password", "password");
                resource.setProperty("maxTotal", "20");
                resource.setProperty("maxIdle", "10");

                context.getNamingResources().addResource(resource);
            }
        };
    }
}

LDAP 연동

LDAP 연결 및 검색

import javax.naming.Context;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.*;
import java.util.Hashtable;

public class LdapExample {

    private DirContext ctx;

    public void connect() throws NamingException {
        Hashtable<String, String> env = new Hashtable<>();

        // LDAP 서비스 프로바이더 설정
        env.put(Context.INITIAL_CONTEXT_FACTORY,
            "com.sun.jndi.ldap.LdapCtxFactory");
        env.put(Context.PROVIDER_URL,
            "ldap://localhost:389");

        // 인증 설정
        env.put(Context.SECURITY_AUTHENTICATION, "simple");
        env.put(Context.SECURITY_PRINCIPAL, "cn=admin,dc=example,dc=com");
        env.put(Context.SECURITY_CREDENTIALS, "adminPassword");

        // 연결 타임아웃
        env.put("com.sun.jndi.ldap.connect.timeout", "5000");

        ctx = new InitialDirContext(env);
        System.out.println("LDAP Connected!");
    }

    public void searchUsers() throws NamingException {
        // 검색 컨트롤 설정
        SearchControls controls = new SearchControls();
        controls.setSearchScope(SearchControls.SUBTREE_SCOPE);
        controls.setReturningAttributes(new String[]{"cn", "mail", "telephoneNumber"});

        // 검색 실행
        String searchBase = "ou=Users,dc=example,dc=com";
        String searchFilter = "(objectClass=person)";

        NamingEnumeration<SearchResult> results =
            ctx.search(searchBase, searchFilter, controls);

        while (results.hasMore()) {
            SearchResult result = results.next();
            Attributes attrs = result.getAttributes();

            System.out.println("DN: " + result.getNameInNamespace());
            System.out.println("  CN: " + attrs.get("cn").get());

            Attribute mail = attrs.get("mail");
            if (mail != null) {
                System.out.println("  Mail: " + mail.get());
            }
        }

        results.close();
    }

    public void addUser(String cn, String email) throws NamingException {
        // 속성 설정
        Attributes attrs = new BasicAttributes();
        attrs.put("objectClass", "inetOrgPerson");
        attrs.put("cn", cn);
        attrs.put("sn", cn);
        attrs.put("mail", email);

        // 엔트리 추가
        String dn = "cn=" + cn + ",ou=Users,dc=example,dc=com";
        ctx.createSubcontext(dn, attrs);
        System.out.println("User added: " + dn);
    }

    public void modifyUser(String cn, String newEmail) throws NamingException {
        String dn = "cn=" + cn + ",ou=Users,dc=example,dc=com";

        // 수정 항목 정의
        ModificationItem[] mods = new ModificationItem[1];
        mods[0] = new ModificationItem(
            DirContext.REPLACE_ATTRIBUTE,
            new BasicAttribute("mail", newEmail)
        );

        ctx.modifyAttributes(dn, mods);
        System.out.println("User modified: " + dn);
    }

    public void deleteUser(String cn) throws NamingException {
        String dn = "cn=" + cn + ",ou=Users,dc=example,dc=com";
        ctx.destroySubcontext(dn);
        System.out.println("User deleted: " + dn);
    }

    public void close() throws NamingException {
        if (ctx != null) {
            ctx.close();
        }
    }
}

Spring LDAP 사용

import org.springframework.ldap.core.LdapTemplate;
import org.springframework.ldap.core.AttributesMapper;
import org.springframework.ldap.query.LdapQueryBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.ldap.core.support.LdapContextSource;

import java.util.List;

@Configuration
public class SpringLdapConfig {

    @Bean
    public LdapContextSource contextSource() {
        LdapContextSource contextSource = new LdapContextSource();
        contextSource.setUrl("ldap://localhost:389");
        contextSource.setBase("dc=example,dc=com");
        contextSource.setUserDn("cn=admin,dc=example,dc=com");
        contextSource.setPassword("adminPassword");
        return contextSource;
    }

    @Bean
    public LdapTemplate ldapTemplate() {
        return new LdapTemplate(contextSource());
    }
}

@Service
public class UserLdapService {

    @Autowired
    private LdapTemplate ldapTemplate;

    public List<String> getAllUserNames() {
        return ldapTemplate.search(
            LdapQueryBuilder.query()
                .where("objectClass").is("person"),
            (AttributesMapper<String>) attrs -> (String) attrs.get("cn").get()
        );
    }

    public List<User> findByEmail(String email) {
        return ldapTemplate.search(
            LdapQueryBuilder.query()
                .where("objectClass").is("person")
                .and("mail").is(email),
            new UserAttributesMapper()
        );
    }
}

EJB와 JMS JNDI

EJB Lookup

import javax.naming.Context;
import javax.naming.InitialContext;

public class EjbLookupExample {

    public void lookupEjb() throws Exception {
        Context ctx = new InitialContext();

        // 로컬 EJB 조회 (java:comp/env 사용)
        UserServiceLocal userService =
            (UserServiceLocal) ctx.lookup("java:comp/env/ejb/UserService");

        // 글로벌 JNDI 이름으로 조회 (Java EE 6+)
        // java:global/[app-name]/[module-name]/[bean-name]
        UserServiceRemote remoteService =
            (UserServiceRemote) ctx.lookup(
                "java:global/myApp/myModule/UserServiceBean");

        // 모듈 범위 조회
        // java:module/[bean-name]
        UserServiceLocal moduleService =
            (UserServiceLocal) ctx.lookup("java:module/UserServiceBean");

        // 애플리케이션 범위 조회
        // java:app/[module-name]/[bean-name]
        UserServiceLocal appService =
            (UserServiceLocal) ctx.lookup("java:app/myModule/UserServiceBean");
    }
}

JMS 리소스 Lookup

import javax.jms.*;
import javax.naming.Context;
import javax.naming.InitialContext;

public class JmsLookupExample {

    public void sendMessage(String text) throws Exception {
        Context ctx = new InitialContext();

        // ConnectionFactory 조회
        ConnectionFactory cf =
            (ConnectionFactory) ctx.lookup("java:comp/env/jms/ConnectionFactory");

        // Queue 조회
        Queue queue =
            (Queue) ctx.lookup("java:comp/env/jms/MyQueue");

        // Topic 조회
        Topic topic =
            (Topic) ctx.lookup("java:comp/env/jms/MyTopic");

        // JMS 2.0 방식
        try (JMSContext jmsContext = cf.createContext()) {
            jmsContext.createProducer().send(queue, text);
            System.out.println("Message sent: " + text);
        }
    }

    public void receiveMessage() throws Exception {
        Context ctx = new InitialContext();

        ConnectionFactory cf =
            (ConnectionFactory) ctx.lookup("java:comp/env/jms/ConnectionFactory");
        Queue queue =
            (Queue) ctx.lookup("java:comp/env/jms/MyQueue");

        try (JMSContext jmsContext = cf.createContext()) {
            JMSConsumer consumer = jmsContext.createConsumer(queue);
            String message = consumer.receiveBody(String.class, 5000);
            System.out.println("Message received: " + message);
        }
    }
}

web.xml에서 JMS 리소스 참조

<web-app>
    <!-- JMS ConnectionFactory -->
    <resource-ref>
        <res-ref-name>jms/ConnectionFactory</res-ref-name>
        <res-type>javax.jms.ConnectionFactory</res-type>
        <res-auth>Container</res-auth>
    </resource-ref>

    <!-- JMS Queue -->
    <resource-env-ref>
        <resource-env-ref-name>jms/MyQueue</resource-env-ref-name>
        <resource-env-ref-type>javax.jms.Queue</resource-env-ref-type>
    </resource-env-ref>

    <!-- JMS Topic -->
    <resource-env-ref>
        <resource-env-ref-name>jms/MyTopic</resource-env-ref-name>
        <resource-env-ref-type>javax.jms.Topic</resource-env-ref-type>
    </resource-env-ref>
</web-app>

Java EE 네임스페이스

표준 JNDI 네임스페이스

┌─────────────────────────────────────────────────────────────────┐
│                  Java EE JNDI Namespaces                         │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│  java:comp/env     컴포넌트 환경 (각 컴포넌트별 private)          │
│  ├── jdbc/         JDBC DataSource                              │
│  ├── jms/          JMS 리소스                                   │
│  ├── mail/         JavaMail 세션                                │
│  ├── url/          URL 리소스                                   │
│  └── ejb/          EJB 참조                                     │
│                                                                  │
│  java:global/      글로벌 네임스페이스 (모든 애플리케이션 접근)    │
│  └── [app]/[module]/[bean]                                      │
│                                                                  │
│  java:app/         애플리케이션 범위 네임스페이스                  │
│  └── [module]/[bean]                                            │
│                                                                  │
│  java:module/      모듈 범위 네임스페이스                         │
│  └── [bean]                                                     │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

네임스페이스별 특징

네임스페이스범위접근 가능 대상
java:comp컴포넌트해당 컴포넌트만
java:module모듈같은 모듈 내 컴포넌트
java:app애플리케이션같은 애플리케이션 내 컴포넌트
java:global글로벌모든 애플리케이션

@Resource 어노테이션

의존성 주입으로 JNDI 사용

import javax.annotation.Resource;
import javax.sql.DataSource;
import javax.jms.ConnectionFactory;
import javax.jms.Queue;

@WebServlet("/example")
public class ResourceInjectionExample extends HttpServlet {

    // DataSource 주입
    @Resource(name = "jdbc/myDB")
    private DataSource dataSource;

    // JMS ConnectionFactory 주입
    @Resource(name = "jms/ConnectionFactory")
    private ConnectionFactory connectionFactory;

    // JMS Queue 주입
    @Resource(name = "jms/MyQueue")
    private Queue queue;

    // 환경 엔트리 주입
    @Resource(name = "maxRetries")
    private int maxRetries;

    // lookup 속성으로 JNDI 이름 지정
    @Resource(lookup = "java:comp/env/jdbc/myDB")
    private DataSource dsWithLookup;

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
        try (Connection conn = dataSource.getConnection()) {
            // 데이터베이스 작업
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

EJB에서 @Resource 사용

import javax.annotation.Resource;
import javax.ejb.Stateless;
import javax.ejb.SessionContext;
import javax.sql.DataSource;

@Stateless
public class MyServiceBean {

    // SessionContext 주입
    @Resource
    private SessionContext ctx;

    // DataSource 주입
    @Resource(name = "jdbc/myDB")
    private DataSource dataSource;

    // 환경 엔트리
    @Resource(name = "appConfig/timeout")
    private int timeout;

    public void doWork() {
        // EJB 컨텍스트 사용
        String caller = ctx.getCallerPrincipal().getName();

        // DataSource 사용
        try (Connection conn = dataSource.getConnection()) {
            // 작업 수행
        } catch (SQLException e) {
            ctx.setRollbackOnly();
        }
    }
}

사용자 정의 객체 바인딩

Reference와 ObjectFactory

import javax.naming.Reference;
import javax.naming.StringRefAddr;
import javax.naming.spi.ObjectFactory;
import javax.naming.Context;
import javax.naming.Name;
import java.util.Hashtable;

// 1. 바인딩할 객체
public class MyConfig {
    private String serverUrl;
    private int timeout;

    // getters, setters, constructors
    public MyConfig(String serverUrl, int timeout) {
        this.serverUrl = serverUrl;
        this.timeout = timeout;
    }

    public String getServerUrl() { return serverUrl; }
    public int getTimeout() { return timeout; }
}

// 2. ObjectFactory 구현
public class MyConfigFactory implements ObjectFactory {

    @Override
    public Object getObjectInstance(Object obj, Name name,
            Context nameCtx, Hashtable<?, ?> environment) throws Exception {

        if (obj instanceof Reference) {
            Reference ref = (Reference) obj;

            String serverUrl = (String) ref.get("serverUrl").getContent();
            int timeout = Integer.parseInt(
                (String) ref.get("timeout").getContent());

            return new MyConfig(serverUrl, timeout);
        }
        return null;
    }
}

// 3. 바인딩 예제
public class CustomObjectBindingExample {

    public void bindCustomObject() throws NamingException {
        Context ctx = new InitialContext();

        // Reference 생성
        Reference ref = new Reference(
            MyConfig.class.getName(),
            MyConfigFactory.class.getName(),
            null  // factory location (classpath면 null)
        );

        // Reference에 속성 추가
        ref.add(new StringRefAddr("serverUrl", "http://api.example.com"));
        ref.add(new StringRefAddr("timeout", "30000"));

        // 바인딩
        ctx.bind("myConfig", ref);
    }

    public void lookupCustomObject() throws NamingException {
        Context ctx = new InitialContext();

        // ObjectFactory가 자동으로 MyConfig 객체 생성
        MyConfig config = (MyConfig) ctx.lookup("myConfig");

        System.out.println("Server URL: " + config.getServerUrl());
        System.out.println("Timeout: " + config.getTimeout());
    }
}

보안 고려사항

JNDI Injection 취약점

JNDI Injection은 신뢰할 수 없는 입력을 JNDI lookup에 사용할 때 발생하는 보안 취약점입니다.

// 취약한 코드 예시 (사용 금지!)
public void vulnerableLookup(String userInput) throws NamingException {
    Context ctx = new InitialContext();
    // 사용자 입력을 직접 lookup에 사용 - 위험!
    Object obj = ctx.lookup(userInput);  // JNDI Injection 취약
}

// 안전한 코드
public void safeLookup(String resourceName) throws NamingException {
    // 화이트리스트 검증
    Set<String> allowedResources = Set.of("jdbc/myDB", "jms/queue");

    if (!allowedResources.contains(resourceName)) {
        throw new IllegalArgumentException("Invalid resource name");
    }

    Context ctx = new InitialContext();
    Object obj = ctx.lookup("java:comp/env/" + resourceName);
}

보안 모범 사례

public class SecureJndiExample {

    // 1. 하드코딩된 JNDI 이름 사용
    private static final String DATASOURCE_NAME = "java:comp/env/jdbc/myDB";

    // 2. 프로퍼티 파일에서 읽되, 검증 수행
    public DataSource getDataSource() throws NamingException {
        Context ctx = new InitialContext();
        return (DataSource) ctx.lookup(DATASOURCE_NAME);
    }

    // 3. JVM 옵션으로 JNDI 제한 (Java 8u191+, 11.0.1+)
    // -Dcom.sun.jndi.ldap.object.trustURLCodebase=false
    // -Dcom.sun.jndi.rmi.object.trustURLCodebase=false

    // 4. Log4j 취약점 대응 (Log4j 2.17.0+)
    // log4j2.formatMsgNoLookups=true
}

JNDI vs 직접 설정

비교

┌─────────────────────────────────────────────────────────────────┐
│                    JNDI vs 직접 설정 비교                        │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│  JNDI 방식                                                       │
│  ┌───────────────────────────────────────────────────────────┐  │
│  │  장점:                                                    │  │
│  │  - 설정과 코드 분리                                        │  │
│  │  - 동일 코드로 여러 환경 지원                               │  │
│  │  - 컨테이너 관리 커넥션 풀                                  │  │
│  │  - 중앙 집중식 설정 관리                                    │  │
│  │                                                           │  │
│  │  단점:                                                    │  │
│  │  - 서버 설정 필요                                          │  │
│  │  - 테스트 환경 구성 복잡                                    │  │
│  │  - 컨테이너 의존성                                         │  │
│  └───────────────────────────────────────────────────────────┘  │
│                                                                  │
│  직접 설정 방식                                                  │
│  ┌───────────────────────────────────────────────────────────┐  │
│  │  장점:                                                    │  │
│  │  - 간단한 설정                                             │  │
│  │  - 컨테이너 독립적                                         │  │
│  │  - 테스트 용이                                             │  │
│  │                                                           │  │
│  │  단점:                                                    │  │
│  │  - 환경별 설정 파일 필요                                    │  │
│  │  - 민감 정보 코드/설정 파일에 포함                          │  │
│  └───────────────────────────────────────────────────────────┘  │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

사용 시나리오

시나리오권장 방식
엔터프라이즈 애플리케이션 서버 배포JNDI
마이크로서비스/클라우드 네이티브직접 설정 (환경 변수)
로컬 개발/테스트직접 설정
여러 애플리케이션이 동일 리소스 공유JNDI
Spring Boot 단독 실행직접 설정 또는 외부 설정 서버

참고 자료