天天看點

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