﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>BlogJava-世事如棋-文章分类-Basic Java</title><link>http://www.blogjava.net/kingwell/category/10891.html</link><description>Aspire to Professionalism</description><language>zh-cn</language><lastBuildDate>Fri, 02 Mar 2007 06:41:38 GMT</lastBuildDate><pubDate>Fri, 02 Mar 2007 06:41:38 GMT</pubDate><ttl>60</ttl><item><title>Java Reflection</title><link>http://www.blogjava.net/kingwell/articles/51747.html</link><dc:creator>KingWell</dc:creator><author>KingWell</author><pubDate>Fri, 09 Jun 2006 09:37:00 GMT</pubDate><guid>http://www.blogjava.net/kingwell/articles/51747.html</guid><wfw:comment>http://www.blogjava.net/kingwell/comments/51747.html</wfw:comment><comments>http://www.blogjava.net/kingwell/articles/51747.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/kingwell/comments/commentRss/51747.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kingwell/services/trackbacks/51747.html</trackback:ping><description><![CDATA[Reflection 是 Java 程序开发语言的特征之一，它允许运行中的 Java 程序对自身进行检查，或者说“自审”，并能直接操作程序的内部属性。例如，使用它能获得 Java 类中各成员的名称并显示出来。<br /><br />Java 的这一能力在实际应用中也许用得不是很多，但是在其它的程序设计语言中根本就不存在这一特性。例如，Pascal、C 或者 C++ 中就没有办法在程序中获得函数定义相关的信息。<br /><br />JavaBean 是 reflection 的实际应用之一，它能让一些工具可视化的操作软件组件。这些工具通过 reflection 动态的载入并取得 Java 组件(类) 的属性。<br /><br /><br /><br />1. 一个简单的例子<br /><br />考虑下面这个简单的例子，让我们看看 reflection 是如何工作的。<br /><br />import java.lang.reflect.*;<br />public class DumpMethods {<br />    public static void main(String args[]) {<br />        try {<br />            Class c = Class.forName(args[0]);<br />            Method m[] = c.getDeclaredMethods();<br />            for (int i = 0; i &lt; m.length; i++)<br />                System.out.println(m[i].toString());<br />        } catch (Throwable e) {<br />            System.err.println(e);<br />        }<br />    }<br />}<br /><br />按如下语句执行：<br /><br />java DumpMethods java.util.Stack<br /><br />它的结果输出为：<br /><br />public java.lang.Object java.util.Stack.push(java.lang.Object)<br /><br />public synchronized java.lang.Object java.util.Stack.pop()<br /><br />public synchronized java.lang.Object java.util.Stack.peek()<br /><br />public boolean java.util.Stack.empty()<br /><br />public synchronized int java.util.Stack.search(java.lang.Object)<br /><br />这样就列出了java.util.Stack 类的各方法名以及它们的限制符和返回类型。<br /><br />这个程序使用 Class.forName 载入指定的类，然后调用 getDeclaredMethods 来获取这个类中定义了的方法列表。java.lang.reflect.Methods 是用来描述某个类中单个方法的一个类。<br /><br />2.开始使用 Reflection<br /><br />用于 reflection 的类，如 Method，可以在 java.lang.relfect 包中找到。使用这些类的时候必须要遵循三个步骤：第一步是获得你想操作的类的 java.lang.Class 对象。在运行中的 Java 程序中，用 java.lang.Class 类来描述类和接口等。<br /><br />下面就是获得一个 Class 对象的方法之一：<br /><br />Class c = Class.forName("java.lang.String");<br /><br />这条语句得到一个 String 类的类对象。还有另一种方法，如下面的语句：<br /><br />Class c = int.class;<br /><br />或者<br /><br />Class c = Integer.TYPE;<br /><br />它们可获得基本类型的类信息。其中后一种方法中访问的是基本类型的封装类 (如 Integer) 中预先定义好的 TYPE 字段。<br /><br />第二步是调用诸如 getDeclaredMethods 的方法，以取得该类中定义的所有方法的列表。<br /><br />一旦取得这个信息，就可以进行第三步了——使用 reflection API 来操作这些信息，如下面这段代码：<br /><br />Class c = Class.forName("java.lang.String");<br /><br />Method m[] = c.getDeclaredMethods();<br /><br />System.out.println(m[0].toString());<br /><br />它将以文本方式打印出 String 中定义的第一个方法的原型。<br /><br />在下面的例子中，这三个步骤将为使用 reflection 处理特殊应用程序提供例证。<br /><br />模拟 instanceof 操作符<br /><br />得到类信息之后，通常下一个步骤就是解决关于 Class 对象的一些基本的问题。例如，Class.isInstance 方法可以用于模拟 instanceof 操作符：<br /><br />class A {<br />}<br /><br />public class instance1 {<br />    public static void main(String args[]) {<br />        try {<br />            Class cls = Class.forName("A");<br />            boolean b1 = cls.isInstance(new Integer(37));<br />            System.out.println(b1);<br />            boolean b2 = cls.isInstance(new A());<br />            System.out.println(b2);<br />        } catch (Throwable e) {<br />            System.err.println(e);<br />        }<br />    }<br />}<br /><br />在这个例子中创建了一个 A 类的 Class 对象，然后检查一些对象是否是 A 的实例。Integer(37) 不是，但 new A() 是。<br /><br />3.找出类的方法<br /><br />找出一个类中定义了些什么方法，这是一个非常有价值也非常基础的 reflection 用法。下面的代码就实现了这一用法：<br /><br />import java.lang.reflect.*;<br /><br />public class method1 {<br />    private int f1(Object p, int x) throws NullPointerException {<br />        if (p == null)<br />            throw new NullPointerException();<br />        return x;<br />    }<br /><br />    public static void main(String args[]) {<br />        try {<br />            Class cls = Class.forName("method1");<br />            Method methlist[] = cls.getDeclaredMethods();<br />            for (int i = 0; i &lt; methlist.length; i++) {<br />                Method m = methlist[i];<br />                System.out.println("name = " + m.getName());<br />                System.out.println("decl class = " + m.getDeclaringClass());<br />                Class pvec[] = m.getParameterTypes();<br />                for (int j = 0; j &lt; pvec.length; j++)<br />                    System.out.println("param #" + j + " " + pvec[j]);<br />                Class evec[] = m.getExceptionTypes();<br />                for (int j = 0; j &lt; evec.length; j++)<br />                    System.out.println("exc #" + j + " " + evec[j]);<br />                System.out.println("return type = " + m.getReturnType());<br />                System.out.println("-----");<br />            }<br />        } catch (Throwable e) {<br />            System.err.println(e);<br />        }<br />    }<br />}<br /><br />这个程序首先取得 method1 类的描述，然后调用 getDeclaredMethods 来获取一系列的 Method 对象，它们分别描述了定义在类中的每一个方法，包括 public 方法、protected 方法、package 方法和 private 方法等。如果你在程序中使用 getMethods 来代替 getDeclaredMethods，你还能获得继承来的各个方法的信息。<br /><br />取得了 Method 对象列表之后，要显示这些方法的参数类型、异常类型和返回值类型等就不难了。这些类型是基本类型还是类类型，都可以由描述类的对象按顺序给出。<br /><br />输出的结果如下：<br /><br />name = f1<br /><br />decl class = class method1<br /><br />param #0 class java.lang.Object<br /><br />param #1 int<br /><br />exc #0 class java.lang.NullPointerException<br /><br />return type = int<br /><br />-----<br /><br />name = main<br /><br />decl class = class method1<br /><br />param #0 class [Ljava.lang.String;<br /><br />return type = void<br /><br />-----<br /><br /><br />4.获取构造器信息<br /><br />获取类构造器的用法与上述获取方法的用法类似，如：<br /><br />import java.lang.reflect.*;<br /><br />public class constructor1 {<br />    public constructor1() {<br />    }<br /><br />    protected constructor1(int i, double d) {<br />    }<br /><br />    public static void main(String args[]) {<br />        try {<br />            Class cls = Class.forName("constructor1");<br />            Constructor ctorlist[] = cls.getDeclaredConstructors();<br />            for (int i = 0; i &lt; ctorlist.length; i++) {<br />                Constructor ct = ctorlist[i];<br />                System.out.println("name = " + ct.getName());<br />                System.out.println("decl class = " + ct.getDeclaringClass());<br />                Class pvec[] = ct.getParameterTypes();<br />                for (int j = 0; j &lt; pvec.length; j++)<br />                    System.out.println("param #" + j + " " + pvec[j]);<br />                Class evec[] = ct.getExceptionTypes();<br />                for (int j = 0; j &lt; evec.length; j++)<br />                    System.out.println("exc #" + j + " " + evec[j]);<br />                System.out.println("-----");<br />            }<br />        } catch (Throwable e) {<br />            System.err.println(e);<br />        }<br />    }<br />}<br /><br />这个例子中没能获得返回类型的相关信息，那是因为构造器没有返回类型。<br /><br />这个程序运行的结果是：<br /><br />name = constructor1<br /><br />decl class = class constructor1<br /><br />-----<br /><br />name = constructor1<br /><br />decl class = class constructor1<br /><br />param #0 int<br /><br />param #1 double<br /><br />-----<br /><br />5.获取类的字段(域)<br /><br />找出一个类中定义了哪些数据字段也是可能的，下面的代码就在干这个事情：<br /><br /><br />import java.lang.reflect.*;<br /><br />public class field1 {<br />    private double d;<br />    public static final int i = 37;<br />    String s = "testing";<br /><br />    public static void main(String args[]) {<br />        try {<br />            Class cls = Class.forName("field1");<br />            Field fieldlist[] = cls.getDeclaredFields();<br />            for (int i = 0; i &lt; fieldlist.length; i++) {<br />                Field fld = fieldlist[i];<br />                System.out.println("name = " + fld.getName());<br />                System.out.println("decl class = " + fld.getDeclaringClass());<br />                System.out.println("type = " + fld.getType());<br />                int mod = fld.getModifiers();<br />                System.out.println("modifiers = " + Modifier.toString(mod));<br />                System.out.println("-----");<br />            }<br />        } catch (Throwable e) {<br />            System.err.println(e);<br />        }<br />    }<br />}<br /><br />这个例子和前面那个例子非常相似。例中使用了一个新东西 Modifier，它也是一个 reflection 类，用来描述字段成员的修饰语，如“private int”。这些修饰语自身由整数描述，而且使用 Modifier.toString 来返回以“官方”顺序排列的字符串描述 (如“static”在“final”之前)。这个程序的输出是：<br /><br />name = d<br /><br />decl class = class field1<br /><br />type = double<br /><br />modifiers = private<br /><br />-----<br /><br />name = i<br /><br />decl class = class field1<br /><br />type = int<br /><br />modifiers = public static final<br /><br />-----<br /><br />name = s<br /><br />decl class = class field1<br /><br />type = class java.lang.String<br /><br />modifiers =<br /><br />-----<br /><br />和获取方法的情况一下，获取字段的时候也可以只取得在当前类中申明了的字段信息 (getDeclaredFields)，或者也可以取得父类中定义的字段 (getFields) 。<br /><br /><br />6.根据方法的名称来执行方法<br /><br />文本到这里，所举的例子无一例外都与如何获取类的信息有关。我们也可以用 reflection 来做一些其它的事情，比如执行一个指定了名称的方法。下面的示例演示了这一操作：<br /><br />import java.lang.reflect.*;<br />public class method2 {<br />    public int add(int a, int b) {<br />        return a + b;<br />    }<br />    public static void main(String args[]) {<br />        try {<br />            Class cls = Class.forName("method2");<br />            Class partypes[] = new Class[2];<br />            partypes[0] = Integer.TYPE;<br />            partypes[1] = Integer.TYPE;<br />            Method meth = cls.getMethod("add", partypes);<br />            method2 methobj = new method2();<br />            Object arglist[] = new Object[2];<br />            arglist[0] = new Integer(37);<br />            arglist[1] = new Integer(47);<br />            Object retobj = meth.invoke(methobj, arglist);<br />            Integer retval = (Integer) retobj;<br />            System.out.println(retval.intValue());<br />        } catch (Throwable e) {<br />            System.err.println(e);<br />        }<br />    }<br />}<br /><br />假如一个程序在执行的某处的时候才知道需要执行某个方法，这个方法的名称是在程序的运行过程中指定的 (例如，JavaBean 开发环境中就会做这样的事)，那么上面的程序演示了如何做到。<br /><br />上例中，getMethod 用于查找一个具有两个整型参数且名为 add 的方法。找到该方法并创建了相应的 Method 对象之后，在正确的对象实例中执行它。执行该方法的时候，需要提供一个参数列表，这在上例中是分别包装了整数 37 和 47 的两个 Integer 对象。执行方法的返回的同样是一个 Integer 对象，它封装了返回值 84。<br /><br />7.创建新的对象<br /><br />对于构造器，则不能像执行方法那样进行，因为执行一个构造器就意味着创建了一个新的对象 (准确的说，创建一个对象的过程包括分配内存和构造对象)。所以，与上例最相似的例子如下：<br /><br />import java.lang.reflect.*;<br /><br />public class constructor2 {<br />    public constructor2() {<br />    }<br /><br />    public constructor2(int a, int b) {<br />        System.out.println("a = " + a + " b = " + b);<br />    }<br /><br />    public static void main(String args[]) {<br />        try {<br />            Class cls = Class.forName("constructor2");<br />            Class partypes[] = new Class[2];<br />            partypes[0] = Integer.TYPE;<br />            partypes[1] = Integer.TYPE;<br />            Constructor ct = cls.getConstructor(partypes);<br />            Object arglist[] = new Object[2];<br />            arglist[0] = new Integer(37);<br />            arglist[1] = new Integer(47);<br />            Object retobj = ct.newInstance(arglist);<br />        } catch (Throwable e) {<br />            System.err.println(e);<br />        }<br />    }<br />}<br /><br />根据指定的参数类型找到相应的构造函数并执行它，以创建一个新的对象实例。使用这种方法可以在程序运行时动态地创建对象，而不是在编译的时候创建对象，这一点非常有价值。<br /><br />8.改变字段(域)的值<br /><br />reflection 的还有一个用处就是改变对象数据字段的值。reflection 可以从正在运行的程序中根据名称找到对象的字段并改变它，下面的例子可以说明这一点：<br /><br />import java.lang.reflect.*;<br /><br />public class field2 {<br />    public double d;<br /><br />    public static void main(String args[]) {<br />        try {<br />            Class cls = Class.forName("field2");<br />            Field fld = cls.getField("d");<br />            field2 f2obj = new field2();<br />            System.out.println("d = " + f2obj.d);<br />            fld.setDouble(f2obj, 12.34);<br />            System.out.println("d = " + f2obj.d);<br />        } catch (Throwable e) {<br />            System.err.println(e);<br />        }<br />    }<br />}<br /><br />这个例子中，字段 d 的值被变为了 12.34。<br /><br />9.使用数组<br /><br />本文介绍的 reflection 的最后一种用法是创建的操作数组。数组在 Java 语言中是一种特殊的类类型，一个数组的引用可以赋给 Object 引用。观察下面的例子看看数组是怎么工作的：<br /><br />import java.lang.reflect.*;<br /><br />public class array1 {<br />    public static void main(String args[]) {<br />        try {<br />            Class cls = Class.forName("java.lang.String");<br />            Object arr = Array.newInstance(cls, 10);<br />            Array.set(arr, 5, "this is a test");<br />            String s = (String) Array.get(arr, 5);<br />            System.out.println(s);<br />        } catch (Throwable e) {<br />            System.err.println(e);<br />        }<br />    }<br />}<br /><br />例中创建了 10 个单位长度的 String 数组，为第 5 个位置的字符串赋了值，最后将这个字符串从数组中取得并打印了出来。<br /><br />下面这段代码提供了一个更复杂的例子：<br /><br />import java.lang.reflect.*;<br /><br />public class array2 {<br />    public static void main(String args[]) {<br />        int dims[] = new int[]{5, 10, 15};<br />        Object arr = Array.newInstance(Integer.TYPE, dims);<br />        Object arrobj = Array.get(arr, 3);<br />        Class cls = arrobj.getClass().getComponentType();<br />        System.out.println(cls);<br />        arrobj = Array.get(arrobj, 5);<br />        Array.setInt(arrobj, 10, 37);<br />        int arrcast[][][] = (int[][][]) arr;<br />        System.out.println(arrcast[3][5][10]);<br />    }<br />}<br />例中创建了一个 5 x 10 x 15 的整型数组，并为处于 [3][5][10] 的元素赋了值为 37。注意，多维数组实际上就是数组的数组，例如，第一个 Array.get 之后，arrobj 是一个 10 x 15 的数组。进而取得其中的一个元素，即长度为 15 的数组，并使用 Array.setInt 为它的第 10 个元素赋值。<br /><br />注意创建数组时的类型是动态的，在编译时并不知道其类型。<br /><br /><br /><br />作者Blog：http://blog.csdn.net/leek2000/ <img src ="http://www.blogjava.net/kingwell/aggbug/51747.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kingwell/" target="_blank">KingWell</a> 2006-06-09 17:37 <a href="http://www.blogjava.net/kingwell/articles/51747.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>ldap介绍</title><link>http://www.blogjava.net/kingwell/articles/46269.html</link><dc:creator>KingWell</dc:creator><author>KingWell</author><pubDate>Mon, 15 May 2006 11:18:00 GMT</pubDate><guid>http://www.blogjava.net/kingwell/articles/46269.html</guid><wfw:comment>http://www.blogjava.net/kingwell/comments/46269.html</wfw:comment><comments>http://www.blogjava.net/kingwell/articles/46269.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/kingwell/comments/commentRss/46269.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kingwell/services/trackbacks/46269.html</trackback:ping><description><![CDATA[之前做过一个project是包含ldap体系的，不过由于ldap模块非本人负责，对ldap也就处于一种模模糊糊的状态。以下为一篇ldap介绍。<br />－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－<br /><br />介绍LDAP<br />原文：http://ldapman.org/articles/intro_to_ldap.html<br />原文作者：Michael Donnelly <br />翻译：Brimmer<br />如果你在计算机行业工作，那么对LDAP可能早有耳闻了。想深入地了解LDAP吗？那么可以好好地读一下这篇文章。这篇介绍性的文章是一系列介绍如何在企业中设计、实现和集成LDAP环境的文章的头一篇。主要是先让你熟悉一下LDAP的基本概念，那些比较困难的细节问题将放到以后讨论。在这篇文章中我们将要介绍：<br />什么是LDAP?<br />什么时候该用LDAP存储数据？<br />LDAP目录树的结构<br />单独的LDAP记录<br />作为例子的一个单独的数据项<br />LDAP复制<br />安全和访问控制<br />现在LDAP技术不仅发展得很快而且也是激动人心的。在企业范围内实现LDAP可以让运行在几乎所有计算机平台上的所有的应用程序从LDAP目录中获取信息。LDAP目录中可以存储各种类型的数据：电子邮件地址、邮件路由信息、人力资源数据、公用密匙、联系人列表，等等。通过把LDAP目录作为系统集成中的一个重要环节，可以简化员工在企业内部查询信息的步骤，甚至连主要的数据源都可以放在任何地方。如果Oracle、Sybase、Informix或Microsoft SQL数据库中已经存储了类似的数据，那么LDAP和这些数据库到底有什么不同呢？是什么让它更具优势？请继续读下去吧！<br />什么是LDAP?<br />LDAP的英文全称是Lightweight Directory Access Protocol，一般都简称为LDAP。它是基于X.500标准的，但是简单多了并且可以根据需要定制。与X.500不同，LDAP支持TCP/IP，这对访问Internet是必须的。LDAP的核心规范在RFC中都有定义，所有与LDAP相关的RFC都可以在LDAPman RFC网页中找到。<br />怎么使用LDAP这个术语呢？<br />在日常交谈中，你可能会听到有些人这么说：“我们要把那些东西存在LDAP中吗？”，或者“从LDAP数据库中取出那些数据！”，又或者“我们怎么把LDAP和关系型数据库集成在一起？”。严格地说，LDAP根本不是数据库而是用来访问存储在信息目录（也就是LDAP目录）中的信息的协议。更为确切和正式的说法应该是象这样的：“通过使用LDAP，可以在信息目录的正确位置读取（或存储）数据”。但是，也没有必要吹毛求疵，尽管表达得不够准确，我们也都知道对方在说什么。<br />LDAP目录是数据库吗？<br />就象Sybase、Oracle、Informix或Microsoft的数据库管理系统（DBMS）是用于处理查询和更新关系型数据库那样，LDAP服务器也是用来处理查询和更新LDAP目录的。换句话来说LDAP目录也是一种类型的数据库，但是不是关系型数据库。不象被设计成每分钟需要处理成百上千条数据变化的数据库，例如：在电子商务中经常用到的在线交易处理（OLTP）系统，LDAP主要是优化数据读取的性能。<br />LDAP目录的优势<br />现在该说说LDAP目录到底有些什么优势了。现在LDAP的流行是很多因数共同作用的结果。我在这里说的不过是一些基本的原因，请你注意一下这不过是一小部分原因。<br />可能LDAP最大的优势是：可以在任何计算机平台上，用很容易获得的而且数目不断增加的LDAP的客户端程序访问LDAP目录。而且也很容易定制应用程序为它加上LDAP的支持。<br />LDAP协议是跨平台的和标准的协议，因此应用程序就不用为LDAP目录放在什么样的服务器上操心了。实际上，LDAP得到了业界的广泛认可，因为它是Internet的标准。产商都很愿意在产品中加入对LDAP的支持，因为他们根本不用考虑另一端（客户端或服务端）是怎么样的。LDAP服务器可以是任何一个开发源代码或商用的LDAP目录服务器（或者还可能是具有LDAP界面的关系型数据库），因为可以用同样的协议、客户端连接软件包和查询命令与LDAP服务器进行交互。与LDAP不同的是，如果软件产商想在软件产品中集成对DBMS的支持，那么通常都要对每一个数据库服务器单独定制。<br />不象很多商用的关系型数据库，你不必为LDAP的每一个客户端连接或许可协议付费。<br />大多数的LDAP服务器安装起来很简单，也容易维护和优化。<br />LDAP服务器可以用“推”或“拉”的方法复制部分或全部数据，例如：可以把数据“推”到远程的办公室，以增加数据的安全性。复制技术是内置在LDAP服务器中的而且很容易配置。如果要在DBMS中使用相同的复制功能，数据库产商就会要你支付额外的费用，而且也很难管理。<br />LDAP允许你根据需要使用ACI（一般都称为ACL或者访问控制列表）控制对数据读和写的权限。例如，设备管理员可以有权改变员工的工作地点和办公室号码，但是不允许改变记录中其它的域。ACI可以根据谁访问数据、访问什么数据、数据存在什么地方以及其它对数据进行访问控制。因为这些都是由LDAP目录服务器完成的，所以不用担心在客户端的应用程序上是否要进行安全检查。<br />LDAP对于这样存储这样的信息最为有用，也就是数据需要从不同的地点读取，但是不需要经常更新。例如，这些信息存储在LDAP目录中是十分有效的：<br />l        公司员工的电话号码簿和组织结构图<br />l        客户的联系信息<br />l        计算机管理需要的信息，包括NIS映射、email假名，等等<br />l        软件包的配置信息<br />l        公用证书和安全密匙<br />什么时候该用LDAP存储数据？<br />大多数的LDAP服务器都为读密集型的操作进行专门的优化。因此，当从LDAP服务器中读取数据的时候会比从专门为OLTP优化的关系型数据库中读取数据快一个数量级。也是因为专门为读的性能进行优化，大多数的LDAP目录服务器并不适合存储需要需要经常改变的数据。例如，用LDAP服务器来存储电话号码是一个很好的选择，但是它不能作为电子商务站点的数据库服务器。<br />如果下面每一个问题的答案都是“是”，那么把数据存在LDAP中就是一个好主意。<br />l        需要在任何平台上都能读取数据吗？<br />l        每一个单独的记录项是不是每一天都只有很少的改变？<br />l        可以把数据存在平面数据库（flat database）而不是关系型数据库中吗？换句话来说，也就是不管什么范式不范式的，把所有东西都存在一个记录中（差不多只要满足第一范式）。<br />最后一个问题可能会唬住一些人，其实用平面数据库去存储一些关系型的数据也是很一般的。例如，一条公司员工的记录就可以包含经理的登录名。用LDAP来存储这类信息是很方便的。一个简单的判断方法：如果可以把保数据存在一张张的卡片里，就可以很容易地把它存在LDAP目录里。<br />LDAP目录树的结构<br />LDAP目录以树状的层次结构来存储数据。如果你对自顶向下的DNS树或UNIX文件的目录树比较熟悉，也就很容易掌握LDAP目录树这个概念了。就象DNS的主机名那样，LDAP目录记录的标识名（Distinguished Name，简称DN）是用来读取单个记录，以及回溯到树的顶部。后面会做详细地介绍。<br />为什么要用层次结构来组织数据呢？原因是多方面的。下面是可能遇到的一些情况：<br />l        如果你想把所有的美国客户的联系信息都“推”到位于到西雅图办公室（负责营销）的LDAP服务器上，但是你不想把公司的资产管理信息“推”到那里。<br />l        你可能想根据目录树的结构给予不同的员工组不同的权限。在下面的例子里，资产管理组对“asset-mgmt”部分有完全的访问权限，但是不能访问其它地方。<br />l        把LDAP存储和复制功能结合起来，可以定制目录树的结构以降低对WAN带宽的要求。位于西雅图的营销办公室需要每分钟更新的美国销售状况的信息，但是欧洲的销售情况就只要每小时更新一次就行了。<br />刨根问底：基准DN<br />LDAP目录树的最顶部就是根，也就是所谓的“基准DN”。基准DN通常使用下面列出的三种格式之一。假定我在名为FooBar的电子商务公司工作，这家公司在Internet上的名字是foobar.com。<br />o="FooBar, Inc.", c=US <br />（以X.500格式表示的基准DN）<br />在这个例子中，o=FooBar, Inc. 表示组织名，在这里就是公司名的同义词。c=US 表示公司的总部在美国。以前，一般都用这种方式来表示基准DN。但是事物总是在不断变化的，现在所有的公司都已经（或计划）上Internet上。随着Internet的全球化，在基准DN中使用国家代码很容易让人产生混淆。现在，X.500格式发展成下面列出的两种格式。<br />o=foobar.com<br />（用公司的Internet地址表示的基准DN）<br />这种格式很直观，用公司的域名作为基准DN。这也是现在最常用的格式。<br />dc=foobar, dc=com<br />（用DNS域名的不同部分组成的基准DN）<br />就象上面那一种格式，这种格式也是以DNS域名为基础的，但是上面那种格式不改变域名（也就更易读），而这种格式把域名：foobar.com分成两部分 dc=foobar, dc=com。在理论上，这种格式可能会更灵活一点，但是对于最终用户来说也更难记忆一点。考虑一下foobar.com这个例子。当foobar.com和gizmo.com合并之后，可以简单的把“dc=com”当作基准DN。把新的记录放到已经存在的dc=gizmo, dc=com目录下，这样就简化了很多工作（当然，如果foobar.com和wocket.edu合并，这个方法就不能用了）。如果LDAP服务器是新安装的，我建议你使用这种格式。再请注意一下，如果你打算使用活动目录（Actrive Directory），Microsoft已经限制你必须使用这种格式。<br />更上一层楼：在目录树中怎么组织数据<br />在UNIX文件系统中，最顶层是根目录（root）。在根目录的下面有很多的文件和目录。象上面介绍的那样，LDAP目录也是用同样的方法组织起来的。<br />在根目录下，要把数据从逻辑上区分开。因为历史上（X.500）的原因，大多数LDAP目录用OU从逻辑上把数据分开来。OU表示“Organization Unit”，在X.500协议中是用来表示公司内部的机构：销售部、财务部，等等。现在LDAP还保留ou=这样的命名规则，但是扩展了分类的范围，可以分类为：ou=people, ou=groups, ou=devices，等等。更低一级的OU有时用来做更细的归类。例如：LDAP目录树（不包括单独的记录）可能会是这样的：<br />    dc=foobar, dc=com <br />        ou=customers <br />            ou=asia <br />            ou=europe <br />            ou=usa <br />        ou=employees <br />        ou=rooms <br />        ou=groups <br />        ou=assets-mgmt <br />        ou=nisgroups <br />        ou=recipes<br />单独的LDAP记录<br />DN是LDAP记录项的名字<br />在LDAP目录中的所有记录项都有一个唯一的“Distinguished Name”，也就是DN。每一个LDAP记录项的DN是由两个部分组成的：相对DN（RDN）和记录在LDAP目录中的位置。<br />RDN是DN中与目录树的结构无关的部分。在LDAP目录中存储的记录项都要有一个名字，这个名字通常存在cn（Common Name）这个属性里。因为几乎所有的东西都有一个名字，在LDAP中存储的对象都用它们的cn值作为RDN的基础。如果我把最喜欢的吃燕麦粥食谱存为一个记录，我就会用cn=Oatmeal Deluxe作为记录项的RDN。<br />l         我的LDAP目录的基准DN是dc=foobar,dc=com<br />l         我把自己的食谱作为LDAP的记录项存在ou=recipes<br />l        我的LDAP记录项的RDN设为cn=Oatmeal Deluxe<br />上面这些构成了燕麦粥食谱的LDAP记录的完整DN。记住，DN的读法和DNS主机名类似。下面就是完整的DN：<br />cn=Oatmeal Deluxe,ou=recipes,dc=foobar,dc=com<br />举一个实际的例子来说明DN<br />现在为公司的员工设置一个DN。可以用基于cn或uid（User ID），作为典型的用户帐号。例如，FooBar的员工Fran Smith（登录名：fsmith）的DN可以为下面两种格式：<br />uid=fsmith,ou=employees,dc=foobar,dc=com<br />（基于登录名）<br />LDAP（以及X.500）用uid表示“User ID”，不要把它和UNIX的uid号混淆了。大多数公司都会给每一个员工唯一的登录名，因此用这个办法可以很好地保存员工的信息。你不用担心以后还会有一个叫Fran Smith的加入公司，如果Fran改变了她的名字（结婚？离婚？或宗教原因？），也用不着改变LDAP记录项的DN。<br />cn=Fran Smith,ou=employees,dc=foobar,dc=com<br />（基于姓名）<br />可以看到这种格式使用了Common Name（CN）。可以把Common Name当成一个人的全名。这种格式有一个很明显的缺点就是：如果名字改变了，LDAP的记录就要从一个DN转移到另一个DN。但是，我们应该尽可能地避免改变一个记录项的DN。<br />定制目录的对象类型<br />你可以用LDAP存储各种类型的数据对象，只要这些对象可以用属性来表示，下面这些是可以在LDAP中存储的一些信息：<br />l        员工信息：员工的姓名、登录名、口令、员工号、他的经理的登录名，邮件服务器，等等。<br />l        物品跟踪信息：计算机名、IP地址、标签、型号、所在位置，等等。<br />l        客户联系列表：客户的公司名、主要联系人的电话、传真和电子邮件，等等。<br />l        会议厅信息：会议厅的名字、位置、可以坐多少人、电话号码、是否有投影机。<br />l        食谱信息：菜的名字、配料、烹调方法以及准备方法。<br />因为LDAP目录可以定制成存储任何文本或二进制数据，到底存什么要由你自己决定。LDAP目录用对象类型（object classes）的概念来定义运行哪一类的对象使用什么属性。在几乎所有的LDAP服务器中，你都要根据自己的需要扩展基本的LDAP目录的功能，创建新的对象类型或者扩展现存的对象类型。<br />LDAP目录以一系列“属性对”的形式来存储记录项，每一个记录项包括属性类型和属性值（这与关系型数据库用行和列来存取数据有根本的不同）。下面是我存在LDAP目录中的一部分食谱记录：<br />  dn: cn=Oatmeal Deluxe, ou=recipes, dc=foobar, dc=com <br />  cn: Instant Oatmeal Deluxe <br />  recipeCuisine: breakfast <br />  recipeIngredient: 1 packet instant oatmeal <br />  recipeIngredient: 1 cup water <br />  recipeIngredient: 1 pinch salt <br />  recipeIngredient: 1 tsp brown sugar <br />  recipeIngredient: 1/4 apple, any type<br />请注意上面每一种配料都作为属性recipeIngredient值。LDAP目录被设计成象上面那样为一个属性保存多个值的，而不是在每一个属性的后面用逗号把一系列值分开。<br />因为用这样的方式存储数据，所以数据库就有很大的灵活性，不必为加入一些新的数据就重新创建表和索引。更重要的是，LDAP目录不必花费内存或硬盘空间处理“空”域，也就是说，实际上不使用可选择的域也不会花费你任何资源。<br />作为例子的一个单独的数据项<br />让我们看看下面这个例子。我们用Foobar, Inc.的员工Fran Smith的LDAP记录。这个记录项的格式是LDIF，用来导入和导出LDAP目录的记录项。<br />  dn: uid=fsmith, ou=employees, dc=foobar, dc=com<br />  objectclass: person<br />  objectclass: organizationalPerson<br />  objectclass: inetOrgPerson<br />  objectclass: foobarPerson<br />  uid: fsmith<br />  givenname: Fran<br />  sn: Smith<br />  cn: Fran Smith<br />  cn: Frances Smith<br />  telephonenumber: 510-555-1234<br />  roomnumber: 122G<br />  o: Foobar, Inc.<br />  mailRoutingAddress: fsmith@foobar.com<br />  mailhost: mail.foobar.com<br />  userpassword: {crypt}3x1231v76T89N<br />  uidnumber: 1234<br />  gidnumber: 1200<br />  homedirectory: /home/fsmith<br />  loginshell: /usr/local/bin/bash<br />属性的值在保存的时候是保留大小写的，但是在默认情况下搜索的时候是不区分大小写的。某些特殊的属性（例如，password）在搜索的时候需要区分大小写。<br />让我们一点一点地分析上面的记录项。<br />dn: uid=fsmith, ou=employees, dc=foobar, dc=com<br />这是Fran的LDAP记录项的完整DN，包括在目录树中的完整路径。LDAP（和X.500）使用uid（User ID），不要把它和UNIX的uid号混淆了。<br />  objectclass: person <br />  objectclass: organizationalPerson <br />  objectclass: inetOrgPerson <br />  objectclass: foobarPerson<br />可以为任何一个对象根据需要分配多个对象类型。person对象类型要求cn（common name）和sn（surname）这两个域不能为空。persion对象类型允许有其它的可选域，包括givenname、telephonenumber，等等。organizational Person给person加入更多的可选域，inetOrgPerson又加入更多的可选域（包括电子邮件信息）。最后，foobarPerson是为Foobar定制的对象类型，加入了很多定制的属性。<br />  uid: fsmith <br />  givenname: Fran <br />  sn: Smith <br />  cn: Fran Smith <br />  cn: Frances Smith <br />  telephonenumber: 510-555-1234 <br />  roomnumber: 122G <br />  o: Foobar, Inc.<br />以前说过了，uid表示User ID。当看到uid的时候，就在脑袋里想一想“login”。<br />请注意CN有多个值。就象上面介绍的，LDAP允许某些属性有多个值。为什么允许有多个值呢？假定你在用公司的LDAP服务器查找Fran的电话号码。你可能只知道她的名字叫Fran，但是对人力资源处的人来说她的正式名字叫做Frances。因为保存了她的两个名字，所以用任何一个名字检索都可以找到Fran的电话号码、电子邮件和办公房间号，等等。<br />  mailRoutingAddress: fsmith@foobar.com <br />  mailhost: mail.foobar.com<br />就象现在大多数的公司都上网了，Foobar用Sendmail发送邮件和处理外部邮件路由信息。Foobar把所有用户的邮件信息都存在LDAP中。最新版本的Sendmail支持这项功能。<br />  Userpassword: {crypt}3x1231v76T89N <br />  uidnumber: 1234 <br />  gidnumber: 1200 <br />  gecos: Frances Smith <br />  homedirectory: /home/fsmith <br />  loginshell: /usr/local/bin/bash<br />注意，Foobar的系统管理员把所有用户的口令映射信息也都存在LDAP中。FoobarPerson类型的对象具有这种能力。再注意一下，用户口令是用UNIX的口令加密格式存储的。UNIX的uid在这里为uidnumber。提醒你一下，关于如何在LDAP中保存NIS信息，有完整的一份RFC。在以后的文章中我会谈一谈NIS的集成。<br />LDAP复制 <img src ="http://www.blogjava.net/kingwell/aggbug/46269.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kingwell/" target="_blank">KingWell</a> 2006-05-15 19:18 <a href="http://www.blogjava.net/kingwell/articles/46269.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>JDK1.5枚举类型</title><link>http://www.blogjava.net/kingwell/articles/45794.html</link><dc:creator>KingWell</dc:creator><author>KingWell</author><pubDate>Fri, 12 May 2006 02:54:00 GMT</pubDate><guid>http://www.blogjava.net/kingwell/articles/45794.html</guid><wfw:comment>http://www.blogjava.net/kingwell/comments/45794.html</wfw:comment><comments>http://www.blogjava.net/kingwell/articles/45794.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/kingwell/comments/commentRss/45794.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kingwell/services/trackbacks/45794.html</trackback:ping><description><![CDATA[Enum作为Sun全新引进的一个关键字，看起来很象是特殊的class, 它也可以有自己的变量，可以定义自己的方法，可以实现一个或者多个接口。 当我们在声明一个enum类型时，我们应该注意到enum类型有如下的一些特征。<br /><br />　　1．它不能有public的<a class="bluekey" href="http://www.yesky.com/key/3379/193379.html" target="_blank">构造函数</a>，这样做可以保证客户代码没有办法新建一个enum的实例。<br /><br />　　2．所有枚举值都是public , static , final的。注意这一点只是针对于枚举值，我们可以和在普通类里面定义 变量一样定义其它任何类型的非枚举变量，这些变量可以用任何你想用的修饰符。<br /><br />　　3．Enum默认实现了java.lang.Comparable接口。<br /><br />　　4．Enum覆载了了<a class="bluekey" href="http://www.yesky.com/key/2245/207245.html" target="_blank">toString</a>方法，因此我们如果调用Color.Blue.toString()默认返回字符串”Blue”.<br /><br />　　5．Enum提供了一个valueOf方法，这个方法和toString方法是相对应的。调用valueOf(“Blue”)将返回Color.Blue.因此我们在自己重写toString方法的时候就要注意到这一点，一把来说应该相对应地重写valueOf方法。<br /><br />　　6．Enum还提供了values方法，这个方法使你能够方便的遍历所有的枚举值。<br /><br />　　7．Enum还有一个oridinal的方法，这个方法返回枚举值在枚举类种的顺序，这个顺序根据枚举值声明的顺序而定，这里Color.Red.ordinal()返回0。<br /><br />　　了解了这些基本特性，我们来看看如何使用它们。<br /><br />　　1．遍历所有有枚举值. 知道了有values方法，我们可以轻车熟路地用ForEach循环来遍历了枚举值了。<br /><br />for (Color c: Color.values())<br />System.out.println(“<a class="bluekey" href="http://www.yesky.com/key/2359/212359.html" target="_blank">find</a> value:” + c);<br /><br />　　2．在enum中定义方法和变量，比如我们可以为Color增加一个方法随机返回一个颜色。<br /><br /><table bordercolor="#ffcc66" width="90%" align="center" bgcolor="#e6e4dd" border="1"><tbody><tr><td>public enum Color {<br />　Red,<br />　Green,<br />　Blue; <br /><br />　/*<br />　*定义一个变量表示枚举值的数目。<br />　*(我有点奇怪为什么sun没有给enum直接提供一个size方法).<br />　*/<br />　private static int number = Color.values().length ;<br /><br />　/**<br />　* 随机返回一个枚举值<br />　@return a random enum value. <br />　*/<br />　public static Color getRandomColor(){<br />　　long random = System.currentTimeMillis() % number;<br />　　switch ((int) random){<br />　　　case 0:<br />　　　　return Color.Red;<br />　　　case 1:<br />　　　　return Color.Green;<br />　　　case 2:<br />　　　　return Color.Blue;<br />　　　default : return Color.Red;<br />　　} <br />　}<br />}</td></tr></tbody></table><br />　　可以看出这在<a class="bluekey" href="http://www.yesky.com/key/3353/193353.html" target="_blank">枚举类型</a>里定义变量和方法和在普通类里面定义方法和变量没有什么区别。唯一要注意的只是变量和方法定义必须放在所有枚举值定义的后面，否则编译器会给出一个错误。<br /><br />　　3．覆载(Override)toString, valueOf方法<br /><br />　　前面我们已经知道enum提供了toString,valueOf等方法，很多时候我们都需要覆载默认的toString方法，那么对于enum我们怎么做呢。其实这和覆载一个普通class的toString方法没有什么区别。<br /><br /><table bordercolor="#ffcc66" width="90%" align="center" bgcolor="#e6e4dd" border="1"><tbody><tr><td>….<br />public String toString(){ <br />　switch (this){<br />　　case Red:<br />　　　return "Color.Red";<br />　　case Green:<br />　　　return "Color.Green";<br />　　case Blue:<br />　　　return "Color.Blue";<br />　　default:<br />　　　return "Unknow Color";<br />　}<br />}<br />….</td></tr></tbody></table><br />　　这时我们可以看到，此时再用前面的遍历代码打印出来的是<br /><br />Color.Red<br />Color.Green<br />Color.Blue<br /><br />　　而不是<br /><br />Red<br />Green<br />Blue.<br /><br />　　可以看到toString确实是被覆载了。一般来说在覆载toString的时候我们同时也应该覆载valueOf方法，以保持它们相互的一致性。<br /><br />　　4．使用构造函数<br /><br />　　虽然enum不可以有public的构造函数，但是我们还是可以定义private的构造函数，在enum内部使用。还是用Color这个例子。<br /><br /><table bordercolor="#ffcc66" width="90%" align="center" bgcolor="#e6e4dd" border="1"><tbody><tr><td>public enum Color {<br />　Red("This is Red"),<br />　Green("This is Green"),<br />　Blue("This is Blue"); <br /><br />　private String desc;<br /><br />　Color(String desc){<br />　　this.desc = desc;<br />　}<br /><br />　public String getDesc(){<br />　　return this.desc;<br />　}<br /><br />}</td></tr></tbody></table><br />　　这里我们为每一个颜色提供了一个说明信息, 然后定义了一个构造函数接受这个说明信息。<br /><br />　　要注意这里构造函数不能为public或者protected, 从而保证构造函数只能在内部使用，客户代码不能new一个枚举值的实例出来。这也是完全符合情理的，因为我们知道枚举值是public static final的常量而已。<br /><br />　　5．实现特定的接口<br /><br />　　我们已经知道enum可以定义变量和方法，它要实现一个接口也和普通class实现一个接口一样，这里就不作示例了。<br /><br />　　6．定义枚举值自己的方法。<br /><br />　　前面我们看到可以为enum定义一些方法，其实我们甚至可以为每一个枚举值定义方法。这样，我们前面覆载 toString的例子可以被改写成这样。<br /><br /><table bordercolor="#ffcc66" width="90%" align="center" bgcolor="#e6e4dd" border="1"><tbody><tr><td>public enum Color {<br />　Red { <br />　　public String toString(){<br />　　　return "Color.Red";<br />　　}<br />　},<br />　Green {<br />　　public String toString(){<br />　　　return "Color.Green";<br />　　}<br />　},<br />　Blue{<br />　　public String toString(){<br />　　　return "Color.Blue";<br />　　}<br />　}; <br />}</td></tr></tbody></table><br />　　从逻辑上来说这样比原先提供一个“全局“的toString方法要清晰一些。<br /><br />　　总的来说，enum作为一个全新定义的类型，是希望能够帮助程序员写出的代码更加<a class="bluekey" href="http://www.yesky.com/key/4779/189779.html" target="_blank">简单易懂</a>，个人觉得一般也不需要过多的使用enum的一些高级特性，否则就和简单易懂的初衷想违背了。<br /><br />（以上代码在win2k+ 1.5.0-beta2-b51下运行通过） <br /><img src ="http://www.blogjava.net/kingwell/aggbug/45794.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kingwell/" target="_blank">KingWell</a> 2006-05-12 10:54 <a href="http://www.blogjava.net/kingwell/articles/45794.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Java中ThreadLocal的设计与使用</title><link>http://www.blogjava.net/kingwell/articles/45163.html</link><dc:creator>KingWell</dc:creator><author>KingWell</author><pubDate>Tue, 09 May 2006 02:55:00 GMT</pubDate><guid>http://www.blogjava.net/kingwell/articles/45163.html</guid><wfw:comment>http://www.blogjava.net/kingwell/comments/45163.html</wfw:comment><comments>http://www.blogjava.net/kingwell/articles/45163.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/kingwell/comments/commentRss/45163.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kingwell/services/trackbacks/45163.html</trackback:ping><description><![CDATA[早在Java 1.2推出之时，Java平台中就引入了一个新的支持：java.lang.ThreadLocal，给我们在编写多线程程序时提供了一种新的选择。使用这个工具类可以很简洁地编写出优美的多线程程序，虽然ThreadLocal非常有用，但是似乎现在了解它、使用它的朋友还不多。 <br /><br />　　 <strong>ThreadLocal是什么<br /></strong><br />　　 ThreadLocal是什么呢？其实ThreadLocal并非是一个线程的本地实现版本，它并不是一个Thread，而是thread local variable（线程局部变量）。也许把它命名为ThreadLocalVar更加合适。线程局部变量（ThreadLocal）其实的功用非常简单，就是为每一个使用该变量的线程都提供一个变量值的副本，是每一个线程都可以独立地改变自己的副本，而不会和其它线程的副本冲突。从线程的角度看，就好像每一个线程都完全拥有该变量。线程局部变量并不是Java的新发明，在其它的一些语言编译器实现（如IBM XL FORTRAN）中，它在语言的层次提供了直接的支持。因为Java中没有提供在语言层次的直接支持，而是提供了一个ThreadLocal的类来提供支持，所以，在Java中编写线程局部变量的代码相对比较笨拙，这也许是线程局部变量没有在Java中得到很好的普及的一个原因吧。<br /><br />　　 <strong>ThreadLocal的设计<br /></strong><br />　　 首先看看ThreadLocal的接口：<br /><br />　　 　Object get() ; // 返回当前线程的线程局部变量副本 protected Object initialValue(); // 返回该线程局部变量的当前线程的初始值<br />　　　 void set(Object value); // 设置当前线程的线程局部变量副本的值<br /><br />　　 ThreadLocal有3个方法，其中值得注意的是initialValue()，该方法是一个protected的方法，显然是为了子类重写而特意实现的。该方法返回当前线程在该线程局部变量的初始值，这个方法是一个延迟调用方法，在一个线程第1次调用get()或者set(Object)时才执行，并且仅执行1次。ThreadLocal中的确实实现直接返回一个null：<br /><br /><table bordercolor="#ffcc66" width="100%" bgcolor="#b3b3b3" border="1"><tbody><tr><td>protected Object initialValue() { return null; }</td></tr></tbody></table><br />　　ThreadLocal是如何做到为每一个线程维护变量的副本的呢？其实实现的思路很简单，在ThreadLocal类中有一个Map，用于存储每一个线程的变量的副本。比如下面的示例实现：<br /><br /><table bordercolor="#ffcc66" width="100%" bgcolor="#b3b3b3" border="1"><tbody><tr><td>public class ThreadLocal<br />{<br />　 private Map values = Collections.synchronizedMap(new HashMap());<br />　 public Object get()<br />　 {<br />　　 Thread curThread = Thread.currentThread(); <br />　　 Object o = values.get(curThread); <br />　　 if (o == null &amp;&amp; !values.containsKey(curThread))<br />　　 {<br />　　　 o = initialValue();<br />　　　 values.put(curThread, o); <br />　　 }<br />　　 return o; <br />　 }<br /><br />　 public void set(Object newValue)<br />　 {<br />　　 values.put(Thread.currentThread(), newValue);<br />　 }<br /><br />　 public Object initialValue()<br />　 {<br />　　 return null; <br />　 }<br />}</td></tr></tbody></table><br />　　当然，这并不是一个工业强度的实现，但JDK中的ThreadLocal的实现总体思路也类似于此。<br /><br />　　<strong> ThreadLocal的使用<br /></strong><br />　　 如果希望线程局部变量初始化其它值，那么需要自己实现ThreadLocal的子类并重写该方法，通常使用一个内部匿名类对ThreadLocal进行子类化，比如下面的例子，SerialNum类为每一个类分配一个序号：<br /><br /><table bordercolor="#ffcc66" width="100%" bgcolor="#b3b3b3" border="1"><tbody><tr><td>public class SerialNum <br />{<br />　 // The next serial number to be assigned<br /><br />　 private static int nextSerialNum = 0; <br />　 private static ThreadLocal serialNum = new ThreadLocal() <br />　 {<br />　　 protected synchronized Object initialValue() <br />　　 {<br />　　　 return new Integer(nextSerialNum++);<br />　　 }<br />　 };<br /><br />　 public static int get() <br />　 {<br />　　 return ((Integer) (serialNum.get())).intValue(); <br />　 }<br />}</td></tr></tbody></table><br />　　SerialNum类的使用将非常地简单，因为get()方法是static的，所以在需要获取当前线程的序号时，简单地调用：<br /><br /><table bordercolor="#ffcc66" width="100%" bgcolor="#b3b3b3" border="1"><tbody><tr><td>int serial = SerialNum.get();</td></tr></tbody></table><br />　　即可。<br /><br />　　 在线程是活动的并且ThreadLocal对象是可访问的时，该线程就持有一个到该线程局部变量副本的隐含引用，当该线程运行结束后，该线程拥有的所以线程局部变量的副本都将失效，并等待垃圾收集器收集。<br /><br />　　 ThreadLocal与其它同步机制的比较<br /><br />　　 ThreadLocal和其它同步机制相比有什么优势呢？ThreadLocal和其它所有的同步机制都是为了解决多线程中的对同一变量的访问冲突，在普通的同步机制中，是通过对象加锁来实现多个线程对同一变量的安全访问的。这时该变量是多个线程共享的，使用这种同步机制需要很细致地分析在什么时候对变量进行读写，什么时候需要锁定某个对象，什么时候释放该对象的锁等等很多。所有这些都是因为多个线程共享了资源造成的。ThreadLocal就从另一个角度来解决多线程的并发访问，ThreadLocal会为每一个线程维护一个和该线程绑定的变量的副本，从而隔离了多个线程的数据，每一个线程都拥有自己的变量副本，从而也就没有必要对该变量进行同步了。ThreadLocal提供了线程安全的共享对象，在编写多线程代码时，可以把不安全的整个变量封装进ThreadLocal，或者把该对象的特定于线程的状态封装进ThreadLocal。<br /><br />　　 由于ThreadLocal中可以持有任何类型的对象，所以使用ThreadLocal get当前线程的值是需要进行强制类型转换。但随着新的Java版本（1.5）将模版的引入，新的支持模版参数的ThreadLocal&lt;T&gt;类将从中受益。也可以减少强制类型转换，并将一些错误检查提前到了编译期，将一定程度地简化ThreadLocal的使用。<br /><br />　　 <strong>总结<br /></strong><br />　　 当然ThreadLocal并不能替代同步机制，两者面向的问题领域不同。同步机制是为了同步多个线程对相同资源的并发访问，是为了多个线程之间进行通信的有效方式；而ThreadLocal是隔离多个线程的数据共享，从根本上就不在多个线程之间共享资源（变量），这样当然不需要对多个线程进行同步了。所以，如果你需要进行多个线程之间进行通信，则使用同步机制；如果需要隔离多个线程之间的共享冲突，可以使用ThreadLocal，这将极大地简化你的程序，使程序更加易读、简洁。<br /><div class="l-height-18" align="right">文章来源：javajia </div><img src ="http://www.blogjava.net/kingwell/aggbug/45163.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kingwell/" target="_blank">KingWell</a> 2006-05-09 10:55 <a href="http://www.blogjava.net/kingwell/articles/45163.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>