1. 程式人生 > >關於JNDI淺談(tomcat為例)

關於JNDI淺談(tomcat為例)

本文算我寫的一個筆記,參考瞭如下部落格:

http://gearever.iteye.com/blog/1560135
http://gearever.iteye.com/blog/1554295
http://blog.sina.com.cn/s/blog_542200310100eiva.html

一:TOMCAT部署web專案的方式:

     1.將WebRoot整體複製到/tomcat/webapps下(也就是eclipse中選中TOMCAT伺服器右鍵ADD AND REMOVE就是這個方式)

     2.在Tomcat的配置檔案中,一個Web應用就是一個特定的Context,Context配置有如下幾種方式:

           1)conf/server.xml    新增Context節點;

           2)conf / context.xml 

           3)conf / {enginename} /{hostname} / xxxx.xml  例如conf/Catalina/localhost/JNDIDemo.xml  (檔名就是路徑名,

                例如訪問的時候http://localhost:8086/JNDIDemo/hello)

二:  

1.新建工程JNDIDemo  在apache-tomcat-7.0.57/conf/Catalina/localhost/JNDIDemo.xml中配置如下:

<Context docBase="C:\Users\hyang\workspace_ee\JNDIDemo\WebContent" debug="5" reloadable="true" crossContext="true">
 <Resource name="jdbc/stcms" auth="Container" type="javax.sql.DataSource"
    maxActive="100" maxIdle="30" maxWait="10000"   
    username="root" password="123456" driverClassName="com.mysql.jdbc.Driver"
     url="jdbc:mysql://localhost:3306/test?useUnicode=true&amp;characterEncoding=utf-8" /> </Context> 2.在web.xml 配置如下:

<resource-ref>
 <description>資料來源</description>
 <res-ref-name>jdbc/stcms</res-ref-name>
 <res-type>javax.sql.DataSource</res-type>
 <res-auth>Container</res-auth>
</resource-ref>

3.寫測試類

public class Demo1{ 
 public static void main(String[] args) {
  Connection conn = null;
  try {
   Context ctx = new InitialContext();
   DataSource ds = (DataSource) ctx.lookup("java:comp/env/jdbc/stcms");
   conn = ds.getConnection();
   System.out.println(conn);
  } catch (Exception e) {
   e.printStackTrace();
  }
 }
}

執行時報錯javax.naming.NoInitialContextException  ,百度後原因:不能直接在main方法裡面呼叫  ,要在頁面訪問

Demo2如下:

public class Demo2 extends HttpServlet {
 @Override
 protected void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
  
  Connection conn = null;
  try {
   Context ctx = new InitialContext();
   DataSource ds = (DataSource) ctx.lookup("java:comp/env/jdbc/stcms");
   conn = ds.getConnection();
   System.out.println(conn);
  } catch (Exception e) {
   e.printStackTrace();
  }
  res.setContentType("text/html");
  PrintWriter w = res.getWriter();
  w.println("<h1>Hello, Servlet.</h1>");
  w.close();
 }
}

然後再在web.xml中配置servlet

通過在瀏覽器輸入http://localhost:8086/JNDIDemo/hello

可以看到在Hello, Servlet. 在瀏覽器顯示

在eclipse的console控制檯可以輸出:jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8, [email protected], MySQL-AB JDBC Driver  說明資料庫連線已經得到。

======

web程式的context元素既定義在META-INF/context.xml的案例如下:

還是JNDIDemo工程

1)刪除apache-tomcat-7.0.57/conf/Catalina/localhost/JNDIDemo.xml 檔案

2)web.xml 中<resource-ref>配置一樣

3)在C:\Users\hyang\workspace_ee\JNDIDemo\WebContent\META-INF\context.xml配置如下:

<Context docBase="C:\Users\hyang\workspace_ee\JNDIDemo\WebContent" debug="5" reloadable="true" crossContext="true">
 <Resource name="jdbc/stcms" auth="Container" type="javax.sql.DataSource"
    maxActive="100" maxIdle="30" maxWait="10000"   
    username="root" password="123456" driverClassName="com.mysql.jdbc.Driver"
     url="jdbc:mysql://localhost:3306/test?useUnicode=true&amp;characterEncoding=utf-8" /> </Context>

4)在web.xml配置 servlet如下:

   <servlet>
   <servlet-name>Demo3</servlet-name>
   <servlet-class>com.Demo3</servlet-class>
  </servlet>
  <servlet-mapping>
   <servlet-name>Demo3</servlet-name>
   <url-pattern>/hello2</url-pattern>
  </servlet-mapping>

5)測試類Demo3如下:

public class Demo3 extends HttpServlet  {
 @Override
 protected void service(HttpServletRequest ewq, HttpServletResponse res) throws ServletException, IOException {
  
  Connection conn = null;
  try {

   //獲得對資料來源的引用
   Context ctx = new InitialContext();
   DataSource ds = (DataSource) ctx.lookup("java:comp/env/jdbc/stcms");

   //獲得資料庫的連線
   conn = ds.getConnection();
   System.out.println(conn);
  } catch (Exception e) {
   e.printStackTrace();
  }
  res.setContentType("text/html");
  PrintWriter w = res.getWriter();
  w.println("<h1>hello,JNDI.</h1>");
  w.close();
 }
}

6)部署JNDIDemo工程到TOMCAT下

7)在瀏覽器輸入:http://localhost:8086/JNDIDemo/hello2

可以看到在瀏覽器顯示:hello,JNDI.

在eclipse控制檯console列印jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8, [email protected], MySQL-AB JDBC Driver 說明已經得到資料庫連線。

注意:DataSource ds = (DataSource) ctx.lookup("java:comp/env/jdbc/stcms");在生產上一般都把這個寫在一個xml檔案中,

在啟動工程的時候載入這個xml得到這個字串。

================

以下的關於JNDI的 描述,我只是個搬運工:

1.JNDI封裝了一個簡單name到實體物件的mapping,通過字串可以方便的得到想要的物件資源。通常這種物件資源有很多種,例如資料庫JDBC,JMS,EJB等。平時用的最多的就是資料庫了。在tomcat中,這些資源都是以java:comp/env開頭的字串來繫結的。

2.context中配置的ResourceLink屬於一箇中轉的作用,這主要是為了在tomcat啟動狀態下,如果新部署一個app,可以在app中指定到相應的全域性的resource。
它們的mapping關係是;

3.這裡是 直接複製的http://gearever.iteye.com/blog/1560135   這篇文章(我覺得講的很好所以引用過來,作為筆記) 先看一個概念圖


JNDI體系分為三個部分;
  1. 在tomcat架構分析 (容器類)中介紹了StandardContext類,它是每個app的一個邏輯封裝。當tomcat初始化時,將根據配置檔案,對StandardContext中的NamingResources物件進行賦值,同時,將例項化一個NamingContextListener物件作為這個context作用域內的事件監聽器,它會響應一些例如系統啟動,系統關閉等事件,作出相應的操作;
  2. 初始化完成後,tomcat啟動,完成啟動邏輯,丟擲一個系統啟動event,由那個NamingContextListener捕獲,進行處理,將初始化時的NamingResources物件中的資料,繫結到相應的JNDI物件樹(namingContext)上,即java:comp/env分支,然後將這個根namingContext與這個app的classloader進行繫結,這樣每個app只有在自己的JNDI物件樹上呼叫,互不影響;
  3. 每個app中的類都由自己app的classloader載入,如果需要用到JNDI繫結物件,也是從自己classloader對應的JNDI物件樹上獲取資源物件

這裡需要說明的是,在後面會經常涉及到兩類context,一個是作為tomcat內部實現邏輯的容器StandardContext;一個是作為JNDI內部分支物件NamingContext;它們實現不同介面,互相沒有任何關係,不要混淆。
開始看看每個部分詳細情況吧。

初始化NamingResources
先看看配置;
<tomcat>/conf/server.xml
Xml程式碼  收藏程式碼
  1. <Serverport="8005">
  2. <Service>
  3.    <Engine>
  4.       <Host>
  5.          <Context>
  6.                 <Resource
  7.                                     name="jdbc/mysql"
  8.                                     type="javax.sql.DataSource"
  9.                                     username="root"
  10.                                     password="root"
  11.                                     driverClassName="com.mysql.jdbc.Driver"
  12.                                     maxIdle="200"
  13.                                     maxWait="5000"
  14.                                     url="……"
  15.                                     maxActive="100"/>
  16.          </Context>
  17.       </Host>
  18.     </Engine>
  19. </Service>
  20. ……  
  21. </Server>

通過這個配置,可以非常清楚的看出tomcat內部的層次結構,不同的層次實現不同的作用域,同時每個層次都有相應的類進行邏輯封裝,這是tomcat面向物件思想的體現。那麼相應的,Context節點下的Resource節點也有類進行封裝;
Java程式碼  收藏程式碼
  1. org.apache.catalina.deploy.ContextResource  
org.apache.catalina.deploy.ContextResource

    上面例子中Resource節點配置的所有屬性會以鍵值對的方式存入ContextResource的一個HashMap物件中,這一步只是初始化,不會用到每個屬性,它只是為了每個真正處理的資源物件用到,例如後面會說的預設的tomcat的資料庫連線池物件BasicDataSourceFactory,如果用其他的資料庫連線池,例如c3p0,那麼其配置的屬性物件就應該按照c3p0中需要的屬性名稱來配。
    但是,這些屬性中的name和type是ContextResource需要的,name是JNDI物件樹的分支節點,上面配的“jdbc/mysql”,那麼這個資料庫連線池物件就對應在“java:comp/env/jdbc/mysql”的位置。type是這個物件的型別,如果是“javax.sql.DataSource”,tomcat會有一些特殊的邏輯處理。
    當tomcat初始化時,StandardContext物件內部會生成一個NamingResources物件,這個物件就是做一些預處理,儲存一些Resource物件,看一下NamingResources儲存Resource物件的邏輯;
Java程式碼  收藏程式碼
  1. publicvoid addResource(ContextResource resource) {  
  2.         //確保每一個資源物件的name都是唯一的
  3.         //不僅是Resource物件之間,包括Service等所有的資源物件
  4.         if (entries.containsKey(resource.getName())) {  
  5.             return;  
  6.         } else {  
  7.             entries.put(resource.getName(), resource.getType());  
  8.         }  
  9.         //建立一個name和資源物件的mapping
  10.         synchronized (resources) {  
  11.             resource.setNamingResources(this);  
  12.             resources.put(resource.getName(), resource);  
  13.         }  
  14.         support.firePropertyChange("resource"null, resource);  
  15.     }  
public void addResource(ContextResource resource) {
	    //確保每一個資源物件的name都是唯一的
        //不僅是Resource物件之間,包括Service等所有的資源物件
        if (entries.containsKey(resource.getName())) {
            return;
        } else {
            entries.put(resource.getName(), resource.getType());
        }
        //建立一個name和資源物件的mapping
        synchronized (resources) {
            resource.setNamingResources(this);
            resources.put(resource.getName(), resource);
        }
        support.firePropertyChange("resource", null, resource);
    }

需要說明的是,不僅僅是Resource一種物件,還有Web Service資源物件,EJB物件等,這裡就是拿資料庫連線的Resource物件舉例。

啟動JNDI繫結
當tomcat啟動時,會丟擲一個start event,由StandardContext的NamingContextListener監聽物件捕捉到,響應start event。
Java程式碼  收藏程式碼
  1. publicvoid lifecycleEvent(LifecycleEvent event) {  
  2.     container = event.getLifecycle();  
  3.     if (container instanceof Context) {  
  4.         //這個namingResources物件就是StandardContext的namingResources物件
  5.         namingResources = ((Context) container).getNamingResources();  
  6.         logger = log;  
  7.     } elseif (container instanceof Server) {  
  8.         namingResources = ((Server) container).getGlobalNamingResources();  
  9.     } else {  
  10.         return;  
  11.     }  
  12.     //響應start event
  13.     if (event.getType() == Lifecycle.START_EVENT) {  
  14.         if (initialized)  
  15.             return;  
  16.         Hashtable contextEnv = new Hashtable();  
  17.         try {  
  18.             //生成這個StandardContext域的JNDI物件樹根NamingContext物件
  19.             namingContext = new NamingContext(contextEnv, getName());  
  20.         } catch (NamingException e) {  
  21.             // Never happens
  22.         }  
  23.         ContextAccessController.setSecurityToken(getName(), container);  
  24.         //將此StandardContext物件與JNDI物件樹根NamingContext物件繫結
  25.         ContextBindings.bindContext(container, namingContext, container);  
  26.         if( log.isDebugEnabled() ) {  
  27.             log.debug("Bound " + container );  
  28.         }  
  29.         // Setting the context in read/write mode
  30.         ContextAccessController.setWritable(getName(), container);  
  31.         try {  
  32.             //將初始化時的資源物件繫結JNDI物件樹
  33.             createNamingContext();  
  34.         } catch (NamingException e) {  
  35.             logger.error  
  36.                 (sm.getString("naming.namingContextCreationFailed", e));  
  37.         }  
  38.         // 針對Context下配置Resource物件而言
  39.         if (container instanceof Context) {  
  40.             // Setting the context in read only mode
  41.             ContextAccessController.setReadOnly(getName());  
  42.             try {  
  43.                 //通過此StandardContext物件獲取到JNDI物件樹根NamingContext物件
  44.                 //同時將此app的classloader與此JNDI物件樹根NamingContext物件繫結
  45.                 ContextBindings.bindClassLoader  
  46.                     (container, container,   
  47.                      ((Container) container).getLoader().getClassLoader());  
  48.             } catch (NamingException e) {  
  49.                 logger.error(sm.getString("naming.bindFailed", e));  
  50.             }  
  51.         }  
  52.         // 針對global資源而言,這裡不用關注
  53.         if (container instanceof Server) {  
  54.             namingResources.addPropertyChangeListener(this);  
  55.             org.apache.naming.factory.ResourceLinkFactory.setGlobalContext  
  56.                 (namingContext);  
  57.             try {  
  58.                 ContextBindings.bindClassLoader  
  59.                     (container, container,   
  60.                      this.getClass().getClassLoader());  
  61.             } catch (NamingException e) {  
  62.                 logger.error(sm.getString("naming.bindFailed", e));  
  63.             }  
  64.             if (container instanceof StandardServer) {  
  65.                 ((StandardServer) container).setGlobalNamingContext  
  66.                     (namingContext);  
  67.             }  
  68.         }  
  69.         initialized = true;  
  70.     }   
  71.     //響應stop event
  72.     elseif (event.getType() == Lifecycle.STOP_EVENT) {  
  73.       ......  
  74.     }  
  75. }  
public void lifecycleEvent(LifecycleEvent event) {

    container = event.getLifecycle();

    if (container instanceof Context) {
        //這個namingResources物件就是StandardContext的namingResources物件
        namingResources = ((Context) container).getNamingResources();
        logger = log;
    } else if (container instanceof Server) {
        namingResources = ((Server) container).getGlobalNamingResources();
    } else {
        return;
    }
    //響應start event
    if (event.getType() == Lifecycle.START_EVENT) {

        if (initialized)
            return;
        Hashtable contextEnv = new Hashtable();
        try {
            //生成這個StandardContext域的JNDI物件樹根NamingContext物件
            namingContext = new NamingContext(contextEnv, getName());
        } catch (NamingException e) {
            // Never happens
        }
        ContextAccessController.setSecurityToken(getName(), container);
        //將此StandardContext物件與JNDI物件樹根NamingContext物件繫結
        ContextBindings.bindContext(container, namingContext, container);
        if( log.isDebugEnabled() ) {
            log.debug("Bound " + container );
        }
        // Setting the context in read/write mode
        ContextAccessController.setWritable(getName(), container);
        try {
            //將初始化時的資源物件繫結JNDI物件樹
            createNamingContext();
        } catch (NamingException e) {
            logger.error
                (sm.getString("naming.namingContextCreationFailed", e));
        }

        // 針對Context下配置Resource物件而言
        if (container instanceof Context) {
            // Setting the context in read only mode
            ContextAccessController.setReadOnly(getName());
            try {
                //通過此StandardContext物件獲取到JNDI物件樹根NamingContext物件
                //同時將此app的classloader與此JNDI物件樹根NamingContext物件繫結
                ContextBindings.bindClassLoader
                    (container, container, 
                     ((Container) container).getLoader().getClassLoader());
            } catch (NamingException e) {
                logger.error(sm.getString("naming.bindFailed", e));
            }
        }
        // 針對global資源而言,這裡不用關注
        if (container instanceof Server) {
            namingResources.addPropertyChangeListener(this);
            org.apache.naming.factory.ResourceLinkFactory.setGlobalContext
                (namingContext);
            try {
                ContextBindings.bindClassLoader
                    (container, container, 
                     this.getClass().getClassLoader());
            } catch (NamingException e) {
                logger.error(sm.getString("naming.bindFailed", e));
            }
            if (container instanceof StandardServer) {
                ((StandardServer) container).setGlobalNamingContext
                    (namingContext);
            }
        }
        initialized = true;
    } 
    //響應stop event
    else if (event.getType() == Lifecycle.STOP_EVENT) {
      ......
    }
}

注意上面方法中有兩層繫結關係;
ContextBindings.bindContext()
Java程式碼  收藏程式碼
  1. publicstaticvoid bindContext(Object name, Context context,   
  2.                                    Object token) {  
  3.         if (ContextAccessController.checkSecurityToken(name, token))  
  4.             //先是將StandardContext物件與JNDI物件樹根NamingContext物件繫結
  5.             //注意,這裡第一個引數name是StandardContext物件
  6.             contextNameBindings.put(name, context);  
  7.     }  
public static void bindContext(Object name, Context context, 
                                   Object token) {
        if (ContextAccessController.checkSecurityToken(name, token))
            //先是將StandardContext物件與JNDI物件樹根NamingContext物件繫結
            //注意,這裡第一個引數name是StandardContext物件
            contextNameBindings.put(name, context);
    }

ContextBindings.bindClassLoader()
Java程式碼  收藏程式碼
  1. publicstaticvoid bindClassLoader(Object name, Object token,   
  2.                                        ClassLoader classLoader)   
  3.         throws NamingException {  
  4.         if (ContextAccessController.checkSecurityToken(name, token)) {  
  5.             //根據上面的StandardContext物件獲取剛才繫結的NamingContext物件
  6.             Context context = (Context) contextNameBindings.get(name);  
  7.             if (context == null)  
  8.                 thrownew NamingException  
  9.                     (sm.getString("contextBindings.unknownContext", name));  
  10.             //將classloader與NamingContext物件繫結
  11.             clBindings.put(classLoader, context);  
  12.             clNameBindings.put(classLoader, name);  
  13.         }  
  14.     }  
public static void bindClassLoader(Object name, Object token, 
                                       ClassLoader classLoader) 
        throws NamingException {
        if (ContextAccessController.checkSecurityToken(name, token)) {
            //根據上面的StandardContext物件獲取剛才繫結的NamingContext物件
            Context context = (Context) contextNameBindings.get(name);
            if (context == null)
                throw new NamingException
                    (sm.getString("contextBindings.unknownContext", name));
            //將classloader與NamingContext物件繫結
            clBindings.put(classLoader, context);
            clNameBindings.put(classLoader, name);
        }
    }

主要看一下將初始化時的資源物件繫結JNDI物件樹的createNamingContext()方法;
Java程式碼  收藏程式碼
  1. privatevoid createNamingContext()  
  2.       throws NamingException {  
  3.       // Creating the comp subcontext
  4.       if (container instanceof Server) {  
  5.           compCtx = namingContext;  
  6.           envCtx = namingContext;  
  7.       } else {  
  8.           //對於StandardContext而言,在JNDI物件樹的根namingContext物件上
  9.           //建立comp樹枝,以及在comp樹枝上建立env樹枝namingContext物件
  10.           compCtx = namingContext.createSubcontext("comp");  
  11.           envCtx = compCtx.createSubcontext("env");  
  12.       }  
  13.       ......  
  14.       // 從初始化的NamingResources物件中獲取Resource物件載入到JNDI物件樹上
  15.       ContextResource[] resources = namingResources.findResources();  
  16.       for (i = 0; i < resources.length; i++) {  
  17.           addResource(resources[i]);  
  18.       }        
  19.       ......  
  20.   }  
private void createNamingContext()
      throws NamingException {

      // Creating the comp subcontext
      if (container instanceof Server) {
          compCtx = namingContext;
          envCtx = namingContext;
      } else {
          //對於StandardContext而言,在JNDI物件樹的根namingContext物件上
          //建立comp樹枝,以及在comp樹枝上建立env樹枝namingContext物件
          compCtx = namingContext.createSubcontext("comp");
          envCtx = compCtx.createSubcontext("env");
      }
      ......
      // 從初始化的NamingResources物件中獲取Resource物件載入到JNDI物件樹上
      ContextResource[] resources = namingResources.findResources();
      for (i = 0; i < resources.length; i++) {
          addResource(resources[i]);
      }      
      ......
  }

看一下addResource的具體載入邏輯;
Java程式碼  收藏程式碼
  1. publicvoid addResource(ContextResource resource) {  
  2.     // Create a reference to the resource.
  3.     Reference ref = new ResourceRef  
  4.         (resource.getType(), resource.getDescription(),  
  5.          resource.getScope(), resource.getAuth());  
  6.     // 遍歷Resource物件的各個屬性,這些屬性存在一個HashMap中
  7.     Iterator params = resource.listProperties();  
  8.     while (params.hasNext()) {  
  9.         String paramName = (String) params.next();  
  10.         String paramValue = (String) resource.getProperty(paramName);  
  11.         //封裝成StringRefAddr,這些都是JNDI的標準API
  12.         StringRefAddr refAddr = new StringRefAddr(paramName, paramValue);  
  13.         ref.add(refAddr);  
  14.     }  
  15.     try {  
  16.         if (logger.isDebugEnabled()) {  
  17.             logger.debug("  Adding resource ref "
  18.                          + resource.getName() + "  " + ref);  
  19.         }  
  20.         //在上面建立的comp/env樹枝節點上,根據Resource配置的name繼續建立新的節點
  21.         //例如配置的name=”jdbc/mysql”,則在comp/env樹枝節點下再建立一個jdbc樹枝節點
  22.         createSubcontexts(envCtx, resource.getName());  
  23.         //繫結葉子節點,它不是namingContext物件,而是最後的Resource物件
  24.         envCtx.bind(resource.getName(), ref);  
  25.     } catch (NamingException e) {  
  26.         logger.error(sm.getString("naming.bindFailed", e));  
  27.     }  
  28.     //這就是上面說的對於配置type="javax.sql.DataSource"時的特殊邏輯
  29.     //將資料庫連線池型別的資源物件註冊到tomcat全域性的JMX中,方便管理及除錯
  30.     if ("javax.sql.DataSource".equals(ref.getClassName())) {  
  31.         try {  
  32.             ObjectName on = createObjectName(resource);  
  33.             Object actualResource = envCtx.lookup(resource.getName());  
  34.             Registry.getRegistry(nullnull).registerComponent(actualResource, on, null);  
  35.             objectNames.put(resource.getName(), on);  
  36.         } catch (Exception e) {  
  37.             logger.warn(sm.getString("naming.jmxRegistrationFailed", e));  
  38.         }  
  39.     }      
  40. }  
public void addResource(ContextResource resource) {

    // Create a reference to the resource.
    Reference ref = new ResourceRef
        (resource.getType(), resource.getDescription(),
         resource.getScope(), resource.getAuth());
    // 遍歷Resource物件的各個屬性,這些屬性存在一個HashMap中
    Iterator params = resource.listProperties();
    while (params.hasNext()) {
        String paramName = (String) params.next();
        String paramValue = (String) resource.getProperty(paramName);
        //封裝成StringRefAddr,這些都是JNDI的標準API
        StringRefAddr refAddr = new StringRefAddr(paramName, paramValue);
        ref.add(refAddr);
    }
    try {
        if (logger.isDebugEnabled()) {
            logger.debug("  Adding resource ref " 
                         + resource.getName() + "  " + ref);
        }
        //在上面建立的comp/env樹枝節點上,根據Resource配置的name繼續建立新的節點
		//例如配置的name=”jdbc/mysql”,則在comp/env樹枝節點下再建立一個jdbc樹枝節點
        createSubcontexts(envCtx, resource.getName());
        //繫結葉子節點,它不是namingContext物件,而是最後的Resource物件
        envCtx.bind(resource.getName(), ref);
    } catch (NamingException e) {
        logger.error(sm.getString("naming.bindFailed", e));
    }
    //這就是上面說的對於配置type="javax.sql.DataSource"時的特殊邏輯
    //將資料庫連線池型別的資源物件註冊到tomcat全域性的JMX中,方便管理及除錯
    if ("javax.sql.DataSource".equals(ref.getClassName())) {
        try {
            ObjectName on = createObjectName(resource);
            Object actualResource = envCtx.lookup(resource.getName());
            Registry.getRegistry(null, null).registerComponent(actualResource, on, null);
            objectNames.put(resource.getName(), on);
        } catch (Exception e) {
            logger.warn(sm.getString("naming.jmxRegistrationFailed", e));
        }
    }    
}

這就是上面配置的jdbc/mysql資料庫連線池的JNDI物件樹;

到目前為止,完成了JNDI物件樹的繫結,可以看到,每個app對應的StandardContext對應一個JNDI物件樹,並且每個app的各個classloader與此JNDI物件樹分別繫結,那麼各個app之間的JNDI可以不互相干擾,各自配置及呼叫。
需要注意的是,NamingContext物件就是JNDI物件樹上的樹枝節點,類似檔案系統中的目錄,各個Resource物件則是JNDI物件樹上的葉子節點,類似檔案系統的具體檔案,通過NamingContext物件將整個JNDI物件樹組織起來,每個Resource物件才是真正儲存資料的地方。