Context ctx=new InitialContext(); Object obj=(Object)ctx.lookup("java:comp/env/XXX");
更是最经常使用的方式。说它陌生,是由于,对于JNDI,我们仅限于主要的使用,本文就是带领大家进入JNDI分析阶段。
以数据源为例。
假设我们没有使用JNDI,代码例如以下
Connection conn=null; try { Class.forName("com.mysql.jdbc.Driver", true, Thread.currentThread().getContextClassLoader()); conn=DriverManager.getConnection("jdbc:mysql://MyDBServer?user=qingfeng&password=mingyue"); /* 使用conn并进行SQL操作 */ ...... conn.close(); } catch(Exception e) { e.printStackTrace(); } finally { if(conn!=null) { try { conn.close(); } catch(SQLException e) {} } }这样的方法有非常大的问题,假设数据库变了怎么办?假设口令变了怎么办?写的太死,灵活性差。
在来看看使用了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>
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) { } } }
使用JNDI,能够将代码和数据库配置分离,减少了耦合,使得程序更灵活,能够看到JNDI的使用方式:
这仅仅是JNDI的用处之中的一个,你能够使用JNDI配置和使用很多其它内容,知道大概怎么使用,開始介绍JNDI。
规范位于命名空间javax.naming下:
上面是部分规范源码,由上图能够知道,JNDI规范分为5部分:
JNDI宏观架构图例如以下:
如上,各种厂商提供的产品,他们的命名和文件夹服务的标准不一致,各个文件夹服务採用的訪问协议也不一样(跟JDBC类同),假设直接訪问,须要编写不同的java代码。因此须要JNDI SPI,它能动态的插入这些命名和文件夹服务,可以将其协议专属的文件夹产品集成到系统中使得开发者仅仅须要知道名字,就可以获取到操作各种类型的内容。
当前JNDI支持的操作类型为:DNS、XNam 、Novell文件夹服务、LDAP(轻型文件夹訪问协议)、 CORBA对象服务、文件系统、Windows注冊表、RMI、DSML、NIS。
/** * Catalina JNDI Context implementation. * * @author Remy Maucherat */ public class NamingContext implements Context { // -------------------------------------------------------------- Constants /** * Name parser for this context. */ protected static final NameParser nameParser = new NameParserImpl(); private static final org.apache.juli.logging.Log log = org.apache.juli.logging.LogFactory.getLog(NamingContext.class); // ----------------------------------------------------------- Constructors /** * Builds a naming context using the given environment. */ public NamingContext(Hashtable<String,Object> env, String name, HashMap<String,NamingEntry> bindings) throws NamingException { this.env = new Hashtable<>(); // FIXME ? Could be put in the environment ? this.name = name; // Populating the environment hashtable if (env != null ) { Enumeration<String> envEntries = env.keys(); while (envEntries.hasMoreElements()) { String entryName = envEntries.nextElement(); addToEnvironment(entryName, env.get(entryName)); } } this.bindings = bindings; } // ----------------------------------------------------- Instance Variables /** * Environment. */ protected final Hashtable<String,Object> env; /** * The string manager for this package. */ protected static final StringManager sm = StringManager.getManager(Constants.Package); /** * Bindings in this Context. */ protected final HashMap<String,NamingEntry> bindings; /** * Name of the associated Catalina Context. */ protected final String name; /** * Determines if an attempt to write to a read-only context results in an * exception or if the request is ignored. */ private boolean exceptionOnFailedWrite = true; // -------------------------------------------------------- Context Methods @Override public void unbind(Name name) throws NamingException { if (!checkWritable()) { return; } while ((!name.isEmpty()) && (name.get(0).length() == 0)) name = name.getSuffix(1); if (name.isEmpty()) throw new NamingException (sm.getString("namingContext.invalidName")); NamingEntry entry = bindings.get(name.get(0)); if (entry == null) { throw new NameNotFoundException (sm.getString("namingContext.nameNotBound", name, name.get(0))); } if (name.size() > 1) { if (entry.type == NamingEntry.CONTEXT) { ((Context) entry.value).unbind(name.getSuffix(1)); } else { throw new NamingException (sm.getString("namingContext.contextExpected")); } } else { bindings.remove(name.get(0)); } } @Override public void rename(Name oldName, Name newName) throws NamingException { Object value = lookup(oldName); bind(newName, value); unbind(oldName); } @Override public NamingEnumeration<NameClassPair> list(Name name) throws NamingException { // Removing empty parts while ((!name.isEmpty()) && (name.get(0).length() == 0)) name = name.getSuffix(1); if (name.isEmpty()) { return new NamingContextEnumeration(bindings.values().iterator()); } NamingEntry entry = bindings.get(name.get(0)); if (entry == null) { throw new NameNotFoundException (sm.getString("namingContext.nameNotBound", name, name.get(0))); } if (entry.type != NamingEntry.CONTEXT) { throw new NamingException (sm.getString("namingContext.contextExpected")); } return ((Context) entry.value).list(name.getSuffix(1)); } /** * Enumerates the names bound in the named context, along with the * objects bound to them. The contents of any subcontexts are not * included. * <p> * If a binding is added to or removed from this context, its effect on * an enumeration previously returned is undefined. * * @param name the name of the context to list * @return an enumeration of the bindings in this context. * Each element of the enumeration is of type Binding. * @exception NamingException if a naming exception is encountered */ @Override public NamingEnumeration<Binding> listBindings(Name name) throws NamingException { // Removing empty parts while ((!name.isEmpty()) && (name.get(0).length() == 0)) name = name.getSuffix(1); if (name.isEmpty()) { return new NamingContextBindingsEnumeration(bindings.values().iterator(), this); } NamingEntry entry = bindings.get(name.get(0)); if (entry == null) { throw new NameNotFoundException (sm.getString("namingContext.nameNotBound", name, name.get(0))); } if (entry.type != NamingEntry.CONTEXT) { throw new NamingException (sm.getString("namingContext.contextExpected")); } return ((Context) entry.value).listBindings(name.getSuffix(1)); } /** * @param name the name of the context to be destroyed; may not be empty * @exception NameNotFoundException if an intermediate context does not * exist * @exception NotContextException if the name is bound but does not name * a context, or does not name a context of the appropriate type */ @Override public void destroySubcontext(Name name) throws NamingException { if (!checkWritable()) { return; } while ((!name.isEmpty()) && (name.get(0).length() == 0)) name = name.getSuffix(1); if (name.isEmpty()) throw new NamingException (sm.getString("namingContext.invalidName")); NamingEntry entry = bindings.get(name.get(0)); if (entry == null) { throw new NameNotFoundException (sm.getString("namingContext.nameNotBound", name, name.get(0))); } if (name.size() > 1) { if (entry.type == NamingEntry.CONTEXT) { ((Context) entry.value).destroySubcontext(name.getSuffix(1)); } else { throw new NamingException (sm.getString("namingContext.contextExpected")); } } else { if (entry.type == NamingEntry.CONTEXT) { ((Context) entry.value).close(); bindings.remove(name.get(0)); } else { throw new NotContextException (sm.getString("namingContext.contextExpected")); } } } /** * Creates and binds a new context. Creates a new context with the given * name and binds it in the target context (that named by all but * terminal atomic component of the name). All intermediate contexts and * the target context must already exist. * * @param name the name of the context to create; may not be empty * @return the newly created context * @exception NameAlreadyBoundException if name is already bound * @exception javax.naming.directory.InvalidAttributesException if creation * of the sub-context requires specification of mandatory attributes * @exception NamingException if a naming exception is encountered */ @Override public Context createSubcontext(Name name) throws NamingException { if (!checkWritable()) { return null; } NamingContext newContext = new NamingContext(env, this.name); bind(name, newContext); newContext.setExceptionOnFailedWrite(getExceptionOnFailedWrite()); return newContext; } /** * Adds a new environment property to the environment of this context. If * the property already exists, its value is overwritten. * * @param propName the name of the environment property to add; may not * be null * @param propVal the value of the property to add; may not be null * @exception NamingException if a naming exception is encountered */ @Override public Object addToEnvironment(String propName, Object propVal) throws NamingException { return env.put(propName, propVal); } /** * Removes an environment property from the environment of this context. * * @param propName the name of the environment property to remove; * may not be null * @exception NamingException if a naming exception is encountered */ @Override public Object removeFromEnvironment(String propName) throws NamingException { return env.remove(propName); } /** * Retrieves the environment in effect for this context. See class * description for more details on environment properties. * The caller should not make any changes to the object returned: their * effect on the context is undefined. The environment of this context * may be changed using addToEnvironment() and removeFromEnvironment(). * * @return the environment of this context; never null * @exception NamingException if a naming exception is encountered */ @Override public Hashtable<?,?> getEnvironment() throws NamingException { return env; } /** * Closes this context. This method releases this context's resources * immediately, instead of waiting for them to be released automatically * by the garbage collector. * This method is idempotent: invoking it on a context that has already * been closed has no effect. Invoking any other method on a closed * context is not allowed, and results in undefined behaviour. * * @exception NamingException if a naming exception is encountered */ @Override public void close() throws NamingException { if (!checkWritable()) { return; } env.clear(); } /** * Retrieves the full name of this context within its own namespace. * <p> * Many naming services have a notion of a "full name" for objects in * their respective namespaces. For example, an LDAP entry has a * distinguished name, and a DNS record has a fully qualified name. This * method allows the client application to retrieve this name. The string * returned by this method is not a JNDI composite name and should not be * passed directly to context methods. In naming systems for which the * notion of full name does not make sense, * OperationNotSupportedException is thrown. * * @return this context's name in its own namespace; never null * @exception OperationNotSupportedException if the naming system does * not have the notion of a full name * @exception NamingException if a naming exception is encountered */ @Override public String getNameInNamespace() throws NamingException { throw new OperationNotSupportedException (sm.getString("namingContext.noAbsoluteName")); //FIXME ? } // ------------------------------------------------------ Protected Methods /** * Retrieves the named object. * * @param name the name of the object to look up * @param resolveLinks If true, the links will be resolved * @return the object bound to name * @exception NamingException if a naming exception is encountered */ protected Object lookup(Name name, boolean resolveLinks) throws NamingException { // Removing empty parts while ((!name.isEmpty()) && (name.get(0).length() == 0)) name = name.getSuffix(1); if (name.isEmpty()) { // If name is empty, a newly allocated naming context is returned return new NamingContext(env, this.name, bindings); } NamingEntry entry = bindings.get(name.get(0)); if (entry == null) { throw new NameNotFoundException (sm.getString("namingContext.nameNotBound", name, name.get(0))); } if (name.size() > 1) { // If the size of the name is greater that 1, then we go through a // number of subcontexts. if (entry.type != NamingEntry.CONTEXT) { throw new NamingException (sm.getString("namingContext.contextExpected")); } return ((Context) entry.value).lookup(name.getSuffix(1)); } else { if ((resolveLinks) && (entry.type == NamingEntry.LINK_REF)) { String link = ((LinkRef) entry.value).getLinkName(); if (link.startsWith(".")) { // Link relative to this context return lookup(link.substring(1)); } else { return (new InitialContext(env)).lookup(link); } } else if (entry.type == NamingEntry.REFERENCE) { try { Object obj = NamingManager.getObjectInstance (entry.value, name, this, env); if(entry.value instanceof ResourceRef) { boolean singleton = Boolean.parseBoolean( (String) ((ResourceRef) entry.value).get( "singleton").getContent()); if (singleton) { entry.type = NamingEntry.ENTRY; entry.value = obj; } } return obj; } catch (NamingException e) { throw e; } catch (Exception e) { log.warn(sm.getString ("namingContext.failResolvingReference"), e); throw new NamingException(e.getMessage()); } } else { return entry.value; } } } /** * Binds a name to an object. All intermediate contexts and the target * context (that named by all but terminal atomic component of the name) * must already exist. * * @param name the name to bind; may not be empty * @param obj the object to bind; possibly null * @param rebind if true, then perform a rebind (ie, overwrite) * @exception NameAlreadyBoundException if name is already bound * @exception javax.naming.directory.InvalidAttributesException if object * did not supply all mandatory attributes * @exception NamingException if a naming exception is encountered */ protected void bind(Name name, Object obj, boolean rebind) throws NamingException { if (!checkWritable()) { return; } while ((!name.isEmpty()) && (name.get(0).length() == 0)) name = name.getSuffix(1); if (name.isEmpty()) throw new NamingException (sm.getString("namingContext.invalidName")); NamingEntry entry = bindings.get(name.get(0)); if (name.size() > 1) { if (entry == null) { throw new NameNotFoundException(sm.getString( "namingContext.nameNotBound", name, name.get(0))); } if (entry.type == NamingEntry.CONTEXT) { if (rebind) { ((Context) entry.value).rebind(name.getSuffix(1), obj); } else { ((Context) entry.value).bind(name.getSuffix(1), obj); } } else { throw new NamingException (sm.getString("namingContext.contextExpected")); } } else { if ((!rebind) && (entry != null)) { throw new NameAlreadyBoundException (sm.getString("namingContext.alreadyBound", name.get(0))); } else { // Getting the type of the object and wrapping it within a new // NamingEntry Object toBind = NamingManager.getStateToBind(obj, name, this, env); if (toBind instanceof Context) { entry = new NamingEntry(name.get(0), toBind, NamingEntry.CONTEXT); } else if (toBind instanceof LinkRef) { entry = new NamingEntry(name.get(0), toBind, NamingEntry.LINK_REF); } else if (toBind instanceof Reference) { entry = new NamingEntry(name.get(0), toBind, NamingEntry.REFERENCE); } else if (toBind instanceof Referenceable) { toBind = ((Referenceable) toBind).getReference(); entry = new NamingEntry(name.get(0), toBind, NamingEntry.REFERENCE); } else { entry = new NamingEntry(name.get(0), toBind, NamingEntry.ENTRY); } bindings.put(name.get(0), entry); } } } }如上的代码是我精简后的代码(源代码966行),删除了各种Overload的函数和其它辅助函数,精简后的函数能够概括为下面几个:
/** * Environment. */ protected final Hashtable<String,Object> env; /** * Bindings in this Context. */ protected final HashMap<String,NamingEntry> bindings; /** * Name of the associated Catalina Context. */这两个成员变量的各种操作,这两个成员变量为:
我们知道JNDI提供的是一个树状的结构,它的最顶是一个initialContext节点,以下就是绑定的一些对象或是一些subContext,用JNDI树能够查找到树中每个节点上内容引用,可是Tomcat的HashMap怎样提供树状组织?从createSubcontext的源码能够看到,它调用了如上代码类的构造函数,又一次构造了一个Context对象,然后再放到bindings中,同一时候存有一个指向父上下文的名称。
另外,JNDI支持分布式,我们知道JNDI在server如Jboss中是以服务的形式存在,配置jboss提供的文件,我们能够訪问其它jboss上的JNDI服务,从而获取其它Jboss中的内容:
java.naming.factory.initial=org.jnp.interfaces.NamingContextFactory java.naming.factory.url.pkgs=org.jboss.naming:org.jnp.interfaces java.naming.provider.url=jnp://localhost:1099