关于JNDI浅谈(tomcat为例)

xiaoxiao2021-02-28  128

本文算我写的一个笔记,参考了如下博客:

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&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, UserName=root@localhost, 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&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, UserName=root@localhost, 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体系分为三个部分; 在tomcat架构分析 (容器类)中介绍了StandardContext类,它是每个app的一个逻辑封装。当tomcat初始化时,将根据配置文件,对StandardContext中的NamingResources对象进行赋值,同时,将实例化一个NamingContextListener对象作为这个context作用域内的事件监听器,它会响应一些例如系统启动,系统关闭等事件,作出相应的操作; 初始化完成后,tomcat启动,完成启动逻辑,抛出一个系统启动event,由那个NamingContextListener捕获,进行处理,将初始化时的NamingResources对象中的数据,绑定到相应的JNDI对象树(namingContext)上,即java:comp/env分支,然后将这个根namingContext与这个app的classloader进行绑定,这样每个app只有在自己的JNDI对象树上调用,互不影响; 每个app中的类都由自己app的classloader加载,如果需要用到JNDI绑定对象,也是从自己classloader对应的JNDI对象树上获取资源对象 这里需要说明的是,在后面会经常涉及到两类context,一个是作为tomcat内部实现逻辑的容器StandardContext;一个是作为JNDI内部分支对象NamingContext;它们实现不同接口,互相没有任何关系,不要混淆。 开始看看每个部分详细情况吧。 初始化NamingResources 先看看配置; <tomcat>/conf/server.xml Xml代码   <Server port="8005">    <Service>     <Engine>        <Host>           <Context>                  <Resource                                      name="jdbc/mysql"                                      type="javax.sql.DataSource"                                      username="root"                                      password="root"                                      driverClassName="com.mysql.jdbc.Driver"                                      maxIdle="200"                                      maxWait="5000"                                      url="……"                                      maxActive="100"/>             </Context>        </Host>      </Engine>  </Service>  ……  </Server>   <Server port="8005"> <Service> <Engine> <Host> <Context> <Resource name="jdbc/mysql" type="javax.sql.DataSource" username="root" password="root" driverClassName="com.mysql.jdbc.Driver" maxIdle="200" maxWait="5000" url="……" maxActive="100"/> </Context> </Host> </Engine> </Service> …… </Server> 通过这个配置,可以非常清楚的看出tomcat内部的层次结构,不同的层次实现不同的作用域,同时每个层次都有相应的类进行逻辑封装,这是tomcat面向对象思想的体现。那么相应的,Context节点下的Resource节点也有类进行封装; Java代码   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代码   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);      }   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代码   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) {        ......      }  }   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代码   public static void bindContext(Object name, Context context,                                      Object token) {          if (ContextAccessController.checkSecurityToken(name, token))              //先是将StandardContext对象与JNDI对象树根NamingContext对象绑定              //注意,这里第一个参数name是StandardContext对象              contextNameBindings.put(name, context);      }   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代码   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);          }      }   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代码   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]);        }              ......    }   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代码   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(nullnull).registerComponent(actualResource, on, null);              objectNames.put(resource.getName(), on);          } catch (Exception e) {              logger.warn(sm.getString("naming.jmxRegistrationFailed", e));          }      }      }   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对象才是真正存储数据的地方。

转载请注明原文地址: https://www.6miu.com/read-62087.html

最新回复(0)