(4) 目录服务操作

1、目录服务的操作

我们会用LDAP作为例子来讲解目录服务的操作。与命名服务不同,目录服务的内容上下文的初始化方式需要
    改变:

java 代码
  1. // Set up the environment for creating the initial context
  2. Hashtable env = new Hashtable();
  3. env.put(Context.INITIAL_CONTEXT_FACTORY,
  4. "com.sun.jndi.ldap.LdapCtxFactory");
  5. env.put(Context.PROVIDER_URL, "ldap://localhost:389/o=JNDITutorial");
  6. DirContext ctx = new InitialDirContext(env);

1.1、如何读取属性

你可以使用 DirContext.getAttributes() 方法来读取一个对象的属性,你需要给这个方法传递一个对象的
    名字作为参数。例如一个对象在命名系统中的名字是“cn=Ted Geisel, ou=People”,那么我们可以使用如下
    的代码得到它的属性:

java 代码
  1. Attributes answer = ctx.getAttributes("cn=Ted Geisel, ou=People");

你也可以输出 “answer”来看看:

java 代码
  1. for (NamingEnumeration ae = answer.getAll(); ae.hasMore();) {
  2. Attribute attr = (Attribute)ae.next();
  3. System.out.println("attribute: " + attr.getID());
  4. /* Print each value */
  5. for (NamingEnumeration e = attr.getAll(); e.hasMore();
  6. System.out.println("value: " + e.next()))
  7. ;
  8. }

输出结果是:

# java GetattrsAll
attribute: sn
value: Geisel
attribute: objectclass
value: top
value: person
value: organizationalPerson
value: inetOrgPerson
attribute: jpegphoto
value: [B@1dacd78b
attribute: mail
value: Ted.Geisel@JNDITutorial.com
attribute: facsimiletelephonenumber
value: +1 408 555 2329
attribute: telephonenumber
value: +1 408 555 5252
attribute: cn
value: Ted Geisel

1.1.1、返回需要的属性

有的时候我们只是需要得到一些属性,而不是全部的属性。这样,你可以把属性作为一个数组,把这个数组
    作为参数传递给那个方法。

java 代码
  1. // Specify the ids of the attributes to return
  2. String[] attrIDs = {"sn", "telephonenumber", "golfhandicap", "mail"};
  3. // Get the attributes requested
  4. Attributes answer = ctx.getAttributes("cn=Ted Geisel, ou=People", attrIDs);

假设我们这个例子里的这个对象,拥有 "sn", "telephonenumber"和"mail"属性,但是没有“golfhandicap”
    属性,那么上面的代码返回的结果就应该是:

# java Getattrs
attribute: sn
value: Geisel
attribute: mail
value: Ted.Geisel@JNDITutorial.com
attribute: telephonenumber
value: +1 408 555 5252

1.2、改变属性

DirContext 接口有一些改变对象属性的方法。

1.2.1、批量改变属性
 
    改变属性的方式之一就是批量改变属性,也就是使用许多 ModificationItem 对象来修改属性。
    每个 ModificationItem 对象都会有一个常量,来表示对属性进行什么样的操作。这些常量如下:

ADD_ATTRIBUTE
REPLACE_ATTRIBUTE
REMOVE_ATTRIBUTE

对属性的改变会按照队列的顺序来执行,要么所有的改变都生效,要么都不生效。
    下面的代码演示了一个例子。它把“mail”这个属性的值,改变成了“geisel@wizards.com”,给“telephonenumber”
    属性增加了一个值,并且删除了“jpegphoto”属性。

java 代码
  1. // Specify the changes to make
  2. ModificationItem[] mods = new ModificationItem[3];
  3. // Replace the "mail" attribute with a new value
  4. mods[0] = new ModificationItem(DirContext.REPLACE_ATTRIBUTE,
  5. new BasicAttribute("mail", "geisel@wizards.com"));
  6. // Add an additional value to "telephonenumber"
  7. mods[1] = new ModificationItem(DirContext.ADD_ATTRIBUTE,
  8. new BasicAttribute("telephonenumber", "+1 555 555 5555"));
  9. // Remove the "jpegphoto" attribute
  10. mods[2] = new ModificationItem(DirContext.REMOVE_ATTRIBUTE,
  11. new BasicAttribute("jpegphoto"));

上面的代码中,我们创建了一个修改属性的 ModificationItem 对象的列表(其实就是一个数组),然后执行
    modifyAttributes() 方法来修改属性。

java 代码
  1. // Perform the requested modifications on the named object
  2. ctx.modifyAttributes(name, mods);

1.2.2 只修改某几个属性

你可以不使用上面的方式,而对属性进行某一种操作:

java 代码
  1. // Save original attributes
  2. Attributes orig = ctx.getAttributes(name,
  3. new String[]{"mail", "telephonenumber", "jpegphoto"});
  4. 。。。。 。。。
  5. // Revert changes
  6. ctx.modifyAttributes(name, DirContext.REPLACE_ATTRIBUTE, orig);

1.3、在目录服务中使用搜索功能

1.3.1、基本的搜索功能
  
    最基本的搜索功能是可以指定一个对象的名字,和一些要搜索的属性的名字。
    下面的代码演示了这个功能。我们要进行这么一个搜索:对象必须有“sn”属性,而且数值必须是“Geisel”,
    而且必须有“mail”这个属性。

java 代码
  1. // Specify the attributes to match
  2. // Ask for objects that has a surname ("sn") attribute with
  3. // the value "Geisel" and the "mail" attribute
  4. Attributes matchAttrs = new BasicAttributes(true); // ignore attribute name case
  5. matchAttrs.put(new BasicAttribute("sn", "Geisel"));
  6. matchAttrs.put(new BasicAttribute("mail"));
  7. // Search for objects that have those matching attributes
  8. NamingEnumeration answer = ctx.search("ou=People", matchAttrs);

你可以打印出这个结果:

java 代码
  1. while (answer.hasMore()) {
  2. SearchResult sr = (SearchResult)answer.next();
  3. System.out.println(">>>" + sr.getName());
  4. printAttrs(sr.getAttributes());
  5. }

输出结果:

# java SearchRetAll
>>>cn=Ted Geisel
attribute: sn
value: Geisel
attribute: objectclass
value: top
value: person
value: organizationalPerson
value: inetOrgPerson
attribute: jpegphoto
value: [B@1dacd78b
attribute: mail
value: Ted.Geisel@JNDITutorial.com
attribute: facsimiletelephonenumber
value: +1 408 555 2329
attribute: cn
value: Ted Geisel
attribute: telephonenumber
value: +1 408 555 5252

1.3.2、返回指定的属性

上一个例子返回了满足条件的全部属性,当然你也可以只返回需要的属性,这仅仅需要把需要返回的属性,
    作为一个数组参数传递给 search() 方法。

java 代码
  1. // Specify the ids of the attributes to return
  2. String[] attrIDs = {"sn", "telephonenumber", "golfhandicap", "mail"};
  3. // Search for objects that have those matching attributes
  4. NamingEnumeration answer = ctx.search("ou=People", matchAttrs, attrIDs);

1.4、搜索过滤

在这里,你可以学到一个高级点的搜索方式,就是在搜索中使用过滤。在搜索中实现过滤,我们需要使用
    表达式来实现这个功能。下面的这个表达式就表示搜索条件是:对象必须有“sn”属性,而且数值必须是
    “Geisel”,而且必须有“mail”这个属性:

(&(sn=Geisel)(mail=*))

下面的例子告诉你如何使用表达式来搜索:

java 代码
  1. // Create the default search controls
  2. SearchControls ctls = new SearchControls();
  3. // Specify the search filter to match
  4. // Ask for objects that have the attribute "sn" == "Geisel"
  5. // and the "mail" attribute
  6. String filter = "(&(sn=Geisel)(mail=*))";
  7. // Search for objects using the filter
  8. NamingEnumeration answer = ctx.search("ou=People", filter, ctls);

下面这个列表有助你使用表达式:

符号                   描述
        &       conjunction (i.e., and -- all in list must be true) 
        |       disjunction (i.e., or -- one or more alternatives must be true) 
        !       negation (i.e., not -- the item being negated must not be true) 
        =       equality (according to the matching rule of the attribute) 
        ~=      approximate equality (according to the matching rule of the attribute) 
        >=      greater than (according to the matching rule of the attribute) 
        <=      less than (according to the matching rule of the attribute) 
        =*      presence (i.e., the entry must have the attribute but its value is irrelevant) 
        *       wildcard (indicates zero or more characters can occur in that position);
                used when specifying attribute values to match 
        \       escape (for escaping '*', '(', or ')' when they occur inside an attribute value)

表达式中的每一个项目,都必须使用属性的名字,也可以使用属性的数值。例如“sn=Geisel”意味着属性
    “sn”的值为“Geisel”,而 "mail=*" 意味着属性 “mail” 必须存在,但是可以为任意值。
    每一个项目必须是在括号中,例如 "(sn=Geisel)"。不同的括号间使用逻辑判断符号来连接起来。
    例如 "(| (& (sn=Geisel) (mail=*)) (sn=L*))"。这表示 属性中 sn 必须等于Geisel 并且有 mail 这个
    属性 或者 有 sn 这个属性。
    详细的内容,请参考 http://www.ietf.org/rfc/rfc2254.txt

当然了,你也可以只返回指定的属性,而不是全部的属性。

java 代码
// Specify the ids of the attributes to return         String[] attrIDs = {"sn", "telephonenumber", "golfhandicap", "mail"};         SearchControls ctls = new SearchControls();         ctls.setReturningAttributes(attrIDs);  

1.5 搜索控制
   
    在上面的章节中,我们已经看到了一个类:SearchControls,通过这个类,你可以控制搜索的行为。这里
    我们就来仔细地看看这个类。
 
    1.5.1 搜索范围

SearchControls 类默认会在整个内容上下文(SearchControls.ONELEVEL_SCOPE)搜索对象,通过设置,你
    可以在某一个范围内搜索对象。

1.5.1.1 在一个子树上搜索

通过下面的代码你可以清晰地了解这一功能:

java 代码
// Specify the ids of the attributes to return         String[] attrIDs = {"sn", "telephonenumber", "golfhandicap", "mail"};         SearchControls ctls = new SearchControls();         ctls.setReturningAttributes(attrIDs);         ctls.setSearchScope(SearchControls.SUBTREE_SCOPE);                 // Specify the search filter to match         // Ask for objects that have the attribute "sn" == "Geisel"         // and the "mail" attribute         String filter = "(&(sn=Geisel)(mail=*))";                 // Search the subtree for objects by using the filter         NamingEnumeration answer = ctx.search("", filter, ctls);  

1.5.1.2 根据名字来搜索

通过下面的代码你可以清晰地了解这一功能:

java 代码
// Specify the ids of the attributes to return         String[] attrIDs = {"sn", "telephonenumber", "golfhandicap", "mail"};         SearchControls ctls = new SearchControls();         ctls.setReturningAttributes(attrIDs);         ctls.setSearchScope(SearchControls.OBJECT_SCOPE);                 // Specify the search filter to match         // Ask for objects that have the attribute "sn" == "Geisel"         // and the "mail" attribute         String filter = "(&(sn=Geisel)(mail=*))";                 // Search the subtree for objects by using the filter         NamingEnumeration answer =              ctx.search("cn=Ted Geisel, ou=People", filter, ctls);  

1.5.2 数量的限制

通过下面的代码你可以控制返回结果的数量:

java 代码
// Set the search controls to limit the count to 1         SearchControls ctls = new SearchControls();         ctls.setCountLimit(1);  

1.5.3 时间的限制

如果一个搜索耗费了很长的时间,那可不是一个好方法。这里你可以设置超时的时间。

java 代码
// Set the search controls to limit the time to 1 second (1000 ms)         SearchControls ctls = new SearchControls();         ctls.setTimeLimit(1000);  

参数的单位是毫秒。

如果发生超时现象,那么就会抛出 TimeLimitExceededException。

1.6 结合命名服务和目录服务的操作

我们已经这样的一个概念,就是目录服务是命名服务的一个扩展。例如,之前我们说过命名服务具有 bind(),
    rebind(), createSubcontext() 等方法,但是在目录服务里却没有介绍这些方法。
    其实目录服务里也有这些方法。下面就用 LDAP 作为例子,介绍一下这些方法。

1.6.1 创建一个具有属性的内容上下文

java 代码
import javax.naming.*;         import javax.naming.directory.*;         import java.util.Hashtable;                 /**          * Demonstrates how to create a new subcontext called "ou=Fruits" with some           * attributes.          * (Run Destroy after this to remove the subcontext).          *          * usage: java Create          */        class Create {             public static void main(String[] args) {                     // Set up the environment for creating the initial context             Hashtable env = new Hashtable(11);             env.put(Context.INITIAL_CONTEXT_FACTORY,                  "com.sun.jndi.ldap.LdapCtxFactory");             env.put(Context.PROVIDER_URL, "ldap://localhost:389/o=JNDITutorial");                     try {                 // Create the initial context                 DirContext ctx = new InitialDirContext(env);                         // Create attributes to be associated with the new context                 Attributes attrs = new BasicAttributes(true); // case-ignore                 Attribute objclass = new BasicAttribute("objectclass");                 objclass.add("top");                 objclass.add("organizationalUnit");                 attrs.put(objclass);                         // Create the context                 Context result = ctx.createSubcontext("ou=Fruits", attrs);                         // Check that it was created by listing its parent                 NamingEnumeration list = ctx.list("");                         // Go through each item in list                 while (list.hasMore()) {                 NameClassPair nc = (NameClassPair)list.next();                 System.out.println(nc);                 }                         // Close the contexts when we're done                 result.close();                 ctx.close();             } catch (NamingException e) {                 System.out.println("Create failed: " + e);             }             }         }  

1.6.2 增加一个具有属性的绑定

java 代码
import javax.naming.*;         import javax.naming.directory.*;                 import java.util.Hashtable;                 /**          * Demonstrates how to add a binding and its attributes to a context.          * (Use Rebind example to overwrite binding; use Unbind to remove binding.)          *          * usage: java Bind          */                class Bind {             public static void main(String[] args) {                     // Set up the environment for creating the initial context             Hashtable env = new Hashtable(11);             env.put(Context.INITIAL_CONTEXT_FACTORY,                  "com.sun.jndi.ldap.LdapCtxFactory");             env.put(Context.PROVIDER_URL, "ldap://localhost:389/o=JNDITutorial");                     try {                 // Create the initial context                 DirContext ctx = new InitialDirContext(env);                         // Create object to be bound                 Fruit fruit = new Fruit("orange");                         // Create attributes to be associated with object                 Attributes attrs = new BasicAttributes(true); // case-ignore                 Attribute objclass = new BasicAttribute("objectclass");                 objclass.add("top");                 objclass.add("organizationalUnit");                 attrs.put(objclass);                         // Perform bind                 ctx.bind("ou=favorite, ou=Fruits", fruit, attrs);                         // Check that it is bound                 Object obj = ctx.lookup("ou=favorite, ou=Fruits");                 System.out.println(obj);                         // Get its attributes                 Attributes retattrs = ctx.getAttributes("ou=favorite, ou=Fruits");                 GetattrsAll.printAttrs(retattrs);                         // Close the context when we're done                 ctx.close();             } catch (NamingException e) {                 System.out.println("Operation failed: " + e);             }             }         }  

1.6.3 替换一个具有属性的绑定

java 代码

import javax.naming.*;         import javax.naming.directory.*;                 import java.util.Hashtable;                 /**          * Demonstrates how to replace a binding and its attributes to a context.          * (Use after Bind example; use Unbind to remove binding.)          *          * usage: java Rebind          */                class Rebind {             public static void main(String[] args) {                     // Set up the environment for creating the initial context             Hashtable env = new Hashtable(11);             env.put(Context.INITIAL_CONTEXT_FACTORY,                  "com.sun.jndi.ldap.LdapCtxFactory");             env.put(Context.PROVIDER_URL, "ldap://localhost:389/o=JNDITutorial");                     try {                 // Create the initial context                 DirContext ctx = new InitialDirContext(env);                         // Create object to be bound                 Fruit fruit = new Fruit("lemon");                         // Create attributes to be associated with object                 Attributes attrs = new BasicAttributes(true); // case-ignore                 Attribute objclass = new BasicAttribute("objectclass");                 objclass.add("top");                 objclass.add("organizationalUnit");                 attrs.put(objclass);                         // Perform bind                 ctx.rebind("ou=favorite, ou=Fruits", fruit, attrs);                         // Check that it is bound                 Object obj = ctx.lookup("ou=favorite, ou=Fruits");                 System.out.println(obj);                         // Get its attributes                 Attributes retattrs = ctx.getAttributes("ou=favorite, ou=Fruits");                 GetattrsAll.printAttrs(retattrs);                         // Close the context when we're done                 ctx.close();             } catch (NamingException e) {                 System.out.println("Operation failed: " + e);             }             }         }

(5) 高级应用之Name

1、jndi 高级应用之 Name

1.1、什么是 Name?
   
    这之前的文档中,我们所用的例子里,对象的名字都是 java.lang.String 类型的,也就是字符串类型。
    在这个文档里,我们则会介绍一些其他的对象名字类型,如 Name,以及他们的使用方法。
    我们首先会讲讲什么是字符串的名字和结构化的名字,以及它们的存在的必要性。
    然后,我们会介绍2个结构化名字的类:(复合名字)composite 和 (混合名字)compound。
    最后,我们介绍如何在对象名字中使用特殊字符,以及如何解析和构成一个复杂的对象的名字。
   
    1.2、字符串名字 vs 结构化名字
   
    在 Context 和 DirContext 接口里,每一个命名服务的方法都有2种方式,一个是接受字符串类型的名字,
    一个是接受结构化的名字(Name 类的对象)。例如:
   
lookup(java.lang.String)
lookup(javax.naming.Name)

1.2.1、字符串名字
   
    使用字符串类型的名字,可以让你不必再生成一个CompositeName类的对象。例如下面的代码是相同的:

java 代码
  1. Object obj1 = ctx.lookup("cn=Ted Geisel, ou=People, o=JNDITutorial");
  2. CompositeName cname = new CompositeName(
  3. "cn=Ted Geisel, ou=People, o=JNDITutorial");
  4. Object obj2 = ctx.lookup(cname);

1.2.1、结构化的名字
   
    结构化的名字的对象可以是 CompositeName 或 CompoundName 类的对象,或者是任何一个实现了 “Name ”
    接口的类。
    如果是 CompositeName 类的实例,那么就被认为是一个复合的名字(composite name)。所谓的复合名字就
    是可以在一个名字里使用多个命名服务系统,而不是仅仅一个。
    如果是 compound 类的实例,那么就被认为是混合的名字(compound name)。混合的名字只包含一个命名服务
    系统。
   
    1.2.2、那么该使用哪种名字呢?
   
    一般来说,如果用户可以提供字符串类型的名字,那么就使用字符串类型的名字;可是如果用户提供的是一
    个组合的名字,那么就应该使用结构化的名字了。
    例如一个应用如果会涉及多个命名服务系统,那么就应该使用结构化的名字了。
   
    1.3 复合名字(composite name)
   
    复合名字就是跨越多个命名系统的名字。例如:
   
cn=homedir,cn=Jon Ruiz,ou=People/tutorial/report.txt

这个名字里包含了两个命名系统:一个 LDAP 系统 "cn=homedir,cn=Jon Ruiz,ou=People" 和一个文件系统
    "tutorial/report.txt"。当你把这个字符串传递给 Context 的 look 方法,那么就会从 LDAP 系统里查找
    那个文件,然后返回那个对象(一个文件对象的实例)。当然,这取决于特定的SPI。例如:

java 代码
  1. File f = (File)ctx.lookup(
  2. "cn=homedir,cn=Jon Ruiz,ou=People/tutorial/report.txt");

1.3.1、字符串的表现形式
   
    我们可以这样想像,一个复合的名字是由不同的“组件”组成的。所谓的“组件”,我们可以想象成是一个名字
    的一小部分,每一个组件表示一个命名服务系统。每一个组件都由正斜杠“/”分开。
    例如 :cn=homedir,cn=Jon Ruiz,ou=People/tutorial/report.txt
    包含三个组件:
   
cn=homedir,cn=Jon Ruiz,ou=People
tutorial
report.txt

第一个组件属于LDAP命名服务系统,第2和第3个组件属于文件命名服务系统。正如我们从这个例子里看到的,
    多个组件(tutorial 和 report.txt)可以属于同一个命名服务系统,但是一个组件是不能跨越多个命名服务
    系统的。
   
    除了使用正斜杠“/”外,复合名字还允许使用其它三个特殊符号:反斜杠“\",单引号"'",双引号" " "。
    这三个符号是内置的符号,也就是说他们三个具有特殊的用途。
   
    反斜杠“\”的作用是转译字符。反斜杠“\”之后的字符,会被认为是普通字符。
    例如“a\/b”,其中的“/”是不会被认为是不同的命名服务系统的分隔符号。
   
    双引号和单引号可以让引号内的字符成为普通字符,例如下面的用法都是一样的:
   
a\/b\/c\/d
"a/b/c/d"
'a/b/b/d'

可见,有的时候使用引号还是很方便的。
   
    复合的名字也可以是空的,空的复合名字意味着没有组件,一般用空的字符串表示。
   
    复合名字的组件也可以是空的,例如:
   
/abc
abc/
abc//xyz

1.3.2、复合名字的类
   
    CompositeName 类是用来构成复合名字的类。你可以给它的构造函数传递一个复合名字的字符串。
    例如:

java 代码
  1. import javax.naming.CompositeName;
  2. import javax.naming.InvalidNameException;
  3. /**
  4. * Demonstrates how to construct a composite name given its
  5. * string representation
  6. *
  7. * usage: java ConstructComposite
  8. */
  9. class ConstructComposite {
  10. public static void main(String[] args) {
  11. if (args.length != 1) {
  12. System.out.println("usage: java ConstructComposite <string></string>");
  13. System.exit(-1);
  14. }
  15. String name = args[0];
  16. try {
  17. CompositeName cn = new CompositeName(name);
  18. System.out.println(cn + " has " + cn.size() + " components: ");
  19. for (int i = 0; i < cn.size(); i++) {
  20. System.out.println(cn.get(i));
  21. }
  22. } catch (InvalidNameException e) {
  23. System.out.println("Cannot parse name: " + name);
  24. }
  25. }
  26. }

运行这个例子,输出的结果就是:
   
a/b/c has 3 components:
a
b
c

CompositeName 类有许多方法,例如查看,修改,比较,以及得到一个复合名字的字符串表现形式。
   
    1.3.3、访问复合名字里的组件
   
    可以通过下列方法访问复和名字里的组件:
   
get(int posn)
getAll()
getPrefix(int posn)
getSuffix(int posn)
clone()

如果你想得到特定位置的组件的名字,那么使用 get(int posn) 方法就非常合适。
   
    getAll() 方法可以返回全部的组件的名字。
    例如:

java 代码
  1. try {
  2. CompositeName cn = new CompositeName(name);
  3. System.out.println(cn + " has " + cn.size() + " components: ");
  4. for (Enumeration all = cn.getAll(); all.hasMoreElements();) {
  5. System.out.println(all.nextElement());
  6. }
  7. } catch (InvalidNameException e) {
  8. System.out.println("Cannot parse name: " + name);
  9. }

你可以用 getPrefix(int posn) 和 getSuffix(int posn)来从前端或者后端查找组件的名字,如:

java 代码
  1. CompositeName cn = new CompositeName("one/two/three");
  2. Name suffix = cn.getSuffix(1);  // 1 <= index < cn.size()
  3. Name prefix = cn.getPrefix(1);  // 0 <= index < 1

运行结果:
   
two/three
one

1.3.4、修改一个复合名字
   
    你可以通过下列方法修改一个复合名字:

add(String comp)
add(int posn, String comp)
addAll(Name comps)
addAll(Name suffix)
addAll(int posn, Name suffix)
remove(int posn)

当你创建了一个复合名字实例后,你可以对它进行修改。看看下面的例子:

java 代码
  1. CompositeName cn = new CompositeName("1/2/3");
  2. CompositeName cn2 = new CompositeName("4/5/6");
  3. System.out.println(cn.addAll(cn2));           // 1/2/3/4/5/6
  4. System.out.println(cn.add(0, "abc"));         // abc/1/2/3/4/5/6
  5. System.out.println(cn.add("xyz"));            // abc/1/2/3/4/5/6/xyz
  6. System.out.println(cn.remove(1));             // 1
  7. System.out.println(cn);               // abc/2/3/4/5/6/xyz

1.3.4、比较复合名字
   
    你可以通过以下的方法对复合名字进行比较:
   
compareTo(Object name)
equals(Object name)
endsWith(Name name)
startsWith(Name name)
isEmpty()

你可以使用 compareTo(Object name) 方法对一个复合名字的列表进行排序。下面是一个例子:

java 代码
  1. import javax.naming.CompositeName;
  2. import javax.naming.InvalidNameException;
  3. /**
  4. * Demonstrates how to sort a list of composite names.
  5. *
  6. * usage: java SortComposites [<name></name>]*
  7. */
  8. class SortComposites {
  9. public static void main(String[] args) {
  10. if (args.length == 0) {
  11. System.out.println("usage: java SortComposites [<names></names>]*");
  12. System.exit(-1);
  13. }
  14. CompositeName[] names = new CompositeName[args.length];
  15. try {
  16. for (int i = 0; i < names.length; i++) {
  17. names[i] = new CompositeName(args[i]);
  18. }
  19. sort(names);
  20. for (int i = 0; i < names.length; i++) {
  21. System.out.println(names[i]);
  22. }
  23. } catch (InvalidNameException e) {
  24. System.out.println(e);
  25. }
  26. }
  27. /**
  28. * Use bubble sort.
  29. */
  30. private static void sort(CompositeName[] names) {
  31. int bound = names.length-1;
  32. CompositeName tmp;
  33. while (true) {
  34. int t = -1;
  35. for (int j=0; j < bound; j++) {
  36. int c = names[j].compareTo(names[j+1]);
  37. if (c > 0) {
  38. tmp = names[j];
  39. names[j] = names[j+1];
  40. names[j+1] = tmp;
  41. t = j;
  42. }
  43. }
  44. if (t == -1) break;
  45. bound = t;
  46. }
  47. }
  48. }

equals() 方法可以让你比较两个复合名字是否相同。只有两个复合名字有相同的组件,而且顺序一样,
    会返回 true。
   
    使用 startsWith() 和 endsWith()方法,你可以判断复合名字是以什么字符串开头和以什么字符串结尾。
   
    isEmpty() 方法可以让你知道一个复合名字是否为空。你也可以使用 size() == 0 来实现同样的功能。
   
    下面是一些例子:

java 代码
  1. CompositeName one = new CompositeName("cn=fs/o=JNDITutorial/tmp/a/b/c");
  2. CompositeName two = new CompositeName("tmp/a/b/c");
  3. CompositeName three = new CompositeName("cn=fs/o=JNDITutorial");
  4. CompositeName four = new CompositeName();
  5. System.out.println(one.equals(two));        // false
  6. System.out.println(one.startsWith(three));  // true
  7. System.out.println(one.endsWith(two));      // true
  8. System.out.println(one.startsWith(four));   // true
  9. System.out.println(one.endsWith(four));     // true
  10. System.out.println(one.endsWith(three));    // false
  11. System.out.println(one.isEmpty());      // false
  12. System.out.println(four.isEmpty());     // true
  13. System.out.println(four.size() == 0);       // true

1.3.5、复合名字的字符串表现形式
   
    你可以使用 toString() 方法来实现这个功能。
    下面是一个例子:

java 代码
  1. import javax.naming.CompositeName;
  2. import javax.naming.InvalidNameException;
  3. /**
  4. * Demonstrates how to get the string representation of a composite name.
  5. *
  6. * usage: java CompositeToString
  7. */
  8. class CompositeToString {
  9. public static void main(String[] args) {
  10. if (args.length != 1) {
  11. System.out.println("usage: java CompositeToString <string></string>");
  12. System.exit(-1);
  13. }
  14. String name = args[0];
  15. try {
  16. CompositeName cn = new CompositeName(name);
  17. String str = cn.toString();
  18. System.out.println(str);
  19. CompositeName cn2 = new CompositeName(str);
  20. System.out.println(cn.equals(cn2));     // true
  21. } catch (InvalidNameException e) {
  22. System.out.println("Cannot parse name: " + name);
  23. }
  24. }
  25. }

1.3.5、复合名字作为Context的参数
   
    直接看一个例子,非常简单:

java 代码
  1. // Create the initial context
  2. Context ctx = new InitialContext(env);
  3. // Parse the string name into a CompositeName
  4. Name cname = new CompositeName(
  5. "cn=homedir,cn=Jon Ruiz,ou=people/tutorial/report.txt");
  6. // Perform the lookup using the CompositeName
  7. File f = (File) ctx.lookup(cname);

1.4、混合名字
   
    混合名字不是跨越命名系统的,例如:
   
    cn=homedir,cn=Jon Ruiz,ou=People
   
    它和复合名字有些类似,我们也可以把它看成是由不同的组件组成的,这个名字里包含了三个组件:
   
    ou=People
    cn=Jon Ruiz
    cn=homedir
   
    1.4.1、混合名字和复合名字的关系
   
    当你给 Context.lookup() 传递一个字符串的时候,首先 lookup 方法会把这个字符串作为复合名字来看
    待,这个复合名字可能只包含一个组件。但是一个组件可能会包含几个混合名字。
   
    1.4.2、混合名字的字符串表现方式
   
    正如上面所说的,一个混合名字是由很多的组件组成的。组件之间的分隔符号依赖于特定的命名服务系统。
    例如在 LDAP 里,分隔符好就是“,”,因此,下面的这个混合名字
   
ou=People
cn=Jon Ruiz
cn=homedir

的字符串形式就是 cn=homedir,cn=Jon Ruiz,ou=People

1.4.3、混合名字的类
   
    处理混合名字,我们可以使用 CompoundName 类。你可以向它的构造函数传递混合名字的字符串,并且还
    得设置一些必要的属性,这些属性一般都是特定的命名服务系统的一些规则。
   
    实际上,只有当你准备编写一个SPI的时候,才会去使用装个构造函数。作为一个开发者,一般你只是会
    涉及混合名字里的各个组件而已。
    下面是一个例子:

java 代码
  1. import javax.naming.*;
  2. import java.util.Hashtable;
  3. /**
  4. * Demonstrates how to get a name parser and parse a name.
  5. *
  6. * usage: java ParseCompound
  7. */
  8. class ParseCompound {
  9. public static void main(String[] args) {
  10. // Set up environment for creating initial context
  11. Hashtable env = new Hashtable(11);
  12. env.put(Context.INITIAL_CONTEXT_FACTORY,
  13. "com.sun.jndi.ldap.LdapCtxFactory");
  14. env.put(Context.PROVIDER_URL, "ldap://localhost:389/o=JNDITutorial");
  15. try {
  16. // Create the initial context
  17. Context ctx = new InitialContext(env);
  18. // Get the parser for this namespace
  19. NameParser parser = ctx.getNameParser("");
  20. // Parse name
  21. Name compoundName = parser.parse("cn=John,ou=People,ou=Marketing");
  22. // List components in name
  23. for (int i = 0; i < compoundName.size(); i++) {
  24. System.out.println(compoundName.get(i));
  25. }
  26. // Close ctx when done
  27. ctx.close();
  28. } catch (NamingException e) {
  29. System.out.println(e);
  30. }
  31. }
  32. }

1.4.4、操作混合名字类
   
    注意,上面的例子中,我们使用 NameParser.parse() 来返回一个实现了Name接口的对象。这个接口可以
    是  CompositeName类 或 CompoundName类。这就意味着,你可以访问或者修改一个混合名字对象,就好
    像我们在复合名字里做的一样!
    下面是一个例子:

java 代码
  1. // Get the parser for this namespace
  2. NameParser parser = ctx.getNameParser("");
  3. // Parse the name
  4. Name cn = parser.parse("cn=John,ou=People,ou=Marketing");
  5. // Remove the second component from the head
  6. System.out.println(cn.remove(1));          // ou=People
  7. // Add to the head (first)
  8. System.out.println(cn.add(0, "ou=East"));  // cn=John,ou=Marketing,ou=East
  9. // Add to the tail (last)
  10. System.out.println(cn.add("cn=HomeDir"));  // cn=HomeDir,cn=John,ou=Marketing,ou=East

输出结果:
   
ou=People
cn=John,ou=Marketing,ou=East
cn=HomeDir,cn=John,ou=Marketing,ou=East

需要的注意的是,LDAP系统里,组件的顺序是从右到左的。也就是右边是名字的开头,而左边是名字
    的结尾!
   
    下面这个例子是修改混合名字的:

java 代码
  1. import javax.naming.*;
  2. import java.util.Hashtable;
  3. import java.io.File;
  4. /**
  5. * Demonstrates how to modify a compound name by adding and removing components.
  6. * Uses file system syntax.
  7. *
  8. * usage: java ModifyCompoundFile
  9. */
  10. class ModifyCompoundFile {
  11. public static void main(String[] args) {
  12. // Set up environment for creating initial context
  13. Hashtable env = new Hashtable(11);
  14. env.put(Context.INITIAL_CONTEXT_FACTORY,
  15. "com.sun.jndi.fscontext.RefFSContextFactory");
  16. try {
  17. // Create the initial context
  18. Context ctx = new InitialContext(env);
  19. // Get the parser for this namespace
  20. NameParser parser = ctx.getNameParser("");
  21. // Parse name
  22. Name cn = parser.parse("Marketing" + File.separator +
  23. "People" + File.separator +
  24. "John");
  25. // Remove 2nd component from head
  26. System.out.println(cn.remove(1));             // People
  27. // Add to head (first)
  28. System.out.println(cn.add(0, "East"));
  29. // East/Marketing/John
  30. // Add to tail (last)
  31. System.out.println(cn.add("HomeDir"));
  32. // /East/Marketing/John/HomeDir
  33. // Close ctx when done
  34. ctx.close();
  35. } catch (NamingException e) {
  36. System.out.println(e);
  37. }
  38. }
  39. }

这个例子使用了文件系统,而不是LDAP系统,输出结果:
   
People
East/Marketing/John
East/Marketing/John/HomeDir

1.4.5、混合名字作为Context的参数
   
    我们只用一个例子就可以了:

java 代码
  1. // Create the initial context
  2. Context ctx = new InitialContext(env);
  3. // Get the parser for the namespace
  4. NameParser parser = ctx.getNameParser("");
  5. // Parse the string name into a compound name
  6. Name compound = parser.parse("cn=Jon Ruiz,ou=people");
  7. // Perform the lookup using the compound name
  8. Object obj = ctx.lookup(compound);

1.4.6、取得完整的混合名字
   
    有的时候,你可能需要根据一个混合名字来的完整的名字。例如一个DNS,你只知道一部分如“www.abc.com”,
    但实际上它的完整的DNS是“www.abc.com.cn”。
   
    其实这个功能已经超出了jndi 的 api 所控制的范围了,因为 jndi 是不能决定完整的名字到底是什么样子,
    那必须依赖特定的命名服务系统。
   
    但是,jndi 仍然提供了一个接口 Context.getNameInNamespace()。这个方法的返回结果,依赖于你所使用
    的命名服务系统。
   
    下面是一个例子:

java 代码
  1. // Create the initial context
  2. Context ctx = new InitialContext(env);
  3. // Perform the lookup
  4. Context jon = (Context)ctx.lookup("cn=Jon Ruiz,ou=people");
  5. String fullname = jon.getNameInNamespace();

输出结果:
   
    cn=Jon Ruiz,ou=people,o=JNDItutorial

1.5、名字解析
   
    名字解析的意思就是根据一个名字的字符串形式,把它解析成结构化的形式,如混合名字或复合名字。
    jndi API里包含了这样的接口,其接口的实现,依赖于特定的SPI。
   
    1.5.1、解析复合名字
   
    下面是一个例子:

java 代码
  1. // Parse the string name into a CompositeName
  2. Name cname = new CompositeName(
  3. "cn=homedir,cn=Jon Ruiz,ou=people/tutorial/report.txt");

其实这个用法我们早就在前面看过了。
   
    1.5.2、解析混合名字
   
    为了解析混合名字,你必须使用 NameParser 接口,这个接口有一个方法:

java 代码
  1. Name parse(String name) throws InvalidNameException

首先,你必须从 SPI 里得到一个 NameParser 接口的实现:

java 代码
  1. // Create the initial context
  2. Context ctx = new InitialContext();
  3. // Get the parser for LDAP
  4. NameParser ldapParser =
  5. ctx.getNameParser("ldap://localhost:389/o=jnditutorial");
  6. // Get the parser for filenames
  7. NameParser fsParser = ctx.getNameParser("file:/");

一旦你得到了 NameParser 实例,你就可以用它来把字符串形式的名字转换成结构化的名字。

java 代码
  1. // Parse the name using the LDAP parser
  2. Name compoundName = ldapParser.parse("cn=John Smith, ou=People, o=JNDITutorial");
  3. // Parse the name using the LDAP parser
  4. Name compoundName = fsParser.parse("tmp/tutorial/beyond/names/parse.html");

如果解析错误,那么会得到 InvalidNameException 。
   
    尽管 parse() 方法可以返回一个结构化的名字,但是我们还是建议仅仅用它来处理混合名字,而不要
    处理复合名字。
    parse()返回的对象未必一定是 CompoundName 类,只要返回的对象实现了 Name 接口就可以--这依赖
    于特定的SPI。
   
   
    1.6、动态生成复合名字
   
    之前我们介绍了访问,操作复合名字的方法。但是还有一些难以处理的情况需要我们去面对。
    例如,如果你要给一个复合名字增加一个组件的话,你是增加复合名字的组件还是混合名字的组件?
    如果是混合名字的组件,那么使用什么规则呢?
    例如我们有这样一个复合名字:
   
    cn=homedir,cn=Jon Ruiz/tutorial
   
    现在你需要增加一个文件名字,如果是windows系统,那么就是:
   
    cn=homedir,cn=Jon Ruiz/tutorial\report.txt
   
    然后我们再增加一个LDAP混合名字 ou=People,那么就需要使用LDAP的规则:
   
    cn=homedir,cn=Jon Ruiz,ou=People/tutorial\report.txt
   
    在这个例子中,我们使用了不同的命名服务系统,我们需要知道什么时候因该使用什么样的命名系统
    的规则,这真的很麻烦。
   
    为了解决这个问题,jndi API 提供了一个接口 Context.composeName(),它用来动态的组装名字,
    当然依赖于特定的SPI了。
    你需要给它提供两个参数:一个是需要追加的组件,一个是被追加的组件的名字。
   
    下面是一个例子:

  1. // Create the initial context
  2. Context ctx = new InitialContext(env);
  3. // Compose a name within the LDAP namespace
  4. Context ldapCtx = (Context)ctx.lookup("cn=Jon Ruiz,ou=people");
  5. String ldapName = ldapCtx.composeName("cn=homedir", "cn=Jon Ruiz,ou=people");
  6. System.out.println(ldapName);
  7. // Compose a name when it crosses into the next naming system
  8. Context homedirCtx = (Context)ctx.lookup(ldapName);
  9. String compositeName = homedirCtx.composeName("tutorial", ldapName);
  10. System.out.println(compositeName);
  11. // Compose a name within the File namespace
  12. Context fileCtx = (Context)ctx.lookup(compositeName);
  13. String fileName = fileCtx.composeName("report.txt", compositeName);
  14. System.out.println(fileName);

(6) 高级应用之 环境属性

1、环境属性

在之前的文档里,我们已经学会如何去初始化一个内容上下文,例如:

java 代码
  1. Hashtable env = new Hashtable();
  2. env.put(Context.INITIAL_CONTEXT_FACTORY,
  3. "com.sun.jndi.ldap.LdapCtxFactory");
  4. env.put(Context.PROVIDER_URL, "ldap://localhost:389/o=JNDITutorial");
  5. Context ctx = new InitialContext(env);

这个例子中的 Hashtable,就是一个环境属性,或者简单的说是“环境”。
    这个章节里,我们要介绍的就是这个“环境属性”,看看他们是如何发挥作用的,以及如何使用的等等。
   
    JNDI其实只是一个接口,为了访问一个命名/目录服务,你必须依赖特定的SPI。这就需要进行一些配置,
    你得告诉JNDI,你需要的SPI是什么。
   
    下面是一些不同种类的环境属性:
   
    a> 标准的
    b> 描述服务的
    c> 描述特性的
    d> 描述提供者的。
   
    下面是标准的环境属性:

1.1、环境属性
   
    配置环境属性可以通过两种方式:一个是把一个Hashtable传递给InitialContext的构造函数,另一个
    是用一个 .properties 文件。
    一些JNDI的环境属性还可以通过系统变量或者Applet参数来设置。
   
    1.1.1、应用程序资源文件(.properties文件)
   
    你可以在 .properties文件 里指定JNDI的配置。这个文件的名字应该是 jndi.properties。例如下面
    就是一个 jndi.properties的例子:
   
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

首先,应用程序会从classpath 里面载入 jndi.properties,如果没有发现,那么会载入JAVA_HOME/lib/
    jndi.properties。
   
    注意:如果你使用这种方式配置JNDI,那么当找不到装个文件的时候,会抛出异常。
   
    当然,我们初始化内容上下文的用法也需要修改一下:

java 代码
  1. InitialContext ctx = new InitialContext();

1.1.2、通过系统变量设置JNDI
   
    我们可以使用如下的方式来实现这个功能:
   
    # java -Djava.naming.factory.initial=com.sun.jndi.ldap.LdapCtxFactory \
      -Djava.naming.provider.url=ldap://localhost:389/o=jnditutorial \
      List

1.1.3、Applet的方式设置JDNI
   
    看这个例子:

java 代码
  1. name=java.naming.factory.initial
  2. value=com.sun.jndi.ldap.LdapCtxFactory>
  3. name=java.naming.provider.url
  4. value=ldap://localhost:389/o=jnditutorial>

同时初始内容上下文也需要修改:

java 代码
  1. // Put this applet instance into the environment
  2. Hashtable env = new Hashtable();
  3. env.put(Context.APPLET, this);
  4. // Pass the environment to the initial context constructor
  5. Context ctx = new InitialContext(env);
  6. // List the objects
  7. NamingEnumeration enum = ctx.list(target);
  8. while (enum.hasMore()) {
  9. out.println(enum.next());
  10. }
  11. ctx.close();

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

java 代码
  1. // Initial environment with various properties
  2. Hashtable env = new Hashtable();
  3. env.put(Context.INITIAL_CONTEXT_FACTORY,
  4. "com.sun.jndi.fscontext.FSContextFactory");
  5. env.put(Context.PROVIDER_URL, "file:/");
  6. env.put(Context.OBJECT_FACTORIES, "foo.bar.ObjFactory");
  7. env.put("foo", "bar");
  8. // Call the constructor
  9. Context ctx = new InitialContext(env);
  10. // See what environment properties you have
  11. 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该怎么办?没关系,我们在下一个小结里介绍这个问题。
   
    1.3、定制使用SPI
   
    你可以通过一个SPI的属性文件,来为某一个SPI进行单独的设置。这个文件也是 .properties 文件。
    SPI的属性文件应该类似下面:
   
    前缀/jndiprovider.properties
   
    这个前缀是什么呢?就是这个SPI实现的内容上下文类(Context)的包的结构。
    例如,我们要使用的是 com.sun.jndi.ldap.LdapCtx 这个内容上下文,那么对应于它的属性配置
    文件就应该是:"com/sun/jndi/ldap/jndiprovider.properties"。
    一个应用可以使用多个这样的属性配置文件。
   
    那么我们为什么要使用属性配置文件?
    有两个原因。第一,我们可以单独的配置某一个命名/服务系统。第二,部署的时候会有用。例如,
    你可以单独配置一个针对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)。

转载于:https://www.cnblogs.com/aurawing/articles/1887036.html

jndi step by step(2)相关推荐

  1. Linux内核同步机制之(四):spin lock【转】

    转自:http://www.wowotech.net/kernel_synchronization/spinlock.html 一.前言 在linux kernel的实现中,经常会遇到这样的场景:共享 ...

  2. Linux 内核同步(二):自旋锁(Spinlock)

    自旋锁 内核当发生访问资源冲突的时候,可以有两种锁的解决方案选择: 一个是原地等待 一个是挂起当前进程,调度其他进程执行(睡眠) Spinlock 是内核中提供的一种比较常见的锁机制,自旋锁是&quo ...

  3. ElasticSearch:为中文分词器增加对英文的支持(让中文分词器可以处理中英文混合文档)(转)

    本文地址,需转载请注明出处: http://blog.csdn.net/hereiskxm/article/details/47441911 当我们使用中文分词器的时候,其实也希望它能够支持对于英文的 ...

  4. 自旋锁使用场景和实现分析(转载)

    自旋锁 最近看到的一篇文章,觉得写的很清晰,通过场景应用解答了我对自旋锁使用的一些疑问,推荐给大家. 引入问题: (1)如果cpu0持有锁,cpu1一直不释放锁怎么办? (2)什么场景下必须要用自旋锁 ...

  5. python入门(中)

    python入门(中) 一.简介 二.列表 1.列表的定义 2.列表的创建 3.向列表中添加元素 4.删除列表中的元素 5.获取列表中的元素 6.列表的常用操作符 7.列表的其它方法 三.元组 1.创 ...

  6. 机器学习 基础理论 学习笔记 (8)特征选择(feature selection)(一)

    0.说明 本文也许比较乱,请看目录再食用. 后续会 出文 机器学习 基础理论 学习笔记 (8)特征选择(feature selection)(二) 将 分类问题 和 回归问题 分开总结. 以及或将出文 ...

  7. 基于卷积神经网络的多目标图像检测研究(四)

    第4章 Faster R-CNN网络模型设计 4.1 Faster RCN文件结构介绍 本文在介绍具体的训练模型之前,先介绍Faster RCNN的文件结构.如图4.1所示,主要包括了7个文件夹和4个 ...

  8. 【第七周:Python(一)】7周成为数据分析师

    本课程共七个章节,课程地址:7周成为数据分析师(完结)_哔哩哔哩_bilibili 数据分析思维 业务知识 Excel 数据可视化 SQL 统计学 Python 第七周:Python(P86-P143 ...

  9. tf.estimator API技术手册(16)——自定义Estimator

    tf.estimator API技术手册(16)--自定义Estimator (一)前 言 (二)自定义estimator的一般步骤 (三)准备训练数据 (四)自定义estimator实践 (1)创建 ...

  10. NHibernate Step by Step:序篇 (转)

    原文出处:http://www.cnblogs.com/abluedog/archive/2006/04/15/375676.html 原文作者:abluedog 很久以前,你可能习惯了connect ...

最新文章

  1. 黑苹果没有找到触控板为什么还是能用_为什么Macbook触控板体验领先Windows那么多,却难以取代鼠标?...
  2. 《剑指offer》-斐波那契数列
  3. QgridLayout例子,在qlayout添加button实例(添加窗体过程类似)
  4. Mybatis 系列2-配置文件
  5. vue怎么获取用户的位置经纬度_vue 实现Web端的定位功能 获取经纬度
  6. 栈的应用--中缀表达式转后缀表达式
  7. java fx 重绘_如何重绘JAVA FX 2.2中的窗口(舞台)
  8. IEEE1588 ( PTP ) 协议简介
  9. 开源无国界,从openEuler Maintainer到Spark Committer的贡献开源之路
  10. MongoBD命令大全
  11. MMDetection3D 1.1:统一易用的 3D 感知平台
  12. 微信支付宝收款二维码还能用吗?权威解读
  13. JWT手动签发| 自动签发
  14. 大数据风控是在做什么?
  15. [Python] 相关性分析
  16. pes时处理PTS和DTS的方法
  17. 旋转接头出现故障的原因分享
  18. Linux 系统假死的解决方案
  19. 前端初学之利用html,css,js实现车牌摇号程序(一)
  20. EasyUI表单验证

热门文章

  1. webpack打包告警We noticed you‘re using the `useBuiltIns` option without declaring a core-js version.
  2. 162天,从华为外包5k转岗正式员工15k,心酸只有自己知道
  3. 英国脱离欧盟Brexit目前票数:脱欧阵营51.4%领先!!!
  4. 截止失真放大电路_【电子干货377】晶体三极管的一些常见应用电路
  5. 职业等级计算机操作员,计算机操作员是什么职业?
  6. unity游戏开发需要学什么?
  7. 微信小程序在线考试项目开发-用户授权登录、身份信息获取
  8. 如何设计高扩展的在线网页制作平台
  9. 软件测试工程师APUS一面二面汇总
  10. matlab心电显示,请问如何在GUI界面中打开并显示心电信号