JNDI 简介

一、JNDI 是什么

JNDI是 Java 命名与目录接口(Java Naming and Directory Interface),在J2EE规范中是重要的规范之一,不少专家认为,没有透彻理解JNDI的意义和作用,就没有真正掌握J2EE特别是EJB的知识。

JNDI(JavaNaming and Directory Interface)是一个应用程序设计的API,为开发人员提供了查找和访问各种命名和目录服务的通用、统一的接口,类似JDBC都是构建在抽象层上。现在JNDI已经成为J2EE的标准之一,所有的J2EE容器都必须提供一个JNDI的服务。

举个例子数据源配置:JBOSS配置一个数据源,并绑定到JNDI

<?xml version="1.0" encoding="UTF-8"?>
<datasources>
    <local-tx-datasource>
        <jndi-name>MySqlDS</jndi-name>
        <connection-url>jdbc:mysql://localhost:3306/lw</connection-url>
        <driver-class>com.mysql.jdbc.Driver</driver-class>
        <user-name>root</user-name>
        <password>rootpassword</password>
        <exception-sorter-class-name>org.jboss.resource.adapter.jdbc.vendor.MySQLExceptionSorter
        </exception-sorter-class-name>
        <metadata>
            <type-mapping>mySQL</type-mapping>
        </metadata>
    </local-tx-datasource>
</datasources> 
View Code

在程序中引用数据源:

Connection conn=null;
try {
    Context ctx=new InitialContext();
    Object datasourceRef=ctx.lookup("java:MySqlDS"); //引用数据源
    DataSource ds=(Datasource)datasourceRef;
    conn=ds.getConnection();
    /* 使用conn进行数据库SQL操作 */
    ......
    c.close();
}catch(Exception e) {
    e.printStackTrace();
}finally {
    if(conn!=null) {
    try {
        conn.close();
} catch(SQLException e) { }
 
View Code

直接使用JDBC或者通过JNDI引用数据源的编程代码量相差无几,但是现在的程序可以不用关心具体JDBC参数了。
在系统部署后,如果数据库的相关参数变更,只需要重新配置 mysql-ds.xml 修改其中的JDBC参数,只要保证数据源的名称不变,那么程序源代码就无需修改。

由此可见,JNDI避免了程序与数据库之间的紧耦合,使应用更加易于配置、易于部署。

JNDI的扩展:JNDI在满足了数据源配置的要求的基础上,还进一步扩充了作用:所有与系统外部的资源的引用,都可以通过JNDI定义和引用。

所以,在J2EE规范中,J2EE 中的资源并不局限于 JDBC 数据源。引用的类型有很多,其中包括资源引用(已经讨论过)、环境实体和 EJB 引用。特别是 EJB 引用,它暴露了 JNDI 在 J2EE 中的另外一项关键角色:查找其他应用程序组件。

EJB 的 JNDI 引用非常类似于 JDBC 资源的引用。在服务趋于转换的环境中,这是一种很有效的方法。可以对应用程序架构中所得到的所有组件进行这类配置管理,从 EJB 组件到 JMS 队列和主题,再到简单配置字符串或其他对象,这可以降低随时间的推移服务变更所产生的维护成本,同时还可以简化部署,减少集成工作。 外部资源”。

总 结:

J2EE 规范要求所有 J2EE 容器都要提供 JNDI 规范的实现。JNDI 在 J2EE 中的角色就是“交换机” —— J2EE 组件在运行时间接地查找其他组件、资源或服务的通用机制。在多数情况下,提供 JNDI 供应者的容器可以充当有限的数据存储,这样管理员就可以设置应用程序的执行属性,并让其他应用程序引用这些属性(Java 管理扩展(Java Management Extensions,JMX)也可以用作这个目的)。JNDI 在 J2EE 应用程序中的主要角色就是提供间接层,这样组件就可以发现所需要的资源,而不用了解这些间接性。

在 J2EE 中,JNDI 是把 J2EE 应用程序合在一起的粘合剂,JNDI 提供的间接寻址允许跨企业交付可伸缩的、功能强大且很灵活的应用程序。这是 J2EE 的承诺,而且经过一些计划和预先考虑,这个承诺是完全可以实现的。

最近一直在对J2EE的笔记进行整理和复习,虽然J2EE视频是看过一遍了,但是当我看自己做的笔记的时候陌生程度还是很大,而真正的对某个概念有所认识的时候是将笔记和以前看过的视频印象进行摩擦,J2EE主要讲解的内容是各个规范,再清楚一些就是各个概念,现阶段的目标并不是掌握J2EE,而是对J2EE进行轮廓和概念上的了解和认识,到下一步DRP项目中再深层次的对各个规范进行摩擦和认识。

JNDI,翻译为Java命名和目录结构(JavaNaming And Directory Interface)官方对其解释为JNDI是一组在Java应用中访问命名和目录服务的API(ApplicationProgramming Interface)说明很精炼,但是比较抽象。

具体描述参见:http://blog.csdn.net/sunkobe2494/article/details/50824359

二、JNDI的使用

我们直接实现一个JNDI:

public class JNDIClient {

    public static void main(String[] args) {
        try {
            JNDIContainer container = new JNDIContainer();
            container.init();

            // JNDI客户端使用标准JNDI接口访问命名服务。
            Context ctx = container.getContext();
            DBService db = (DBService) ctx.lookup("DBService");
            System.out.println("db location is:" + db.getLocation()
                    + ",state is:" + db.getState());
            db.accessDB();

            LogService ls = (LogService) ctx.lookup("LogService");
            ls.log("this is a log message.");

            container.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
View Code

这是client调用,模拟的JNDIcontainer如下:

public class JNDIContainer {
    private Context ctx = null;

    public void init() throws Exception {
        // 初始化JNDI提供者。
        Hashtable env = new Hashtable();
        env.put(Context.INITIAL_CONTEXT_FACTORY,
                "com.sun.jndi.fscontext.RefFSContextFactory");
        env.put(Context.PROVIDER_URL, "file:/c:/sample"); // fscontext的初始目录,我们需要在c:\下创建sample目录。
        ctx = new InitialContext(env);
        loadServices();
    }

    // 从配置文件JNDIContainer.properties中读取DBService和LogService实现,绑定到Context中。
    private void loadServices() throws Exception {
        InputStream in = getClass().getResourceAsStream(
                "JNDIContainer.properties");
        Properties props = new Properties();
        props.load(in);
    
        // inject dbservice
        String s = props.getProperty("DBServiceClass");
        Object obj = Class.forName(s).newInstance();
        if (obj instanceof DBService) {
            DBService db = (DBService) obj;
            String[] ss = props.getProperty("DBServiceProperty").split(";");
            for (int i = 0; i < ss.length; i++)
                db.setProperty(i, ss[i]);
            ctx.rebind(props.getProperty("DBServiceName"), db);
        }

        // inject logservice
        s = props.getProperty("LogServiceClass");
        obj = Class.forName(s).newInstance();
        if (obj instanceof LogService) {
            LogService log = (LogService) obj;
            ctx.rebind(props.getProperty("LogServiceName"), log);
        }
    }

    public void close() throws NamingException {
        ctx.close();
    }

    public Context getContext() {
        return ctx;
    }
}
View Code

相应的配置文件:

DBServiceName=DBService
DBServiceClass=xyz.serviceprovider.SimpleDBService
DBServiceProperty=mydb//192.168.1.2:8421/testdb;start
LogServiceName=LogService
LogServiceClass=xyz.serviceprovider.SimpleLogService
View Code

具体的service:

//为了将数据库服务实例加入JNDI的Context中,我们需要实现Referenceable接口,并实现RetReference方法。
//关于Reference和Referenceable,请参考上一篇:Java技术回顾之JNDI:JNDI API
public class SimpleDBService implements Referenceable, DBService {

    private String location = "mydb//local:8421/defaultdb";// 数据库服务属性之一:数据库位置
    private String state = "start"; // 数据库服务属性之二:数据库状态

    public Reference getReference() throws NamingException {
        // Reference是对象的引用,Context中存放的是Reference,为了从Reference中还原出对象实例,
        // 我们需要添加RefAddr,它们是创建对象实例的线索。在我们的SimpleDBService中,location和state是这样两个线索。
        Reference ref = new Reference(getClass().getName(),
                SimpleDBServiceFactory.class.getName(), null);
        ref.add(new StringRefAddr("location", location));
        ref.add(new StringRefAddr("state", state));
        return ref;
    }

    public void accessDB() {
        if (state.equals("start"))
            System.out.println("we are accessing DB.");
        else
            System.out.println("DB is not start.");
    }

    public String getLocation() {
        return location;
    }

    public String getState() {
        return state;
    }

    public void setProperty(int index, String property) {
        if (index == 0)
            location = property;
        else
            state = property;
    }
}
View Code
//数据库服务对象工厂类被JNDI提供者调用来创建数据库服务实例,对使用JNDI的客户不可见。
public class SimpleDBServiceFactory implements ObjectFactory {
    // 根据Reference中存储的信息创建出SimpleDBService实例
    public Object getObjectInstance(Object obj, Name name, Context ctx,
            Hashtable<?, ?> env) throws Exception {
        if (obj instanceof Reference) {
            Reference ref = (Reference) obj;
            String location = (String) ref.get("location").getContent();
            String state = (String) ref.get("state").getContent();
            SimpleDBService db = new SimpleDBService();
            db.setProperty(0, location);
            db.setProperty(1, state);
            return db;
        }
        return null;
    }
}
View Code

这样我们就手工模拟了JNDI整个流程。client通过JNDICONTENT找到DBService,然后找到具体服务提供对象。

具体代码参见:http://blog.csdn.net/xiancaieeee/article/details/7881441

三、实际项目应用

JPA使用Bitronix数据源和事务管理:

EntityManagerFactory emf =
Persistence.createEntityManagerFactory("HelloWorldPU");

其中持久化单元配置为:META-INF->persistence.xml

<persistence version="2.1"
    xmlns="http://xmlns.jcp.org/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence
                            http://xmlns.jcp.org/xml/ns/persistence_2_1.xsd">

    <!-- The <code>persistence.xml</code> file configures at least one persistence 
        unit; each unit must have a unique name. -->
    <persistence-unit name="HelloWorldPU">

        <!-- Each persistence unit must have a database connection. Here you delegate 
            to an existing <code>java.sql.DataSource</code>. Hibernate will find the 
            data source by name with a JNDI lookup on startup. -->
        <jta-data-source>myDS</jta-data-source>

        <!-- A persistent unit has persistent (mapped) classes, you list them here. -->
        <class>org.jpwh.model.helloworld.Message</class>

        <!-- Hibernate can scan your classpath for mapped classes and add them 
            automatically to your persistence unit. This setting disables that feature. -->
        <exclude-unlisted-classes>true</exclude-unlisted-classes>

        <!-- Standard or vendor-specific options can be set as properties on a 
            persistence unit. Any standard properties have the <code>javax.persistence</code> 
            name prefix, Hibernate's settings use <code>hibernate</code> -->
        <properties>

            <!-- The JPA engine should drop and re-create the SQL schema in the database 
                automatically when it boots. This is ideal for automated testing, when you 
                want to work with a clean database for every test run. -->
            <property name="javax.persistence.schema-generation.database.action"
                value="drop-and-create" />

            <!-- When printing SQL in logs, let Hibernate format the SQL nicely and 
                generate comments into the SQL string so we know why Hibernate executed the 
                SQL statement. -->
            <property name="hibernate.format_sql" value="true" />
            <property name="hibernate.use_sql_comments" value="true" />

            <!-- Disable Hibernate scanning completely, we also don't want any hbm.xml 
                files discovered and added automatically. -->
            <property name="hibernate.archive.autodetection" value="none" />

        </properties>
    </persistence-unit>
</persistence>
View Code

配置的数据源为: <jta-data-source>myDS</jta-data-source>

JPA开始寻找JNDI.properties文件:

# Bitronix has a built-in JNDI context, for binding and looking up datasources
java.naming.factory.initial=bitronix.tm.jndi.BitronixInitialContextFactory

这样就会到Bitronix提供商去找该数据源。

注意的是 我们在之前已经配置好了Bitronix辅助帮助类:

/**
 * Provides a database connection pool with the Bitronix JTA transaction
 * manager (http://docs.codehaus.org/display/BTM/Home).
 * <p>
 * Hibernate will look up the datasource and <code>UserTransaction</code> through
 * JNDI, that's why you also need a <code>jndi.properties</code> file. A minimal
 * JNDI context is bundled with and started by Bitronix.
 * </p>
 */
public class TransactionManagerSetup {

    public static final String DATASOURCE_NAME = "myDS";

    private static final Logger logger =
        Logger.getLogger(TransactionManagerSetup.class.getName());

    protected final Context context = new InitialContext();
    protected final PoolingDataSource datasource;
    public final DatabaseProduct databaseProduct;

    public TransactionManagerSetup(DatabaseProduct databaseProduct) throws Exception {
        this(databaseProduct, null);
    }

    public TransactionManagerSetup(DatabaseProduct databaseProduct,
                                   String connectionURL) throws Exception {

        logger.fine("Starting database connection pool");

        logger.fine("Setting stable unique identifier for transaction recovery");
        TransactionManagerServices.getConfiguration().setServerId("myServer1234");

        logger.fine("Disabling JMX binding of manager in unit tests");
        TransactionManagerServices.getConfiguration().setDisableJmx(true);

        logger.fine("Disabling transaction logging for unit tests");
        TransactionManagerServices.getConfiguration().setJournal("null");

        logger.fine("Disabling warnings when the database isn't accessed in a transaction");
        TransactionManagerServices.getConfiguration().setWarnAboutZeroResourceTransaction(false);

        logger.fine("Creating connection pool");
        datasource = new PoolingDataSource();
        datasource.setUniqueName(DATASOURCE_NAME);
        datasource.setMinPoolSize(1);
        datasource.setMaxPoolSize(5);
        datasource.setPreparedStatementCacheSize(10);

        // Our locking/versioning tests assume READ COMMITTED transaction
        // isolation. This is not the default on MySQL InnoDB, so we set
        // it here explicitly.
        datasource.setIsolationLevel("READ_COMMITTED");

        // Hibernate's SQL schema generator calls connection.setAutoCommit(true)
        // and we use auto-commit mode when the EntityManager is in suspended
        // mode and not joined with a transaction.
        datasource.setAllowLocalTransactions(true);

        logger.info("Setting up database connection: " + databaseProduct);
        this.databaseProduct = databaseProduct;
        databaseProduct.configuration.configure(datasource, connectionURL);

        logger.fine("Initializing transaction and resource management");
        datasource.init();
    }

    public Context getNamingContext() {
        return context;
    }

    public UserTransaction getUserTransaction() {
        try {
            return (UserTransaction) getNamingContext()
                .lookup("java:comp/UserTransaction");
        } catch (Exception ex) {
            throw new RuntimeException(ex);
        }
    }

    public DataSource getDataSource() {
        try {
            return (DataSource) getNamingContext().lookup(DATASOURCE_NAME);
        } catch (Exception ex) {
            throw new RuntimeException(ex);
        }
    }

    public void rollback() {
        UserTransaction tx = getUserTransaction();
        try {
            if (tx.getStatus() == Status.STATUS_ACTIVE ||
                tx.getStatus() == Status.STATUS_MARKED_ROLLBACK)
                tx.rollback();
        } catch (Exception ex) {
            System.err.println("Rollback of transaction failed, trace follows!");
            ex.printStackTrace(System.err);
        }
    }

    public void stop() throws Exception {
        logger.fine("Stopping database connection pool");
        datasource.close();
        TransactionManagerServices.getTransactionManager().shutdown();
    }

}
View Code

这样Bitronix已经将数据源注册到JNDI Content中了。代码中的还提供了事务接口。我们需要的时候可以从这里获取事务实现。

  @Test
    public void storeLoadMessage() throws Exception {

        EntityManagerFactory emf =
            Persistence.createEntityManagerFactory("HelloWorldPU");

        try {
            {
                /* 
                    Get access to the standard transaction API <code>UserTransaction</code> and
                    begin a transaction on this thread of execution.
                 */
                UserTransaction tx = TM.getUserTransaction();
                tx.begin();

                /* 
                    Begin a new session with the database by creating an <code>EntityManager</code>, this
                    is your context for all persistence operations.
                 */
                EntityManager em = emf.createEntityManager();

                /* 
                    Create a new instance of the mapped domain model class <code>Message</code> and
                    set its <code>text</code> property.
                 */
                Message message = new Message();
                message.setText("Hello World!");

                /* 
                    Enlist the transient instance with your persistence context, you make it persistent.
                    Hibernate now knows that you wish to store that data, it doesn't necessarily call the
                    database immediately, however.
                 */
                em.persist(message);

                /* 
                    Commit the transaction, Hibernate now automatically checks the persistence context and
                    executes the necessary SQL <code>INSERT</code> statement.
                 */
                tx.commit();
                // INSERT into MESSAGE (ID, TEXT) values (1, 'Hello World!')

                /* 
                    If you create an <code>EntityManager</code>, you must close it.
                 */
                em.close();
            }

            {
                /* 
                    Every interaction with your database should occur within explicit transaction boundaries,
                    even if you are only reading data.
                 */
                UserTransaction tx = TM.getUserTransaction();
                tx.begin();

                EntityManager em = emf.createEntityManager();

                /* 
                    Execute a query to retrieve all instances of <code>Message</code> from the database.
                 */
                List<Message> messages =
                    em.createQuery("select m from Message m").getResultList();
                // SELECT * from MESSAGE


                assertEquals(messages.size(), 1);
                assertEquals(messages.get(0).getText(), "Hello World!");

                /* 
                    You can change the value of a property, Hibernate will detect this automatically because
                    the loaded <code>Message</code> is still attached to the persistence context it was loaded in.
                 */
                messages.get(0).setText("Take me to your leader!");

                /* 
                    On commit, Hibernate checks the persistence context for dirty state and executes the
                    SQL <code>UPDATE</code> automatically to synchronize the in-memory with the database state.
                 */
                tx.commit();
                // UPDATE MESSAGE set TEXT = 'Take me to your leader!' where ID = 1

                em.close();
            }

        } finally {
            TM.rollback();
            emf.close();
        }
    }
View Code

四、JNDI API 相关

代码:

//javax.naming.Context提供了查找JNDI 的接口  
Context ctx = new InitialContext();  
//java:comp/env/为前缀  
String testjndi = (String) ctx.lookup("java:comp/env/tjndi");  
out.println("JNDI: " + testjndi);  

为了避免JNDI命名空间中的资源名称互相冲突,并且避免可移植性问题,javaEE应用程序中的所有名称应该以字符串“java:comp/env”开始

InitialContext的构造方法主要是准备JNDI的访问环境,如果不加参数,那就意味着是用本地匿名访问,也就是说,用户角色是匿名,ctx.PROVIDER_URL是LOCALHOST
所以,对于本地测试(并且JNDI资源没有设置安全属性)这两段代码没有区别,如果要访问远程的JNDI资源,就必须用饱含JNDI环境参数Hashtable初始化InitialContext。

必要的环境参数如:
Context.INITIAL_CONTEXT_FACTORY//连接工厂
Context.PROVIDER_URL//访问连接
Context.SECURITY_PRINCIPAL//安全用户
Context.SECURITY_CREDENTIALS//用户密码 

J2EE中“ctx = new InitialContext(hash)”和“ctx = new InitialContext();”有什么区别

如果你要连接远程服务器
那就要把Factory,URL放到Properties之类的里
作为InitialContext()的参数
如果是无参的
那连接的是本地服务器

jndi.properties文件为所有Initial Contexts设置默认属性。

jndi.properties文件的搜索顺序为CLASSPATH、$JAVA_HOME/lib/

示例jndi.properties:

java.naming.factory.initial=weblogic.jndi.WLInitialContextFactory

//上下文工厂的类名。这是服务器提供者给定的类名。

java.naming.provider.url=t3://localhost:7001

//服务提供者所使用的配置信息。在这里指出网络位置信息,如:ip地址

java.naming.securiry.principal=system

//使用服务提供者的人员身份标识,如:用户名

java.naming.securiry.credentials=password

//该身份的密码

Context context=new InitialContext();

jndi.properties是jndi初始化文件。通常我们有两种方式来创建一个初始上下文:
1.通过创建一个Properties对象,设置 Context.PROVIDER_UR,Context.InitialContextFactroy等等属性,创建InitialContext,例如:
Properties p = new Properties();
p.put(Cotnext.PROVIDER_URL, "localhost:1099 ");//主机名和端口号
//InitialContext的创建工厂类(类名是我乱写的)
p.put(Context.InitialContextFactroy, "com.sun.InitialContextFactory ");
InitialContext ctx = new InitialContext(p);

2.通过jndi.properties文件创建初始上下文
java.naming.factory.initial=com.sun.NamingContextFactory
java.naming.provider.url=localhost:1099
如果直接创建初始上下文,如下:
InitialContext ctx = new InitialContext();
InitialContext的构造器会在类路径中找jndi.properties文件,如果找到,通过里面的属性,创建初始上下文。

所以从上面可以看出,两种方式完成的目标是相同的。

posted on 2018-01-28 12:30  TrustNature  阅读(300)  评论(0编辑  收藏  举报