<?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-sealyu-文章分类-Java基础</title><link>http://www.blogjava.net/sealyu/category/30731.html</link><description /><language>zh-cn</language><lastBuildDate>Thu, 10 Apr 2008 16:06:21 GMT</lastBuildDate><pubDate>Thu, 10 Apr 2008 16:06:21 GMT</pubDate><ttl>60</ttl><item><title>Java反射机制详解（转载）</title><link>http://www.blogjava.net/sealyu/articles/191995.html</link><dc:creator>seal</dc:creator><author>seal</author><pubDate>Thu, 10 Apr 2008 14:31:00 GMT</pubDate><guid>http://www.blogjava.net/sealyu/articles/191995.html</guid><wfw:comment>http://www.blogjava.net/sealyu/comments/191995.html</wfw:comment><comments>http://www.blogjava.net/sealyu/articles/191995.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/sealyu/comments/commentRss/191995.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/sealyu/services/trackbacks/191995.html</trackback:ping><description><![CDATA[<strong>摘要</strong><br />
&nbsp;&nbsp;&nbsp;&nbsp;
Reflection&nbsp;是Java被视为动态（或准动态）语言的一个关键性质。这个机制允许程序在运行时透过Reflection&nbsp;APIs取得任何一个
已知名称的class的内部信息，包括其modifiers（诸如public,&nbsp;static&nbsp;等等）、superclass（例如Object）、实
现之interfaces（例如Cloneable），也包括fields和methods的所有信息，并可于运行时改变fields内容或唤起
methods。本文借由实例，大面积示范Reflection&nbsp;APIs。<br />
&nbsp;<br />
<strong>关于本文：</strong><br />
读者基础：具备Java&nbsp;语言基础。<br />
本文适用<a href="http://www.programbbs.com/tool/">工具</a>：JDK1.5<br />
&nbsp;<br />
<strong>关键词：</strong><br />
Introspection（内省、内观）<br />
Reflection（反射）<br />
&nbsp;<br />
&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;
有时候我们说某个语言具有很强的动态性，有时候我们会区分动态和静态的不同技术与作法。我们朗朗上口动态绑定（dynamic&nbsp;binding）、动态链
接（dynamic&nbsp;linking）、动态加载（dynamic&nbsp;loading）等。然而&#8220;动
态&#8221;一词其实没有绝对而普遍适用的严格定义，有时候甚至像对象导向当初被导入<a href="http://www.programbbs.com/">编程</a>领域一样，一人一把号，各吹各的调。<br />
&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp; 一般而言，开发者社群说到动态语言，大致认同的一个定义是：&#8220;程序运行时，允许改变程序结构或变量类型，这种语言称为动态语言&#8221;。从这个观点看，Perl，Python，Ruby是动态语言，C++，Java，C#不是动态语言。<br />
&nbsp;<br />
&nbsp;&nbsp;&nbsp;
尽管在这样的定义与分类下Java不是动态语言，它却有着一个非常突出的动态相关机制：Reflection。这个字的意思是&#8220;反射、映象、倒影&#8221;，用在
Java身上指的是我们可以于运行时加载、探知、使用编译期间完全未知的classes。换句话说，Java程序可以加载一个运行时才得知名称的
class，获悉其完整构造（但不包括methods定义），并生成其对象实体、或对其fields设值、或唤起其methods1。这种&#8220;看透
class&#8221;的能力（the&nbsp;ability&nbsp;of&nbsp;the&nbsp;program&nbsp;to&nbsp;examine&nbsp;itself）被称为introspection
（内省、内观、反省）。Reflection和introspection是常被并提的两个术语。<br />
&nbsp;<br />
&nbsp;&nbsp;&nbsp;
Java如何能够做出上述的动态特性呢？这是一个深远话题，本文对此只简单介绍一些概念。整个篇幅最主要还是介绍Reflection&nbsp;APIs，也就是
让读者知道如何探索class的结构、如何对某个&#8220;运行时才获知名称的class&#8221;生成一份实体、为其fields设值、调用其methods。本文将谈
到java.lang.Class，以及java.lang.reflect中的Method、Field、Constructor等等classes。<br />
&nbsp;<br />
<strong>&#8220;Class&#8221;class</strong><br />
&nbsp;&nbsp;&nbsp;&nbsp;
众所周知Java有个Object&nbsp;class，是所有Java&nbsp;classes的继承根源，其内声明了数个应该在所有Java&nbsp;class中被改写的
methods：hashCode()、equals()、clone()、toString()、getClass()等。其中getClass()返
回一个Class&nbsp;object。<br />
&nbsp;<br />
&nbsp;&nbsp;&nbsp;
Class&nbsp;class十分特殊。它和一般classes一样继承自Object，其实体用以表达Java程序运行时的classes和
interfaces，也用来表达enum、array、primitive&nbsp;Java&nbsp;types（boolean,&nbsp;byte,&nbsp;char,
&nbsp;short,&nbsp;int,&nbsp;long,&nbsp;float,&nbsp;double）以及关键词void。当一个class被加载，或当加载器
（class&nbsp;loader）的defineClass()被JVM调用，JVM&nbsp;便自动产生一个Class&nbsp;object。如果您想借由&#8220;修改Java
标准库<a href="http://www.programbbs.com/code/">源码</a>&#8221;来观察Class&nbsp;object的实际生成时机（例如在Class的constructor内添加一个println()），不能够！因为Class并没有public&nbsp;constructor（见图1）。本文最后我会拨一小块篇幅顺带谈谈Java标准库<a href="http://www.programbbs.com/code/">源码</a>的改动办法。<br />
&nbsp;<br />
&nbsp;&nbsp;&nbsp; Class是Reflection故事起源。针对任何您想探勘的class，唯有先为它产生一个Class&nbsp;object，接下来才能经由后者唤起为数十多个的Reflection&nbsp;APIs。这些APIs将在稍后的探险活动中一一亮相。<br />
&nbsp;<br />
#001&nbsp;public&nbsp;final<br />
#002&nbsp;class&nbsp;Class&nbsp;implements&nbsp;java.io.Serializable,<br />
#003&nbsp;java.lang.reflect.GenericDeclaration,<br />
#004&nbsp;java.lang.reflect.Type,<br />
#005&nbsp;java.lang.reflect.AnnotatedElement&nbsp;{<br />
#006&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;private&nbsp;Class()&nbsp;{}<br />
#007&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;public&nbsp;String&nbsp;toString()&nbsp;{<br />
#008&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;(&nbsp;isInterface()&nbsp;?&nbsp;"interface&nbsp;"&nbsp;:<br />
#009&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;(isPrimitive()&nbsp;?&nbsp;""&nbsp;:&nbsp;"class&nbsp;"))<br />
#010&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;+&nbsp;getName();<br />
#011&nbsp;}<br />
...<br />
图1：Class&nbsp;class片段。注意它的private&nbsp;empty&nbsp;ctor，意指不允许任何人经由<a href="http://www.programbbs.com/">编程</a>方式产生Class&nbsp;object。是的，其object&nbsp;只能由JVM&nbsp;产生。<br />
&nbsp;<br />
<strong>&#8220;Class&#8221;&nbsp;object的取得途径</strong><br />
Java允许我们从多种管道为一个class生成对应的Class&nbsp;object。图2是一份整理。<br />
Class&nbsp;object&nbsp;诞生管道&nbsp;&nbsp;&nbsp;&nbsp;示例<br />
运用getClass()<br />
注：每个class&nbsp;都有此函数&nbsp;&nbsp;&nbsp;&nbsp;String&nbsp;str&nbsp;=&nbsp;"abc";<br />
Class&nbsp;c1&nbsp;=&nbsp;str.getClass();<br />
运用<br />
Class.getSuperclass()2&nbsp;&nbsp;&nbsp;&nbsp;Button&nbsp;b&nbsp;=&nbsp;new&nbsp;Button();<br />
Class&nbsp;c1&nbsp;=&nbsp;b.getClass();<br />
Class&nbsp;c2&nbsp;=&nbsp;c1.getSuperclass();<br />
运用static&nbsp;method<br />
Class.forName()<br />
（最常被使用）&nbsp;&nbsp;&nbsp;&nbsp;Class&nbsp;c1&nbsp;=&nbsp;Class.forName&nbsp;("java.lang.String");<br />
Class&nbsp;c2&nbsp;=&nbsp;Class.forName&nbsp;("java.awt.Button");<br />
Class&nbsp;c3&nbsp;=&nbsp;Class.forName&nbsp;("java.util.LinkedList$Entry");<br />
Class&nbsp;c4&nbsp;=&nbsp;Class.forName&nbsp;("I");<br />
Class&nbsp;c5&nbsp;=&nbsp;Class.forName&nbsp;("[I");<br />
运用<br />
.class&nbsp;语法&nbsp;&nbsp;&nbsp;&nbsp;Class&nbsp;c1&nbsp;=&nbsp;String.class;<br />
Class&nbsp;c2&nbsp;=&nbsp;java.awt.Button.class;<br />
Class&nbsp;c3&nbsp;=&nbsp;Main.InnerClass.class;<br />
Class&nbsp;c4&nbsp;=&nbsp;int.class;<br />
Class&nbsp;c5&nbsp;=&nbsp;int[].class;<br />
运用<br />
primitive&nbsp;wrapper&nbsp;classes<br />
的TYPE&nbsp;语法<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Class&nbsp;c1&nbsp;=&nbsp;Boolean.TYPE;<br />
Class&nbsp;c2&nbsp;=&nbsp;Byte.TYPE;<br />
Class&nbsp;c3&nbsp;=&nbsp;Character.TYPE;<br />
Class&nbsp;c4&nbsp;=&nbsp;Short.TYPE;<br />
Class&nbsp;c5&nbsp;=&nbsp;Integer.TYPE;<br />
Class&nbsp;c6&nbsp;=&nbsp;Long.TYPE;<br />
Class&nbsp;c7&nbsp;=&nbsp;Float.TYPE;<br />
Class&nbsp;c8&nbsp;=&nbsp;Double.TYPE;<br />
Class&nbsp;c9&nbsp;=&nbsp;Void.TYPE;<br />
图2：Java&nbsp;允许多种管道生成Class&nbsp;object。<br />
&nbsp;
<p><br />
<strong>Java&nbsp;classes&nbsp;组成分析</strong><br />
首先容我以图3的java.util.LinkedList为例，将Java&nbsp;class的定义大卸八块，每一块分别对应图4所示的Reflection&nbsp;API。图5则是&#8220;获得class各区块信息&#8221;的程序示例及执行结果，它们都取自本文示例程序的对应片段。<br />
&nbsp;<br />
package&nbsp;java.util;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//(1)<br />
import&nbsp;java.lang.*;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//(2)<br />
public&nbsp;class&nbsp;LinkedList&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//(3)(4)(5)<br />
extends&nbsp;AbstractSequentialList&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//(6)<br />
implements&nbsp;List,&nbsp;Queue,<br />
Cloneable,&nbsp;java.io.Serializable&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//(7)<br />
{<br />
private&nbsp;static&nbsp;class&nbsp;Entry&nbsp;{&nbsp;&#8230;&nbsp;}//(8)<br />
public&nbsp;LinkedList()&nbsp;{&nbsp;&#8230;&nbsp;}&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//(9)<br />
public&nbsp;LinkedList(Collection&nbsp;c)&nbsp;{&nbsp;&#8230;&nbsp;}<br />
public&nbsp;E&nbsp;getFirst()&nbsp;{&nbsp;&#8230;&nbsp;}&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//(10)<br />
public&nbsp;E&nbsp;getLast()&nbsp;{&nbsp;&#8230;&nbsp;}<br />
private&nbsp;transient&nbsp;Entry&nbsp;header&nbsp;=&nbsp;&#8230;;&nbsp;&nbsp;&nbsp;&nbsp;//(11)<br />
private&nbsp;transient&nbsp;int&nbsp;size&nbsp;=&nbsp;0;<br />
}<br />
图3：将一个Java&nbsp;class&nbsp;大卸八块，每块相应于一个或一组Reflection&nbsp;APIs（图4）。<br />
&nbsp;<br />
Java&nbsp;classes&nbsp;各成份所对应的Reflection&nbsp;APIs<br />
图3的各个Java&nbsp;class成份，分别对应于图4的Reflection&nbsp;API，其中出现的Package、Method、Constructor、Field等等classes，都定义于java.lang.reflect。<br />
Java&nbsp;class&nbsp;内部模块（参见图3）&nbsp;&nbsp;&nbsp;&nbsp;Java&nbsp;class&nbsp;内部模块说明&nbsp;&nbsp;&nbsp;&nbsp;相应之Reflection&nbsp;API，多半为Class&nbsp;methods。&nbsp;&nbsp;&nbsp;&nbsp;返回值类型(return&nbsp;type)<br />
(1)&nbsp;package&nbsp;&nbsp;&nbsp;&nbsp;class隶属哪个package&nbsp;&nbsp;&nbsp;&nbsp;getPackage()&nbsp;&nbsp;&nbsp;&nbsp;Package<br />
(2)&nbsp;import&nbsp;&nbsp;&nbsp;&nbsp;class导入哪些classes&nbsp;&nbsp;&nbsp;&nbsp;无直接对应之API。<br />
解决办法见图5-2。&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br />
(3)&nbsp;modifier&nbsp;&nbsp;&nbsp;&nbsp;class（或methods,&nbsp;fields）的属性<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;int&nbsp;getModifiers()<br />
Modifier.toString(int)<br />
Modifier.isInterface(int)&nbsp;&nbsp;&nbsp;&nbsp;int<br />
String<br />
bool<br />
(4)&nbsp;class&nbsp;name&nbsp;or&nbsp;interface&nbsp;name&nbsp;&nbsp;&nbsp;&nbsp;class/interface&nbsp;&nbsp;&nbsp;&nbsp;名称getName()&nbsp;&nbsp;&nbsp;&nbsp;String<br />
(5)&nbsp;type&nbsp;parameters&nbsp;&nbsp;&nbsp;&nbsp;参数化类型的名称&nbsp;&nbsp;&nbsp;&nbsp;getTypeParameters()&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;TypeVariable&nbsp;[]<br />
(6)&nbsp;base&nbsp;class&nbsp;&nbsp;&nbsp;&nbsp;base&nbsp;class（只可能一个）&nbsp;&nbsp;&nbsp;&nbsp;getSuperClass()&nbsp;&nbsp;&nbsp;&nbsp;Class<br />
(7)&nbsp;implemented&nbsp;interfaces&nbsp;&nbsp;&nbsp;&nbsp;实现有哪些interfaces&nbsp;&nbsp;&nbsp;&nbsp;getInterfaces()&nbsp;&nbsp;&nbsp;&nbsp;Class[]<br />
&nbsp;<br />
(8)&nbsp;inner&nbsp;classes&nbsp;&nbsp;&nbsp;&nbsp;内部classes&nbsp;&nbsp;&nbsp;&nbsp;getDeclaredClasses()&nbsp;&nbsp;&nbsp;&nbsp;Class[]<br />
(8')&nbsp;outer&nbsp;class&nbsp;&nbsp;&nbsp;&nbsp;如果我们观察的class&nbsp;本身是inner&nbsp;classes，那么相对它就会有个outer&nbsp;class。&nbsp;&nbsp;&nbsp;&nbsp;getDeclaringClass()&nbsp;&nbsp;&nbsp;&nbsp;Class<br />
(9)&nbsp;constructors&nbsp;&nbsp;&nbsp;&nbsp;构造函数getDeclaredConstructors()&nbsp;&nbsp;&nbsp;&nbsp;不论&nbsp;public&nbsp;或private&nbsp;或其它access&nbsp;level，皆可获得。另有功能近似之取得函数。&nbsp;&nbsp;&nbsp;&nbsp;Constructor[]<br />
(10)&nbsp;methods&nbsp;&nbsp;&nbsp;&nbsp;操作函数getDeclaredMethods()&nbsp;&nbsp;&nbsp;&nbsp;不论&nbsp;public&nbsp;或private&nbsp;或其它access&nbsp;level，皆可获得。另有功能近似之取得函数。&nbsp;&nbsp;&nbsp;&nbsp;Method[]<br />
(11)&nbsp;fields&nbsp;&nbsp;&nbsp;&nbsp;字段（成员变量）&nbsp;&nbsp;&nbsp;&nbsp;getDeclaredFields()不论&nbsp;public&nbsp;或private&nbsp;或其它access&nbsp;level，皆可获得。另有功能近似之取得函数。&nbsp;&nbsp;&nbsp;&nbsp;Field[]<br />
图4：Java&nbsp;class大卸八块后（如图3），每一块所对应的Reflection&nbsp;API。本表并非<br />
Reflection&nbsp;APIs&nbsp;的全部。<br />
&nbsp;<br />
Java&nbsp;Reflection&nbsp;API&nbsp;运用示例<br />
图5
示范图4提过的每一个Reflection&nbsp;API，及其执行结果。程序中出现的tName()是个辅助函数，可将其第一自变量所代表的
&#8220;Java&nbsp;class完整路径字符串&#8221;剥除路径部分，留下class名称，储存到第二自变量所代表的一个hashtable去并返回（如果第二自变量为
null，就不储存而只是返回）。<br />
&nbsp;<br />
#001&nbsp;Class&nbsp;c&nbsp;=&nbsp;null;<br />
#002&nbsp;c&nbsp;=&nbsp;Class.forName(args[0]);<br />
#003<br />
#004&nbsp;Package&nbsp;p;<br />
#005&nbsp;p&nbsp;=&nbsp;c.getPackage();<br />
#006<br />
#007&nbsp;if&nbsp;(p&nbsp;!=&nbsp;null)<br />
#008&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.out.println("package&nbsp;"+p.getName()+";");<br />
&nbsp;<br />
执行结果（例）：<br />
package&nbsp;java.util;<br />
图5-1：找出class&nbsp;隶属的package。其中的c将继续沿用于以下各程序片段。<br />
&nbsp;<br />
#001&nbsp;ff&nbsp;=&nbsp;c.getDeclaredFields();<br />
#002&nbsp;for&nbsp;(int&nbsp;i&nbsp;=&nbsp;0;&nbsp;i&nbsp;&lt;&nbsp;ff.length;&nbsp;i++)<br />
#003&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;x&nbsp;=&nbsp;tName(ff[i].getType().getName(),&nbsp;classRef);<br />
#004<br />
#005&nbsp;cn&nbsp;=&nbsp;c.getDeclaredConstructors();<br />
#006&nbsp;for&nbsp;(int&nbsp;i&nbsp;=&nbsp;0;&nbsp;i&nbsp;&lt;&nbsp;cn.length;&nbsp;i++)&nbsp;{<br />
#007&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Class&nbsp;cx[]&nbsp;=&nbsp;cn[i].getParameterTypes();<br />
#008&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;for&nbsp;(int&nbsp;j&nbsp;=&nbsp;0;&nbsp;j&nbsp;&lt;&nbsp;cx.length;&nbsp;j++)<br />
#009&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;x&nbsp;=&nbsp;tName(cx[j].getName(),&nbsp;classRef);<br />
#010&nbsp;}<br />
#011<br />
#012&nbsp;mm&nbsp;=&nbsp;c.getDeclaredMethods();<br />
#013&nbsp;for&nbsp;(int&nbsp;i&nbsp;=&nbsp;0;&nbsp;i&nbsp;&lt;&nbsp;mm.length;&nbsp;i++)&nbsp;{<br />
#014&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;x&nbsp;=&nbsp;tName(mm[i].getReturnType().getName(),&nbsp;classRef);<br />
#015&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Class&nbsp;cx[]&nbsp;=&nbsp;mm[i].getParameterTypes();<br />
#016&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;for&nbsp;(int&nbsp;j&nbsp;=&nbsp;0;&nbsp;j&nbsp;&lt;&nbsp;cx.length;&nbsp;j++)<br />
#017&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;x&nbsp;=&nbsp;tName(cx[j].getName(),&nbsp;classRef);<br />
#018&nbsp;}<br />
#019&nbsp;classRef.remove(c.getName());&nbsp;//不必记录自己（不需import&nbsp;自己）<br />
&nbsp;<br />
执行结果（例）：<br />
import&nbsp;java.util.ListIterator;<br />
import&nbsp;java.lang.Object;<br />
import&nbsp;java.util.LinkedList$Entry;<br />
import&nbsp;java.util.Collection;<br />
import&nbsp;java.io.ObjectOutputStream;<br />
import&nbsp;java.io.ObjectInputStream;<br />
图5-2：找出导入的classes，动作细节详见内文说明。<br />
&nbsp;<br />
#001&nbsp;int&nbsp;mod&nbsp;=&nbsp;c.getModifiers();<br />
#002&nbsp;System.out.print(Modifier.toString(mod));&nbsp;//整个modifier<br />
#003<br />
#004&nbsp;if&nbsp;(Modifier.isInterface(mod))<br />
#005&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.out.print("&nbsp;");&nbsp;//关键词&nbsp;"interface"&nbsp;已含于modifier<br />
#006&nbsp;else<br />
#007&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.out.print("&nbsp;class&nbsp;");&nbsp;//关键词&nbsp;"class"<br />
#008&nbsp;System.out.print(tName(c.getName(),&nbsp;null));&nbsp;//class&nbsp;名称<br />
</p>
<p>执行结果（例）：<br />
public&nbsp;class&nbsp;LinkedList<br />
图5-3：找出class或interface&nbsp;的名称，及其属性（modifiers）。<br />
&nbsp;<br />
#001&nbsp;TypeVariable[]&nbsp;tv;<br />
#002&nbsp;tv&nbsp;=&nbsp;c.getTypeParameters();&nbsp;//warning:&nbsp;unchecked&nbsp;conversion<br />
#003&nbsp;for&nbsp;(int&nbsp;i&nbsp;=&nbsp;0;&nbsp;i&nbsp;&lt;&nbsp;tv.length;&nbsp;i++)&nbsp;{<br />
#004&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;x&nbsp;=&nbsp;tName(tv[i].getName(),&nbsp;null);&nbsp;//例如&nbsp;E,K,V...<br />
#005&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if&nbsp;(i&nbsp;==&nbsp;0)&nbsp;//第一个<br />
#006&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.out.print("&lt;"&nbsp;+&nbsp;x);<br />
#007&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;else&nbsp;//非第一个<br />
#008&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.out.print(","&nbsp;+&nbsp;x);<br />
#009&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if&nbsp;(i&nbsp;==&nbsp;tv.length-1)&nbsp;//最后一个<br />
#010&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.out.println("&gt;");<br />
#011&nbsp;}<br />
&nbsp;<br />
执行结果（例）：<br />
public&nbsp;abstract&nbsp;interface&nbsp;Map<br />
或&nbsp;public&nbsp;class&nbsp;LinkedList<br />
图5-4：找出parameterized&nbsp;types&nbsp;的名称<br />
&nbsp;<br />
#001&nbsp;Class&nbsp;supClass;<br />
#002&nbsp;supClass&nbsp;=&nbsp;c.getSuperclass();<br />
#003&nbsp;if&nbsp;(supClass&nbsp;!=&nbsp;null)&nbsp;//如果有super&nbsp;class<br />
#004&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.out.print("&nbsp;extends"&nbsp;+<br />
#005&nbsp;tName(supClass.getName(),classRef));<br />
&nbsp;<br />
执行结果（例）：<br />
public&nbsp;class&nbsp;LinkedList<br />
extends&nbsp;AbstractSequentialList,<br />
图5-5：找出base&nbsp;class。执行结果多出一个不该有的逗号于尾端。此非本处重点，为简化计，不多做处理。<br />
&nbsp;<br />
#001&nbsp;Class&nbsp;cc[];<br />
#002&nbsp;Class&nbsp;ctmp;<br />
#003&nbsp;//找出所有被实现的interfaces<br />
#004&nbsp;cc&nbsp;=&nbsp;c.getInterfaces();<br />
#005&nbsp;if&nbsp;(cc.length&nbsp;!=&nbsp;0)<br />
#006&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.out.print(",&nbsp;"r"n"&nbsp;+&nbsp;"&nbsp;implements&nbsp;");&nbsp;//关键词<br />
#007&nbsp;for&nbsp;(Class&nbsp;cite&nbsp;:&nbsp;cc)&nbsp;//JDK1.5&nbsp;新式循环写法<br />
#008&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.out.print(tName(cite.getName(),&nbsp;null)+",&nbsp;");<br />
&nbsp;<br />
执行结果（例）：<br />
public&nbsp;class&nbsp;LinkedList<br />
extends&nbsp;AbstractSequentialList,<br />
implements&nbsp;List,&nbsp;Queue,&nbsp;Cloneable,&nbsp;Serializable,<br />
图5-6：找出implemented&nbsp;interfaces。执行结果多出一个不该有的逗号于尾端。此非本处重点，为简化计，不多做处理。<br />
&nbsp;<br />
#001&nbsp;cc&nbsp;=&nbsp;c.getDeclaredClasses();&nbsp;//找出inner&nbsp;classes<br />
#002&nbsp;for&nbsp;(Class&nbsp;cite&nbsp;:&nbsp;cc)<br />
#003&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.out.println(tName(cite.getName(),&nbsp;null));<br />
#004<br />
#005&nbsp;ctmp&nbsp;=&nbsp;c.getDeclaringClass();&nbsp;//找出outer&nbsp;classes<br />
#006&nbsp;if&nbsp;(ctmp&nbsp;!=&nbsp;null)<br />
#007&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.out.println(ctmp.getName());<br />
&nbsp;<br />
执行结果（例）：<br />
LinkedList$Entry<br />
LinkedList$ListItr<br />
图5-7：找出inner&nbsp;classes&nbsp;和outer&nbsp;class<br />
&nbsp;<br />
#001&nbsp;Constructor&nbsp;cn[];<br />
#002&nbsp;cn&nbsp;=&nbsp;c.getDeclaredConstructors();<br />
#003&nbsp;for&nbsp;(int&nbsp;i&nbsp;=&nbsp;0;&nbsp;i&nbsp;&lt;&nbsp;cn.length;&nbsp;i++)&nbsp;{<br />
#004&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;int&nbsp;md&nbsp;=&nbsp;cn[i].getModifiers();<br />
#005&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.out.print("&nbsp;"&nbsp;+&nbsp;Modifier.toString(md)&nbsp;+&nbsp;"&nbsp;"&nbsp;+<br />
#006&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;cn[i].getName());<br />
#007&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Class&nbsp;cx[]&nbsp;=&nbsp;cn[i].getParameterTypes();<br />
#008&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.out.print("(");<br />
#009&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;for&nbsp;(int&nbsp;j&nbsp;=&nbsp;0;&nbsp;j&nbsp;&lt;&nbsp;cx.length;&nbsp;j++)&nbsp;{<br />
#010&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.out.print(tName(cx[j].getName(),&nbsp;null));<br />
#011&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if&nbsp;(j&nbsp;&lt;&nbsp;(cx.length&nbsp;-&nbsp;1))&nbsp;System.out.print(",&nbsp;");<br />
#012&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br />
#013&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.out.print(")");<br />
#014&nbsp;}<br />
&nbsp;<br />
执行结果（例）：<br />
public&nbsp;java.util.LinkedList(Collection)<br />
public&nbsp;java.util.LinkedList()<br />
图5-8a：找出所有constructors<br />
&nbsp;<br />
#004&nbsp;System.out.println(cn[i].toGenericString());<br />
&nbsp;<br />
执行结果（例）：<br />
public&nbsp;java.util.LinkedList(java.util.Collection)<br />
public&nbsp;java.util.LinkedList()<br />
图5-8b：找出所有constructors。本例在for&nbsp;循环内使用toGenericString()，省事。<br />
&nbsp;<br />
#001&nbsp;Method&nbsp;mm[];<br />
#002&nbsp;mm&nbsp;=&nbsp;c.getDeclaredMethods();<br />
#003&nbsp;for&nbsp;(int&nbsp;i&nbsp;=&nbsp;0;&nbsp;i&nbsp;&lt;&nbsp;mm.length;&nbsp;i++)&nbsp;{<br />
#004&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;int&nbsp;md&nbsp;=&nbsp;mm[i].getModifiers();<br />
#005&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.out.print("&nbsp;"+Modifier.toString(md)+"&nbsp;"+<br />
#006&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;tName(mm[i].getReturnType().getName(),&nbsp;null)+"&nbsp;"+<br />
#007&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;mm[i].getName());<br />
#008&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Class&nbsp;cx[]&nbsp;=&nbsp;mm[i].getParameterTypes();<br />
#009&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.out.print("(");<br />
#010&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;for&nbsp;(int&nbsp;j&nbsp;=&nbsp;0;&nbsp;j&nbsp;&lt;&nbsp;cx.length;&nbsp;j++)&nbsp;{<br />
#011&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.out.print(tName(cx[j].getName(),&nbsp;null));<br />
#012&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if&nbsp;(j&nbsp;&lt;&nbsp;(cx.length&nbsp;-&nbsp;1))&nbsp;System.out.print(",&nbsp;");<br />
#013&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br />
#014&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.out.print(")");<br />
#015&nbsp;}<br />
&nbsp;<br />
执行结果（例）：<br />
public&nbsp;Object&nbsp;get(int)<br />
public&nbsp;int&nbsp;size()<br />
图5-9a：找出所有methods<br />
&nbsp;<br />
#004&nbsp;System.out.println(mm[i].toGenericString());<br />
&nbsp;<br />
public&nbsp;E&nbsp;java.util.LinkedList.get(int)<br />
public&nbsp;int&nbsp;java.util.LinkedList.size()<br />
图5-9b：找出所有methods。本例在for&nbsp;循环内使用toGenericString()，省事。<br />
&nbsp;<br />
#001&nbsp;Field&nbsp;ff[];<br />
#002&nbsp;ff&nbsp;=&nbsp;c.getDeclaredFields();<br />
#003&nbsp;for&nbsp;(int&nbsp;i&nbsp;=&nbsp;0;&nbsp;i&nbsp;&lt;&nbsp;ff.length;&nbsp;i++)&nbsp;{<br />
#004&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;int&nbsp;md&nbsp;=&nbsp;ff[i].getModifiers();<br />
#005&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.out.println("&nbsp;"+Modifier.toString(md)+"&nbsp;"+<br />
#006&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;tName(ff[i].getType().getName(),&nbsp;null)&nbsp;+"&nbsp;"+<br />
#007&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ff[i].getName()+";");<br />
#008&nbsp;}<br />
&nbsp;<br />
</p>
<p>执行结果（例）：<br />
private&nbsp;transient&nbsp;LinkedList$Entry&nbsp;header;<br />
private&nbsp;transient&nbsp;int&nbsp;size;<br />
图5-10a：找出所有fields<br />
&nbsp;<br />
#004&nbsp;System.out.println("G:&nbsp;"&nbsp;+&nbsp;ff[i].toGenericString());<br />
&nbsp;<br clear="all" />
private&nbsp;transient&nbsp;java.util.LinkedList.java.util.LinkedList$Entry&nbsp;??<br />
java.util.LinkedList.header<br />
private&nbsp;transient&nbsp;int&nbsp;java.util.LinkedList.size<br />
图5-10b：找出所有fields。本例在for&nbsp;循环内使用toGenericString()，省事。<br />
&nbsp;<br />
找出class参用（导入）的所有classes<br />
没
有直接可用的Reflection&nbsp;API可以为我们找出某个class参用的所有其它classes。要获得这项信息，必须做苦工，一步一脚印逐一记
录。我们必须观察所有fields的类型、所有methods（包括constructors）的参数类型和回返类型，剔除重复，留下唯一。这正是为什么
图5-2程序代码要为tName()指定一个hashtable（而非一个null）做为第二自变量的缘故：hashtable可为我们储存元素（本例为
字符串），又保证不重复。<br />
&nbsp;<br />
本文讨论至此，几乎可以还原一个class的原貌（唯有methods&nbsp;和ctors的定义无法取得）。接下来讨论Reflection&nbsp;的另三个动态性质：(1)&nbsp;运行时生成instances，(2)&nbsp;执<br />
行期唤起methods，(3)&nbsp;运行时改动fields。<br />
&nbsp;<br />
运行时生成instances<br />
欲生成对象实体，在Reflection&nbsp;动态机制中有两种作法，一个针对&#8220;无自变量ctor&#8221;，<br />
一
个针对&#8220;带参数ctor&#8221;。图6是面对&#8220;无自变量ctor&#8221;的例子。如果欲调用的是&#8220;带参数ctor&#8220;就比较麻烦些，图7是个例子，其中不再调用
Class的newInstance()，而是调用Constructor&nbsp;的newInstance()。图7首先准备一个Class[]做为ctor
的参数类型（本例指定为一个double和一个int），然后以此为自变量调用getConstructor()，获得一个专属ctor。接下来再准备一
个Object[]&nbsp;做为ctor实参值（本例指定3.14159和125），调用上述专属ctor的newInstance()。<br />
&nbsp;<br />
#001&nbsp;Class&nbsp;c&nbsp;=&nbsp;Class.forName("DynTest");<br />
#002&nbsp;Object&nbsp;obj&nbsp;=&nbsp;null;<br />
#003&nbsp;obj&nbsp;=&nbsp;c.newInstance();&nbsp;//不带自变量<br />
#004&nbsp;System.out.println(obj);<br />
图6：动态生成&#8220;Class&nbsp;object&nbsp;所对应之class&#8221;的对象实体；无自变量。<br />
&nbsp;<br />
#001&nbsp;Class&nbsp;c&nbsp;=&nbsp;Class.forName("DynTest");<br />
#002&nbsp;Class[]&nbsp;pTypes&nbsp;=&nbsp;new&nbsp;Class[]&nbsp;{&nbsp;double.class,&nbsp;int.class&nbsp;};<br />
#003&nbsp;Constructor&nbsp;ctor&nbsp;=&nbsp;c.getConstructor(pTypes);<br />
#004&nbsp;//指定parameter&nbsp;list，便可获得特定之ctor<br />
#005<br />
#006&nbsp;Object&nbsp;obj&nbsp;=&nbsp;null;<br />
#007&nbsp;Object[]&nbsp;arg&nbsp;=&nbsp;new&nbsp;Object[]&nbsp;{3.14159,&nbsp;125};&nbsp;//自变量<br />
#008&nbsp;obj&nbsp;=&nbsp;ctor.newInstance(arg);<br />
#009&nbsp;System.out.println(obj);<br />
图7：动态生成&#8220;Class&nbsp;object&nbsp;对应之class&#8221;的对象实体；自变量以Object[]表示。<br />
&nbsp;<br />
运行时调用methods<br />
这
个动作和上述调用&#8220;带参数之ctor&#8221;相当类似。首先准备一个Class[]做为ctor的参数类型（本例指定其中一个是String，另一个是
Hashtable），然后以此为自变量调用getMethod()，获得特定的Method&nbsp;object。接下来准备一个Object[]放置自变
量，然后调用上述所得之特定Method&nbsp;object的invoke()，如图8。知道为什么索取Method&nbsp;object时不需指定回返类型吗？因
为method&nbsp;overloading机制要求signature（署名式）必须唯一，而回返类型并非signature的一个成份。换句话说，只要指
定了method名称和参数列，就一定指出了一个独一无二的method。<br />
&nbsp;<br />
#001&nbsp;public&nbsp;String&nbsp;func(String&nbsp;s,&nbsp;Hashtable&nbsp;ht)<br />
#002&nbsp;{<br />
#003&nbsp;&#8230;System.out.println("func&nbsp;invoked");&nbsp;return&nbsp;s;<br />
#004&nbsp;}<br />
#005&nbsp;public&nbsp;static&nbsp;void&nbsp;main(String&nbsp;args[])<br />
#006&nbsp;{<br />
#007&nbsp;Class&nbsp;c&nbsp;=&nbsp;Class.forName("Test");<br />
#008&nbsp;Class&nbsp;ptypes[]&nbsp;=&nbsp;new&nbsp;Class[2];<br />
#009&nbsp;ptypes[0]&nbsp;=&nbsp;Class.forName("java.lang.String");<br />
#010&nbsp;ptypes[1]&nbsp;=&nbsp;Class.forName("java.util.Hashtable");<br />
#011&nbsp;Method&nbsp;m&nbsp;=&nbsp;c.getMethod("func",ptypes);<br />
#012&nbsp;Test&nbsp;obj&nbsp;=&nbsp;new&nbsp;Test();<br />
#013&nbsp;Object&nbsp;args[]&nbsp;=&nbsp;new&nbsp;Object[2];<br />
#014&nbsp;arg[0]&nbsp;=&nbsp;new&nbsp;String("Hello,world");<br />
#015&nbsp;arg[1]&nbsp;=&nbsp;null;<br />
#016&nbsp;Object&nbsp;r&nbsp;=&nbsp;m.invoke(obj,&nbsp;arg);<br />
#017&nbsp;Integer&nbsp;rval&nbsp;=&nbsp;(String)r;<br />
#018&nbsp;System.out.println(rval);<br />
#019&nbsp;}<br />
图8：动态唤起method<br />
&nbsp;<br />
运行时变更fields内容<br />
与先前两个动作相比，&#8220;变更field内容&#8221;轻松多了，因为它不需要参数和自变量。首先调用Class的getField()并指定field名称。获得特定的Field&nbsp;object之后便可直接调用Field的get()和set()，如图9。<br />
&nbsp;<br />
#001&nbsp;public&nbsp;class&nbsp;Test&nbsp;{<br />
#002&nbsp;public&nbsp;double&nbsp;d;<br />
#003<br />
#004&nbsp;public&nbsp;static&nbsp;void&nbsp;main(String&nbsp;args[])<br />
#005&nbsp;{<br />
#006&nbsp;Class&nbsp;c&nbsp;=&nbsp;Class.forName("Test");<br />
#007&nbsp;Field&nbsp;f&nbsp;=&nbsp;c.getField("d");&nbsp;//指定field&nbsp;名称<br />
#008&nbsp;Test&nbsp;obj&nbsp;=&nbsp;new&nbsp;Test();<br />
#009&nbsp;System.out.println("d=&nbsp;"&nbsp;+&nbsp;(Double)f.get(obj));<br />
#010&nbsp;f.set(obj,&nbsp;12.34);<br />
#011&nbsp;System.out.println("d=&nbsp;"&nbsp;+&nbsp;obj.d);<br />
#012&nbsp;}<br />
#013&nbsp;}<br />
图9：动态变更field&nbsp;内容<br />
&nbsp;<br />
<strong>Java&nbsp;<a href="http://www.programbbs.com/code/">源码</a>改动办法</strong><br />
&nbsp;&nbsp;&nbsp; 先前我曾提到，原本想借由&#8220;改动Java标准库<a href="http://www.programbbs.com/code/">源码</a>&#8221;来测知Class&nbsp;object的生成，但由于其ctor原始设计为private，也就是说不可能透过这个管道生成Class&nbsp;object（而是由class&nbsp;loader负责生成），因此&#8220;在ctor中打印出某种信息&#8221;的企图也就失去了意义。<br />
&nbsp;<br />
&nbsp;&nbsp;&nbsp; 这里我要谈点题外话：如何修改Java标准库<a href="http://www.programbbs.com/code/">源码</a>并让它反应到我们的应用程序来。假设我想修改java.lang.Class，让它在某些情况下打印某种信息。首先必须找出标准<a href="http://www.programbbs.com/code/">源码</a>！当你下载JDK&nbsp;套件并安装妥当，你会发现jdk150"src"java"lang&nbsp;目录（见图10）之中有Class.java，这就是我们此次行动的标准<a href="http://www.programbbs.com/code/">源码</a>。备份后加以修改，编译获得Class.class。接下来准备将.class&nbsp;搬移到jdk150"jre"lib"endorsed（见图10）。<br />
&nbsp;<br />
&nbsp;&nbsp;&nbsp;
这是一个十分特别的目录，class&nbsp;loader将优先从该处读取内含classes的.jar文件??成功的条件是.jar内的classes压缩路
径必须和Java标准库的路径完全相同。为此，我们可以将刚才做出的Class.class先搬到一个为此目的而刻意做出来的"java"lang目录
中，压缩为foo.zip（任意命名，唯需夹带路径java"lang），再将这个foo.zip搬到jdk150"jre"lib"endorsed并
改名为foo.jar。此后你的应用程序便会优先用上这里的java.lang.Class。整个过程可写成一个批处理文件（batch&nbsp;file），如
图11，在DOS&nbsp;Box中使用。<br />
&nbsp;&nbsp;<br />
<br />
图10：JDK1.5&nbsp;安装后的目录组织。其中的endorsed&nbsp;是我新建。<br />
&nbsp;<br />
del&nbsp;e:"java"lang"*.class&nbsp;//清理干净<br />
del&nbsp;c:"jdk150"jre"lib"endorsed"foo.jar&nbsp;//清理干净<br />
c:<br />
cd&nbsp;c:"jdk150"src"java"lang<br />
javac&nbsp;-Xlint:unchecked&nbsp;Class.java&nbsp;//编译<a href="http://www.programbbs.com/code/">源码</a><br />
javac&nbsp;-Xlint:unchecked&nbsp;ClassLoader.java&nbsp;//编译另一个<a href="http://www.programbbs.com/code/">源码</a>（如有必要）<br />
move&nbsp;*.class&nbsp;e:"java"lang&nbsp;//搬移至刻意制造的目录中<br />
e:<br />
cd&nbsp;e:"java"lang&nbsp;//以下压缩至适当目录<br />
pkzipc&nbsp;-add&nbsp;-path=root&nbsp;c:"jdk150"jre"lib"endorsed"foo.jar&nbsp;*.class<br />
cd&nbsp;e:"test&nbsp;//进入测试目录<br />
javac&nbsp;-Xlint:unchecked&nbsp;Test.java&nbsp;//编译测试程序<br />
java&nbsp;Test&nbsp;//执行测试程序<br />
图11：一个可在DOS&nbsp;Box中使用的批处理文件（batch&nbsp;file），用以自动化java.lang.Class<br />
的修改动作。Pkzipc(.exe)是个命令列压缩<a href="http://www.programbbs.com/tool/">工具</a>，add和path都是其命令。<br />
&nbsp;<br />
更多信息<br />
以下是视野所及与本文主题相关的更多讨论。这些信息可以弥补因文章篇幅限制而带来的不足，或带给您更多视野。<br />
&nbsp;<br />
?&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"Take&nbsp;an&nbsp;in
-depth&nbsp;look&nbsp;at&nbsp;the&nbsp;Java&nbsp;Reflection&nbsp;API&nbsp;--&nbsp;Learn&nbsp;about&nbsp;the&nbsp;new&nbsp;Java&nbsp;1.1&nbsp;tools&nbsp;forfinding&nbsp;out&nbsp;information&nbsp;about&nbsp;classes",
&nbsp;by&nbsp;Chuck&nbsp;McManis。此篇文章所附程序代码是本文示例程序的主要依据（本文示例程序示范了更多Reflection&nbsp;APIs，并采用
JDK1.5&nbsp;新式的for-loop&nbsp;写法）。<br />
?&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"Take&nbsp;a&nbsp;look&nbsp;inside&nbsp;Java&nbsp;classes&nbsp;--&nbsp;Learn&nbsp;to&nbsp;deduce&nbsp;properties&nbsp;of&nbsp;a&nbsp;Java&nbsp;class&nbsp;from&nbsp;inside&nbsp;aJava&nbsp;program",
&nbsp;by&nbsp;Chuck&nbsp;McManis。<br />
?&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"The&nbsp;basics&nbsp;of&nbsp;Java&nbsp;class&nbsp;loaders&nbsp;--&nbsp;The&nbsp;fundamentals&nbsp;of&nbsp;this&nbsp;key&nbsp;component&nbsp;of&nbsp;the&nbsp;Javaarchitecture",
&nbsp;by&nbsp;Chuck&nbsp;McManis。<br />
?&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;《The&nbsp;Java&nbsp;Tutorial&nbsp;Continued》,&nbsp;Sun&nbsp;microsystems.&nbsp;Lesson58-61,&nbsp;"Reflection".<br />
&nbsp;<br />
注1
用过诸如MFC这类所谓&nbsp;Application&nbsp;Framework的程序员也许知道，MFC有所谓的dynamic&nbsp;creation。但它并不等同
于Java的动态加载或动态辨识；所有能够在MFC程序中起作用的classes，都必须先在编译期被编译器&#8220;看见&#8221;。<br />
</p>
<img src ="http://www.blogjava.net/sealyu/aggbug/191995.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/sealyu/" target="_blank">seal</a> 2008-04-10 22:31 <a href="http://www.blogjava.net/sealyu/articles/191995.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>java通过修改类的字节码实现aop功能（转载）</title><link>http://www.blogjava.net/sealyu/articles/191993.html</link><dc:creator>seal</dc:creator><author>seal</author><pubDate>Thu, 10 Apr 2008 14:30:00 GMT</pubDate><guid>http://www.blogjava.net/sealyu/articles/191993.html</guid><wfw:comment>http://www.blogjava.net/sealyu/comments/191993.html</wfw:comment><comments>http://www.blogjava.net/sealyu/articles/191993.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/sealyu/comments/commentRss/191993.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/sealyu/services/trackbacks/191993.html</trackback:ping><description><![CDATA[在jdk5.0版本中，添加了一个新的接口java.lang.instrument.Instrumentation,这个接口可以用来在类加载的时候
对类的字节码进行修改，改变类的功能的实现。通过这种方法，我们就可以实现aop的功能，这是和proxy动态代理的不同的另一种aop的实现。<br />
&nbsp;&nbsp;&nbsp;&nbsp; 这种方法的原理很简单，就是通过修改编译器已经编译好的字节码来修改类，和我们通常修改类源码来实现对类的修改差不多。只不过是在两个不同的层次进行的修改。<br />
&nbsp;&nbsp;&nbsp;&nbsp; 一下是使用这个接口的具体方法。<br />
&nbsp;&nbsp;&nbsp; <span style="font-weight: bold;">第一步：</span><br />
&nbsp;&nbsp;&nbsp; 需要实现&nbsp; java.lang.instrument.Instrumentation这个接口，通过在这个接口的实现类中，在加载类的时候修改类的字节码。以下是接口的实现 Transformer.java<br />
<span style="color: rgb(0, 0, 255);">public class Transformer implements ClassFileTransformer {</span><br style="color: rgb(0, 0, 255);" />
<span style="color: rgb(0, 0, 255);">&nbsp;&nbsp; //在程序运行之前就被jvm调用的方法</span><br style="color: rgb(0, 0, 255);" />
<span style="color: rgb(0, 0, 255);">&nbsp;&nbsp; public static void premain(String args, Instrumentation inst) {</span><br style="color: rgb(0, 0, 255);" />
<span style="color: rgb(0, 0, 255);">&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; inst.addTransformer(new Transformer());</span><br style="color: rgb(0, 0, 255);" />
<span style="color: rgb(0, 0, 255);">&nbsp;&nbsp;&nbsp; }</span><br style="color: rgb(0, 0, 255);" />
<span style="color: rgb(0, 0, 255);">&nbsp;&nbsp;&nbsp; public byte[] transform(ClassLoader loader, String className,</span><br style="color: rgb(0, 0, 255);" />
<span style="color: rgb(0, 0, 255);">&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; Class&lt;?&gt; classBeingRedefined, ProtectionDomain protectionDomain,</span><br style="color: rgb(0, 0, 255);" />
<span style="color: rgb(0, 0, 255);">&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; byte[] classfileBuffer) throws IllegalClassFormatException {</span><br style="color: rgb(0, 0, 255);" />
<span style="color: rgb(0, 0, 255);">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; byte[] result =null; //定义新的字节码存储变量</span><br style="color: rgb(0, 0, 255);" />
<span style="color: rgb(0, 0, 255);">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //在此通过修改类的字节码 classfileBuffer，来更改类。</span><br style="color: rgb(0, 0, 255);" />
<span style="color: rgb(0, 0, 255);">&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; return result; //返回新的字节码</span><br style="color: rgb(0, 0, 255);" />
<span style="color: rgb(0, 0, 255);">&nbsp;&nbsp;&nbsp; }</span><br style="color: rgb(0, 0, 255);" />
<span style="color: rgb(0, 0, 255);"><span style="color: rgb(0, 0, 255);">}<br />
<span style="font-weight: bold; color: rgb(0, 0, 0);">第二步：</span><br style="color: rgb(0, 0, 0);" />
<span style="color: rgb(0, 0, 0);">需要将</span></span></span>Transformer类的class文件生成jar文件，如t.jar,在jar文件的MANIFEST.MF添加 <span style="color: rgb(0, 0, 255);">Premain-Class: </span><span style="color: rgb(0, 0, 255);">Transformer <span style="color: rgb(0, 0, 0);">这一项。在运行程序时，需要在jvm使用如下参数 -javaagent:t.jar 。这样jvm在运行时，就会先找到</span></span><span style="color: rgb(0, 0, 255);">Premain-Class: </span><span style="color: rgb(0, 0, 255);">Transformer&nbsp; <span style="color: rgb(0, 0, 0);">这一项指定的</span></span><span style="color: rgb(0, 0, 255);">Transformer<span style="color: rgb(0, 0, 0);">类的premain方法，运行此方法。也就实现了在以后的类的加载中，对类的字节码进行修改的功能。<br />
<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 就是使用</span></span>java.lang.instrument.Instrumentation接口的方法。那么如何实现aop功能呢，这就需要在更改字节码的时候，在类的功能上添加aop方面的功能。<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 现在我们看看如何实现在方法的调用前和返回时打印当前时间的aop功能。<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 先更改<span style="color: rgb(0, 0, 255);">Transformer的</span><span style="color: rgb(0, 0, 255);">transform方法</span><br />
<span style="color: rgb(0, 0, 255);">public byte[] transform(ClassLoader loader, String className,</span><br style="color: rgb(0, 0, 255);" />
<span style="color: rgb(0, 0, 255);">&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; Class&lt;?&gt; classBeingRedefined, ProtectionDomain protectionDomain,</span><br style="color: rgb(0, 0, 255);" />
<span style="color: rgb(0, 0, 255);">&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; byte[] classfileBuffer) throws IllegalClassFormatException {</span><br style="color: rgb(0, 0, 255);" />
<span style="color: rgb(0, 0, 255);">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; byte[] result =</span><span style="color: rgb(0, 0, 255);">classfileBuffer</span><span style="color: rgb(0, 0, 255);">; //定义新的字节码存储变量</span><br style="color: rgb(0, 0, 255);" />
<span style="color: rgb(0, 0, 255);">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //在此通过修改类的字节码 classfileBuffer，来更改类。<br />
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; if (loader != ClassLoader.getSystemClassLoader()) {<br />
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; return classfileBuffer;<br />
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; }<br />
<br />
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; ClassReader reader = new ClassReader(classfileBuffer);<br />
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; ClassWriter writer = new ClassWriter(true);<br />
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; ClassAdapter adapter = new PerfClassAdapter(writer, className);<br />
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; reader.accept(adapter, true);<br />
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; result = writer.toByteArray();<br style="color: rgb(0, 0, 255);" />
</span><span style="color: rgb(0, 0, 255);">&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; return result; //返回新的字节码</span><br style="color: rgb(0, 0, 255);" />
<span style="color: rgb(0, 0, 255);">&nbsp;&nbsp;&nbsp; }<br />
<br />
</span><span style="color: rgb(0, 0, 255);"><span style="color: rgb(0, 0, 255);"><span style="color: rgb(0, 0, 0);">这样就通过</span></span></span><span style="color: rgb(0, 0, 255);">PerfClassAdapter<span style="color: rgb(0, 0, 0);">类来修改字节码.<br />
</span></span><span style="color: rgb(0, 0, 0);">PerfClassAdapter.java</span><br />
<span style="color: rgb(0, 0, 255);"><span style="color: rgb(0, 0, 255);">public class PerfClassAdapter extends ClassAdapter {<br />
&nbsp;&nbsp;&nbsp; private String className;<br />
&nbsp;&nbsp;&nbsp; public PerfClassAdapter(ClassVisitor visitor, String theClass) {<br />
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; super(visitor);<br />
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; this.className = theClass;<br />
&nbsp;&nbsp;&nbsp; }<br />
&nbsp;&nbsp;&nbsp; public MethodVisitor visitMethod(int arg,<br />
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; String name,<br />
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; String descriptor,<br />
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; String signature,<br />
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; String[] exceptions) {<br />
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; MethodVisitor mv = super.visitMethod(arg, <br />
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; name, <br />
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; descriptor, <br />
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; signature, <br />
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; exceptions);<br />
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; MethodAdapter ma = new PerfMethodAdapter(mv, className, name);<br />
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; return ma;<br />
&nbsp;&nbsp;&nbsp; }&nbsp; <br />
}<br />
</span></span><span style="color: rgb(0, 0, 255);"><span style="color: rgb(0, 0, 255);"><span style="color: rgb(0, 0, 0);">PerfMethodAdapter.java</span><br />
public class PerfMethodAdapter extends MethodAdapter {<br />
&nbsp;&nbsp;&nbsp; private String _className, _methodName;<br />
&nbsp;&nbsp;&nbsp; <br />
&nbsp;&nbsp;&nbsp; public PerfMethodAdapter(MethodVisitor visitor, <br />
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; String className,<br />
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; String methodName) { <br />
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; super(visitor);<br />
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; _className = className;<br />
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; _methodName = methodName;<br />
&nbsp;&nbsp;&nbsp; }<br />
<br />
&nbsp;&nbsp;&nbsp; public void visitCode() {<br />
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; this.visitLdcInsn(_className);<br />
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; this.visitLdcInsn(_methodName);<br />
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; this.visitMethodInsn(INVOKESTATIC, <br />
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; "</span></span><span style="color: rgb(0, 0, 255);"><span style="color: rgb(0, 0, 255);">AOP_LOG</span></span><span style="color: rgb(0, 0, 255);"><span style="color: rgb(0, 0, 255);">", <br />
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; "start", <br />
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; "(Ljava/lang/String;Ljava/lang/String;)V");<br />
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; super.visitCode();<br />
&nbsp;&nbsp;&nbsp; }<br />
<br />
&nbsp;&nbsp;&nbsp; public void visitInsn(int inst) {<br />
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; switch (inst) {<br />
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; case Opcodes.ARETURN:<br />
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; case Opcodes.DRETURN:<br />
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; case Opcodes.FRETURN:<br />
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; case Opcodes.IRETURN:<br />
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; case Opcodes.LRETURN:<br />
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; case Opcodes.RETURN:<br />
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; case Opcodes.ATHROW:<br />
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; this.visitLdcInsn(_className);<br />
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; this.visitLdcInsn(_methodName);<br />
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; this.visitMethodInsn(INVOKESTATIC, <br />
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; "AOP_LOG", <br />
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; "end", <br />
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; "(Ljava/lang/String;Ljava/lang/String;)V");<br />
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; break;<br />
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; default:<br />
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; break;<br />
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; }<br />
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; super.visitInsn(inst);<br />
&nbsp;&nbsp;&nbsp; }<br />
}<br />
</span></span><span style="color: rgb(0, 0, 255);"><span style="color: rgb(0, 0, 255);"><span style="color: rgb(0, 0, 0);">AOP_LOG.java</span><br />
public class </span></span><span style="color: rgb(0, 0, 255);"><span style="color: rgb(0, 0, 255);">AOP_LOG</span></span><span style="color: rgb(0, 0, 255);"><span style="color: rgb(0, 0, 255);">{<br />
&nbsp;&nbsp;&nbsp; <br />
&nbsp;&nbsp;&nbsp; public static void start(String className, String methodName) {<br />
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; System.out.println(new StringBuilder(className)<br />
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; .append('"t')<br />
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; .append(methodName)<br />
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; .append(""tstart"t")<br />
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; .append(System.currentTimeMillis()));&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; <br />
&nbsp;&nbsp;&nbsp; }<br />
&nbsp;&nbsp;&nbsp; public static void end(String className, String methodName) {<br />
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; System.out.println(new StringBuilder(className)<br />
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; .append('"t')<br />
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; .append(methodName)<br />
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; .append(""tend"t")<br />
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; .append(System.currentTimeMillis()));&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; <br />
&nbsp;&nbsp;&nbsp; }<br />
}<br />
<span style="color: rgb(0, 0, 0);">这些类结合起来，就实现了在方法的调用前，会调用</span></span></span><span style="color: rgb(0, 0, 255);"><span style="color: rgb(0, 0, 255);"><span style="color: rgb(0, 0, 0);">AOP_LOG类的start方法，在方法返回后，调用</span></span></span><span style="color: rgb(0, 0, 255);"><span style="color: rgb(0, 0, 255);"><span style="color: rgb(0, 0, 0);">AOP_LOG类的</span></span></span><span style="color: rgb(0, 0, 255);"><span style="color: rgb(0, 0, 255);"><span style="color: rgb(0, 0, 0);">end方法。<br />
<br />
现在写个测试类来测试一下。<br />
</span></span></span><span style="color: rgb(0, 0, 255);"><span style="color: rgb(0, 0, 255);"><span style="color: rgb(0, 0, 0);">TSMain.java</span></span></span><br />
<span style="color: rgb(0, 0, 255);">public class TSMain {<br />
<br />
&nbsp;&nbsp;&nbsp; public static void main(String[] args) {<br />
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; TSMain tsm = new TSMain();<br />
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; tsm.printClassName();<br />
&nbsp;&nbsp;&nbsp; }<br />
&nbsp;&nbsp;&nbsp; <br />
&nbsp;&nbsp;&nbsp; public void printClassName(){<br />
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; System.out.println("hello "+this.getClass().getName());<br />
&nbsp;&nbsp;&nbsp; }<br />
}<br />
<br />
<span style="color: rgb(0, 0, 0);">运行命令如下：<br />
=&gt;java -javaagent:t.jar&nbsp; classpath <span style="font-weight: bold; font-style: italic;">cpdir</span> TSMain&nbsp; # </span></span><span style="color: rgb(0, 0, 255);"><span style="color: rgb(0, 0, 0);">cpdir 需要用到的类的路径</span></span><br />
<span style="color: rgb(0, 0, 255);"><span style="color: rgb(0, 0, 0);"><br />
输出结果如下：<br />
TSMain&nbsp;&nbsp;&nbsp; main&nbsp;&nbsp;&nbsp; start&nbsp;&nbsp;&nbsp; 1179374205562<br />
TSMain&nbsp;&nbsp;&nbsp; &lt;init&gt;&nbsp;&nbsp;&nbsp; start&nbsp;&nbsp;&nbsp; 1179374205562<br />
TSMain&nbsp;&nbsp;&nbsp; &lt;init&gt;&nbsp;&nbsp;&nbsp; end&nbsp;&nbsp;&nbsp; 1179374205562<br />
TSMain&nbsp;&nbsp;&nbsp; printClassName&nbsp;&nbsp;&nbsp; start&nbsp;&nbsp;&nbsp; 1179374205562<br />
hello TSMain<br />
TSMain&nbsp;&nbsp;&nbsp; printClassName&nbsp;&nbsp;&nbsp; end&nbsp;&nbsp;&nbsp; 1179374205562<br />
TSMain&nbsp;&nbsp;&nbsp; main&nbsp;&nbsp;&nbsp; end&nbsp;&nbsp;&nbsp; 1179374205562<br />
<br />
<br />
到这里就完成整个aop的功能实现了。需要补充说明的是，这里修改类的字节码是使用了asm的基础类库，所以需要导入这些asm的jar文件。</span></span>
<img src ="http://www.blogjava.net/sealyu/aggbug/191993.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/sealyu/" target="_blank">seal</a> 2008-04-10 22:30 <a href="http://www.blogjava.net/sealyu/articles/191993.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>RMI-IIOP 简介（转载）</title><link>http://www.blogjava.net/sealyu/articles/191989.html</link><dc:creator>seal</dc:creator><author>seal</author><pubDate>Thu, 10 Apr 2008 14:27:00 GMT</pubDate><guid>http://www.blogjava.net/sealyu/articles/191989.html</guid><wfw:comment>http://www.blogjava.net/sealyu/comments/191989.html</wfw:comment><comments>http://www.blogjava.net/sealyu/articles/191989.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/sealyu/comments/commentRss/191989.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/sealyu/services/trackbacks/191989.html</trackback:ping><description><![CDATA[<div>
<div>
<div><font>RMI 和 CORBA
常被视为相互竞争的技术，因为两者都提供对远程分布式对象的透明访问。但这两种技术实际上是相互补充的，一者的长处正好可以弥补另一者的短处。RMI
和 CORBA 的结合产生了 RMI-IIOP，RMI-IIOP 是企业服务器端 Java 开发的基础。在本文中，Java 开发者
Damian Hagge 简要介绍了 RMI-IIOP，然后为您展示如何构建和运行一个简单的、基于 Java 的 RMI-IIOP
客户机／服务器应用程序。请亲自看看 RMI 能在 IIOP 上工作得多么好。</font></div>
</div>
<div style="font-size: 14px; line-height: 20px;">
<p><font>1997 年，IBM 和 Sun Microsystems 启动了一项旨在促进 Java
作为企业开发技术的发展的合作计划。两家公司特别着力于如何将 Java
用作服务器端语言，生成可以结合进现有体系结构的企业级代码。所需要的就是一种远程传输技术，它兼有 Java 的 RMI（Remote
Method Invocation，远程方法调用）较少的资源占用量和更成熟的 CORBA（Common Object Request
Broker Architecture，公共对象请求代理体系结构）技术的健壮性。出于这一需要，RMI-IIOP 问世了，它帮助将 Java
语言推向了目前服务器端企业开发的主流语言的领先地位。</font></p>
<p><font>在本文中，我将简要介绍 RMI-IIOP，目标是使您能开始在企业开发解决方案中使用这一技术。要解释
RMI-IIOP 究竟是什么，我认为提供一些关于 CORBA 和 RMI 的信息是重要的，这些信息您在各个技术的典型介绍中可能找不到。如果您对
CORBA 或 RMI 的基础知识不熟悉，我建议您在往下读之前先阅读一些介绍性信息。请参阅<a href="http://www-900.ibm.com/developerWorks/cn/java/j-rmi-iiop/index.shtml#resources">参考资料</a>，那里挑选了一些文章和教程。</font></p>
<p><font>在我具体讨论 RMI-IIOP 之前，我们将先看一下 CORBA 和 RMI
用来对请求进行数据编入的机制。CORBA 将是我们的主要示例，因为 RMI-IIOP 数据编入是建立在 CORBA
传输协议（IIOP）的基础上的。我们将回顾一下该传输协议和 ORB（object request
broker，对象请求代理）在网络上发送请求、定位远程对象和传输对象方面的基本功能。</font></p>
<p><font><a id="1" name="1">远程对象传输</a><br />
对
CORBA 请求进行数据编入是通过使用 IIOP 协议做到的。简言之，IIOP 将以标准化格式构造的任何 IDL（Interface
Definition Language，接口定义语言）的元素表示为一系列字节。那就假设有一个 Java 客户机正在将一个 CORBA
请求分派到 C++ 服务器吧。客户机应用程序以 Java
接口的形式拥有远程对象的引用，并调用该接口的一个操作。本质上是，接口调用它对该操作的相应实现，这个实现将位于存根（stub）（存根是您将已经用
<code>idlj</code> 从 IDL 生成了的）。</font></p>
<p><font>存根把方法调用分派到 ORB 中，ORB 由两部分组成：客户机 ORB 和服务器 ORB。客户机
ORB 的职责是对请求进行数据编入，放到网络上，传往特定位置。服务器 ORB
的职责是侦听从网络上传下来的请求，并将这些请求转换成语言实现能够理解的方法调用。要了解对 CORBA ORB 的角色的更深入讨论，请参阅<a href="http://www-900.ibm.com/developerWorks/cn/java/j-rmi-iiop/index.shtml#resources">参考资料</a>部分。</font></p>
<p><font>存根分派了方法调用之后，客户机 ORB 将请求和所有参数转换成标准化字节格式，在这种情况中是
IIOP。接着，请求通过导线被发送到服务器 ORB，服务器 ORB 应该正在侦听传入请求。服务器端 ORB 将读进数据的字节并将请求转换成对
C++ 服务器实现有意义的东西。C++ 服务器方法将执行它的功能（即调用所请求的方法）并使用相同的机制通过 IIOP 将结果返回给客户机。</font></p>
<p>RMI 以类似的方式处理请求，但是它使用 JRMP（Java Remote Messaging Protocol，Java 远程消息传递协议）作为其传输协议。当然，RMI 传输还涉及 Java 对象的序列化。</p>
<table align="right" border="1" cellpadding="5" cellspacing="0" width="30%">
    <tbody>
        <tr>
            <td background="rmi-iiop.files/bg-gold.gif"><a id="sidebar1" name="sidebar1"><strong>CORBA 和 RMI 的差异</strong></a><br />
            <ul>
                <li>CORBA 运行在 IIOP 协议之上；RMI 使用 JRMP。
                </li>
                <li>CORBA 是独立于语言的；RMI 是纯粹 Java 到 Java 的。
                </li>
                <li>RMI 使用 JNDI 定位远程对象；CORBA 使用 CosNaming。
                </li>
                <li>RMI 会将对象序列化；CORBA 则不然。 </li>
            </ul>
            </td>
        </tr>
    </tbody>
</table>
<p><font><a id="2" name="2">远程对象定位</a><br />
CORBA
使用 CosNaming 命名服务定位远程对象。CosNaming 为名称服务器保存对 CORBA
服务器进程的绑定（或引用）提供了一个框架。当 CORBA 客户机向名称服务发送 CosNaming
请求，请求给定名称的服务器进程时，名称服务返回该进程的<em>可互操作对象引用</em>（interoperable object reference（IOR））。接着，客户机使用该 IOR 直接与服务器进程通信。</font></p>
<p>IOR 包含关于服务器进程的信息，例如服务器进程的位置。CosNaming 服务的缺点之一是，IOR 对人类而言是难以看懂的 — 至少对我们这些没有电子大脑的人来说是这样。相反地，RMI 对用户则要友好一些。它使用运行在 JNDI 之上的<em>注册中心</em>（与命名服务极为相似）来定位远程对象。RMI 注册中心使用 Java <code>Reference</code> 对象（它由若干个 <code>RefAddr</code> 对象组成）来识别和定位远程对象。这些 Java 对象比 IOR 对用户更加友好。</p>
<p><font>不久前，COBRA 将可互操作命名服务（Interoperable Naming
Service（INS））结合进了它的对象-定位（object-location）模式。INS 在 CosNaming
上运行，使用人类可以阅读的 URL 作它的对象位置。INS 不使用命名服务；相反地，它将调用直接发送到指定的 URL。请参阅<a href="http://www-900.ibm.com/developerWorks/cn/java/j-rmi-iiop/index.shtml#resources">参考资料</a>了解关于 INS 的更多信息。</font></p>
<p><font><a id="3" name="3">RMI 对 CORBA</a><br />
那
么，哪一个更好呢：是 CORBA 还是 RMI？答案取决于您想做什么。CORBA
是一个运行在业界标准的第三或第四代协议上的、经过试验和测试的大体系结构。如果考虑到 CORBA
提供的所有附件（例如：事务处理、安全拦截器、事件通道，还有更多）的话，则 CORBA 看来是企业应用程序的解决方案。CORBA
的最大缺点是它很复杂。要熟练使用 CORBA，开发者通常要经历陡峭的培训曲线。</font></p>
<p><font>相反地，RMI 相当容易学习。创建一个客户机／服务器实现，绑定到注册中心和远程对象，使用 RMI
调用和／或接收请求都相当简单。RMI 的资源占用量也比 CORBA 小得多，因为 JRMP 是开销比 IIOP 小得多的协议。但是，RMI
缺乏 CORBA 的工业级的附件，而且是纯基于 Java 的机制。那么，我们真正需要的就是 RMI 的灵活性和易用性以及 CORBA
的企业就绪性，对吗？那就开始讨论 RMI-IIOP 吧。</font></p>
<table align="right" border="1" cellpadding="5" cellspacing="0" width="30%">
    <tbody>
        <tr>
            <td background="rmi-iiop.files/bg-gold.gif"><a id="sidebar2" name="sidebar2"><strong>为什么是 RMI-IIOP？</strong></a><br />
            <ul>
                <li>RMI-IIOP 兼有 CORBA 的强度和 RMI 的灵活性。
                </li>
                <li>开发者很容易就可以使用 RMI-IIOP，RMI-IIOP 也易于集成到多数企业基础架构中。 </li>
            </ul>
            </td>
        </tr>
    </tbody>
</table>
<p><a id="4" name="4">RMI-IIOP 概览</a><br />
RMI-IIOP 让您仅需极少修改就可以在 IIOP 上运行 RMI 调用。借助于 RMI-IIOP，您可以编写简单易懂的 Java 代码，同时使用 CORBA 提供的丰富的企业功能套件。而且，代码的灵活性足够大，可以运行在 RMI <em>或</em> IIOP 上。这意味着，您的代码可以在纯 Java 环境中运行（当小的资源占用量和灵活性很关键时），或者对代码作少量修改后集成到现有的 CORBA 基础架构中。</p>
<p><font>RMI-IIOP 很强大的功能之一是，它让您编写纯 Java 客户机／服务器实现而不丧失 RMI
类序列化的灵活性。RMI-IIOP 通过覆盖 Java 序列化并在导线上将 Java 类转换成 IIOP 做到这一点。在另一端，Java
类被作为 IIOP 从导线上读下来，接着创建这个类的一个新实例（使用反射），类的所有成员的值都完整无缺 — <em>瞧</em>：这就是 IIOP 上的 Java 序列化！</font></p>
<p>为了让 RMI-IIOP 实现透明的对象定位，ORB 供应商历史上曾经使用 Java CosNaming 服务提供者（或用外行人的话说，是<em>插件</em>）。该插件在 JNDI API 之下工作，访问 CORBA 命名服务。尽管我没有在这里花篇幅来说明原因，但这种命名解决方案并不理想。其结果是，许多供应商 — 尤其是应用服务器供应商 — 为 RMI-IIOP 开发了专门的对象定位机制。</p>
<p>RMI-IIOP 也支持作为 Java CosNaming 服务的一个扩展的 INS。因为我相信 INS 将确定对象定位的未来方向，所以我们在本文将讨论的代码示例使用 INS。</p>
<p><strong>注：</strong>因为 Sun 尚未完全遵循 OMG INS 标准，也尚未公开 <code>org.omg.CORBA.ORB</code> 接口的 <code>register_initial_reference</code>，所以本文提供的源代码将不能与 Sun JDK 一起工作。您将需要 IBM Developer Kit for Java technology，版本 1.3.1 或更高版本。不过，我已经创建了一个使用命名服务的与 Sun 兼容的示例，您可以从<a href="http://www-900.ibm.com/developerWorks/cn/java/j-rmi-iiop/index.shtml#resources">参考资料</a>部分下载它。</p>
<p><font><a id="5" name="5">自己动手构建 RMI-IIOP</a><br />
说
得够多了，让我们来编写代码吧！在以下几部分中，我们将构建一个简单的、基于 Java 的客户机／服务器 RMI-IIOP
应用程序。这个应用程序由三个部分组成：RMI 接口、服务器应用程序和客户机应用程序。示例以在 IIOP 之上的 Java
序列化为特色，所以您可以看到 Java 类如何被客户机实例化，如何传递到服务器，由服务器更改，然后将所有修改完整地回传到客户机。</font></p>
<p><a id="6" name="6">第 1 部分：定义接口</a><br />
在 RMI-IIOP 下，我们可以选择使用 RMI 或 IDL 来定义接口。因为我们想看看 RMI 如何运行在 IIOP 上，所以我们将使用 RMI 定义示例接口。清单 1 是我们的简单示例的 RMI 接口：</p>
<p><a id="code1" name="code1"><strong>清单 1. RMIInterface.java</strong></a></p>
<table bgcolor="#cccccc" border="1" cellpadding="5" cellspacing="0" width="100%">
    <tbody>
        <tr>
            <td>
            <pre><code>/* * Remote interface */public interface RMIInterface extends java.rmi.Remote {    public String hello() throws java.rmi.RemoteException;    public SerClass alterClass(SerClass classObject)        throws java.rmi.RemoteException;}</code></pre>
            </td>
        </tr>
    </tbody>
</table>
<font><br />
<br />
</font>
<p><code>RMIInterface</code> 定义一个 <code>hello()</code> 方法和一个 <code>alterClass(SerClass)</code> 方法。后一个方法用 <code>SerClass</code> 作参数，<code>SerClass</code> 是一个实现 <code>Serializable</code> 的 Java 类，<code>alterClass(SerClass)</code> 方法返回一个类型与其参数的类型相同的类。<code>SerClass</code> 是一个有几个成员的简单的类，每个成员有相应的 getter 方法。这些方法如清单 2 所示：</p>
<font><a id="code1" name="code1"><strong>清单 2. SerClass.java</strong></a>
<table bgcolor="#cccccc" border="1" cellpadding="5" cellspacing="0" width="100%">
    <tbody>
        <tr>
            <td>
            <pre><code>/** *  This class is intended to be serialized over RMI-IIOP. */public class SerClass implements java.io.Serializable {    // members    private int x;    private String myString;    // constructor    public SerClass(int x, String myString)        throws java.rmi.RemoteException {        this.x=x;        this.myString=myString;    }         // some accessor methods    public int getX() {  return x;}    public void setX(int x) { this.x=x; }    public String getString() {  return myString;  }    public void setString(String str) { myString=str; }}</code></pre>
            </td>
        </tr>
    </tbody>
</table>
</font>
<p>这就是我们简单的接口的全部。现在我们来研究一下服务器类。</p>
<p><a id="7" name="7">第 2 部分：构建服务器</a><br />
我们将使用一个既充当 <code>RMIInterface</code> 实现类又包含 main 方法（以启动我们的服务）的服务器类（<code>Server.java</code>）。<code>Server.java</code> 继承 <code>javax.rmi.PortableRemoteObject</code>。这样，它就包含了将自己作为 <code>Remote</code> 接口绑定到 ORB 和开始侦听请求所需要的全部功能。清单 3 是该服务器的代码：</p>
<font><a id="code1" name="code1"><strong>清单 3. Server.java</strong></a>
<table bgcolor="#cccccc" border="1" cellpadding="5" cellspacing="0" width="100%">
    <tbody>
        <tr>
            <td>
            <pre><code>/* * Simple server */import java.util.*;import java.rmi.Remote;import java.rmi.RemoteException;import javax.rmi.PortableRemoteObject;import javax.rmi.CORBA.Tie;import javax.rmi.CORBA.Util;import org.omg.PortableServer.POA;import org.omg.PortableServer.*;import org.omg.PortableServer.Servant;import org.omg.CORBA.ORB;public class Server extends PortableRemoteObject     implements RMIInterface {    // must explicitly create default constructor     // to throw RemoteException    public Server() throws RemoteException {    }    // implementation of RMIInterface methods    public String hello() throws RemoteException {        return "Hello there!";    }    public SerClass alterClass(SerClass classObject)         throws RemoteException {        // change the values of SerClass and return it.    // add 5 to X        classObject.setX(            classObject.getX() + 5 );     // alter the string        classObject.setString(            classObject.getString() + " : I've altered you" );         return classObject;    }       public static void main(String[] args) {        try {            // create the ORB passing in the port to listen on            Properties props = new Properties();            props.put("com.ibm.CORBA.ListenerPort","8080");            ORB orb = ORB.init(args, props);                // instantiate the Server            // this will automatically call exportObject(this)            Server s = new Server();                        // now get the Stub for our server object -          // this will be both            // a remote interface and an org.omg.CORBA.Object            Remote r=PortableRemoteObject.toStub(s);                                // register the process under the name          // by which it can be found                ((com.ibm.CORBA.iiop.ORB)orb).            register_initial_reference("OurLittleClient",            (org.omg.CORBA.Object)r);                System.out.println("Hello Server waiting...");            // it's that easy -         // we're registered and listening for incoming requests            orb.run();        } catch (Exception e) {            e.printStackTrace();        }    }}</code></pre>
            </td>
        </tr>
    </tbody>
</table>
</font>
<p><a id="8" name="8">呃，这里发生着什么呢？</a><br />
服务器应用程序的代码很长，那我们就分开来讲吧。首先，如前面提到过的，<code>Server</code> 类实现 <code>RMIInterface</code> 并为它的所有方法提供实现。您可以在代码的前面部分看到 <code>RMIInterface</code> 的 <code>hello()</code> 方法和 <code>alterClass(SerClass)</code> 方法的实现。<code>hello()</code> 方法只是返回字符串&#8220;Hello there!&#8221;。<code>alterClass(SerClass)</code> 方法用 <code>SerClass</code> 对象作参数，修改成员的值，然后返回新的对象 — 全都通过 RMI-IIOP。</p>
<p><code>Server.java</code> 的 main 方法初始化一个 ORB。这个 ORB 将设置为 8080 的 <code>com.ibm.CORBA.ListenerPort</code> 属性作为参数传入。这将使得 ORB 在端口 8080 上侦听传入请求。请注意，<code>com.ibm.CORBA.ListenerPort</code> 是一个专有的 IBM 属性。如果您想在另一供应商的 ORB 上运行这些代码，那您应该参阅该供应商的文档，找到适当的属性。（Sun 使用 <code>com.sun.CORBA.POA.ORBPersistentServerPort</code>，但它只在您使用 POA（portable object adapter，可移植对象适配器）伺服器（servant）时才能够工作。）</p>
<p>初始化 ORB 后，main 方法接着对 <code>Server</code> 对象进行实例化。因为这个 server 对象也是一个 <code>PortableRemoteObject</code>，所以缺省构造函数会自动调用 <code>exportObject(this)</code>。这个对象现在已经就绪于接收远程调用。</p>
<p>接着，我们需要通过调用 <code>ORB.register_initial_reference(String,orb.omg.CORBA.Object)</code> 注册这个对象。为此，我们需要把我们的 server 对象作为 <code>org.omg.CORBA.Object</code> 的引用。调用 <code>PortableRemoteObject.toStub(s)</code> 实现了这一点，因为所返回的对象都实现了 <code>java.rmi.Remote</code> 和 <code>org.omg.CORBA.Object</code>。</p>
<p><font>然后，返回的 <code>org.omg.CORBA.Object</code> 对象向服务器端 ORB 注册为&#8220;OurLittleClient&#8221;。为了确保 INS 请求能够定位对象，我们使用注册调用 <code>register_initial_reference</code>。
当 INS 调用进入 ORB 时，ORB
将查找已经以正在被请求的名称注册的对象。由于我们将对象注册为&#8220;OurLittleClient&#8221;，所以，当一个 INS 调用进入我们的服务器
ORB 要求&#8220;OurLittleClient&#8221;时，我们将知道客户机正在查找的是哪个对象。</font></p>
<p>最后，我确信您已经注意到我们将 ORB 强制转型成 <code>com.ibm.CORBA.iiop.ORB</code>。因为 Sun 尚未公开 <code>org.omg.CORBA.ORB</code> 接口的 <code>register_initial_reference</code>，所以 IBM SDK 也不能将它公开。因此，我们必须将我们的 ORB 强制转型成 IBM ORB。随着 Sun 越来越遵循 OMG，JDK 的未来版本（1.4.0 后）将可能不需要这种强制转型。</p>
<p>就是这样！<em>很简单吧</em> — 嗯，是有点。我们的服务器现在正在等待传入客户机 INS 请求。但客户机怎么样呢？</p>
<p><a id="9" name="9">第 3 部分：构建客户机</a><br />
客户机应用程序的代码如清单 4 所示：</p>
<font><a id="code1" name="code1"><strong>清单 4. Client.java</strong></a>
<table bgcolor="#cccccc" border="1" cellpadding="5" cellspacing="0" width="100%">
    <tbody>
        <tr>
            <td>
            <pre><code>/* * Client application */import javax.rmi.PortableRemoteObject;import org.omg.CORBA.ORB;public class Client {  public static void main(String[] args) {    try {      ORB orb = ORB.init(args, null);             // here's the URL for the local host         String INSUrl =         "corbaloc:iiop:1.2@localhost:8080/OurLittleClient";                  // get the reference to the remote process         org.omg.CORBA.Object objRef=orb.string_to_object(INSUrl);         // narrow it into our RMIInterface         RMIInterface ri =   (RMIInterface)PortableRemoteObject.narrow(objRef, RMIInterface.class);              // call the hello method         System.out.println("received from server: "+ri.hello()+""n");            // try RMI serialization         SerClass se = new SerClass(5, "Client string! ");         // pass the class to be altered on the server         // of course behind the scenes this class is being       // serialized over IIOP         se = ri.alterClass(se);         // now let's see the result         System.out.println("Serialization results :"n"+            "Integer was 5 now is "+se.getX()+""n"+            "String was ""Client String! ""          now is """+se.getString()+"""");           } catch (Exception e) {            e.printStackTrace();        }    }}</code></pre>
            </td>
        </tr>
    </tbody>
</table>
</font>
<p><a id="10" name="10">如何分解客户机代码</a><br />
客户机代码比服务器代码要简单一些。我们初始化一个 ORB，然后调用 <code>string_to_object(String)</code>，其中的 string 是我们的 INS URL。构造 INS URL 相当简单：首先，我们指定我们使用 <em>corbaloc URL</em>（请参阅<a href="http://www-900.ibm.com/developerWorks/cn/java/j-rmi-iiop/index.shtml#resources">参考资料</a>）和 IIOP 协议版本 1.2。接着，我们将主机名（www.whatever.com）和要连接的端口添加进去。最后，我们指定我们要查找的服务的名称。结果 INS URL 是 <em>corbaloc:iiop:1.2@localhost:8080/OurLittleClient</em>。</p>
<p>当我们将这个 URL 传递到 <code>ORB.string_to_object(String)</code> 时，ORB 将分派一个请求到所指定的服务器，以请求所请求的服务。假设一切运转正常，则 ORB 将接收回该服务的一个对象引用（实际上是一个 IOR）。然后，我们将该对象引用强制转型（narrow）成我们能够使用的东西，即 <code>RMIInterface</code>，这样，我们就为开始调用方法做好了准备。</p>
<p><font>在调用了简单的 hello 方法（它应该不需要任何解释吧）之后，我们可以开始探讨 RMI-IIOP 的序列化功能了。首先，我们创建一个 <code>SerClass</code>，
一个可序列化的 Java 类，并初始化它的成员变量。接着，我们将这个类传入到我们的方法，方法通过 IIOP
将类写出到服务器。服务器读入类并将它重创建为服务器端 Java 对象，修改它的成员值，然后返回它（使用
IIOP）作为方法的返回值。当接收到在远程方法调用之后重创建的对象时，我们看到它的成员确实已被服务器修改了。就是这么简单：在 IIOP 上进行
Java 序列化。</font></p>
<p><font><a id="11" name="11">第 4 部分：运行示例</a><br />
请注意，我们这里所创建的示例必须在 IBM Developer Kit for Java technology，版本 1.3.1 或更高版本中运行。如果您宁愿使用 Sun JDK，请下载<a href="http://www-900.ibm.com/developerWorks/cn/java/j-rmi-iiop/rmiiiop-Sun_example.zip">特定于 Sun 的源代码</a>，
您应该在 Sun 1.4.0 JDK 或更高版本中运行它。这个源代码包括一个解释 IBM SDK 版本和 Sun JDK 版本之间的差异的
readme.txt 文件。如果您没有 IBM Developer Kit for Java technology（而您又想要一个），请<a href="http://www-105.ibm.com/developerWorks/tools.nsf/dw/java-devkits-byname">现在就下载一个</a>；它们是免费的。</font></p>
<p>这里是运行示例的步骤：</p>
<ol><font>
    <li>下载<a href="http://www-900.ibm.com/developerWorks/cn/java/j-rmi-iiop/rmiiiop-IBM_example.zip">源文件</a>。<br />
    <br />
    </li>
    <li>输入 <code>javac *.java</code>，javac 所有文件。<br />
    <br />
    </li>
    <li>对 server 类运行 <code>rmic</code>（带 IIOP 标志）：<code>rmic -iiop Server</code>。<br />
    <br />
    </li>
    <li>启动服务器：在 Windows 中，请输入 <code>start java Server</code>。<br />
    <br />
    </li>
    <li>启动客户机：在 Windows 中，请输入 <code>start java Client</code>。 </li>
    </font></ol>
    <font><br />
    <br />
    </font>
    <p><font><a id="12" name="12">关于 RMI-IIOP 和 EJB 组件的一点注释</a><br />
    EJB
    2.0 规范指出，EJB 组件必须能在 RMI 和 RMI-IIOP 上运行。添加 RMI-IIOP 作为针对 EJB
    组件的在线协议，已经给将 J2EE 环境集成到现有的企业基础设施（多数是 CORBA 相当密集的）带来了很大帮助。但它也引起了一些问题。</font></p>
    <p><font>简单地说，就是将定制构建的组件和 EJB
    组件集成起来要求您（开发者）处理管道（plumbing），否则在 EJB
    体系结构中它们对您来说将很抽象。到目前为止，还没有解决这个问题的简单方案，可能永远也不会有。随着诸如 Web
    服务这样的技术的发展，或许会出现解决方案，但目前尚未可知。</font></p>
    <p><a id="13" name="13">结束语：此后该做什么</a><br />
    我希望本文已经向您展示了构建和运行 RMI-IIOP 客户机／服务器应用程序是多么容易。您可以修改一下我们使用的示例，用纯 CORBA 替代客户机或服务器，不过这样做将除去您应用程序中的 Java 序列化。</p>
    <p><font>如果您想在 CORBA 环境中使用 RMI-IIOP，那么看看 IDL 如何映射成 Java 以及
    Java 如何映射成 IDL 是值得的。如果您想在不安全的环境（即不是您自己的 PC）中部署 RMI-IIOP，那么研究一下 CORBA
    安全功能（如拦截器和 CORBA 安全模型）以及其它 CORBA 企业功能（如事务处理）是个不错的主意。CORBA 所有的丰富功能在您运行
    RMI-IIOP 时都可以使用。</font></p>
    <p><a id="resources" name="resources">参考资料</a></p>
    <p><br />
    </p>
    <ul><font>
        <li>下载<a href="http://www-900.ibm.com/developerWorks/cn/java/j-rmi-iiop/rmiiiop-IBM_example.zip">本文与 IBM 兼容的源代码</a>。<br />
        <br />
        </li>
        <li>下载<a href="http://www-900.ibm.com/developerWorks/cn/java/j-rmi-iiop/rmiiiop-Sun_example.zip">本文与 Sun 兼容的源代码</a>。<br />
        <br />
        </li>
        <li>IIOP 协议是由<a href="http://www.omg.org/">对象管理组织（Object Management Group（OMG））</a>制定的，这个组织还制定并维护着 CORBA 规范。<br />
        <br />
        </li>
        <li>要更多了解 CORBA，请访问 OMG 的 <a href="http://www.corba.org/">CORBA Web 站点</a>。<br />
        <br />
        </li>
        <li>要更多了解 RMI，请访问 <a href="http://java.sun.com/products/jdk/rmi/">RMI 主页</a>。<br />
        <br />
        </li>
        <li>&#8220;The Java Developer Connection&#8221;提供了一篇 <a href="http://java.sun.com/j2se/1.4/docs/guide/idl/INStutorial.html">INS 教程</a>，它也对命名服务、CosNaming 服务和 corbaloc URL 格式做了一般介绍。<br />
        <br />
        </li>
        <li>想探索一下您有哪些 Java 技术选择吗？请参阅 <a href="http://www-106.ibm.com/developerWorks/java/jdk/index.html">IBM Developer Kit for Java technology</a> 的完整清单。<br />
        <br />
        </li>
        <li>要更多了解 IBM Developer Kit for Java technology，版本 1.3 和 RMI-IIOP，请参阅 <a href="http://www-900.ibm.com/developerWorks/cn/cgi-bin/click.cgi?url=http://robocode.alphaworks.ibm.com/docs/jdk1.3/api/javax/rmi/CORBA/package-tree.html&amp;origin=j">javax.rmi.CORBA 类层次结构</a>。<br />
        <br />
        </li>
        <li>要获得对用 RMI 和 CORBA 编程的更进一步介绍，请学习教程&#8220;<a href="http://www-105.ibm.com/developerWorks/education.nsf/java-onlinecourse-bytitle/92336034DA5B81FB85256758000A3D1B?OpenDocument">RMI, CORBA, and Distributed Objects</a>&#8221;（<em>developerWorks</em>，2000 年 10 月）。<br />
        <br />
        </li>
        <li>如果您是用 EJB 组件编程的新手，您可能需要学习教程&#8220;<a href="http://www-105.ibm.com/developerWorks/education.nsf/java-onlinecourse-bytitle/EB2ADE177F8C3EF386256A0A006DCBCD?OpenDocument">EnterpriseJavaBeans fundamentals</a>&#8221;（<em>developerWorks</em>，2001 年 3 月）。<br />
        <br />
        </li>
        <li>要更多了解 EJB 技术和 CORBA 之间的关系，请参阅 Ken Nordby 的&#8220;<a href="http://www-106.ibm.com/developerWorks/java/library/what-are-ejbs/part3/index.html">Deployingand using Enterprise JavaBeans components</a>&#8221;— 对 EJB 技术的分为三个部分的介绍的第 3 部分（<em>developerWorks</em>，2000 年 6 月）。<br />
        <br />
        </li>
        <li>RMI-IIOP 的主要设计师在 <em>JavaWorld</em> 上的文章&#8220;<a href="http://www.javaworld.com/jw-12-1999/jw-12-iiop.html">RMIover IIOP</a>&#8221;讲述了他对此项技术的看法（<em>JavaWorld</em>，1999 年 12 月）。<br />
        <br />
        </li>
        <li>要全方位了解 RMI-IIOP，请参阅 theServerSide.com 上的文章&#8220;<a href="http://www.theserverside.com/resources/article.jsp?l=RMI-IIOP">RMI/IIOP, nice idea but the reality is turning out to be different</a>&#8221;，它着重讲述 RMI-IIOP <em>不</em>提供的东西（TheServerside.com）。<br />
        </li>
        </font></ul>
        </div>
        </div>
        <table>
            <tbody>
                <tr>
                    <td><br />
                    </td>
                </tr>
            </tbody>
        </table>
<img src ="http://www.blogjava.net/sealyu/aggbug/191989.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/sealyu/" target="_blank">seal</a> 2008-04-10 22:27 <a href="http://www.blogjava.net/sealyu/articles/191989.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>LDAP简介（转载）</title><link>http://www.blogjava.net/sealyu/articles/191987.html</link><dc:creator>seal</dc:creator><author>seal</author><pubDate>Thu, 10 Apr 2008 14:25:00 GMT</pubDate><guid>http://www.blogjava.net/sealyu/articles/191987.html</guid><wfw:comment>http://www.blogjava.net/sealyu/comments/191987.html</wfw:comment><comments>http://www.blogjava.net/sealyu/articles/191987.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/sealyu/comments/commentRss/191987.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/sealyu/services/trackbacks/191987.html</trackback:ping><description><![CDATA[<div>
<div>LDAP</div>
<table wr="" border="0" cellpadding="0" cellspacing="0" width="100%">
    <tbody>
        <tr>
            <td>
            <div p14="">LDAP的英文全称是Lightweight Directory Access Protocol，一般都简称为LDAP。它是基于<a href="http://baike.baidu.com/view/487586.htm" target="_blank">X.500</a>标准的，但是简单多了并且可以根据需要定制。与X.500不同，LDAP支持<a href="http://baike.baidu.com/view/7729.htm" target="_blank">TCP/IP</a>，
            这对访问Internet是必须的。LDAP的核心规范在RFC中都有定义，所有与LDAP相关的RFC都可以在LDAPman
            RFC网页中找到。现在LDAP技术不仅发展得很快而且也是激动人心的。在企业范围内实现LDAP可以让运行在几乎所有计算机平台上的所有的应用程序从
            LDAP目录中获取信息。LDAP目录中可以存储各种类型的数据：电子邮件地址、邮件路由信息、人力资源数据、公用密匙、联系人列表，等等。通过把
            LDAP目录作为系统集成中的一个重要环节，可以简化员工在企业内部查询信息的步骤，甚至连主要的数据源都可以放在任何地方。<br />
            <br />
            LDAP目录的优势<br />
            <br />
            如
            果需要开发一种提供公共信息查询的系统一般的设计方法可能是采用基于WEB的数据库设计方式，即前端使用浏览器而后端使用WEB服务器加上关系数据库。后
            端在Windows的典型实现可能是Windows NT + IIS +
            Acess数据库或者是SQL服务器，IIS和数据库之间通过ASP技术使用ODBC进行连接，达到通过填写表单查询数据的功能；<br />
            <br />
            后端在
            Linux系统的典型实现可能是Linux+ Apache + postgresql，Apache
            和数据库之间通过PHP3提供的函数进行连接。使用上述方法的缺点是后端关系数据库的引入导致系统整体的性能降低和系统的管理比较繁琐，因为需要不断的进
            行数据类型的验证和事务的完整性的确认；并且前端用户对数据的控制不够灵活，用户权限的设置一般只能是设置在表一级而不是设置在记录一级。<br />
            <br />
            目
            录服务的推出主要是解决上述数据库中存在的问题。目录与关系数据库相似，是指具有描述性的基于属性的记录集合，但它的数据类型主要是字符型，为了检索的需
            要添加了BIN
            （二进制数据）、CIS（忽略大小写）、CES（大小写敏感）、TEL（电话型）等语法（Syntax），而不是关系数据库提供的整数、浮点数、日期、货
            币等类型，同样也不提供象关系数据库中普遍包含的大量的函数，它主要面向数据的查询服务（查询和修改操作比一般是大于10:1），不提供事务的回滚
            （rollback）机制，它的数据修改使用简单的锁定机制实现All-or-Nothing，它的目标是快速响应和大容量查询并且提供多目录服务器的信
            息复制功能。<br />
            <br />
            现在该说说LDAP目录到底有些什么优势了。现在LDAP的流行是很多因数共同作用的结果。可能LDAP最大的优势是：可以在任何计算机平台上，用很容易获得的而且数目不断增加的LDAP的客户端程序访问LDAP目录。而且也很容易定制应用程序为它加上LDAP的支持。<br />
            <br />
            LDAP
            协议是跨平台的和标准的协议，因此应用程序就不用为LDAP目录放在什么样的服务器上操心了。实际上，LDAP得到了业界的广泛认可，因为它是
            Internet的标准。产商都很愿意在产品中加入对LDAP的支持，因为他们根本不用考虑另一端（客户端或服务端）是怎么样的。LDAP服务器可以是任
            何一个开发源代码或商用的LDAP目录服务器（或者还可能是具有LDAP界面的关系型数据库），因为可以用同样的协议、客户端连接软件包和查询命令与
            LDAP服务器进行交互。与LDAP不同的是，如果软件产商想在软件产品中集成对DBMS的支持，那么通常都要对每一个数据库服务器单独定制。不象很多商
            用的关系型数据库，你不必为LDAP的每一个客户端连接或许可协议付费 大多数的LDAP服务器安装起来很简单，也容易维护和优化。<br />
            <br />
            LDAP服务器可以用&#8220;推&#8221;或&#8220;拉&#8221;的方法复制部分或全部数据，例如：可以把数据&#8220;推&#8221;到远程的办公室，以增加数据的安全性。复制技术是内置在LDAP服务器中的而且很容易配置。如果要在DBMS中使用相同的复制功能，数据库产商就会要你支付额外的费用，而且也很难管理。<br />
            <br />
            LDAP
            允许你根据需要使用ACI（一般都称为ACL或者访问控制列表）控制对数据读和写的权限。例如，设备管理员可以有权改变员工的工作地点和办公室号码，但是
            不允许改变记录中其它的域。ACI可以根据谁访问数据、访问什么数据、数据存在什么地方以及其它对数据进行访问控制。因为这些都是由LDAP目录服务器完
            成的，所以不用担心在客户端的应用程序上是否要进行安全检查。<br />
            <br />
            LDAP（Lightweight Directory Acess Protocol）是目录服务在TCP/IP上的实现（RFC 1777 V2版和RFC 2251<br />
            <br />
            V3版）。它是对X500的目录协议的移植，但是简化了实现方法，所以称为轻量级的目录服务。在LDAP中目录是按照树型结构组织，目录由条目（Entry）组成，条目相当于关系数据库中表的记录；条目是具有区别名DN（Distinguished<br />
            <br />
            Name）的属性（Attribute）集合，DN相当于关系数据库表中的关键字（Primary<br />
            <br />
            Key）；
            属性由类型（Type）和多个值（Values）组成，相当于关系数据库中的域（Field）由域名和数据类型组成，只是为了方便检索的需要，LDAP中
            的Type可以有多个Value，而不是关系数据库中为降低数据的冗余性要求实现的各个域必须是不相关的。LDAP中条目的组织一般按照地理位置和组织关
            系进行组织，非常的直观。LDAP把数据存放在文件中，为提高效率可以使用基于索引的文件数据库，而不是关系数据库。LDAP协议集还规定了DN的命名方
            法、存取控制方法、搜索格式、复制方法、URL格式、开发接口等<br />
            <br />
            LDAP对于这样存储这样的信息最为有用，也就是数据需要从不同的地点读取，但是不需要经常更新。<br />
            <br />
            例如，这些信息存储在LDAP目录中是十分有效的：<br />
            <br />
            l 公司员工的电话号码簿和组织结构图<br />
            <br />
            l 客户的联系信息<br />
            <br />
            l 计算机管理需要的信息，包括NIS映射、email假名，等等<br />
            <br />
            l 软件包的配置信息<br />
            <br />
            l 公用证书和安全密匙<br />
            <br />
            什么时候该用LDAP存储数据<br />
            <br />
            大
            多数的LDAP服务器都为读密集型的操作进行专门的优化。因此，当从LDAP服务器中读取数据的时候会比从专门为OLTP优化的关系型数据库中读取数据快
            一个数量级。也是因为专门为读的性能进行优化，大多数的LDAP目录服务器并不适合存储需要经常改变的数据。例如，用LDAP服务器来存储电话号码是一个
            很好的选择，但是它不能作为电子商务站点的数据库服务器。<br />
            <br />
            如果下面每一个问题的答案都是&#8220;是&#8221;，那么把数据存在LDAP中就是一个好主意。<br />
            <br />
            l 需要在任何平台上都能读取数据吗？<br />
            <br />
            l 每一个单独的记录项是不是每一天都只有很少的改变？<br />
            <br />
            l 可以把数据存在平面数据库（flat database）而不是关系型数据库中吗？换句话来说，也就是不管什么范式不范式的，把所有东西都存在一个记录中（差不多只要满足第一范式）。<br />
            <br />
            最
            后一个问题可能会唬住一些人，其实用平面数据库去存储一些关系型的数据也是很一般的。例如，一条公司员工的记录就可以包含经理的登录名。用LDAP来存储
            这类信息是很方便的。一个简单的判断方法：如果可以把保数据存在一张张的卡片里，就可以很容易地把它存在LDAP目录里。<br />
            <br />
            安全和访问控制<br />
            <br />
            LDAP提供很复杂的不同层次的访问控制或者ACI。因这些访问可以在服务器端控制，这比用客户端的软件保证数据的安全可安全多了。<br />
            <br />
            用LDAP的ACI，可以完成：<br />
            <br />
            l 给予用户改变他们自己的电话号码和家庭地址的权限，但是限制他们对其它数据（如，职务名称，经理的登录名，等等）只有&#8220;只读&#8221;权限。<br />
            <br />
            l 给予&#8220;HR-admins"组中的所有人权限以改变下面这些用户的信息：经理、工作名称、员工号、部门名称和部门号。但是对其它域没有写权限。<br />
            <br />
            l 禁止任何人查询LDAP服务器上的用户口令，但是可以允许用户改变他或她自己的口令。<br />
            <br />
            l 给予经理访问他们上级的家庭电话的只读权限，但是禁止其他人有这个权限。<br />
            <br />
            l 给予&#8220;host-admins"组中的任何人创建、删除和编辑所有保存在LDAP服务器中的与计算机主机有关的信息<br />
            <br />
            l 通过Web，允许&#8220;foobar-sales"组中的成员有选择地给予或禁止他们自己读取一部分客户联系数据的读权限。这将允许他们把客户联系信息下载到本地的笔记本电脑或个人数字助理（PDA）上。（如果销售人员的软件都支持LDAP，这将非常有用）<br />
            <br />
            l
            通过Web，允许组的所有者删除或添加他们拥有的组的成员。例如：可以允许销售经理给予或禁止销售人员改变Web页的权限。也可以允许邮件假名（mail
            aliase）的所有者不经过IT技术人员就直接从邮件假名中删除或添加用户。&#8220;公用&#8221;的邮件列表应该允许用户从邮件假名中添加或删除自己（但是只能是自
            己）。也可以对IP地址或主机名加以限制。例如，某些域只允许用户IP地址以192.168.200.*开头的有读的权限，或者用户反向查找DNS得到的
            主机名必须为*.foobar.com。<br />
            <br />
            LDAP目录树的结构<br />
            <br />
            LDAP目录以树状的层次结构来存储数据。如果你对自顶向
            下的DNS树或UNIX文件的目录树比较熟悉，也就很容易掌握LDAP目录树这个概念了。就象DNS的主机名那样，LDAP目录记录的标识名
            （Distinguished Name，简称DN）是用来读取单个记录，以及回溯到树的顶部。后面会做详细地介绍。<br />
            <br />
            为什么要用层次结构来组织数据呢？原因是多方面的。下面是可能遇到的一些情况：<br />
            <br />
            l 如果你想把所有的美国客户的联系信息都&#8220;推&#8221;到位于到西雅图办公室（负责营销）的LDAP服务器上，但是你不想把公司的资产管理信息&#8220;推&#8221;到那里。<br />
            <br />
            l 你可能想根据目录树的结构给予不同的员工组不同的权限。在下面的例子里，资产管理组对&#8220;asset-mgmt"部分有完全的访问权限，但是不能访问其它地方。<br />
            <br />
            l 把LDAP存储和复制功能结合起来，可以定制目录树的结构以降低对WAN带宽的要求。位于西雅图的营销办公室需要每分钟更新的美国销售状况的信息，但是欧洲的销售情况就只要每小时更新一次就行了。<br />
            <br />
            刨根问底：基准DN<br />
            <br />
            LDAP目录树的最顶部就是根，也就是所谓的&#8220;基准DN"。基准DN通常使用下面列出的三种格式之一。假定我在名为FooBar的电子商务公司工作，这家公司在Internet上的名字是foobar.com。<br />
            <br />
            o="FooBar, Inc.", c=US<br />
            <br />
            （以X.500格式表示的基准DN）<br />
            <br />
            在
            这个例子中，o=FooBar, Inc. 表示组织名，在这里就是公司名的同义词。c=US
            表示公司的总部在美国。以前，一般都用这种方式来表示基准DN。但是事物总是在不断变化的，现在所有的公司都已经（或计划）上Internet上。随着
            Internet的全球化，在基准DN中使用国家代码很容易让人产生混淆。现在，X.500格式发展成下面列出的两种格式。<br />
            <br />
            o=foobar.com<br />
            <br />
            （用公司的Internet地址表示的基准DN）<br />
            <br />
            这种格式很直观，用公司的域名作为基准DN。这也是现在最常用的格式。<br />
            <br />
            dc=foobar, dc=com<br />
            <br />
            （用DNS域名的不同部分组成的基准DN）<br />
            <br />
            就
            象上面那一种格式，这种格式也是以DNS域名为基础的，但是上面那种格式不改变域名（也就更易读），而这种格式把域名：
            foobar.com分成两部分 dc=foobar,
            dc=com。在理论上，这种格式可能会更灵活一点，但是对于最终用户来说也更难记忆一点。考虑一下foobar.com这个例子。当
            foobar.com和gizmo.com合并之后，可以简单的把&#8220;dc=com"当作基准DN。把新的记录放到已经存在的dc=gizmo,
            dc=com目录下，这样就简化了很多工作（当然，如果foobar.com和wocket.edu合并，这个方法就不能用了）。如果LDAP服务器是新
            安装的，我建议你使用这种格式。再请注意一下，如果你打算使用活动目录（Actrive
            Directory），Microsoft已经限制你必须使用这种格式。<br />
            <br />
            更上一层楼：在目录树中怎么组织数据<br />
            <br />
            在UNIX文件系统中，最顶层是根目录（root）。在根目录的下面有很多的文件和目录。象上面介绍的那样，LDAP目录也是用同样的方法组织起来的。<br />
            <br />
            在
            根目录下，要把数据从逻辑上区分开。因为历史上（X.500）的原因，大多数LDAP目录用OU从逻辑上把数据分开来。OU表示
            &#8220;Organization
            Unit"，在X.500协议中是用来表示公司内部的机构：销售部、财务部，等等。现在LDAP还保留ou=这样的命名规则，但是扩展了分类的范围，可以
            分类为：ou=people, ou=groups,
            ou=devices，等等。更低一级的OU有时用来做更细的归类。例如：LDAP目录树（不包括单独的记录）可能会是这样的：<br />
            <br />
            dc=foobar, dc=com<br />
            <br />
            ou=customers<br />
            <br />
            ou=asia<br />
            <br />
            ou=europe<br />
            <br />
            ou=usa<br />
            <br />
            ou=employees<br />
            <br />
            ou=rooms<br />
            <br />
            ou=groups<br />
            <br />
            ou=assets-mgmt<br />
            <br />
            ou=nisgroups<br />
            <br />
            ou=recipes<br />
            <br />
            单独的LDAP记录<br />
            <br />
            DN是LDAP记录项的名字<br />
            <br />
            在LDAP目录中的所有记录项都有一个唯一的&#8220;Distinguished Name"，也就是DN。每一个LDAP记录项的DN是由两个部分组成的：相对DN（RDN）和记录在LDAP目录中的位置。<br />
            <br />
            RDN
            是DN中与目录树的结构无关的部分。在LDAP目录中存储的记录项都要有一个名字，这个名字通常存在cn（Common
            Name）这个属性里。因为几乎所有的东西都有一个名字，在LDAP中存储的对象都用它们的cn值作为RDN的基础。如果我把最喜欢的吃燕麦粥食谱存为一
            个记录，我就会用cn=Oatmeal Deluxe作为记录项的RDN。<br />
            <br />
            l 我的LDAP目录的基准DN是dc=foobar,dc=com<br />
            <br />
            l 我把自己的食谱作为LDAP的记录项存在ou=recipes<br />
            <br />
            l 我的LDAP记录项的RDN设为cn=Oatmeal Deluxe<br />
            <br />
            上面这些构成了燕麦粥食谱的LDAP记录的完整DN。记住，DN的读法和DNS主机名类似。下面就是完整的DN：<br />
            <br />
            cn=Oatmeal Deluxe,ou=recipes,dc=foobar,dc=com<br />
            <br />
            举一个实际的例子来说明DN<br />
            <br />
            现在为公司的员工设置一个DN。可以用基于cn或uid（User ID），作为典型的用户帐号。例如，FooBar的员工Fran Smith（登录名：fsmith）的DN可以为下面两种格式：<br />
            <br />
            uid=fsmith,ou=employees,dc=foobar,dc=com<br />
            <br />
            （基于登录名）<br />
            <br />
            LDAP
            （以及X.500）用uid表示&#8220;User
            ID"，不要把它和UNIX的uid号混淆了。大多数公司都会给每一个员工唯一的登录名，因此用这个办法可以很好地保存员工的信息。你不用担心以后还会有
            一个叫Fran Smith的加入公司，如果Fran改变了她的名字（结婚？离婚？或宗教原因？），也用不着改变LDAP记录项的DN。<br />
            <br />
            cn=Fran Smith,ou=employees,dc=foobar,dc=com<br />
            <br />
            （基于姓名）<br />
            <br />
            可以看到这种格式使用了Common Name（CN）。可以把Common Name当成一个人的全名。这种格式有一个很明显的缺点就是：如果名字改变了，LDAP的记录就要从一个DN转移到另一个DN。但是，我们应该尽可能地避免改变一个记录项的DN。<br />
            <br />
            定制目录的对象类型<br />
            <br />
            你可以用LDAP存储各种类型的数据对象，只要这些对象可以用属性来表示，下面这些是可以在LDAP中存储的一些信息：<br />
            <br />
            l 员工信息：员工的姓名、登录名、口令、员工号、他的经理的登录名，邮件服务器，等等。<br />
            <br />
            l 物品跟踪信息：计算机名、IP地址、标签、型号、所在位置，等等。<br />
            <br />
            l 客户联系列表：客户的公司名、主要联系人的电话、传真和电子邮件，等等。<br />
            <br />
            l 会议厅信息：会议厅的名字、位置、可以坐多少人、电话号码、是否有投影机。<br />
            <br />
            l 食谱信息：菜的名字、配料、烹调方法以及准备方法。<br />
            <br />
            因
            为LDAP目录可以定制成存储任何文本或二进制数据，到底存什么要由你自己决定。LDAP目录用对象类型（object
            classes）的概念来定义运行哪一类的对象使用什么属性。在几乎所有的LDAP服务器中，你都要根据自己的需要扩展基本的LDAP目录的功能，创建新
            的对象类型或者扩展现存的对象类型。<br />
            <br />
            LDAP目录以一系列&#8220;属性对&#8221;的形式来存储记录项，每一个记录项包括属性类型和属性值（这与关系型数据库用行和列来存取数据有根本的不同）。下面是我存在LDAP目录中的一部分食谱记录：<br />
            <br />
            dn: cn=Oatmeal Deluxe, ou=recipes, dc=foobar, dc=com<br />
            <br />
            cn: Instant Oatmeal Deluxe<br />
            <br />
            recipeCuisine: breakfast<br />
            <br />
            recipeIngredient: 1 packet instant oatmeal<br />
            <br />
            recipeIngredient: 1 cup water<br />
            <br />
            recipeIngredient: 1 pinch salt<br />
            <br />
            recipeIngredient: 1 tsp brown sugar<br />
            <br />
            recipeIngredient: 1/4 apple, any type<br />
            <br />
            请注意上面每一种配料都作为属性recipeIngredient值。LDAP目录被设计成象上面那样为一个属性保存多个值的，而不是在每一个属性的后面用逗号把一系列值分开。<br />
            <br />
            因为用这样的方式存储数据，所以数据库就有很大的灵活性，不必为加入一些新的数据就重新创建表和索引。更重要的是，LDAP目录不必花费内存或硬盘空间处理&#8220;空&#8221;域，也就是说，实际上不使用可选择的域也不会花费你任何资源。<br />
            <br />
            作为例子的一个单独的数据项<br />
            <br />
            让我们看看下面这个例子。我们用Foobar, Inc.的员工Fran Smith的LDAP记录。这个记录项的格式是LDIF，用来导入和导出LDAP目录的记录项。<br />
            <br />
            dn: uid=fsmith, ou=employees, dc=foobar, dc=com<br />
            <br />
            objectclass: person<br />
            <br />
            objectclass: organizationalPerson<br />
            <br />
            objectclass: inetOrgPerson<br />
            <br />
            objectclass: foobarPerson<br />
            <br />
            uid: fsmith<br />
            <br />
            givenname: Fran<br />
            <br />
            sn: Smith<br />
            <br />
            cn: Fran Smith<br />
            <br />
            cn: Frances Smith<br />
            <br />
            telephonenumber: 510-555-1234<br />
            <br />
            roomnumber: 122G<br />
            <br />
            o: Foobar, Inc.<br />
            <br />
            mailRoutingAddress: <a href="&#109;&#97;&#105;&#108;&#116;&#111;&#58;&#102;&#115;&#109;&#105;&#116;&#104;&#64;&#102;&#111;&#111;&#98;&#97;&#114;&#46;&#99;&#111;&#109;">fsmith@foobar.com</a>mailhost: mail.foobar.com<br />
            <br />
            userpassword: 3x1231v76T89N<br />
            <br />
            uidnumber: 1234<br />
            <br />
            gidnumber: 1200<br />
            <br />
            homedirectory: /home/fsmith<br />
            <br />
            loginshell: /usr/local/bin/bash<br />
            <br />
            属性的值在保存的时候是保留大小写的，但是在默认情况下搜索的时候是不区分大小写的。某些特殊的属性（例如，password）在搜索的时候需要区分大小写。<br />
            <br />
            让我们一点一点地分析上面的记录项。<br />
            <br />
            dn: uid=fsmith, ou=employees, dc=foobar, dc=com<br />
            <br />
            这是Fran的LDAP记录项的完整DN，包括在目录树中的完整路径。LDAP（和X.500）使用uid（User ID），不要把它和UNIX的uid号混淆了。<br />
            <br />
            objectclass: person<br />
            <br />
            objectclass: organizationalPerson<br />
            <br />
            objectclass: inetOrgPerson<br />
            <br />
            objectclass: foobarPerson<br />
            <br />
            可
            以为任何一个对象根据需要分配多个对象类型。person对象类型要求cn（common
            name）和sn（surname）这两个域不能为空。persion对象类型允许有其它的可选域，包括givenname、
            telephonenumber，等等。organizational
            Person给person加入更多的可选域，inetOrgPerson又加入更多的可选域（包括电子邮件信息）。最后，foobarPerson是为
            Foobar定制的对象类型，加入了很多定制的属性。<br />
            <br />
            uid: fsmith<br />
            <br />
            givenname: Fran<br />
            <br />
            sn: Smith<br />
            <br />
            cn: Fran Smith<br />
            <br />
            cn: Frances Smith<br />
            <br />
            telephonenumber: 510-555-1234<br />
            <br />
            roomnumber: 122G<br />
            <br />
            o: Foobar, Inc.<br />
            <br />
            以前说过了，uid表示User ID。当看到uid的时候，就在脑袋里想一想&#8220;login"。<br />
            <br />
            请
            注意CN有多个值。就象上面介绍的，LDAP允许某些属性有多个值。为什么允许有多个值呢？假定你在用公司的LDAP服务器查找Fran
            的电话号码。你可能只知道她的名字叫Fran，但是对人力资源处的人来说她的正式名字叫做Frances。因为保存了她的两个名字，所以用任何一个名字检
            索都可以找到Fran的电话号码、电子邮件和办公房间号，等等。<br />
            <br />
            mailRoutingAddress: <a href="&#109;&#97;&#105;&#108;&#116;&#111;&#58;&#102;&#115;&#109;&#105;&#116;&#104;&#64;&#102;&#111;&#111;&#98;&#97;&#114;&#46;&#99;&#111;&#109;">fsmith@foobar.com</a>mailhost: mail.foobar.com<br />
            <br />
            就象现在大多数的公司都上网了，Foobar用Sendmail发送邮件和处理外部邮件路由信息。Foobar把所有用户的邮件信息都存在LDAP中。最新版本的Sendmail支持这项功能。<br />
            <br />
            Userpassword: 3x1231v76T89N<br />
            <br />
            uidnumber: 1234<br />
            <br />
            gidnumber: 1200<br />
            <br />
            gecos: Frances Smith<br />
            <br />
            homedirectory: /home/fsmith<br />
            <br />
            loginshell: /usr/local/bin/bash<br />
            <br />
            注
            意，Foobar的系统管理员把所有用户的口令映射信息也都存在LDAP中。FoobarPerson类型的对象具有这种能力。再注意一下，用户口令是用
            UNIX的口令加密格式存储的。UNIX的uid在这里为uidnumber。提醒你一下，关于如何在LDAP中保存NIS信息，有完整的一份RFC。在
            以后的文章中我会谈一谈NIS的集成。<br />
            <br />
            LDAP复制<br />
            <br />
            LDAP服务器可以使用基于&#8220;推&#8221;或者&#8220;拉&#8221;的技术，用简单或基于安全证书的安全验证，复制一部分或者所有的数据。<br />
            <br />
            例
            如，Foobar有一个&#8220;公用的&#8221;LDAP服务器，地址为ldap.foobar.com，端口为389。Netscape
            Communicator的电子邮件查询功能、UNIX的&#8220;ph"命令要用到这个服务器，用户也可以在任何地方查询这个服务器上的员工和客户联系信息。公
            司的主LDAP服务器运行在相同的计算机上，不过端口号是1389。<br />
            <br />
            你可能即不想让员工查询资产管理或食谱的信息，又不想让信息技术人员
            看到整个公司的LDAP目录。为了解决这个问题，Foobar有选择地把子目录树从主LDAP服务器复制到&#8220;公用&#8221;LDAP服务器上，不复制需要隐藏的信
            息。为了保持数据始终是最新的，主目录服务器被设置成即时&#8220;推&#8221;同步。这些种方法主要是为了方便，而不是安全，因为如果有权限的用户想查询所有的数据，可
            以用另一个LDAP端口。<br />
            <br />
            假定Foobar通过从奥克兰到欧洲的低带宽数据的连接用LDAP管理客户联系信息。可以建立从ldap.foobar.com:1389到munich-ldap.foobar.com:389的数据复制，象下面这样：<br />
            <br />
            periodic pull: ou=asia,ou=customers,o=sendmail.com<br />
            <br />
            periodic pull: ou=us,ou=customers,o=sendmail.com<br />
            <br />
            immediate push: ou=europe,ou=customers,o=sendmail.com<br />
            <br />
            &#8220;拉&#8221;连接每15分钟同步一次，在上面假定的情况下足够了。&#8220;推&#8221;连接保证任何欧洲的联系信息发生了变化就立即被&#8220;推&#8221;到Munich。<br />
            <br />
            用
            上面的复制模式，用户为了访问数据需要连接到哪一台服务器呢？在Munich的用户可以简单地连接到本地服务器。如果他们改变了数据，本地的LDAP服务
            器就会把这些变化传到主LDAP服务器。然后，主LDAP服务器把这些变化&#8220;推&#8221;回本地的&#8220;公用&#8221;LDAP服务器保持数据的同步。这对本地的用户有很大的
            好处，因为所有的查询（大多数是读）都在本地的服务器上进行，速度非常快。当需要改变信息的时候，最终用户不需要重新配置客户端的软件，因为LDAP目录
            服务器为他们完成了所有的数据交换工作。 </div>
            </td>
        </tr>
    </tbody>
</table>
</div>
<table>
    <tbody>
        <tr>
            <td><br />
            </td>
        </tr>
    </tbody>
</table>
<script type="text/javascript"><!--
google_ad_client = "pub-6625678643128649";
google_alternate_color = "FFFFFF";
google_ad_width = 728;
google_ad_height = 90;
google_ad_format = "728x90_as";
google_ad_type = "text_image";
google_ad_channel ="";
//--></script>
<img src ="http://www.blogjava.net/sealyu/aggbug/191987.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/sealyu/" target="_blank">seal</a> 2008-04-10 22:25 <a href="http://www.blogjava.net/sealyu/articles/191987.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>2008年软件产业和技术展望 </title><link>http://www.blogjava.net/sealyu/articles/191985.html</link><dc:creator>seal</dc:creator><author>seal</author><pubDate>Thu, 10 Apr 2008 14:24:00 GMT</pubDate><guid>http://www.blogjava.net/sealyu/articles/191985.html</guid><wfw:comment>http://www.blogjava.net/sealyu/comments/191985.html</wfw:comment><comments>http://www.blogjava.net/sealyu/articles/191985.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/sealyu/comments/commentRss/191985.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/sealyu/services/trackbacks/191985.html</trackback:ping><description><![CDATA[1）关于SaaS:2007年是SaaS/S+S的概念推广年，2008年会有相当一部分中小企业采用SaaS模式来作为自己企业的信息化手段。
2007年初，我在自己的Blog上说，未来3-5年，对于规模较小的中小企业的信息化，我判断有20%-30%的中小企业，必定会走向SaaS模式(<a href="http://blog.csdn.net/bjblues/archive/2007/01/27/1495580.aspx">http://blog.csdn.net/bjblues/archive/2007/01/27/1495580.aspx</a>)。<br />
&nbsp;&nbsp; 现在这个结论依然有效，速度可能会加快，可能未来2-3年内，30%-40%的中小企业，都会采用SaaS模式作为自己企业的信息化手段。<br />
&nbsp;&nbsp; 下面咱们来看看国内的软件厂商都为SaaS的到来作了哪些准备<br />
&nbsp; （1）金蝶：金蝶友商网（<a href="http://www.youshang.com/">http://www.youshang.com/</a>）
的推出，我只能用&#8220;震惊&#8221;来形容，不仅有阿里巴巴那样的B2B网站，而且
在线进销存/在线会计/在线仓库等等SaaS模式软件，是我见过的最成熟的SaaS模式的进销存，金蝶在技术上厚积而薄发，金蝶
在技术上的优势逐渐表现出来了。就像在市场上，金蝶和用友的差距大一样，在技术上，个人认为，用友和金蝶的差距也是蛮
大的，技术和市场的倒置是蛮有意思的一件事情，不过我个人认为，未来3年，金蝶凭借自己的技术优势和进一步的引进管理人才，
再加上IBM的巨额投入和大力相助，在SaaS等新的商业模式上，在市场上超越用友，倒不是不可能。<br />
&nbsp; （2）用友：用友对SaaS这块大蛋糕，不可能无动于衷，再说，用友进销存的传统客户，也都是中小企业，听说用友2年前就成立了SaaS应用 的在线事业部，只是一直未看到用友SaaS软件的推出。<br />
&nbsp; （3）神州数码：神州数码也在推自己的SaaS软件，我下载了，并安装了一下来试用，只能用一个字&#8220;烂&#8221;来形容，我想神州数码并没有 在思想上和行动上为SaaS做好准备，只是为了推出SaaS软件，赶上SaaS这趟班车而不得不从某处找来一个软件和凑数。<br />
&nbsp; （4）阿里巴巴：阿里巴巴也推出自己的SaaS应用的在线进销存软件，客观地讲，阿里的在线进销存，可以说麻雀虽小，五脏俱全，但真的是 太简单了，好像是专门为陶宝上的小卖家来用的，和金蝶的友商网，不是一个重量级的。<br />
&nbsp; （5）金算盘：金算盘是最早推出&#8220;进销存&#8221;等SaaS应用的软件公司，不过，金算盘老板的格局不够大，所以虽然最早发现了SaaS的蓝海，并 最早推出了自己的产品，但是否能真正笑到最后，不被金蝶/用友超越，不是很好说。<br />
&nbsp; （6）中企动力，中企动力也是较早的推出了自己的SaaS应用的平台，基于中企动力SaaS平台的客户群还不小，建议有实力的厂商把 中企动力的这一块业务买过来<br />
&nbsp; （7）其余一些中小企业：比如铭万（也是得到了IBM的赞助）的CRM等等，都会不遗余力地去争抢这份蛋糕，市场前景如何，我们可以拭目以待。
<p>&nbsp;&nbsp; SaaS这块软件厂商，我最看好的还是金蝶。<br />
&nbsp;<br />
2）关于软件外包<br />
&nbsp;&nbsp;
2007年11月16号，第一家软件外包公司在纽约证交所上市，文思创新的上市，是外界对外包这个行业的认可，前2年并不看好外包行业的哥们，真的应该去
买那本&#8220;世界是平的&#8221;的书好好来看看了。2008年，个人依然很看好外包行业，不过，外包公司面临的以下难题依然没有得到解决：<br />
&nbsp;&nbsp;
（1）对精通英语的技术高手/项目经理这类人才的竞争会加剧，文思创新的上市，会进一步刺激软通动力/博彦/海辉/中软国际/长城软件/浪潮
等等外包公司，对精通英语的技术高手/项目经理这类人才的竞争会加剧，每家企业不得不付出更大的努力/更高的薪水甚至是股票和期权
来吸引这类人才，所以大家要想加薪，还是得拼命学习英语阿。<br />
&nbsp;&nbsp; （2）由于外包企业/软件公司，向来不是真正的以人为本来对待程序员这类技术工人，所以程序员之间的安全感很差，流动率较大， 较高的流动率对外包行业来讲，也是一个硬伤。<br />
&nbsp;&nbsp; （3）人民币的升值，对于以欧美为主的外包软件公司，他们和被外包公司之间的结算往往是按照美元来结算的，在可预见的将来，由于人民币 持续升值，也给这些外包公司的盈利，带来阴影<br />
3）关于Ajax和富客户端<br />
&nbsp;&nbsp; (1)Ajax，在2007年，得到了平稳的发展，不过，我不建议在2008年，依然对Ajax做更大的投入，保持目前现状，为更新技术的出现做好准备就可以了。<br />
&nbsp;&nbsp;
(2)关于富客户端，个人比较看好Adobe的Flex，随着Flex
SDK的开源，Flex会被越来越多的大公司所采用，比如Oracle和SAP.&nbsp;
对于JSF,在国内，由于金蝶的大力推广的关系，会有一小部分公司基于JSF开发，不过，很难形成主流。基于JSF的Seam倒是很好的一个
Framework,如果JSF能够被换成Flex，将会对未来的Framework造成巨大冲击。Flex的问题就是缺少一个很好的完成增加/修改/删
除/查询的&nbsp; 成熟的Framework，就像ROR的 ActiveRecord那样的神兵利器。<br />
4）关于Spring/Hibernate/EJB<br />
&nbsp;&nbsp; （1）Hibernate在2008年，依然会保持平稳发展，不过，对于SaaS应用来说，采用Hibernate，倒不是很合适的，Apache DAS/SDO的推出，在 很多应用，都可以替换Hibernate，我想Hibeinate以后，会逐步被替换掉<br />
&nbsp;&nbsp;
（2）Spring:在稍微大一些的产品和项目中，个人一直不是很看好Spring,只是国内的某些大虾一直在鼓吹Spring.
Spring只是在EJB2.0给大家 带来巨大开发成本/开发/部署难度的时候 ，带来了一缕春风，好一个&lt;&lt;J2EE without
EJB&gt;&gt;,只是Spring解决一部分J2EE开发难题的同时，带来
了另外的什么都是XML配置的难题，虽然Spring也意识到了这个问题，并不断推出新的版本，但是Apache SCA规范开源实现的推出<br />
&nbsp;&nbsp; (<a href="http://incubator.apache.org/tuscany/sca-java-releases.html">http://incubator.apache.org/tuscany/sca-java-releases.html</a>),使我们看到了&lt;&lt;J2EE with SCA without Spring&gt;&gt;的实现. 个人认为,在2008年以及以后的1-2年,Spring会逐渐被SCA规范的开源实现代替.&nbsp; <br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br />
5） 关于Eclipse和NetBeans<br />
&nbsp;&nbsp;&nbsp; NetBeans已经推出了6.0,仅仅对于IDE来说,Netbanans会有自己的一块之地,但是对于整个产业链和开发平台,NetBeans想全面超越Eclipse,还是不可 能的<br />
6）关于中间件和中间件厂商之间的并购<br />
&nbsp;&nbsp;
2007年，Oracle和IBM购买了大量的中小企业中间件厂商，甚至像BEA这样的大公司，也差一点被Oracle买掉，不过，在2008年，随着该
卖的已经卖了，该买的也已经买了，2008年企业之间的并购，会减少了很多（倒是国内，像工作流之类的中间件公司之间的并购，很可能会发生几起）。<br />
&nbsp;&nbsp;
BEA公司的去向，还是一个未知数，我在这里大胆说出一个大公司之间的并购，如果BEA/TIBCO/Sybase之间互相合并了，不知道会不会跟
Oracle有一拼了？这3个公司的主要优质客户都是电信和银行，BEA的优势在于Weblogic这样的基础中间件，Tibco的优势在于EAI和消息
中间件，Sybase的优势当然是数据库了。</p>
<img src ="http://www.blogjava.net/sealyu/aggbug/191985.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/sealyu/" target="_blank">seal</a> 2008-04-10 22:24 <a href="http://www.blogjava.net/sealyu/articles/191985.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>正则表达式30分钟入门教程</title><link>http://www.blogjava.net/sealyu/articles/191984.html</link><dc:creator>seal</dc:creator><author>seal</author><pubDate>Thu, 10 Apr 2008 14:22:00 GMT</pubDate><guid>http://www.blogjava.net/sealyu/articles/191984.html</guid><wfw:comment>http://www.blogjava.net/sealyu/comments/191984.html</wfw:comment><comments>http://www.blogjava.net/sealyu/articles/191984.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/sealyu/comments/commentRss/191984.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/sealyu/services/trackbacks/191984.html</trackback:ping><description><![CDATA[<p id="meta">版本：v2.21 (2007-8-3) 作者：<a href="http://www.unibetter.com/members/deerchao.aspx">deerchao</a> 来源:<a href="http://www.unibetter.com/">unibetter大学生社区</a> 转载请注明来源</p>
<h2 id="contents">目录</h2>
<ol>
    <li><a href="http://www.unibetter.com/deerchao/zhengzhe-biaodashi-jiaocheng-se.htm#mission">本文目标</a>
    </li>
    <li><a href="http://www.unibetter.com/deerchao/zhengzhe-biaodashi-jiaocheng-se.htm#howtouse">如何使用本教程</a>
    </li>
    <li><a href="http://www.unibetter.com/deerchao/zhengzhe-biaodashi-jiaocheng-se.htm#introduction">正则表达式到底是什么？</a>
    </li>
    <li><a href="http://www.unibetter.com/deerchao/zhengzhe-biaodashi-jiaocheng-se.htm#getstarted">入门</a>
    </li>
    <li><a href="http://www.unibetter.com/deerchao/zhengzhe-biaodashi-jiaocheng-se.htm#testing">测试正则表达式</a>
    </li>
    <li><a href="http://www.unibetter.com/deerchao/zhengzhe-biaodashi-jiaocheng-se.htm#metacode">元字符</a>
    </li>
    <li><a href="http://www.unibetter.com/deerchao/zhengzhe-biaodashi-jiaocheng-se.htm#escape">字符转义</a>
    </li>
    <li><a href="http://www.unibetter.com/deerchao/zhengzhe-biaodashi-jiaocheng-se.htm#repeat">重复</a>
    </li>
    <li><a href="http://www.unibetter.com/deerchao/zhengzhe-biaodashi-jiaocheng-se.htm#charclass">字符类</a>
    </li>
    <li><a href="http://www.unibetter.com/deerchao/zhengzhe-biaodashi-jiaocheng-se.htm#negation">反义</a>
    </li>
    <li><a href="http://www.unibetter.com/deerchao/zhengzhe-biaodashi-jiaocheng-se.htm#alternative">替换</a>
    </li>
    <li><a href="http://www.unibetter.com/deerchao/zhengzhe-biaodashi-jiaocheng-se.htm#grouping">分组</a>
    </li>
    <li><a href="http://www.unibetter.com/deerchao/zhengzhe-biaodashi-jiaocheng-se.htm#backreference">后向引用</a>
    </li>
    <li><a href="http://www.unibetter.com/deerchao/zhengzhe-biaodashi-jiaocheng-se.htm#lookaround">零宽断言</a>
    </li>
    <li><a href="http://www.unibetter.com/deerchao/zhengzhe-biaodashi-jiaocheng-se.htm#negativelookaround">负向零宽断言</a>
    </li>
    <li><a href="http://www.unibetter.com/deerchao/zhengzhe-biaodashi-jiaocheng-se.htm#commenting">注释</a>
    </li>
    <li><a href="http://www.unibetter.com/deerchao/zhengzhe-biaodashi-jiaocheng-se.htm#greedyandlazy">贪婪与懒惰</a>
    </li>
    <li><a href="http://www.unibetter.com/deerchao/zhengzhe-biaodashi-jiaocheng-se.htm#regexoptions">处理选项</a>
    </li>
    <li><a href="http://www.unibetter.com/deerchao/zhengzhe-biaodashi-jiaocheng-se.htm#balancedgroup">平衡组/递归匹配</a>
    </li>
    <li><a href="http://www.unibetter.com/deerchao/zhengzhe-biaodashi-jiaocheng-se.htm#more">还有些什么东西没提到</a>
    </li>
    <li><a href="http://www.unibetter.com/deerchao/zhengzhe-biaodashi-jiaocheng-se.htm#contact">联系作者</a>
    </li>
    <li><a href="http://www.unibetter.com/deerchao/zhengzhe-biaodashi-jiaocheng-se.htm#reference">一些我认为你可能已经知道的术语的参考</a>
    </li>
    <li><a href="http://www.unibetter.com/deerchao/zhengzhe-biaodashi-jiaocheng-se.htm#resources">网上的资源及本文参考文献</a>
    </li>
    <li><a href="http://www.unibetter.com/deerchao/zhengzhe-biaodashi-jiaocheng-se.htm#updatelog">更新说明</a> </li>
</ol>
<h2 id="mission">本文目标</h2>
<p>30分钟内让你明白正则表达式是什么，并对它有一些基本的了解，让你可以在自己的程序或网页里使用它。</p>
<h2 id="howtouse">如何使用本教程</h2>
<p>最重要的是——请给我<strong>30分钟</strong>，如果你没有使用正则表达式的经验，请不要试图在30<strong>