转载请注明出处 http://blog.csdn.net/lovingprince
第一部分
引子
我们应用想用统一的方式去查找我们想要的服务,通过格式化的名称例如:
jdbc:comp/testa
去获取
jdbc
的服务,
Ldap:comp/testa
去获取
ldap
服务,每种服务如何去查找我们并不关心,服务查找实现方式变更了,我应用程序也不需要改变。
看下要求:
1、
统一根据特定名称就可以获取到特定的服务
2、
应用程序不关心具体如何查找的方式
3、
服务查找实现方式变更应用程序不需要任何更改
我们可以如何去做来满足这种需求,来分析一下,
²
第一点需求:需要统一根据名称来获取服务,可以考虑统一的操作界面如:
public interface Context {
public Object lookup(String name);//name可以是简单的testa,也可以是jdbc:comp/testa
}
²
第二点需求:应用不关心服务具体的查找方法,具体如何查找服务可以由服务提供方来实现
这点需求稍微麻烦点,首先需要根据用户提供的名称来确定使用的是什么类型的服务,然后再使用该类型的服务工厂提供一个查找该类服务方法,例如定义以下:
public interface ObjectFactory{//专门负责创建提供者自己的上下文环境用于查找他自己的服务
public Object getObjectInstance();
}
public class InitContext implements Context{
public Object lookup(String name){
if(name.startsWith("jdbc:")){//查找JDBC相关的服务
ObjectFactory jdbcUrlContextFactory=new jdbcURLContextFactory();//
Context c=(Context)jdbcUrlContextFactory.getObjectInstance();
//终于是服务提供者自己的环境了,用他来查找服务
return c.lookup(name.substring(5));//去掉jdbc:后的部分
}else if(name.startsWith("ldap:")){
//........
return ….;
}else{
return …;//可以提供一个默认的工厂来处理这类名字
}
}
}
应用程序要使用只需要这样
public static void main(String[] args) {
//最简单的
InitContext initContext=new InitContext();
//我想根据url不同找不同服务例如jdbc:/xxxxx,我不关心你内部怎么去找到,我只需要这个服务
initContext.lookup("jdbc:/xxxxx");
}
²
第三点需求:
服务查找实现方式变更应用程序不需要任何更改,其实第二部就已经实现了部分,比如服务提供者把查找的实现方式都封装在一个用工厂创建的上下文中了,如果查找实现方式更改了,由于是硬编码,需要修改
InitContext
,也就需要应用修改
,
那么就需要将这个工厂实现想办法与
InitContext
剥离出来,如何剥离?
可以这样修改下
lookup:
public Object lookup(String name){
String scheme = getURLScheme(name);//找到协议头,例如jdbc:comp/testa 中的jdbc
if (scheme != null) {
Context ctx = NamingManager. getURLContext(scheme);
if (ctx != null) {
return ctx.lookup(name);
}
}
return ...;
}
//专门包含一些工具方法
public class NamingManager{
public static Context getURLContext(String scheme){
//确定工厂类,通过当前线程中的classloader加载这个类,并得到其查找环境
String factoryPrefix=scheme+"URLContextFactory";
ObjectFactory
factory=(ObjectFactory)(Thread.currentThread().getContextClassLoader().loadClass(factoryPrefix).newInstance());
Context ctx =factory.getObjectInstance() ;
}
}
这样,如果实现变了,或者有新的服务,完全不需要修改修改
InitContext
了,当然应用程序也不需要修改了,只需要一个
jar
包中包含了对应的
URLContextFactory
工厂实现就可以了。这样就完全剥离了服务提供者的服务查找实现与应用之间的耦合,可以支持各种不同服务的查找方式。
以上就是一个简单的查找服务的分析过程。可以简单总结为几个部分:
统一的界面的
API(Context)->
服务类型路由工具
(NamingManager)->
服务提供者具体查找实现
(jdbc
URLContextFactory
和其实现了
Context
接口的具体路由方法
)
其中只有第三步底层需要服务提供者自己来实现,对于应用程序而言,则无需知道。
第二部分
JNDI
JNDI
(
Java Naming and Directory
Interface
)框架其实已经解决了上面的问题,思路一致,不过他包含的东西却比上面要复杂得多,毕竟上面只是一个简单的演示过程。
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
基本使用方法:
这里以查找文件系统中文件为例:
String name ="TEST.LOG";
Hashtable env = new Hashtable(11);
//制定创建实际查找上下文的初始化工厂类,如果没有其他工厂可用就会用这个工厂
env.put(Context.INITIAL_CONTEXT_FACTORY,
"com.sun.jndi.fscontext.RefFSContextFactory");
//提供文件系统的基准位置,根据工厂不同,这个参数的含义也不一样
env.put(Context.PROVIDER_URL,
"file:d:/");
try {
// Create the initial context
Context ctx = new InitialContext(env);
// Look up an object
File obj = (File)ctx.lookup(name);
// Print it out
System.out.println(name + " is bound to: " + obj);
// Close the context when we're done
ctx.close();
} catch (NamingException e) {
System.err.println("Problem looking up " + name + ": " + e);
}
是不是很简单
?
在介绍上面几步操作内部的具体工作之前,再来熟悉几个
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()
中获取以下几个属性
final static String[] PROPS = new String[] {
javax.naming.Context.INITIAL_CONTEXT_FACTORY,
javax.naming.Context.OBJECT_FACTORIES,
javax.naming.Context.URL_PKG_PREFIXES,
javax.naming.Context.STATE_FACTORIES,
javax.naming.Context.PROVIDER_URL,
javax.naming.Context.DNS_URL,
javax.naming.ldap.LdapContext.CONTROL_FACTORIES
};
如果
HashTable
中没有,就
put
进去,可以看出,这里是构造参数中的
hashtable
中的值优先。
二、依次搜索应用中
classpath
中的
jndi.properties
文件属性,再搜索
<java.home>/lib/jndi.properties
属性,这里
classpath
中可能有多个
jndi.properties
文件,他们中如果都出现了配置项,他们的合并规则是:
1
、如果是以下属性之一
private static final String[] listProperties = {
Context.OBJECT_FACTORIES,
Context.URL_PKG_PREFIXES,
Context.STATE_FACTORIES,
javax.naming.ldap.LdapContext.CONTROL_FACTORIES
};
则将属性值做串联起来,用冒号分隔,例如
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
接口,实现
Context
|
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
中有如下配置:
java.naming.factory.url.pkgs=org.eclipse.jetty.jndi java.naming.factory.initial=org.eclipse.jetty.jndi.InitialContextFactory
那么就会根据以上规则就会查找
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
文件,文件中的配置:
java.naming.factory.url.pkgs=org.eclipse.jetty.jndi java.naming.factory.initial=org.eclipse.jetty.jndi.InitialContextFactory
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
范围的资源。
也就是说定义好后,所有的应用都可以使用
<New id="myds" class="org.eclipse.jetty.plus.jndi.Resource">
<Arg></Arg><!—不填写任何东西,定义JVM范围-->
<Arg>jdbc/mydatasource</Arg>
<Arg>
<New class="org.apache.derby.jdbc.EmbeddedDataSource">
<Set name="DatabaseName">test</Set>
<Set name="createDatabase">create</Set>
</New>
</Arg>
</New>
<New id="myds" class="org.eclipse.jetty.plus.jndi.Resource">
<Arg><Ref id="Server"/></Arg><!—server的引用,定义Server范围-->
<Arg>jdbc/mydatasource</Arg>
<Arg>
<New class="org.apache.derby.jdbc.EmbeddedDataSource">
<Set name="DatabaseName">test</Set>
<Set name="createDatabase">create</Set>
</New>
</Arg>
</New>
<New id="myds" class="org.eclipse.jetty.plus.jndi.Resource">
<Arg><Ref id='webappContext'/></Arg><!—webappContext的引用,定义app范围-->
<Arg>jdbc/mydatasource</Arg>
<Arg>
<New class="org.apache.derby.jdbc.EmbeddedDataSource">
<Set name="DatabaseName">test</Set>
<Set name="createDatabase">create</Set>
</New>
</Arg>
</New>
三类资源在开始创建时都会在默认的上下文
(
org.eclipse.jetty.jndi.InitialContextFactory
创建的上下文中
)
中先进行注册,注册名字为
scope.getClass().getName()+"@"+Long.toHexString(scope.hashCode())+”/ myds”
其中
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
中配置
<Set name="configurationClasses">
<Array type="java.lang.String">
…..
<Item>org.eclipse.jetty.plus.webapp.EnvConfiguration</Item>
<Item>org.eclipse.jetty.plus.webapp.PlusConfiguration</Item>
…….
</Array>
</Set>
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
中做的初始化,并且默认值如下:
java.naming.factory.url.pkgs=org.apache.naming java.naming.factory.initial=org.apache.naming.java.javaURLContextFactory
具体分析,各位可以自行去看看。
第四部分
参考
http://download.oracle.com/javase/jndi/tutorial/trailmap.html
http://docs.codehaus.org/display/JETTY/JNDI |