天天看点

JNDI学习<五>

今天跟踪代码,发现在IntialContext的构造方法中会调用System.getProperties(),竟然从中得到了在jndi.properties文件中配置的信息,于是就将InitialContext的API中内容又重新读了一遍。

API中写道:

JNDI 通过按顺序合并取自以下两个源的值来确定每个属性值:

  1. 构造方法的环境参数、(适当属性的)applet 参数,以及系统属性中最先出现的属性。
  2. 应用程序资源文件 (jndi.properties)。

对于同时存在于两个源或多个应用程序资源文件中的每个属性,用以下方式确定属性值。如果该属性是指定 JNDI 工厂列表的标准 JNDI 属性之一(参见 Context),则所有值都被串联成一个以冒号分隔的列表。对于其他属性,只使用最先找到的值。

这一定位初始上下文和 URL 上下文工厂的默认策略可以通过调用 NamingManager.setInitialContextFactoryBuilder() 重写。

资源文件

要简化设置 JNDI 应用程序所需环境的任务,可以将资源文件 与应用程序组件和服务提供程序一起发布。JNDI 资源文件是使用属性文件格式的文件(参见 

java.util.Properties

),包括一个键/值对列表。键是属性的名称(例如 "java.naming.factory.object"),而值是使用为该属性定义的格式的字符串。以下是 JNDI 资源文件的一个示例:

java.naming.factory.object=com.sun.jndi.ldap.AttrsToCorba:com.wiz.from.Person java.naming.factory.state=com.sun.jndi.ldap.CorbaToAttrs:com.wiz.from.Person java.naming.factory.control=com.sun.jndi.ldap.ResponseControlFactory      

JNDI 类库读取资源文件,并使属性值随意可用。因此应该认为 JNDI 资源文件是“所有人可读的”,敏感信息(比如明文密码)不应该存储在那里。

有两种 JNDI 资源文件:提供程序 和应用程序。

提供程序资源文件

每个服务提供程序都有一个可选的资源,该资源列出了特定于该提供程序的属性。此资源的名称是:

[prefix/]jndiprovider.properties

其中 prefix 是提供程序的上下文实现的包名称,其每个句点 (".") 都被转换成一个斜杠 ("/")。 例如,假设服务提供程序定义了一个带有类名称 com.sun.jndi.ldap.LdapCtx 的上下文实现。此提供程序的提供程序资源被命名为 com/sun/jndi/ldap/jndiprovider.properties。如果该类不在一个包中,则资源的名称就是jndiprovider.properties。

JNDI 类库中的某些方法使用指定 JNDI 工厂列表的标准 JNDI 属性:

  • java.naming.factory.object
  • java.naming.factory.state
  • java.naming.factory.control
  • java.naming.factory.url.pkgs

在确定这些属性的值时,JNDI 库将参考提供程序资源文件。这以外的属性可由服务提供程序在提供程序资源文件中设置。服务提供程序的文档应该明确声明哪些属性是被允许的;文件中的其他属性将被忽略。

应用程序资源文件

在部署应用程序时,该应用程序通常将在其类路径中生成若干代码基目录和 JAR。类似地,在部署 applet 时,它将有一个指定 applet 类所处地址的代码基和档案文件。JNDI 查找(使用 

ClassLoader.getResources()

)类路径中所有名为 jndi.properties 的应用程序资源文件。此外,如果文件java.home/lib/jndi.properties 存在并且是可读的,则 JNDI 会将其视为一个额外的应用程序资源文件。(java.home 指示由 java.home 系统属性命名的目录。)包含在这些文件中的所有属性都被放置在初始上下文环境中。然后此环境由其他上下文继承。

对于同时出现在多个应用程序资源文件中的每个属性,JNDI 使用最先找到的值,或者在少数有意义的情况下串联所有这些值(细节在下文给出)。例如,如果在三个 jndi.properties 资源文件中存在 "java.naming.factory.object" 属性,则对象工厂列表是所有三个文件中的属性值的串联。使用此方案,每个可部署组件都要负责列出它导出的工厂。JNDI 在搜索工厂类时自动收集和使用所有这些导出列表。

从 Java 2 Platform 开始可使用应用程序资源文件,java.home/lib 中的文件除外,它在较早的 Java 平台上也可以使用。

属性的搜索算法

当 JNDI 构造一个初始上下文时,该上下文的环境是使用传递给构造方法的环境参数中定义的属性、系统属性、applet 参数和应用程序资源文件进行初始化的。有关细节请参见 InitialContext。然后此初始环境由其他上下文实例继承。

如果 JNDI 类库需要确定某一属性的值,它将通过按顺序合并取自以下两个源的值来实现这一点:

  1. 将在其上执行操作的上下文的环境。
  2. 将在其上执行操作的上下文的提供程序资源文件 (jndiprovider.properties)。

对于每个同时存在于这两个源中的属性,JNDI 用以下方式确定属性的值。如果该属性是指定 JNDI 工厂列表的标准 JNDI 属性之一(如上文所列),则这些值被串联成一个以冒号分隔的列表。对于其他属性,只使用最先找到的值。

当服务提供程序需要确定某一属性的值时,它通常将直接从环境中获取该值。服务提供程序可以定义将置于其本身提供程序资源文件中的特定于提供程序的属性。在这种情况下,它应该根据上文所述合并这些值。

这样,每个服务提供程序开发人员便可以指定与该服务提供程序一起使用的工厂列表。这可以由应用程序或 applet 的部署方指定的应用程序资源修改,而这些资源又可以由用户修改。

如上即为所查API关于jndi.properties文件的全部说明。总结起来即为:

jndi.properties是jndi初始化文件。通常我们有两种方式来创建一个初始上下文:

1.通过创建一个Properties对象,设置Context.PROVIDER_UR,Context.InitialContextFactroy等等属性,创建InitialContext,例如:

Properties   p   =   new   Properties();

p.put(Cotnext.PROVIDER_URL, "localhost:1099 ");//主机名和端口号

//InitialContext的创建工厂类(类名是我乱写的)

p.put(Context.InitialContextFactroy, "com.sun.InitialContextFactory ");

InitialContext   ctx   =   new   InitialContext(p);

2.通过jndi.properties文件创建初始上下文

java.naming.factory.initial=com.sun.NamingContextFactory

java.naming.provider.url=localhost:1099

如果直接创建初始上下文,如下:

InitialContext   ctx   =   new   InitialContext();

InitialContext的构造器会在类路径中找jndi.properties文件,如果找到,通过里面的属性,创建初始上下文。

所以从上面可以看出,两种方式完成的目标是相同的。

因此,对于本地测试(并且JNDI资源没有设置安全属性)可以不添加properties属性,但是如果要访问远程的JNDI资源,就必须用饱含JNDI环境参数Hashtable初始化InitialContext。

必要的环境参数如:

Context.INITIAL_CONTEXT_FACTORY//连接工厂

Context.PROVIDER_URL//访问连接

Context.SECURITY_PRINCIPAL//安全用户

Context.SECURITY_CREDENTIALS//用户密码

有时会出现如NoInitialContextException是因为无法从System.properties中获得必要的JNDI参数中获得必要的JNDI参数,在服务器环境下,服务器启动时就把这些参数放到System.properties中了,于是直接new InitialContext()就搞定了,不要搞env那么麻烦,搞了env你的代码还无法移植,弄不好管理员设置服务器用的不是标准端口还照样抛异常。

但是在单机环境下,可没有JNDI服务在运行,那就手动启动一个JNDI服务。我在JDK 5的rt.jar中一共找到了4种SUN自带的JNDI实现:LDAP,CORBA,RMI,DNS。

这4种JNDI要正常运行还需要底层的相应服务。一般我们没有LDAP或CORBA服务器,也就无法启动这两种JNDI服务,DNS用于查域名的,以后再研究,唯一可以在main()中启动的就是基于RMI的JNDI服务。

现在我们就在main()中启动基于RMI的JNDI服务并且绑一个Date对象到JNDI上:

LocateRegistry.createRegistry(1099);

System.setProperty(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.rmi.registry.RegistryContextFactory");

System.setProperty(Context.PROVIDER_URL, "rmi://localhost:1099");

InitialContext ctx = new InitialContext();

class RemoteDate extends Date implements Remote {};

ctx.bind("java:comp/env/systemStartTime", new RemoteDate());

ctx.close();

注意,我直接把JNDI的相关参数放入了System.properties中,这样,后面的代码如果要查JNDI,直接new InitialContext()就可以了,否则,你又得写Hashtable env = ...

这段话里提到了system.properties属性,这就是调用system.getProperties的来由,猜想应该是先找到jndi.properties文件,之后通过System.setProperties(),而后通过System.getProperties()来得到。

转自:http://blog.sina.com.cn/s/blog_605f5b4f0100qwra.html

Application Resource Files

An application resource file has the name  jndi.properties . It contains a list of key-value pairs presented in the properties file format (see  java.util.Properties ). The key is the name of the property (for example,  java.naming.factory.object ) and the value is a string in the format defined for that property. Here is an example of an application resource file:

java.naming.factory.object=com.sun.jndi.ldap.AttrsToCorba:com.wiz.from.Person java.naming.factory.state=com.sun.jndi.ldap.CorbaToAttrs:com.wiz.from.Person java.naming.factory.control=com.sun.jndi.ldap.ResponseControlFactory java.naming.factory.initial=com.sun.jndi.ldap.LdapCtxFactory java.naming.provider.url=ldap://localhost:389/o=jnditutorial com.sun.jndi.ldap.netscape.schemaBugs=true      

Notice that there is no restriction on the type of environment property that you can have in this file.

The JNDI automatically reads the application resource files from all components in the applications' classpath and JAVA_HOME/lib/jndi.properties, where JAVA_HOME is the file directory where your Java runtime (JRE) has been installed. The JNDI then makes the properties from these files available to the service providers and other components that need to use them. Therefore, these files should be considered world readable and should not contain sensitive information such as clear-text passwords.

Note: Except for  JAVA_HOME/lib/jndi.properties, application resource files are only supported when you use the  Java 2 Platform.  If you use the JDK 1.1, you can only see JAVA_HOME/lib/jndi.properties.

   首先,应用程序会从classpath 里面载入 jndi.properties,如果没有发现,那么会载入JAVA_HOME/lib/jndi.properties。

    注意:如果你使用这种方式配置JNDI,那么当找不到装个文件的时候,会抛出异常。

For example, here is  a program  that lists a context without specifying any environment properties in the  InitialContext  constructor:

InitialContext ctx = new InitialContext(); NamingEnumeration enum = ctx.list("");      

If you run this program with the  jndi.properties  file shown above, it will list the contents of the  o=jnditutorial  entry on of the specified LDAP server.

The use of application resource files to specify any JNDI environment properties allows the JNDI to be configured with minimal programmatic setup. By using theJAVA_HOME/lib/jndi.properties file, you can also configure the JNDI for all applications and applets using the same Java interpreter.

If you use application resource files, you must remember to grant your applet or application permission to read all of the application resource files.

System Properties

A  system property  is a key-value pair that the Java runtime defines to describe the user, system environment and Java system. The runtime defines and uses a set of default system properties. Other properties can be made available to a Java program via the  -D command line option to the Java interpreter. For example, running the interpreter as follows:

#java -Dmyenviron=abc Main      

adds the property  myenviron  with value  abc  to the list of system properties visible to the program  Main . The  java.lang.System  class contains static methods for reading and updating system properties. The ability to read or update any system property is controlled by the security policy of the Java runtime system.

The JNDI reads the following standard JNDI properties from the system properties:

java.naming.factory.initial java.naming.factory.object java.naming.factory.state java.naming.factory.control java.naming.factory.url.pkgs java.naming.provider.url java.naming.dns.url      

When set as system properties, these environment properties affect all of the application's or applet's (if the applet is allowed permission to read these properties) contexts.

Using the same program used for the application resource file example above, specify the initial context factory to use by specifying the initial context to use on the command line. Here are two examples:

#java -Djava.naming.factory.initial=com.sun.jndi.fscontext.RefFSContextFactory \ -Djava.naming.provider.url=file:/tmp \ List #java -Djava.naming.factory.initial=com.sun.jndi.ldap.LdapCtxFactory \ -Djava.naming.provider.url=ldap://localhost:389/o=jnditutorial \ List      

The first one uses LDAP while the second one uses the file system.

The use of system properties to specify standard JNDI environment properties allows the JNDI to be configured with minimal programmatic setup. However, they are probably convenient to use only from scripts because items with long property names must be specified on the command line. Also, applets generally do not have permission to read arbitrary system properties and must be explicitly granted permission to do so.

Applet Parameters

You can pass parameters to an applet by using simple key-value pairs. These are specified in the HTML file that references the applet. How you specify these parameters depends on the applet context. For example, if the applet is referenced from an  applet tag, you specify the parameters by using the  param  tag. Here is  an example :

<param name=java.naming.factory.initial value=com.sun.jndi.ldap.LdapCtxFactory> <param name=java.naming.provider.url value=ldap://localhost:389/o=jnditutorial>      

If the applet is referenced from the  Java Plug-in , its parameters are specified by using key-value pairs. Here is  an example :

java.naming.provider.url="ldap://localhost:389/o=jnditutorial" java.naming.factory.initial="com.sun.jndi.ldap.LdapCtxFactory"      

In order for the JNDI to access an applet's parameters, you must set theContext.APPLET

JNDI学习&lt;五&gt;

("java.naming.applet") environment property. The JNDI reads the following standard JNDI properties from the applet parameters:

java.naming.factory.initial java.naming.factory.object java.naming.factory.state java.naming.factory.control java.naming.factory.url.pkgs java.naming.provider.url java.naming.dns.url      

Here is an example that adds a single property (java.naming.applet) to the environment:

// Put this applet instance into environment Hashtable env = new Hashtable(); env.put(Context.APPLET, this); // Pass environment to initial context constructor Context ctx = new InitialContext(env); // List objects NamingEnumeration enum = ctx.list(target); while (enum.hasMore()) { out.println(enum.next()); } ctx.close();      

The JNDI then obtains the necessary environment properties from the applet parameters (shown earlier).

This use of applet parameters to specify standard JNDI environment properties allows the JNDI to be configured in the same way that an applet typically performs configuration for other subsystems or components. System properties and application resource files are not good mechanisms for applets to depend upon because applets typically cannot read system properties or arbitrary files (including jndi.properties).

2、内容上下文环境的探讨

    我们已经知道可以通过三种方式来设置JDNI的属性。但是,如果我们同时使用了两种方式会怎么样呢?看看下面的例子:

java 代码

// Initial environment with various properties   

Hashtable env = new Hashtable();   

env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.fscontext.FSContextFactory");   

env.put(Context.PROVIDER_URL, "file:/");   

env.put(Context.OBJECT_FACTORIES, "foo.bar.ObjFactory");   

env.put("foo", "bar");   

// Call the constructor   

Context ctx = new InitialContext(env);   

// See what environment properties you have   

System.out.println(ctx.getEnvironment());  

    我们同时配置一个jndi.properties在classpath里:

java.naming.factory.object=com.sun.jndi.ldap.AttrsToCorba:com.wiz.from.Person

java.naming.factory.state=com.sun.jndi.ldap.CorbaToAttrs:com.wiz.from.Person

java.naming.factory.control=com.sun.jndi.ldap.ResponseControlFactory

java.naming.factory.initial=com.sun.jndi.ldap.LdapCtxFactory

java.naming.provider.url=ldap://localhost:389/o=jnditutorial

com.sun.jndi.ldap.netscape.schemaBugs=true

    然后看看运行的结果:

com.sun.jndi.ldap.netscape.schemaBugs=true

java.naming.factory.object=foo.bar.ObjFactory:com.sun.jndi.ldap.AttrsToCorba:com.wiz.from.Person

java.naming.factory.initial=com.sun.jndi.fscontext.FSContextFactory

foo=bar

java.naming.provider.url=file:/

java.naming.factory.state=com.sun.jndi.ldap.CorbaToAttrs:com.wiz.from.Person

java.naming.factory.control=com.sun.jndi.ldap.ResponseControlFactory

    下面来分析一下这个结果:

    a> 在 Hashtable 里的“foo”项,和在文件里的 “com.sun.jndi.ldap.netscape.schemaBugs”,都出现在结果里。

    b> “java.naming.factory.object” 项是两者的组合。

    c> 其他的属性如“java.naming.factory.initial” 都是用的 Hashtable里的。

    是不是有些混乱?如果我们要使用多个SPI该怎么办?没关系,我们在下一个小结里介绍这个问题。

3、定制使用SPI

    你可以通过一个SPI的属性文件,来为某一个SPI进行单独的设置。这个文件也是 .properties 文件。SPI的属性文件应该类似下面:

    前缀/jndiprovider.properties

    这个前缀是什么呢?就是这个SPI实现的内容上下文类(Context)的包的结构。

    例如,我们要使用的是 com.sun.jndi.ldap.LdapCtx 这个内容上下文,那么对应于它的属性配置

    文件就应该是:"com/sun/jndi/ldap/jndiprovider.properties"。

    一个应用可以使用多个这样的属性配置文件。

引用自:http://hi.baidu.com/zim_it/blog/item/23bfb31562b70215962b4319.html 

http://gnu.private.webstar.co.uk/docs/other/jndi/beyond/env/source.html    

    那么我们为什么要使用属性配置文件?

    有两个原因。第一,我们可以单独的配置某一个命名/服务系统。第二,部署的时候会有用。例如,

    你可以单独配置一个针对LDAP的属性,而不用去修改 jndi.properties,或者增加系统变量。

    但是,我们也并不是可以在SPI属性配置文件里设置全部的属性,我们可以设置的属性如下:

java.naming.factory.object

java.naming.factory.state

java.naming.factory.control

java.naming.factory.url.pkgs

    不过并不象 jndi.properties 或者 那个Hastable,SPI属性配置文件里的属性不会自动载入到环境里,只有SPI调用了下列方法时才会这样做:

NamingManager.getObjectInstance(Object, Name, Context, Hashtable)

DirectoryManager.getObjectInstance(Object, Name, Context, Hashtable, Attributes)

NamingManager.getStateToBind(Object, Name, Context, Hashtable)

DirectoryManager.getStateToBind(Object, Name, Context, Hashtable, Attributes)

ControlFactory.getControlInstance(Control, Context, Hashtable)

    例如,假设我们使用一个LDAP服务,它的实现类是com.sun.jndi.ldap.LdapCtx,当这个类调用DirectoryManager.getObjectInstance() 方法时,JNDI将会从com/sun/jndi/ldap/jndiprovider.properties里找到“java.naming.factory.object”项,然后把它追加到环境里已经定义的项里(例如那个Hashtable或者 jndi.properties)。

转自:http://blog.sina.com.cn/s/blog_605f5b4f0100r094.html