转载请注明出处 http://blog.csdn.net/lovingprince
第一部分 引子
我们应用想用统一的方式去查找我们想要的服务,通过格式化的名称例如:jdbc:comp/testa去获取jdbc的服务,Ldap:comp/testa去获取ldap服务,每种服务如何去查找我们并不关心,服务查找实现方式变更了,我应用程序也不需要改变。
看下要求:
1、 统一根据特定名称就可以获取到特定的服务
2、 应用程序不关心具体如何查找的方式
3、 服务查找实现方式变更应用程序不需要任何更改
我们可以如何去做来满足这种需求,来分析一下,
² 第一点需求:需要统一根据名称来获取服务,可以考虑统一的操作界面如:
² 第二点需求:应用不关心服务具体的查找方法,具体如何查找服务可以由服务提供方来实现
这点需求稍微麻烦点,首先需要根据用户提供的名称来确定使用的是什么类型的服务,然后再使用该类型的服务工厂提供一个查找该类服务方法,例如定义以下:
应用程序要使用只需要这样
² 第三点需求:服务查找实现方式变更应用程序不需要任何更改,其实第二部就已经实现了部分,比如服务提供者把查找的实现方式都封装在一个用工厂创建的上下文中了,如果查找实现方式更改了,由于是硬编码,需要修改InitContext,也就需要应用修改,那么就需要将这个工厂实现想办法与InitContext剥离出来,如何剥离?
可以这样修改下lookup:
这样,如果实现变了,或者有新的服务,完全不需要修改修改InitContext了,当然应用程序也不需要修改了,只需要一个jar包中包含了对应的URLContextFactory工厂实现就可以了。这样就完全剥离了服务提供者的服务查找实现与应用之间的耦合,可以支持各种不同服务的查找方式。
以上就是一个简单的查找服务的分析过程。可以简单总结为几个部分:
统一的界面的API(Context)->服务类型路由工具(NamingManager)->服务提供者具体查找实现(jdbcURLContextFactory和其实现了Context接口的具体路由方法)
其中只有第三步底层需要服务提供者自己来实现,对于应用程序而言,则无需知道。
第二部分 JNDI
JNDI(Java Naming and DirectoryInterface)框架其实已经解决了上面的问题,思路一致,不过他包含的东西却比上面要复杂得多,毕竟上面只是一个简单的演示过程。JNDI包含了命名服务和目录服务两部分,我这里以命名服务做解释。
JNDI框架结构如下图:
基本上也是包含了统一的JNDI API,NamingManger中包含了一些路由到不同URL工具,JNDI SPI则是服务提供者在提供实现时需要实现的接口。
JDK中将这一系列API划分为了以下几个包
n javax.naming,包含访问命名服务的类和接口定义
n javax.naming.directory,包含访问目录服务的类和接口定义
n javax.naming.ldap,为ldapv3提供的扩展操作提供支持
n javax.naming.event,为访问命名和目录服务时的事件通知提供支持
n javax.naming.spi,为服务提供商提供的接口
我们先关注下命名服务中的几个基本的概念:
名字:通过名字我们可以来查找关联的对象,格式可能根据系统不同,命名规范也会有所不同。
绑定:将名字和一个对象进行关联
上下文:它在其中存储了名字和对象绑定的集合,并且提供了统一绑定、取消绑定、查找、创建子上下文等常用操作,也就是说这里绑定的对象也可以是另外一个上下文,熟称子上下文。上下文存储结构可以理解成我们常用目录,目录中的文件就是我们的对象,当然目录中也可以再有目录。上下文用绿色表示,其他真实对象用橙色表示
上面的表示可以是如下的名字:
Java:comp/env
Java:TestDB
Java:jms/TestJms
基本使用方法:
这里以查找文件系统中文件为例:
是不是很简单?在介绍上面几步操作内部的具体工作之前,再来熟悉几个JNDI中标准环境属性的含义:
- java.naming.factory.initial 创建上下文的工厂,如果没有找到支持对应name上下文环境,就用这个工厂创建创建默认的context
- java.naming.provider.url,用来配置context的初始url
- java.naming.factory.object 创建特定的对象的工厂
- java.naming.factory.state,用来创建查询jndi state状态的工厂
- java.naming.factory.url.pkgs 包名列表,在指定的包名下查找创建特定url的上下文的工厂,如果没有,则使用
- java.naming.factory.initial创建的上下文
其中,我们最初常用,也经常设置的标准属性其实只有两个,java.naming.factory.initial和java.naming.factory.url.pkgs,包括Tomcat和Jetty容器对JNDI的支持也是只设置了这两个属性。
我们可以有很多不同方式来设置,这些属性值都被保存在上下文中,子上下文可以从父上下文中继承。
现在来看下内部工作:
- Context ctx = new InitialContext(env);
这个是执行命名操作的初始上下文,我们所有的访问入口点都在这里。所有命名操作都相对于某一上下文。该初始上下文实现 Context 接口并提供解析名称的起始点。
说的比较抽象,简单点,就是说这个创建工作做了以下工作:
1、合并JNDI标准环境属性
2、构造初始化默认上下文
Ø 合并JNDI标准环境属性
JNDI标准获取这些参数的方式:
1、 InitContext时指定HashTable
2、 System.getProperties() 系统属性中获取
3、 从classpath的jndi.properties文件中获取
由于可以从多个地方获取,如果多个地方都存在时,他们的合并规则分为三个大步骤:
一、从System.getProperties()中获取以下几个属性
如果HashTable中没有,就put进去,可以看出,这里是构造参数中的hashtable中的值优先。
二、依次搜索应用中classpath中的jndi.properties文件属性,再搜索<java.home>/lib/jndi.properties属性,这里classpath中可能有多个jndi.properties文件,他们中如果都出现了配置项,他们的合并规则是:
1、如果是以下属性之一
则将属性值做串联起来,用冒号分隔,例如
java.naming.factory.object=com.sun.jndi.ldap.AttrsToCorba:com.wiz.from.Person
2、其他属性,只取第一个首先遇到的属性值
三、将第二步得到的属性值合并到第一步的table,规则同第二步的合并规则一致。最后得到一份最终的上下文的属性配置。
最终
Context.OBJECT_FACTORIES,
Context.URL_PKG_PREFIXES,
Context.STATE_FACTORIES,
javax.naming.ldap.LdapContext.CONTROL_FACTORIES
可能会有多个值。
Ø 构造初始化默认上下文
如果javax.naming.Context.INITIAL_CONTEXT_FACTORY最终存在,这个工厂需要实现 javax.naming.spi.InitialContextFactor接口,实现
getInitialContext(Hashtable<?,?> environment) |
方法,并且用这个初始化工厂来构建一个默认的上下文Context.
- File obj =(File)ctx.lookup(name);
name如果使用了URL形式的参数例如:file:/test或者java:comp/env或者ldap:/xxx,则分析出scheme(这里是file或者java或者ldap),在Context.URL_PKG_PREFIXES ="java.naming.factory.url.pkgs"指定的包下查找
包名+ "." + scheme + "." + scheme +"URLContextFactory" 的工厂类,并且使用该工厂创建上下文。没有找到对应的工厂就使用javax.naming.Context.INITIAL_CONTEXT_FACTORY创建的默认上下文。
然后用上面得到的上下文来查找这个name对应的服务。
举例:
ctx.lookup(“java:comp/env”);
在jetty中提供者在jndi.properties中有如下配置:
那么就会根据以上规则就会查找org.eclipse.jetty.jndi.java.javaURLContextFactory工厂类,如果存在那么使用他创建上下文,如果没有,就会直接用org.eclipse.jetty.jndi.InitialContextFactory创建默认上下文。
第三部分 Jetty 容器中JNDI实现(SPI)
Jetty中对JNDI进行了支持,在7.3.1版本中,如果要使用JNDI,首先配置start.ini的OPTIONS=Server,jsp,jmx,resources,websocket,ext,jndi,此时就引入了jetty JNDI相关的jar包.在jetty-jndi-7.3.1.v20110307.jar中有一个jndi.properties文件,文件中的配置:
org.eclipse.jetty.jndi.java.javaURLContextFactory工厂维护了java:这个上下文,所有的java:comp和java:的查找都会使用这个工厂创建的上下文。我们平常使用的java:comp/env私有环境也是在这里维护的,为什么叫env为私有环境,是因为每个应用都有自己的env环境,即使部署在同一个容器上也一样,互相不影响。
org.eclipse.jetty.jndi.InitialContextFactory 除了java:以外,其他的上下文都会使用初始化上下文来绑定和查找。
意思已经很明了,不再多做解释。
现在我们就可以在jetty中定义资源了,jetty中资源可以分为三类:JVM范围的资源、server范围的资源、app范围的资源。
- JVM范围的资源
也就是说定义好后,所有的应用都可以使用
- Server范围的资源
- App范围的资源
三类资源在开始创建时都会在默认的上下文(org.eclipse.jetty.jndi.InitialContextFactory创建的上下文中)中先进行注册,注册名字为
其中scope就是对应的范围的类。
如果scope为null,则 注册名字是myds
例:
App范围的资源就会在默认的上下文中以下名字放置资源
org.eclipse.jetty.webapp.WebAppContext@20f237/myds
而JVM范围的名字资源会是
myds
Server范围的资源名字:
org.eclipse.jetty.server@20f238/myds
也就说,如果你在应用中使用ctx.loopup()时,如果能够构造这些资源的名字,你同样可以访问资源,但是我们不建议这么做,因为这样不利于应用移植,我们应该使用我们的标准ENC环境,也就是java:comp/env私有资源。这就要求私有资源与全局资源进行一个映射,这个映射如何完成?
这就还需要在jetty的context.xml中配置
org.eclipse.jetty.plus.webapp.EnvConfiguration负责创建java:comp/env子环境,同时将EnvEntry定义的资源放置到java:comp/env中,也就是说EnvEntry不需要在web.xml中申明也可以使用。
org.eclipse.jetty.plus.webapp.PlusConfiguration负责处理将创建处理器,处理web.xml等中的env-entry、resource-ref、resource-env-ref、message-destination-ref标签,只要在这些标签里面提到的资源名都会加到自己应用的java:comp/env/上下文下,加进去后,就可以通过ctx.loopup(“java:comp/env/test”)等方式访问该资源了。也就说除了env-entry外,其他的jetty 中的Resource定义的资源都需要在web.xml中进行申明,否则,是不会加入到java:comp/env子上下文中的,这点请注意。
最后再强调下,jetty中定义的资源都需要在web.xml中进行一次申明,如此jetty才会把默认上下文中资源在java:comp/env下做一个映射。
YY一下,Tomcat 7中没有jndi.properties这个属性文件,他是在org.apache.naming.NameService这个MBean中做的初始化,并且默认值如下:
具体分析,各位可以自行去看看。
第四部分 参考
http://download.oracle.com/javase/jndi/tutorial/trailmap.html
http://docs.codehaus.org/display/JETTY/JNDI