﻿<?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-cmd-文章分类-j2ee基础</title><link>http://www.blogjava.net/cmd/category/7473.html</link><description /><language>zh-cn</language><lastBuildDate>Fri, 02 Mar 2007 03:13:19 GMT</lastBuildDate><pubDate>Fri, 02 Mar 2007 03:13:19 GMT</pubDate><ttl>60</ttl><item><title>在学习webwork案例中碰到了静态的内部类,抄篇文章来</title><link>http://www.blogjava.net/cmd/articles/42248.html</link><dc:creator>静夜思</dc:creator><author>静夜思</author><pubDate>Thu, 20 Apr 2006 17:13:00 GMT</pubDate><guid>http://www.blogjava.net/cmd/articles/42248.html</guid><wfw:comment>http://www.blogjava.net/cmd/comments/42248.html</wfw:comment><comments>http://www.blogjava.net/cmd/articles/42248.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/cmd/comments/commentRss/42248.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/cmd/services/trackbacks/42248.html</trackback:ping><description><![CDATA[
		<font size="2">
				<span class="javascript" id="text629"> （一）  <br /> Java1.1以后版本添加了嵌套类（Inner Class，嵌套类、内部类）。嵌套类定义在类（外部类）里面。  <br /> 嵌套类可以体现逻辑上的从属关系。同时对于其他类可以控制内部类不可见等。  <br /> 外部类的成员变量作用域是整个外部类，包括嵌套类。但外部类不能访问嵌套类的private成员。例子：  <br /> public class Outer {  <br /> private int size;  <br /> public class Inner{  <br /> private int counter = 10;  <br /> public void doStuff(){ size++; }  <br /> }  <br /> public static void main(String args[]){  <br /> Inner inner= new Inner();  <br /> inner.doStuff();  <br /> System.out.println(size);  <br /> System.out.println(inner.counter);  <br /> System.out.println(counter); //编译错  <br /> }  <br /> }  <br /><br /> （二）下面演示了如何在外部类之外实例出一个public Inner类对象。  <br /><br /> 类名前加上外部类类名，new语句前加上外部类的引用变量。这很类似package的使用。  <br /> 例子：  <br /> public class Outer {  <br /> private int size;  <br /> public class Inner{  <br /> public void doStuff(){ size++; }  <br /> }  <br /> }  <br /><br /> public class TestInner{  <br /> public static void main(String args[]){  <br /> Outer outer = new Outer();  <br /> Outer.Inner inner = outer.new Inner();  <br /> inner.doStuff();  <br /> }  <br /> }  <br /> （三）当内部类与外部类有同名变量时，缺省指本类中的变量。  <br /> 例子：public class Outer {  <br /> private int size;  <br /> public class Inner{  <br /> private size;  <br /> public void doStuff(int size){  <br /> size++; this.size++;  <br /> Outer.this.size++; // 错误，Outer.size++因为size不是静态变量  <br /> }  <br /> }  <br /> public static void main(String args[]){  <br /> this.size++; //错误，静态方法使用非静态变量this  <br /> }  <br /> }  <br /> （四）在方法中定义一个嵌套类，注意嵌套类不可以访问方法的局部变量。  <br /> 因为方法的局部变量只存在与方法作用期间，故localvalue不可访问。而final变量的生命周期超出了方法，所以fianlvalue可用。  <br /> public class Outer {  <br /> private int size =5;  <br /> public Object makeTheInner(int localvalue){  <br /> final int finalvalue = 6;  <br /> class Inner{  <br /> public String toString(){return (“Inner is”+size+localvalue+finalvalue);}  <br /> }  <br /> return new Inner();  <br /> }  <br /><br /> public static void main(String args[]){  <br /> Outer outer = new Outer();  <br /> Object obj = outer.makeTheInner(47);  <br /> System.out.println(“The object is” + obj);  <br /> }  <br /> }  <br /> （五）  <br /> 嵌套类的类名必须与包装它的外部类区别，且嵌套类的类名仅仅可以作用于定义范围中。方法中定义类，其类名名就只能出现在方法中。  <br /> 定义在方法中的嵌套类只能使用定义为final的局部变量，不可以使用方法中的非静态变量。  <br /> 嵌套类可以使用的变量种类包括：类变量、实例变量、final局部变量。  <br /> 嵌套类同样具有所有的访问控制权限。高级类属性：  <br /> 嵌套类可以是abstract类  <br /> 嵌套类可以是接口，被其它嵌套类实现。  <br /> 定义为static的内部类将成为顶级类(top-level)。它们不依赖于外部类的对象而生成，所以不可以访问外部类的对象成员。  <br /> 非static内部类不能定义static成员  <br /> 当外部类编译时，内部类也会编译，生成的类文件格式为:  <br /> OuterClass$InnerClass，如Outer$Inner.class  <br /><br /> 例子：  <br /> class Outer {  <br /> private int size =5;  <br /> static class Inner{  <br /> int value=5;  <br /> public void doStuff(){ size+=value; }// 错误，不能访问非static 外部类程序  <br /> }  <br /> }  <br /><br /> public class TestInner{  <br /> public static void main(String args[]){  <br /> Outer outer = new Outer();  <br /> Inner inner = new Inner();// 这种实例也不对  <br /> 应该 Out.Inner innner=new Outer.Inner() ;  <br /> }  <br /> }  <br /><br /> 非static内部类不能定义static成员 ，  <br /> （1）定义 static 的 内部类， 如果成员变量也定义成 static ,那么外围类可以直接通过类名来访问。  <br /> （2）定义 static 的 内部类， 如果成员变量不是 static ,那么外围类需要生成内部类的对象才能访问，否则直接访问导致编译出错！！ 
			</span>
		</font>
<img src ="http://www.blogjava.net/cmd/aggbug/42248.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/cmd/" target="_blank">静夜思</a> 2006-04-21 01:13 <a href="http://www.blogjava.net/cmd/articles/42248.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>一个关于优化SQL的文章</title><link>http://www.blogjava.net/cmd/articles/36355.html</link><dc:creator>静夜思</dc:creator><author>静夜思</author><pubDate>Mon, 20 Mar 2006 08:08:00 GMT</pubDate><guid>http://www.blogjava.net/cmd/articles/36355.html</guid><wfw:comment>http://www.blogjava.net/cmd/comments/36355.html</wfw:comment><comments>http://www.blogjava.net/cmd/articles/36355.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/cmd/comments/commentRss/36355.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/cmd/services/trackbacks/36355.html</trackback:ping><description><![CDATA[
		<font size="2">

			

			大家都在讨论关于数据库优化方面的东东，刚好参与开发了一个数据仓库方面的项目，以下的一点东西算是数据库优化方面的学习+实战的一些心得体会了，拿出来大家共享。欢迎批评指正阿！ <br /><br />SQL语句： <br />是对数据库(数据)进行操作的惟一途径； <br />消耗了70%~90%的数据库资源；独立于程序设计逻辑，相对于对程序源代码的优化，对SQL语句的优化在时间成本和风险上的代价都很低； <br />可以有不同的写法；易学，难精通。 <br /><br />SQL优化： <br />固定的SQL书写习惯，相同的查询尽量保持相同，存储过程的效率较高。 <br />应该编写与其格式一致的语句，包括字母的大小写、标点符号、换行的位置等都要一致 <br /><br />ORACLE优化器： <br />在任何可能的时候都会对表达式进行评估，并且把特定的语法结构转换成等价的结构，这么做的原因是 <br />要么结果表达式能够比源表达式具有更快的速度 <br />要么源表达式只是结果表达式的一个等价语义结构 <br />不同的SQL结构有时具有同样的操作（例如：= ANY (subquery) and IN (subquery)），ORACLE会把他们映射到一个单一的语义结构。 <br /><br />1 常量优化： <br />常量的计算是在语句被优化时一次性完成，而不是在每次执行时。下面是检索月薪大于2000的的表达式： <br />sal &gt; 24000/12 <br />sal &gt; 2000 <br />sal*12 &gt; 24000 <br />如果SQL语句包括第一种情况，优化器会简单地把它转变成第二种。 <br />优化器不会简化跨越比较符的表达式，例如第三条语句，鉴于此，应尽量写用常量跟字段比较检索的表达式，而不要将字段置于表达式当中。否则没有办法优化，比如如果sal上有索引，第一和第二就可以使用，第三就难以使用。 <br /><br />2 操作符优化： <br />优化器把使用LIKE操作符和一个没有通配符的表达式组成的检索表达式转换为一个“=”操作符表达式。 <br />例如：优化器会把表达式ename LIKE 'SMITH'转换为ename = 'SMITH' <br />优化器只能转换涉及到可变长数据类型的表达式，前一个例子中，如果ENAME字段的类型是CHAR(10)， 那么优化器将不做任何转换。 <br />一般来讲LIKE比较难以优化。 <br /><br />其中： <br />~~ IN 操作符优化： <br />优化器把使用IN比较符的检索表达式替换为等价的使用“=”和“OR”操作符的检索表达式。 <br />例如，优化器会把表达式ename IN ('SMITH','KING','JONES')替换为 <br />ename = 'SMITH' OR ename = 'KING' OR ename = 'JONES‘ <br /><br />~~ ANY和SOME 操作符优化: <br />优化器将跟随值列表的ANY和SOME检索条件用等价的同等操作符和“OR”组成的表达式替换。 <br />例如，优化器将如下所示的第一条语句用第二条语句替换： <br />sal &gt; ANY (:first_sal, :second_sal) <br />sal &gt; :first_sal OR sal &gt; :second_sal <br />优化器将跟随子查询的ANY和SOME检索条件转换成由“EXISTS”和一个相应的子查询组成的检索表达式。 <br />例如，优化器将如下所示的第一条语句用第二条语句替换： <br />x &gt; ANY (SELECT sal FROM emp WHERE job = 'ANALYST') <br />EXISTS (SELECT sal FROM emp WHERE job = 'ANALYST' AND x &gt; sal) <br /><br />~~ ALL操作符优化: <br />优化器将跟随值列表的ALL操作符用等价的“=”和“AND”组成的表达式替换。例如： <br />sal &gt; ALL (:first_sal, :second_sal)表达式会被替换为： <br />sal &gt; :first_sal AND sal &gt; :second_sal <br />对于跟随子查询的ALL表达式，优化器用ANY和另外一个合适的比较符组成的表达式替换。例如 <br />x &gt; ALL (SELECT sal FROM emp WHERE deptno = 10) 替换为： <br />NOT (x &lt;= ANY (SELECT sal FROM emp WHERE deptno = 10)) <br />接下来优化器会把第二个表达式适用ANY表达式的转换规则转换为下面的表达式： <br />NOT EXISTS (SELECT sal FROM emp WHERE deptno = 10 AND x &lt;= sal) <br /><br />~~ BETWEEN 操作符优化: <br />优化器总是用“&gt;=”和“&lt;=”比较符来等价的代替BETWEEN操作符。 <br />例如：优化器会把表达式sal BETWEEN 2000 AND 3000用sal &gt;= 2000 AND sal &lt;= 3000来代替。 <br /><br />~~ NOT 操作符优化: <br />优化器总是试图简化检索条件以消除“NOT”逻辑操作符的影响，这将涉及到“NOT”操作符的消除以及代以相应的比较运算符。 <br />例如，优化器将下面的第一条语句用第二条语句代替： <br />NOT deptno = (SELECT deptno FROM emp WHERE ename = 'TAYLOR') <br />deptno &lt;&gt; (SELECT deptno FROM emp WHERE ename = 'TAYLOR') <br />通常情况下一个含有NOT操作符的语句有很多不同的写法，优化器的转换原则是使“NOT”操作符后边的子句尽可能的简单，即使可能会使结果表达式包含了更多的“NOT”操作符。 <br />例如，优化器将如下所示的第一条语句用第二条语句代替： <br />NOT (sal &lt; 1000 OR comm IS NULL) <br />NOT sal &lt; 1000 AND comm IS NOT NULL sal &gt;= 1000 AND comm IS NOT NULL <br /><br />如何编写高效的SQL: <br />当然要考虑sql常量的优化和操作符的优化啦，另外，还需要： <br /><br />1 合理的索引设计： <br />例：表record有620000行，试看在不同的索引下，下面几个SQL的运行情况： <br />语句A <br />SELECT count(*) FROM record <br />WHERE date &gt;'19991201' and date &lt; '19991214‘ and amount &gt;2000 <br /><br />语句B <br />SELECT count(*) FROM record <br />WHERE date &gt;'19990901' and place IN ('BJ','SH') <br /><br />语句C <br />SELECT date,sum(amount) FROM record <br />group by date <br />1 在date上建有一个非聚集索引 <br />A：(25秒) <br />B：(27秒) <br />C：(55秒) <br />分析： <br />date上有大量的重复值，在非聚集索引下，数据在物理上随机存放在数据页上，在范围查找时，必须执行一次表扫描才能找到这一范围内的全部行。 <br />2 在date上的一个聚集索引 <br />A：（14秒） <br />B：（14秒） <br />C：（28秒） <br />分析： <br />在聚集索引下，数据在物理上按顺序在数据页上，重复值也排列在一起，因而在范围查找时，可以先找到这个范围的起末点，且只在这个范围内扫描数据页，避免了大范围扫描，提高了查询速度。 <br />3 在place，date，amount上的组合索引 <br />A：（26秒） <br />C：（27秒） <br />B：（&lt; 1秒） <br />分析： <br />这是一个不很合理的组合索引，因为它的前导列是place，第一和第二条SQL没有引用place，因此也没有利用上索引；第三个SQL使用了place，且引用的所有列都包含在组合索引中，形成了索引覆盖，所以它的速度是非常快的。 <br />4 在date，place，amount上的组合索引 <br />A： (&lt; 1秒) <br />B：（&lt; 1秒） <br />C：（11秒） <br />分析： <br />这是一个合理的组合索引。它将date作为前导列，使每个SQL都可以利用索引，并且在第一和第三个SQL中形成了索引覆盖，因而性能达到了最优。 <br /><br />总结1 <br />缺省情况下建立的索引是非聚集索引，但有时它并不是最佳的；合理的索引设计要建立在对各种查询的分析和预测上。一般来说： <br />有大量重复值、且经常有范围查询（between, &gt;,&lt; ，&gt;=,&lt; =）和order by、group by发生的列，考虑建立聚集索引； <br />经 
常同时存取多列，且每列都含有重复值可考虑建立组合索引；在条件表达式中经常用到的不同值较多的列上建立检索，在不同值少的列上不要建立索引。比如在雇员
 表的“性别”列上只有“男”与“女”两个不同值，因此就无必要建立索引。如果建立索引不但不会提高查询效率，反而会严重降低更新速度。 <br />组合索引要尽量使关键查询形成索引覆盖，其前导列一定是使用最频繁的列。 <br /><br />2 避免使用不兼容的数据类型： <br />例如float和INt、char和varchar、bINary和varbINary是不兼容的。数据类型的不兼容可能使优化器无法执行一些本来可以进行的优化操作。例如: <br />SELECT name FROM employee WHERE salary ＞ 60000 <br />在这条语句中,如salary字段是money型的,则优化器很难对其进行优化,因为60000是个整型数。我们应当在编程时将整型转化成为钱币型,而不要等到运行时转化。 <br /><br />3 IS NULL 与IS NOT NULL： <br />不 
能用null作索引，任何包含null值的列都将不会被包含在索引中。即使索引有多列这样的情况下，只要这些列中有一列含有null，该列就会从索引中排
 除。也就是说如果某列存在空值，即使对该列建索引也不会提高性能。任何在WHERE子句中使用is null或is not null的语句优化器是不
允 许使用索引的。 <br /><br />4 IN和EXISTS： <br />EXISTS要远比IN的效率高。里面关系到full table scan和range scan。几乎将所有的IN操作符子查询改写为使用EXISTS的子查询。 <br />例子： <br />语句1 <br />SELECT dname, deptno FROM dept <br />WHERE deptno NOT IN <br />(SELECT deptno FROM emp); <br />语句2 <br />SELECT dname, deptno FROM dept <br />WHERE NOT EXISTS <br />(SELECT deptno FROM emp <br />WHERE dept.deptno = emp.deptno); <br />明显的，2要比1的执行性能好很多 <br />因为1中对emp进行了full table scan,这是很浪费时间的操作。而且1中没有用到emp的INdex， <br />因为没有WHERE子句。而2中的语句对emp进行的是range scan。 <br /><br />5 IN、OR子句常会使用工作表，使索引失效： <br />如果不产生大量重复值，可以考虑把子句拆开。拆开的子句中应该包含索引。 <br /><br />6 避免或简化排序： <br />应当简化或避免对大型表进行重复的排序。当能够利用索引自动以适当的次序产生输出时，优化器就避免了排序的步骤。以下是一些影响因素： <br />索引中不包括一个或几个待排序的列； <br />group by或order by子句中列的次序与索引的次序不一样； <br />排序的列来自不同的表。 <br />为了避免不必要的排序，就要正确地增建索引，合理地合并数据库表（尽管有时可能影响表的规范化，但相对于效率的提高是值得的）。如果排序不可避免，那么应当试图简化它，如缩小排序的列的范围等。 <br /><br />7 消除对大型表行数据的顺序存取： <br />在 
嵌套查询中，对表的顺序存取对查询效率可能产生致命的影响。比如采用顺序存取策略，一个嵌套3层的查询，如果每层都查询1000行，那么这个查询就要查询
 10亿行数据。避免这种情况的主要方法就是对连接的列进行索引。例如，两个表：学生表（学号、姓名、年龄??）和选课表（学号、课程号、成绩）。如果两
个 表要做连接，就要在“学号”这个连接字段上建立索引。 <br />还可以使用并集来避免顺序存取。尽管在所有的检查列上都有索引，但某些形式的WHERE子句强迫优化器使用顺序存取。下面的查询将强迫对orders表执行顺序操作： <br />SELECT ＊ FROM orders WHERE (customer_num=104 AND order_num&gt;1001) OR order_num=1008 <br />虽然在customer_num和order_num上建有索引，但是在上面的语句中优化器还是使用顺序存取路径扫描整个表。因为这个语句要检索的是分离的行的集合，所以应该改为如下语句： <br />SELECT ＊ FROM orders WHERE customer_num=104 AND order_num&gt;1001 <br />UNION <br />SELECT ＊ FROM orders WHERE order_num=1008 <br />这样就能利用索引路径处理查询。 <br /><br />8 避免相关子查询： <br />一个列的标签同时在主查询和WHERE子句中的查询中出现，那么很可能当主查询中的列值改变之后，子查询必须重新查询一次。查询嵌套层次越多，效率越低，因此应当尽量避免子查询。如果子查询不可避免，那么要在子查询中过滤掉尽可能多的行。 <br /><br />9 避免困难的正规表达式： <br />MATCHES和LIKE关键字支持通配符匹配，技术上叫正规表达式。但这种匹配特别耗费时间。例如：SELECT ＊ FROM customer WHERE zipcode LIKE “98_ _ _” <br />即使在zipcode字段上建立了索引，在这种情况下也还是采用顺序扫描的方式。如果把语句改为SELECT ＊ FROM customer WHERE zipcode &gt;“98000”，在执行查询时就会利用索引来查询，显然会大大提高速度。 <br />另外，还要避免非开始的子串。例如语句：SELECT ＊ FROM customer WHERE zipcode[2，3] &gt;“80”，在WHERE子句中采用了非开始子串，因而这个语句也不会使用索引。 <br /><br />10 不充份的连接条件： <br />例：表card有7896行，在card_no上有一个非聚集索引，表account有191122行，在account_no上有一个非聚集索引，试看在不同的表连接条件下，两个SQL的执行情况： <br />SELECT sum(a.amount) FROM account a,card b WHERE a.card_no = b.card_no <br />（20秒） <br />将SQL改为： <br />SELECT sum(a.amount) FROM account a,card b WHERE a.card_no = b.card_no and a.account_no=b.account_no <br />（&lt; 1秒） <br />分析： <br />在第一个连接条件下，最佳查询方案是将account作外层表，card作内层表，利用card上的索引，其I/O次数可由以下公式估算为： <br />外层表account上的22541页+（外层表account的191122行*内层表card上对应外层表第一行所要查找的3页）=595907次I/O <br />在第二个连接条件下，最佳查询方案是将card作外层表，account作内层表，利用account上的索引，其I/O次数可由以下公式估算为： <br />外层表card上的1944页+（外层表card的7896行*内层表account上对应外层表每一行所要查找的4页）= 33528次I/O <br />可见，只有充份的连接条件，真正的最佳方案才会被执行。 <br />多表操作在被实际执行前，查询优化器会根据连接条件，列出几组可能的连接方案并从中找出系统开销最小的最佳方案。连接条件要充份考虑带有索引的表、行数多的表；内外表的选择可由公式：外层表中的匹配行数*内层表中每一次查找的次数确定，乘积最小为最佳方案。 <br />不可优化的WHERE子句 <br />例1 <br />下列SQL条件语句中的列都建有恰当的索引，但执行速度却非常慢： <br />SELECT * FROM record WHERE substrINg(card_no,1,4)='5378' <br />(13秒) <br />SELECT * FROM record WHERE amount/30&lt; 1000 <br />（11秒） <br />SELECT * FROM record WHERE convert(char(10),date,112)='19991201' <br />（10秒） <br />分析： <br />WHERE子句中对列的任何操作结果都是在SQL运行时逐列计算得到的，因此它不得不进行表搜索，而没有使用该列上面的索引；如果这些结果在查询编译时就能得到，那么就可以被SQL优化器优化，使用索引，避免表搜索，因此将SQL重写成下面这样： <br />SELECT * FROM record WHERE card_no like '5378%' <br />（&lt; 1秒） <br />SELECT * FROM record WHERE amount&lt; 1000*30 <br />（&lt; 1秒） <br />SELECT * FROM record WHERE date= '1999/12/01' <br />（&lt; 1秒） <br /><br />11 存储过程中，采用临时表优化查询： <br />例 <br />1．从parven表中按vendor_num的次序读数据： <br />SELECT part_num，vendor_num，price FROM parven ORDER BY vendor_num <br />INTO temp pv_by_vn <br />这个语句顺序读parven（50页），写一个临时表（50页），并排序。假定排序的开销为200页，总共是300页。 <br />2．把临时表和vendor表连接，把结果输出到一个临时表，并按part_num排序： <br />SELECT pv_by_vn，＊ vendor.vendor_num FROM pv_by_vn，vendor <br />WHERE pv_by_vn.vendor_num=vendor.vendor_num <br />ORDER BY pv_by_vn.part_num <br />INTO TMP pvvn_by_pn <br />DROP TABLE pv_by_vn <br />这 
个查询读取pv_by_vn(50页)，它通过索引存取vendor表1.5万次，但由于按vendor_num次序排列，实际上只是通过索引顺序地读
 vendor表（40＋2=42页），输出的表每页约95行，共160页。写并存取这些页引发5＊160=800次的读写，索引共读写892页。 <br />3．把输出和part连接得到最后的结果： <br />SELECT pvvn_by_pn.＊，part.part_desc FROM pvvn_by_pn，part <br />WHERE pvvn_by_pn.part_num=part.part_num <br />DROP TABLE pvvn_by_pn <br />这样，查询顺序地读pvvn_by_pn(160页)，通过索引读part表1.5万次，由于建有索引，所以实际上进行1772次磁盘读写，优化比例为30∶1。 <br /><br />好了，搞定。 <br />其实sql的优化，各种数据库之间都是互通的。

</font>
<img src ="http://www.blogjava.net/cmd/aggbug/36355.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/cmd/" target="_blank">静夜思</a> 2006-03-20 16:08 <a href="http://www.blogjava.net/cmd/articles/36355.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>介绍一篇关于session的好文章,写的很详细(jsp-servlet 技术)</title><link>http://www.blogjava.net/cmd/articles/34646.html</link><dc:creator>静夜思</dc:creator><author>静夜思</author><pubDate>Fri, 10 Mar 2006 05:10:00 GMT</pubDate><guid>http://www.blogjava.net/cmd/articles/34646.html</guid><wfw:comment>http://www.blogjava.net/cmd/comments/34646.html</wfw:comment><comments>http://www.blogjava.net/cmd/articles/34646.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/cmd/comments/commentRss/34646.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/cmd/services/trackbacks/34646.html</trackback:ping><description><![CDATA[<font size="2"><span class="content">摘要：虽然session机制在web应用程序中被采用已经很长时间了，但是仍然有很多人不清楚
session机制的本质，以至不能正确的应用这一技术。本文将详细讨论session的工作机制并且对在Java&nbsp;web&nbsp;application中应
用session机制时常见的问题作出解答。<br><br>目录：<br>一、术语session<br>二、HTTP协议与状态保持<br>三、理解cookie机制<br>四、理解session机制<br>五、理解javax.servlet.http.HttpSession<br>六、HttpSession常见问题<br>七、跨应用程序的session共享<br>八、总结<br>参考文档<br><br>一、术语session<br>在我的经验里，session这个词被滥用的程度大概仅次于transaction，更加有趣的是transaction与session在某些语境下的含义是相同的。<br><br>session，
中文经常翻译为会话，其本来的含义是指有始有终的一系列动作/消息，比如打电话时从拿起电话拨号到挂断电话这中间的一系列过程可以称之为一个
&nbsp;session。有时候我们可以看到这样的话“在一个浏览器会话期间，...”，这里的会话一词用的就是其本义，是指从一个浏览器窗口打开到关闭这个期
间&nbsp;①。最混乱的是“用户（客户端）在一次会话期间”这样一句话，它可能指用户的一系列动作（一般情况下是同某个具体目的相关的一系列动作，比如从登录到
选购商品到结账登出这样一个网上购物的过程，有时候也被称为一个transaction），然而有时候也可能仅仅是指一次连接，也有可能是指含义①，其中
的差别只能靠上下文来推断②。<br><br>然而当session一词与网络协议相关联时，它又往往隐含了“面向连接”和/或“保持状态”这样两个含
义，&nbsp;“面向连接”指的是在通信双方在通信之前要先建立一个通信的渠道，比如打电话，直到对方接了电话通信才能开始，与此相对的是写信，在你把信发出去的
时候你并不能确认对方的地址是否正确，通信渠道不一定能建立，但对发信人来说，通信已经开始了。“保持状态”则是指通信的一方能够把一系列的消息关联起
来，使得消息之间可以互相依赖，比如一个服务员能够认出再次光临的老顾客并且记得上次这个顾客还欠店里一块钱。这一类的例子有“一个
TCP&nbsp;session”或者&nbsp;“一个POP3&nbsp;session”③。<br><br>而到了web服务器蓬勃发展的时代，session在web开发语
境下的语义又有了新的扩展，它的含义是指一类用来在客户端与服务器之间保持状态的解决方案④。有时候session也用来指这种解决方案的存储结构，如
“把xxx保存在session&nbsp;里”⑤。由于各种用于web开发的语言在一定程度上都提供了对这种解决方案的支持，所以在某种特定语言的语境下，
session也被用来指代该语言的解决方案，比如经常把Java里提供的javax.servlet.http.HttpSession简称为
session⑥。<br><br>鉴于这种混乱已不可改变，本文中session一词的运用也会根据上下文有不同的含义，请大家注意分辨。<br>在本文中，使用中文“浏览器会话期间”来表达含义①，使用“session机制”来表达含义④，使用“session”表达含义⑤，使用具体的“HttpSession”来表达含义⑥<br><br>二、HTTP协议与状态保持<br>HTTP&nbsp;
协议本身是无状态的，这与HTTP协议本来的目的是相符的，客户端只需要简单的向服务器请求下载某些文件，无论是客户端还是服务器都没有必要纪录彼此过去
的行为，每一次请求之间都是独立的，好比一个顾客和一个自动售货机或者一个普通的（非会员制）大卖场之间的关系一样。<br><br>然而聪明（或者贪
心？）的人们很快发现如果能够提供一些按需生成的动态信息会使web变得更加有用，就像给有线电视加上点播功能一样。这种需求一方面迫使HTML逐步添加
了表单、脚本、DOM等客户端行为，另一方面在服务器端则出现了CGI规范以响应客户端的动态请求，作为传输载体的HTTP协议也添加了文件上载、
&nbsp;cookie这些特性。其中cookie的作用就是为了解决HTTP协议无状态的缺陷所作出的努力。至于后来出现的session机制则是又一种在客户
端与服务器之间保持状态的解决方案。<br><br>让我们用几个例子来描述一下cookie和session机制之间的区别与联系。笔者曾经常去的一家咖啡店有喝5杯咖啡免费赠一杯咖啡的优惠，然而一次性消费5杯咖啡的机会微乎其微，这时就需要某种方式来纪录某位顾客的消费数量。想象一下其实也无外乎下面的几种方案：<br>1、该店的店员很厉害，能记住每位顾客的消费数量，只要顾客一走进咖啡店，店员就知道该怎么对待了。这种做法就是协议本身支持状态。<br>2、发给顾客一张卡片，上面记录着消费的数量，一般还有个有效期限。每次消费时，如果顾客出示这张卡片，则此次消费就会与以前或以后的消费相联系起来。这种做法就是在客户端保持状态。<br>3、发给顾客一张会员卡，除了卡号之外什么信息也不纪录，每次消费时，如果顾客出示该卡片，则店员在店里的纪录本上找到这个卡号对应的纪录添加一些消费信息。这种做法就是在服务器端保持状态。<br><br>由
于HTTP协议是无状态的，而出于种种考虑也不希望使之成为有状态的，因此，后面两种方案就成为现实的选择。具体来说cookie机制采用的是在客户端保
持状态的方案，而session机制采用的是在服务器端保持状态的方案。同时我们也看到，由于采用服务器端保持状态的方案在客户端也需要保存一个标识，所
以session机制可能需要借助于cookie机制来达到保存标识的目的，但实际上它还有其他选择。<br><br>三、理解cookie机制&nbsp;<br>cookie机制的基本原理就如上面的例子一样简单，但是还有几个问题需要解决：“会员卡”如何分发；“会员卡”的内容；以及客户如何使用“会员卡”。<br><br>正统的cookie分发是通过扩展HTTP协议来实现的，服务器通过在HTTP的响应头中加上一行特殊的指示以提示浏览器按照指示生成相应的cookie。然而纯粹的客户端脚本如JavaScript或者VBScript也可以生成cookie。<br><br>而cookie&nbsp;
的使用是由浏览器按照一定的原则在后台自动发送给服务器的。浏览器检查所有存储的cookie，如果某个cookie所声明的作用范围大于等于将要请求的
资源所在的位置，则把该cookie附在请求资源的HTTP请求头上发送给服务器。意思是麦当劳的会员卡只能在麦当劳的店里出示，如果某家分店还发行了自
己的会员卡，那么进这家店的时候除了要出示麦当劳的会员卡，还要出示这家店的会员卡。<br><br>cookie的内容主要包括：名字，值，过期时间，路径和域。<br>其中域可以指定某一个域比如.google.com，相当于总店招牌，比如宝洁公司，也可以指定一个域下的具体某台机器比如www.google.com或者froogle.google.com，可以用飘柔来做比。<br>路径就是跟在域名后面的URL路径，比如/或者/foo等等，可以用某飘柔专柜做比。<br>路径与域合在一起就构成了cookie的作用范围。<br>如
果不设置过期时间，则表示这个cookie的生命期为浏览器会话期间，只要关闭浏览器窗口，cookie就消失了。这种生命期为浏览器会话期的
&nbsp;cookie被称为会话cookie。会话cookie一般不存储在硬盘上而是保存在内存里，当然这种行为并不是规范规定的。如果设置了过期时间，浏览
器就会把cookie保存到硬盘上，关闭后再次打开浏览器，这些cookie仍然有效直到超过设定的过期时间。<br><br>存储在硬盘上的
cookie&nbsp;可以在不同的浏览器进程间共享，比如两个IE窗口。而对于保存在内存里的cookie，不同的浏览器有不同的处理方式。对于IE，在一个打
开的窗口上按&nbsp;Ctrl-N（或者从文件菜单）打开的窗口可以与原窗口共享，而使用其他方式新开的IE进程则不能共享已经打开的窗口的内存cookie；
对于&nbsp;Mozilla&nbsp;Firefox0.8，所有的进程和标签页都可以共享同样的cookie。一般来说是用javascript的
window.open打开的窗口会与原窗口共享内存cookie。浏览器对于会话cookie的这种只认cookie不认人的处理方式经常给采用
session机制的web应用程序开发者造成很大的困扰。<br><br>下面就是一个goolge设置cookie的响应头的例子<br>HTTP/1.1&nbsp;302&nbsp;Found<br>Location:&nbsp;<a href="http://www.google.com/intl/zh-CN/" target="_blank">http://www.google.com/intl/zh-CN/</a><br>Set-Cookie:&nbsp;PREF=ID=0565f77e132de138:NW=1:TM=1098082649:LM=1098082649:S=KaeaCFPo49RiA_d8; <br>expires=Sun, 17-Jan-2038&nbsp;19:14:07&nbsp;GMT;&nbsp;path=/;&nbsp;domain=.google.com<br>Content-Type:&nbsp;text/html<br><br><br><br><br>这是使用HTTPLook这个HTTP&nbsp;Sniffer软件来俘获的HTTP通讯纪录的一部分<br><br><br><br><br>浏览器在再次访问goolge的资源时自动向外发送cookie<br><br><br><br><br>使用Firefox可以很容易的观察现有的cookie的值<br>使用HTTPLook配合Firefox可以很容易的理解cookie的工作原理。<br><br><br><br><br>IE也可以设置在接受cookie前询问<br><br><br><br><br>这是一个询问接受cookie的对话框。<br><br>四、理解session机制<br>session机制是一种服务器端的机制，服务器使用一种类似于散列表的结构（也可能就是使用散列表）来保存信息。<br><br>当
程序需要为某个客户端的请求创建一个session的时候，服务器首先检查这个客户端的请求里是否已包含了一个session标识&nbsp;-&nbsp;称为
&nbsp;session&nbsp;id，如果已包含一个session&nbsp;id则说明以前已经为此客户端创建过session，服务器就按照session&nbsp;id把这个
&nbsp;session检索出来使用（如果检索不到，可能会新建一个），如果客户端请求不包含session&nbsp;id，则为此客户端创建一个session并且生
成一个与此session相关联的session&nbsp;id，session&nbsp;id的值应该是一个既不会重复，又不容易被找到规律以仿造的字符串，这个
&nbsp;session&nbsp;id将被在本次响应中返回给客户端保存。<br><br>保存这个session&nbsp;id的方式可以采用cookie，这样在交互过程中
浏览器可以自动的按照规则把这个标识发挥给服务器。一般这个cookie的名字都是类似于SEEESIONID，而。比如weblogic对于web应用
程序生成的cookie，JSESSIONID=
&nbsp;ByOK3vjFD75aPnrF7C2HmdnV6QZcEbzWoWiBYEnLerjQ99zWpBng!-145788764，它的名字就是
&nbsp;JSESSIONID。<br><br>由于cookie可以被人为的禁止，必须有其他机制以便在cookie被禁止时仍然能够把session&nbsp;id
传递回服务器。经常被使用的一种技术叫做URL重写，就是把session&nbsp;id直接附加在URL路径的后面，附加方式也有两种，一种是作为URL路径的
附加信息，表现形式为<a href="http://...../xxx;jsessionid=" target="_blank">http://...../xxx;jsessionid=</a>&nbsp;ByOK3vjFD75aPnrF7C2HmdnV6QZcEbzWoWiBYEnLerjQ99zWpBng!-145788764<br>另一种是作为查询字符串附加在URL后面，表现形式为<a href="http://...../xxx?jsessionid=ByOK3vjFD75aPnrF7C2HmdnV6QZcEbzWoWiBYEnLerjQ99zWpBng%21-145788764" target="_blank">http://...../xxx?jsessionid=ByOK3vjFD75aPnrF7C2HmdnV6QZcEbzWoWiBYEnLerjQ99zWpBng!-145788764</a><br>这两种方式对于用户来说是没有区别的，只是服务器在解析的时候处理的方式不同，采用第一种方式也有利于把session&nbsp;id的信息和正常程序参数区分开来。<br>为了在整个交互过程中始终保持状态，就必须在每个客户端可能请求的路径后面都包含这个session&nbsp;id。<br><br>另一种技术叫做表单隐藏字段。就是服务器会自动修改表单，添加一个隐藏字段，以便在表单提交时能够把session&nbsp;id传递回服务器。比如下面的表单<br>&lt;form&nbsp;name="testform"&nbsp;action="/xxx"&gt;<br>&lt;input&nbsp;type="text"&gt;<br>&lt;/form&gt;<br>在被传递给客户端之前将被改写成<br>&lt;form&nbsp;name="testform"&nbsp;action="/xxx"&gt;<br>&lt;input&nbsp;type="hidden"&nbsp;name="jsessionid" <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; value="ByOK3vjFD75aPnrF7C2HmdnV6QZcEbzWoWiBYEnLerjQ99zWpBng!-145788764"&gt;<br>&lt;input&nbsp;type="text"&gt;<br>&lt;/form&gt;<br>这种技术现在已较少应用，笔者接触过的很古老的iPlanet6(SunONE应用服务器的前身)就使用了这种技术。<br>实际上这种技术可以简单的用对action应用URL重写来代替。<br><br>在
谈论session机制的时候，常常听到这样一种误解“只要关闭浏览器，session就消失了”。其实可以想象一下会员卡的例子，除非顾客主动对店家提
出销卡，否则店家绝对不会轻易删除顾客的资料。对session来说也是一样的，除非程序通知服务器删除一个session，否则服务器会一直保留，程序
一般都是在用户做log&nbsp;off的时候发个指令去删除session。然而浏览器从来不会主动在关闭之前通知服务器它将要关闭，因此服务器根本不会有机会
知道浏览器已经关闭，之所以会有这种错觉，是大部分session机制都使用会话cookie来保存session&nbsp;id，而关闭浏览器后这个
&nbsp;session&nbsp;id就消失了，再次连接服务器时也就无法找到原来的session。如果服务器设置的cookie被保存到硬盘上，或者使用某种手段改
写浏览器发出的HTTP请求头，把原来的session&nbsp;id发送给服务器，则再次打开浏览器仍然能够找到原来的session。<br><br>恰恰是由于关闭浏览器不会导致session被删除，迫使服务器为seesion设置了一个失效时间，当距离客户端上一次使用session的时间超过这个失效时间时，服务器就可以认为客户端已经停止了活动，才会把session删除以节省存储空间。<br><br>五、理解javax.servlet.http.HttpSession<br>HttpSession是Java平台对session机制的实现规范，因为它仅仅是个接口，具体到每个web应用服务器的提供商，除了对规范支持之外，仍然会有一些规范里没有规定的细微差异。这里我们以BEA的Weblogic&nbsp;Server8.1作为例子来演示。<br><br>首
先，Weblogic&nbsp;Server提供了一系列的参数来控制它的HttpSession的实现，包括使用cookie的开关选项，使用URL重写的开关
选项，session持久化的设置，session失效时间的设置，以及针对cookie的各种设置，比如设置cookie的名字、路径、域，
&nbsp;cookie的生存时间等。<br><br>一般情况下，session都是存储在内存里，当服务器进程被停止或者重启的时候，内存里的session
也会被清空，如果设置了session的持久化特性，服务器就会把session保存到硬盘上，当服务器进程重新启动或这些信息将能够被再次使用，
&nbsp;Weblogic&nbsp;Server支持的持久性方式包括文件、数据库、客户端cookie保存和复制。<br><br>复制严格说来不算持久化保存，因为session实际上还是保存在内存里，不过同样的信息被复制到各个cluster内的服务器进程中，这样即使某个服务器进程停止工作也仍然可以从其他进程中取得session。<br><br>cookie生存时间的设置则会影响浏览器生成的cookie是否是一个会话cookie。默认是使用会话cookie。有兴趣的可以用它来试验我们在第四节里提到的那个误解。<br><br>cookie的路径对于web应用程序来说是一个非常重要的选项，Weblogic&nbsp;Server对这个选项的默认处理方式使得它与其他服务器有明显的区别。后面我们会专题讨论。<br><br>关于session的设置参考[5]&nbsp;<a href="http://e-docs.bea.com/wls/docs70/webapp/weblogic_xml.html#1036869" target="_blank">http://e-docs.bea.com/wls/docs70/webapp/weblogic_xml.html#1036869</a><br><br>六、HttpSession常见问题<br>（在本小节中session的含义为⑤和⑥的混合）<br><br><br>1、session在何时被创建<br>一
个常见的误解是以为session在有客户端访问时就被创建，然而事实是直到某server端程序调用
&nbsp;HttpServletRequest.getSession(true)这样的语句时才被创建，注意如果JSP没有显示的使用&nbsp;&lt;%
&nbsp;@page&nbsp;session="false"%&gt;&nbsp;关闭session，则JSP文件在编译成Servlet时将会自动加上这样一条语句
&nbsp;HttpSession&nbsp;session&nbsp;=&nbsp;HttpServletRequest.getSession(true);这也是JSP中隐含的
&nbsp;session对象的来历。<br><br>由于session会消耗内存资源，因此，如果不打算使用session，应该在所有的JSP中关闭它。<br><br>2、session何时被删除<br>综合前面的讨论，session在下列情况下被删除a.程序调用HttpSession.invalidate();或b.距离上一次收到客户端发送的session&nbsp;id时间间隔超过了session的超时设置;或c.服务器进程被停止（非持久session）<br><br>3、如何做到在浏览器关闭时删除session<br>严格的讲，做不到这一点。可以做一点努力的办法是在所有的客户端页面里使用javascript代码window.oncolose来监视浏览器的关闭动作，然后向服务器发送一个请求来删除session。但是对于浏览器崩溃或者强行杀死进程这些非常规手段仍然无能为力。<br><br>4、有个HttpSessionListener是怎么回事<br>你
可以创建这样的listener去监控session的创建和销毁事件，使得在发生这样的事件时你可以做一些相应的工作。注意是session的创建和销
毁动作触发listener，而不是相反。类似的与HttpSession有关的listener还有
&nbsp;HttpSessionBindingListener，HttpSessionActivationListener和
&nbsp;HttpSessionAttributeListener。<br><br>5、存放在session中的对象必须是可序列化的吗<br>不是必需
的。要求对象可序列化只是为了session能够在集群中被复制或者能够持久保存或者在必要时server能够暂时把session交换出内存。在
&nbsp;Weblogic&nbsp;Server的session中放置一个不可序列化的对象在控制台上会收到一个警告。我所用过的某个iPlanet版本如果
&nbsp;session中有不可序列化的对象，在session销毁时会有一个Exception，很奇怪。<br><br>6、如何才能正确的应付客户端禁止cookie的可能性<br>对所有的URL使用URL重写，包括超链接，form的action，和重定向的URL，具体做法参见[6]<br><a href="http://e-docs.bea.com/wls/docs70/webapp/sessions.html#100770" target="_blank">http://e-docs.bea.com/wls/docs70/webapp/sessions.html#100770</a><br><br>7、开两个浏览器窗口访问应用程序会使用同一个session还是不同的session<br>参见第三小节对cookie的讨论，对session来说是只认id不认人，因此不同的浏览器，不同的窗口打开方式以及不同的cookie存储方式都会对这个问题的答案有影响。<br><br>8、如何防止用户打开两个浏览器窗口操作导致的session混乱<br>这
个问题与防止表单多次提交是类似的，可以通过设置客户端的令牌来解决。就是在服务器每次生成一个不同的id返回给客户端，同时保存在session里，客
户端提交表单时必须把这个id也返回服务器，程序首先比较返回的id与保存在session里的值是否一致，如果不一致则说明本次操作已经被提交过了。可
以参看《J2EE核心模式》关于表示层模式的部分。需要注意的是对于使用javascript&nbsp;window.open打开的窗口，一般不设置这个id，
或者使用单独的id，以防主窗口无法操作，建议不要再window.open打开的窗口里做修改操作，这样就可以不用设置。<br><br>9、为什么在Weblogic&nbsp;Server中改变session的值后要重新调用一次session.setValue<br>做这个动作主要是为了在集群环境中提示Weblogic&nbsp;Server&nbsp;session中的值发生了改变，需要向其他服务器进程复制新的session值。<br><br>10、为什么session不见了<br>排
除session正常失效的因素之外，服务器本身的可能性应该是微乎其微的，虽然笔者在iPlanet6SP1加若干补丁的Solaris版本上倒也遇到
过；浏览器插件的可能性次之，笔者也遇到过3721插件造成的问题；理论上防火墙或者代理服务器在cookie处理上也有可能会出现问题。<br>出现这一问题的大部分原因都是程序的错误，最常见的就是在一个应用程序中去访问另外一个应用程序。我们在下一节讨论这个问题。<br><br>七、跨应用程序的session共享<br><br>常
常有这样的情况，一个大项目被分割成若干小项目开发，为了能够互不干扰，要求每个小项目作为一个单独的web应用程序开发，可是到了最后突然发现某几个小
项目之间需要共享一些信息，或者想使用session来实现SSO(single&nbsp;sign&nbsp;on)，在session中保存login的用户信息，最自
然的要求是应用程序间能够访问彼此的session。<br><br>然而按照Servlet规范，session的作用范围应该仅仅限于当前应用程序
下，不同的应用程序之间是不能够互相访问对方的session的。各个应用服务器从实际效果上都遵守了这一规范，但是实现的细节却可能各有不同，因此解决
跨应用程序session共享的方法也各不相同。<br><br>首先来看一下Tomcat是如何实现web应用程序之间session的隔离的，从
&nbsp;Tomcat设置的cookie路径来看，它对不同的应用程序设置的cookie路径是不同的，这样不同的应用程序所用的session&nbsp;id是不同
的，因此即使在同一个浏览器窗口里访问不同的应用程序，发送给服务器的session&nbsp;id也可以是不同的。<br><br><br>&nbsp;&nbsp;<br><br>根据这个特性，我们可以推测Tomcat中session的内存结构大致如下。<br><br><br><br><br>笔
者以前用过的iPlanet也采用的是同样的方式，估计SunONE与iPlanet之间不会有太大的差别。对于这种方式的服务器，解决的思路很简单，实
际实行起来也不难。要么让所有的应用程序共享一个session&nbsp;id，要么让应用程序能够获得其他应用程序的session&nbsp;id。<br><br>iPlanet中有一种很简单的方法来实现共享一个session&nbsp;id，那就是把各个应用程序的cookie路径都设为/（实际上应该是/NASApp，对于应用程序来讲它的作用相当于根）。<br>&lt;session-info&gt;<br>&lt;path&gt;/NASApp&lt;/path&gt;<br>&lt;/session-info&gt;<br><br>需
要注意的是，操作共享的session应该遵循一些编程约定，比如在session&nbsp;attribute名字的前面加上应用程序的前缀，使得
&nbsp;setAttribute("name",&nbsp;"neo")变成setAttribute("app1.name",&nbsp;"neo")，以防止命名空间冲
突，导致互相覆盖。<br><br><br>在Tomcat中则没有这么方便的选择。在Tomcat版本3上，我们还可以有一些手段来共享
session。对于版本4以上的Tomcat，目前笔者尚未发现简单的办法。只能借助于第三方的力量，比如使用文件、数据库、JMS或者客户端
cookie，URL参数或者隐藏字段等手段。<br><br>我们再看一下Weblogic&nbsp;Server是如何处理session的。<br><br><br>&nbsp;&nbsp;<br><br>从
截屏画面上可以看到Weblogic&nbsp;Server对所有的应用程序设置的cookie的路径都是/，这是不是意味着在Weblogic&nbsp;Server中
默认的就可以共享session了呢？然而一个小实验即可证明即使不同的应用程序使用的是同一个session，各个应用程序仍然只能访问自己所设置的那
些属性。这说明Weblogic&nbsp;Server中的session的内存结构可能如下<br><br><br><br><br>对于这样一种结构，在
&nbsp;session机制本身上来解决session共享的问题应该是不可能的了。除了借助于第三方的力量，比如使用文件、数据库、JMS或者客户端
&nbsp;cookie，URL参数或者隐藏字段等手段，还有一种较为方便的做法，就是把一个应用程序的session放到ServletContext中，这样
另外一个应用程序就可以从ServletContext中取得前一个应用程序的引用。示例代码如下，<br><br>应用程序A<br>context.setAttribute("appA",&nbsp;session);&nbsp;<br><br>应用程序B<br>contextA&nbsp;=&nbsp;context.getContext("/appA");<br>HttpSession&nbsp;sessionA&nbsp;=&nbsp;(HttpSession)contextA.getAttribute("appA");&nbsp;<br><br>值得注意的是这种用法不可移植，因为根据ServletContext的JavaDoc，应用服务器可以处于安全的原因对于context.getContext("/appA");返回空值，以上做法在Weblogic&nbsp;Server&nbsp;8.1中通过。<br><br>那
么Weblogic&nbsp;Server为什么要把所有的应用程序的cookie路径都设为/呢？原来是为了SSO，凡是共享这个session的应用程序都可
以共享认证的信息。一个简单的实验就可以证明这一点，修改首先登录的那个应用程序的描述符weblogic.xml，把cookie路径修改为
/appA&nbsp;访问另外一个应用程序会重新要求登录，即使是反过来，先访问cookie路径为/的应用程序，再访问修改过路径的这个，虽然不再提示登录，但
是登录的用户信息也会丢失。注意做这个实验时认证方式应该使用FORM，因为浏览器和web服务器对basic认证方式有其他的处理方式，第二次请求的认
证不是通过&nbsp;session来实现的。具体请参看[7]&nbsp;secion&nbsp;14.8&nbsp;Authorization，你可以修改所附的示例程序来做这些试验。<br><br>八、总结<br>session机制本身并不复杂，然而其实现和配置上的灵活性却使得具体情况复杂多变。这也要求我们不能把仅仅某一次的经验或者某一个浏览器，服务器的经验当作普遍适用的经验，而是始终需要具体情况具体分析。<br>摘
要：虽然session机制在web应用程序中被采用已经很长时间了，但是仍然有很多人不清楚session机制的本质，以至不能正确的应用这一技术。本
文将详细讨论session的工作机制并且对在Java&nbsp;web&nbsp;application中应用session机制时常见的问题作出解答。</span></font><img src ="http://www.blogjava.net/cmd/aggbug/34646.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/cmd/" target="_blank">静夜思</a> 2006-03-10 13:10 <a href="http://www.blogjava.net/cmd/articles/34646.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>J2EE 中的安全</title><link>http://www.blogjava.net/cmd/articles/30939.html</link><dc:creator>静夜思</dc:creator><author>静夜思</author><pubDate>Thu, 16 Feb 2006 03:03:00 GMT</pubDate><guid>http://www.blogjava.net/cmd/articles/30939.html</guid><wfw:comment>http://www.blogjava.net/cmd/comments/30939.html</wfw:comment><comments>http://www.blogjava.net/cmd/articles/30939.html#Feedback</comments><slash:comments>1</slash:comments><wfw:commentRss>http://www.blogjava.net/cmd/comments/commentRss/30939.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/cmd/services/trackbacks/30939.html</trackback:ping><description><![CDATA[<h1><font size="2">J2EE 中的安全第一部分</font></h1><font size="2"><br>现在越来越多的企业应用构建在j2ee平台上,这得益于j2ee为企业应用的开发提供了良好的框架和服务的支持.j2ee为企业应用提供了多方面的服务
(Security、Transaction、Naming等).本文将介绍j2ee提供的安全服务.作者首先介绍j2ee中的安全概念和j2ee的安全
体系架构.然后结合具体的实例向读者展示如何在自己的程序中应用j2ee提供的安全特性。<br></font><p><font size="2"><a name="1"><span class="atitle">一.简介</span></a></font></p>

      <p>

      </p><p><font size="2">现
在越来越多的企业应用构建在j2ee平台上,这得益于j2ee为企业应用的开发提供了良好的框架和服务的支持.j2ee为企业应用提供了多方面的服务
(Security、Transaction、Naming等).本文将介绍j2ee提供的安全服务.作者首先介绍j2ee中的安全概念和j2ee的安全
体系架构.然后结合具体的实例向读者展示如何在自己的程序中应用j2ee提供的安全特性。本文所介绍的内容是基于j2ee1.3版本的。</font></p><p><font size="2"><a name="2"><span class="atitle">二．j2ee中的安全概念</span></a></font></p>

      <p>

      </p><p>

        <font size="2"><b>主体（Principal）：</b>主
体（Principal）是被在企业安全服务验证了的实体。主体（Principal）用主体名作为它的标识，通过与主体相关的验证数据进行验证。通常情
况下主体名就是用户的登陆名，验证数据就是登陆的密码。J2EE规范中并没有限定J2EE
产品提供商使用怎样的认证方法，因此主体名和验证数据的内容和格式依不同的认证协议而不同。 </font></p>

      <p>

        <font size="2"><b>安全策略域（Security Policy Domain）：</b>也
称安全域（security domain）或
realm，它是一个逻辑范围或区域，在这一范围或区域中安全服务的管理员定义和实施通用的安全策略。它是从安全策略的角度划分的区域。比如可以将企业应
用系统划分为企业员工、供应商、合作伙伴等不同的安全域，对这些安全区域采用不同的安全策略。 </font></p>

      <p>

        <font size="2"><b>安全技术域（Security Technology Domain）：</b>它是从安全技术的角度划分的区域，在一个安全技术域中使用同样的安全机制来执行安全策略。一个安全技术域可以包括多个安全策略域。
      </font></p>

      <p>

        <font size="2"><b>安全属性（Security Attributes）：</b>每
个主体（Principal）都有一系列与之相关的安全属性。安全属性可用来访问被保护的资源，检查用户的身份和完成其他一些安全相关的用途。J2EE产
品提供商或具体的验证服务的实现来决定怎样将安全属性与一个主体联系起来。J2EE规范并没有限定什么样的安全属性将与主体相联系。 </font></p>

      <p>

        <font size="2"><b>凭证（Credential）：</b>凭证包含或引用为J2EE 系统验证一个主体的验证信息（安全属性）。如果成功的通过了验证，主体将获得一个包括安全属性的凭证。如果被允许的话，一个主体也可能获取另一个主体的凭证。在这种情况下两个主体在同一安全域中具有相同的安全属性。</font></p><p><font size="2"><a name="3"><span class="atitle">三．j2ee的安全体系结构</span></a></font></p>

      <p>


      </p><p><font size="2"><a name="N10074"><span class="smalltitle">1．  基于容器的安全</span></a></font></p>


      <p><font size="2">在j2ee
的环境中，组件的安全是由他们各自的容器来负责的，组件的开发人员几乎可以不用或者很少在组件中添加有关安全的代码。这种安全逻辑和业务逻辑相对独立的架
构，使得企业级应用系统有更好的灵活性和扩展性。J2ee规范要求j2ee
产品必须为应用程序开发者提供两种形式的基于容器的安全性－说明性的安全性和可编程的安全性。</font></p>

      <p>

        <font size="2"><b>a.  说明性的安全性</b></font>

      </p>

      <p><font size="2">说
明性的安全性通过安全结构描述的方式来代表应用程序的安全需求，安全结构一般包括安全角色，访问控制和验证要求等。在j2ee平台中部署描述符充当了说明
的安全性的主要工具。部署描述符是组件开发者和应用程序部署者或应用程序组装者之间的交流工具。应用程序的开发者用它来表示应用中的安全需求，应用程序部
署者或应用程序组装者将安全角色与部署环境中的用户和组映射起来。</font></p>

      <p><font size="2">在程序运行时容器从部署描述符中提取出相应的安全策略，然后容器根据安全策略执行安全验证。说明的安全性不需要开发人员编写任何安全相关的代码，一切都是通过配置部署描述符来完成的。</font></p>

      <p>

        <font size="2"><b>b.      可编程的安全性</b></font>

      </p>

      <p><font size="2">可
编程的安全性在说明性的安全性的基础上，使安全敏感的应用可以通过调用被容器提供的API来对安全作出决断。这在说明性的安全性不足以满足企业的安全模型
的情况是非常有用的。J2ee在EJB EjbConext interface和servlet HttpServletRequest
interface中各提供两个方法：</font></p>

 
      <table bgcolor="#eeeeee" border="1" cellpadding="5" cellspacing="0" width="100%"><tbody><tr><td><pre><font size="2"><code class="section"> <br>isCallerInRole (EJBContext)<br>getCallerPrincipal (EJBContext)<br>isUserInRole (HttpServletRequest)<br>getUserPrincipal (HttpServletRequest) <br></code></font></pre></td></tr></tbody></table><font size="2"><br></font> 


      <p><font size="2">这些方法允许组件根据调用者或远程用户的安全角色来作出商业判断。在文章的后面部分将有这些方法的详细介绍和例程，以便读者更好的理解可编程的安全性的用途。</font></p>


      <p><font size="2"><a name="N10099"><span class="smalltitle">2．J2ee的验证模型</span></a></font></p>


      <p><font size="2">身份验证是用户或组件调用者向系统证明其身份的过程。用户通过某种方式向系统提交验证信息（通常是用户名和密码或者是用户的数字证书），系统用用户提供的验证信息和系统的安全策略来验证用户的身份。</font></p>


      

        <font size="2"><br><a name="N100A4"><b>图一  初始验证过程</b></a><br><img alt="图一 初始验证过程" src="http://www-128.ibm.com/developerworks/cn/java/l-j2eeSecurity/init.jpg" height="200" width="500">

      <br> 图一  初始验证过程


      

        <br><a name="N100B3"><b>图二  验证URL</b></a><br><img alt="图二 验证URL" src="http://www-128.ibm.com/developerworks/cn/java/l-j2eeSecurity/url.jpg" height="200" width="500">

      <br><br><a name="N100C2"><b>图三 验证EJB方法调用</b></a><br><img alt="图三 验证EJB方法调用" src="http://www-128.ibm.com/developerworks/cn/java/l-j2eeSecurity/ejb-invoke.jpg" height="300" width="650">

      <br></font>

        

        


      

        

        

      <p>

        <font size="2"><b>用户的验证</b></font>

      </p>

      <p><font size="2">用户的验证根据其客户端类型不同分为两种：Web 客户端的验证和Application客户端的验证</font></p>

      <p>

        <font size="2"><b>a.	 Web 客户端的验证</b></font>

      </p>

      <p><font size="2">Web
客户端通常通过http协议来请求web服务器端的资源，这些web资源通常包括html网页、jsp（java server
page）文件、java
servlet和其他一些二进制或多媒体文件。在企业环境中，企业的某些资源往往要求只允许某些人访问，有些资源甚至是机密的或安全敏感的。因此对企业中
各种web资源进行访问控制是十分必要的。为了满足企业中的不同安全级别和客户化的需求，j2ee提供了三种基于web客户端的验证方式：</font></p>

      <p>

        <font size="2"><b>HTTP基本验证（HTTP Basic Authentication）</b>
        <br>
HTTP基本验证
是HTTP协议所支持的验证机制。这种验证机制使用用户的用户名和密码作为验证信息。Web客户端从用户获取用户名和密码，然后传递他们给web服务器，
web服务器在指定的区域（realm）中验证用户。但需要注意的是，这种验证方法是不够安全的。因为这种验证方法并不对用户密码进行加密，而只是对密码
进行基本的base64的编码。而且目标web服务器对用户来说也是非验证过的。不能保证用户访问到的web服务器就是用户希望访问的。可以采用一些安全
措施来克服这个弱点。例如在传输层上应用SSL或者在网络层上使用IPSEC或VPN技术。 </font></p>

      <p>

        <font size="2"><b>基于表单的验证（Form-Based Authentication）</b>
        <br>
基于表单的验证
使系统开发者可以自定义用户的登陆页面和报错页面。这种验证方法与基本HTTP的验证方法的唯一区别就在于它可以根据用户的要求制定登陆和出错页面。基于
表单的验证方法同样具有与基本HTTP验证类似的不安全的弱点。用户在表单中填写用户名和密码，而后密码以明文形式在网路中传递，如果在网路的某一节点将
此验证请求截获，在经过反编码很容易就可以获取用户的密码。因此在使用基本HTTP的验证方式和基于表单的验证方法时，一定确定这两种方式的弱点对你的应
用是可接受的。 </font></p>

      <p>

        <font size="2"><b>基于客户端证书的验证（Client-Certificate Authentication）</b>
        <br>
基于客户端证书的验证方式要比上面两种方式更安全。它通过HTTPS（HTTP over SSL）来保证验证的安全性。安全套接层（Secure
Sockets
Layer）为验证过程提供了数据加密，服务器端认证，信息真实性等方面的安全保证。在此验证方式中，客户端必须提供一个公钥证书，你可以把这个公钥证书
看作是你的数字护照。公钥证书也称数字证书，它是被称作证书授权机构（CA）－一个被信任的组织颁发的。这个数字证书必须符合X509公钥体系结构
（PKI）的标准。如果你指定了这种验证方式，Web服务器将使用客户端提供的数字证书来验证用户的身份。 </font></p>

      <p>

        <font size="2"><b>b.      应用程序客户端的验证（Application Client User Authentication）</b>
        <br>
java客户端程序是执行在用户本地java虚拟机上的java程序，它拥有main方法，通常由用户可通过java.exe或javaw.exe直接启
动执行。J2ee应用程序客户端与java客户端程序相似，也拥有main方法，但他们在运行时存在一定的差别。J2ee应用程序客户端和其他j2ee组
件一样运行在自己的容器中。用户通过容器来执行J2ee应用程序客户端。这样J2ee应用程序客户端容器就有机会在J2ee应用程序客户端被执行之前完成
用户身份的验证。J2ee提供了一种可自定义的方式来获取用户的验证信息。可以选择使用容器提供的缺省的方式来获取j2ee应用客户端程序的用户的验证信
息，也可以选择自定义的方式来获取用户的验证信息。当选择自定义方式时，应用程序开发者必须提供一个实现了
javax.security.auth.callback.CallbackHandler
interfce的类,并且在j2ee部署描述文件application-client.xml中的元素callback-handler中加入这个类
的类名。这样，当系统需要验证用户身份时，客户端程序的容器将部署描述文件中的CallbackHandler实现类的类名传递给系统的登陆模块（验证模
块），登陆模块再实例化这个实现类。这个类的实例负责收集用户验证信息，并将收集到的用户验证信息传递给登陆模块，登陆模块用这些验证信息来验证用户。这
个实现类可以是具有用户界面的，或是通过要求用户输入来收集用户验证信息，也可以是通过命令行来获取用户验证信息，还可能是通过读取本地或在线的用户证书
库来获取用户的电子证书。选取哪种方式取决于验证信息的存储方式。 </font></p>

      <p><font size="2">有些j2ee产品厂商把容器的验证服务和本地系统的验证服务或其他应用系统产品的验证服务集成起来，从而在一定的应用系统的范围内实现单点登陆的能力。</font></p>

      <p>

        <font size="2"><b>单点登陆 （Single Sign-On）</b>
        <br>
单点登从用户的视角是指用户在特定的逻辑安全区域中，只需进行一次登陆即可在访问在此逻辑安全区域中不同应用系统中的被授权的资源，只有超越了安全区域边
缘时才要求再次登陆。这种能力对多种IT应用系统共存的企业显得尤为有价值。随着企业信息化建设程度的不断提高，企业中的应用系统也越来越多。在传统的应
用系统中，各系统各自维护自己的安全策略，这些安全策略典型的包括组织结构定义，安全角色定义，用户身份验证，资源访问控制等。由于各系统互相独立，一个
用户在使用每一应用系统之前，都必须按照相应的系统身份进行系统登陆。这对于用户来说必须记住每一个系统的用户名和密码，给用户带来了不小的麻烦。针对于
这种情况，单点登陆的概念随之产生，并不断的应用到企业的应用系统的集成当中。J2ee1.3也在规范中建议j2ee产品应为应用系统提供单点登陆的能
力。但j2ee1.3规范并没有规定j2ee产品应遵循何种标准，因此不同的厂商的产品在单点登陆上的实现和应用各不相同。有的j2ee产品实现了在本产
品环境范围内的单点登陆，有的实现了特定系统环境之间的单点登陆（如IBM WebSphere Application 4.0 AE
实现了WebSphere Application Server与WebSphere Application Server、WebSphere
Application Server与Lotus Domino server、WebSphere Application
Server与Lotus Domino
server之间的单点登陆能力）。在j2ee中单点登陆是通过传递凭证（Credential）来实现的.当用户进行系统登陆时，客户端容器（包括
WEB客户端和应用程序客户端）根据用户的凭证（Credential）为用户建立一个安全上下文（security
Context），安全上下文包含用于验证用户的安全信息，系统用这个安全上下文和安全策略来判断用户是否有访问系统资源的权限。遗憾的时j2ee规范并
没有规定安全上下文的格式，因此不能在不同厂商的j2ee产品之间传递安全上下文。到目前为止还很少有在不同的j2ee产品间互相共享安全上下文，因此在
不同j2ee产品间实现单点登陆只能通过第三方产品（如LDAP server等）集成的方式。 </font></p>

      <p>

        <font size="2"><b>惰性验证（Lazy Authentication）</b>
        <br>
身份验是有代价的。例如，一次验证过程也许包括多次通过网络信息交换。因此惰性验证就非常有用了。惰性验证使当用户访问受保护的资源时才执行验证过程，而不是在用户第一次发起请求时就执行验证过程。
      </font></p>

      <p><font size="2"><a name="N10114"><span class="smalltitle">3. J2ee的授权模型</span></a></font></p>

      <p>

      </p><p>

        <font size="2"><b>代码授权（Code Authorization）</b>
        <br>
j2ee产品通过java 2 安全模型来限制特定J2SE的类和方法的执行，以保护和确保操作系统的安全。详细描述请参阅《J2SE规范文档》。
      </font></p>

      <p>

        <font size="2"><b>调用者授权（Caller Authorization）</b>
        <br>
安全角色：安全角色是具有相同安全属性的逻辑组。它由应用程序的装配者（Application  Assembler）或应用程序的部署者（Application Deployer）分配的。
      </font></p>

      <p>

        <font size="2"><b>安全角色引用：</b>安全角色引用是应用程序提供者（Application Provider）用来引用安全角色的标识。应用程序提供者（Application Provider）可以用安全角色引用来为安全角色分配资源访问的权限。也在安全相关的程序代码中引用安全角色。
      </font></p>

      <p>

        <font size="2"><b>用户和组：</b>用户和组是在实际系统环境下的用户和用户的集合。它们对应者现实当中的人和群体。
      </font></p>

      <p>

        <font size="2"><b>访问控制：</b>访问控制可以确保安全角色只能访问已授予它安全权限的授权对象。授权对象包括EJB的远程方法、web资源（html网页，jsp/servlet和多媒体或二进制文件）等。在j2ee中访问控制在应用程序描述文件中与安全角色关联起来。
      </font></p>

      <p>

        <font size="2"><b>映射：</b>通过映射应用程序的系统管理员将实际系统环境中的用户和角色与安全角色联系起来，从而是实际的用户拥有对企业资源访问的适当授权。
      </font></p>

      <p>

        <font size="2"><b>被传播的调用者身份标识（Propagated Caller Identities）</b>
        <br>
在j2ee
1.3中可以选择用传播调用者标识作为web组件和ejb组件调用者的标识来进行验证。在这种方式下，整个ejb组件的调用链中interface
EJBContext的方法getCallerPrincipal返回相同的主体名（principal
name）。如果调用链中的第一个ejb是被jsp/servlet调用的，interface
EJBContext的方法getCallerPrincipal返回的主体名（principal name）应与interface
HttpServletRequest的方法getUserPrincipal的返回值相同。要注意的是在调用链中传递的是用户的标识，而不是凭证
（credentials），这一点非常重要，因为在调用链的每个节点上用户可能使用不同的安全属性。 </font></p>

      <p>

        <font size="2"><b>Run As Identities</b>
        <br>
J2ee 1.3中提供了允许组件开发者和部署这来指定组件以什么身份运行的方法。符合j2ee1.3规范的产品会提供将组件设置成Run As
Identities方式的方法。如果Run As Identities方式被选中，在运行中被设置为Run As
Identities的组件的调用者不再是调用链中第一个节点的调用者了，而是在部署时被指定的调用者。而调用链中随后节点的调用者也变为与被设置为
Run As Identities的组件的调用者相同。 </font></p>


      

        <font size="2"><br><a name="N10156"><b>图四 用户标识传递</b></a><br><img alt="图四 用户标识传递" src="http://www-128.ibm.com/developerworks/cn/java/l-j2eeSecurity/t1.jpg" height="300" width="650">

      <br></font>

        

      <p><font size="2">这一部分介绍了j2ee的安全概念，意在使读者能够对j2ee在安全方面有一定的了解，后面还会有应用这些概念的具体例子。</font></p><font size="2"><br></font><p><font size="2"><br></font></p><h1><font size="2">J2EE 中的安全第二部分--j2ee安全应用</font></h1><font size="2"><br></font><blockquote><font size="2">在本系列文章的第一部分作者介绍了j2ee的安全概念、验证模型和授权模型，这部分更偏重于理论的介绍。本文的第二部分作者将通过具体的例子向读者展示如何在开发中应用j2ee提供的安全服务。本部分的重点在于应用与实践。</font></blockquote>

      <p><font size="2">注
释：本文的目的是介绍如何应用j2ee提供的安全服务，而并不针对特定的产品。因此作者选择sun的
j2ee参考实现（j2sdkee）作为演示平台。因为j2sdkee是完全遵照j2ee规范开发的，虽然它不像IBM WebSphere 、BEA
WebLogic等j2ee产品那么产品化和商业化，但它绝对是学习j2ee的理想平台。你可以通过 <a href="http://java.sun.com/j2ee/">http://java.sun.com/j2ee/</a>获取sun 的j2ee参考实现的最新版本。本文选择的是Sun的j2sdkee1.3.1。
      </font></p>

      <p><font size="2">本文将包括以下内容：</font></p>

      <ol><li><font size="2">一个采用HTTP基本的验证的例子</font></li><li><font size="2">一个采用基于表单的验证的例子</font></li><li><font size="2">一个ejb方法授权的例子</font></li><li><font size="2">一个可编程安全性和传播调用者身份标识的例子</font></li></ol>

      <p><font size="2"><a name="1"><span class="atitle">采用HTTP基本的验证的例子</span></a></font></p>

      <p>

      </p><p><font size="2">http基本验证是Web客户端验证的一种，它和系统的授权机制一起控制受保护资源的访问。</font></p>

      <p><font size="2">步骤：</font></p>


      <p><font size="2"><a name="N1005F"><span class="smalltitle">1．	创建一个j2ee应用</span></a></font></p>


      <p><font size="2">在应用程序部署工具的File菜单选中New子菜单中的Application菜单项（见图1）。会弹出新建应用程序对话框。填写应用程序文件名和应用程序显示名（见图2）。</font></p>


      

        <font size="2"><br><a name="N1006A"><b>图1</b></a><br><img alt="图1" src="http://www-128.ibm.com/developerworks/cn/java/l-j2eesecurity2/1.gif" height="260" width="390">

      <br><br><a name="N10079"><b>图2</b></a><br><img alt="图2" src="http://www-128.ibm.com/developerworks/cn/java/l-j2eesecurity2/2.gif" height="161" width="390">

      <br></font>

        


      

        

        

      <p><font size="2"><a name="N10086"><span class="smalltitle">2．	创建一个web组件</span></a></font></p>


      <p><font size="2">在
应用程序部署工具的File菜单选中New子菜单中的Web Compent菜单项,会弹出新建web组件向导对话框(见图3)。选择Create
New WAR File in Application,在下拉框中选择步骤1创建的应用test，在WAR Display
Name框中填写WebAppTest.点击Content栏中
的Eidt按钮选择此Web组件包含的文件。在这个例子中只有一个webtest.jsp文件。然后点击Next，进入下一个对话框（见图4）。由于我们
的web组件是一个jsp文件，因此在组件类型中选择JSP。然后一直按Next直到结束。此时我们已经创建了一个只包含一个jsp文件的web组件。接
下来是配置安全属性的步骤。</font></p>


      

        <font size="2"><br><a name="N10091"><b>图3</b></a><br><img alt="图3" src="http://www-128.ibm.com/developerworks/cn/java/l-j2eesecurity2/3.gif" height="360" width="482">

      <br><br><a name="N100A0"><b>图4</b></a><br><img alt="图4" src="http://www-128.ibm.com/developerworks/cn/java/l-j2eesecurity2/4.gif" height="352" width="482">

      <br></font>

        


      

        

        


      <p><font size="2"><a name="N100AD"><span class="smalltitle">3．	配置安全属性</span></a></font></p>


      <p>

        <font size="2"><b>3．1创建安全角色</b></font>

      </p>

      <p><font size="2">在部署工具的左导航栏中点中步骤2创建的web组件WebAppTest，在右边的属性页中选择Roles属性页（见图5）。点击Add按钮，在Name栏中填写安全角色名user,Description栏填写描述信息。安全角色代表具有相同安全权限用户的集合。</font></p>


      

        <font size="2"><br><a name="N100BE"><b>图5</b></a><br><img alt="图5" src="http://www-128.ibm.com/developerworks/cn/java/l-j2eesecurity2/5.gif" height="360" width="498">

      <br></font>

        

      <p>

        <font size="2"><b>3.2 配置安全策略</b></font>

      </p>

      <p><font size="2">创建了安全角色后，应该对安全角色配置相应的安全策略。点击Security属性页（见图6）。</font></p>


      

        <font size="2"><br><a name="N100D6"><b>图6</b></a><br><img alt="图6" src="http://www-128.ibm.com/developerworks/cn/java/l-j2eesecurity2/6.gif" height="387" width="498">

      <br></font>

        

      <p><font size="2">首
先选择你想用的验证方式，从User Authentication
Method下拉框中选择Basic。这意味着你将通过基本的HTTP验证方式验证用户。下面我们进行web资源的授权。点击Security
Constraint栏中的Add按钮添加一条安全约束，约束名可以自定。接下来对创建好的约束添加Web资源。首先在Web Resource
Collections中添加资源集合，然后选取资源集合包含的资源。此例中WRCollection资源集合中包含webtest.jsp文件，也可以
包含各种属于这个web组件的文件。接下来选择哪些web操作要收到约束，j2sdkee1.3.1中只包含两种操作（GET和POST），不同的产品支
持的操作有所不同，在开发是应结合具体产品提供的操作来选取。现在应该指定安全角色了，点击Authorized
Roles栏中的Edit按钮，会弹出安全角色列表对话框，从中选取已定义的安全角色。本例中选择user。至此安全策略已经配置完毕，下面的步骤是将实
际环境中的用户和用户组映射与安全角色进行映射。</font></p>


      <p><font size="2"><a name="N100E6"><span class="smalltitle">4．	映射</span></a></font></p>


      <p><font size="2">在
左导航栏中选中应用程序test在右边选择Security属性页（见图7），在Role Name
Reference栏中选中user，点击正下方的Add按钮，会弹出用户和用户组列表对话框，从中选择要映射成安全角色user的用户或组。此例中我们
将用户j2ee映射为安全角色user。这样用户J2ee将具有为安全角色user分配的访问授权。</font></p>


      

        <font size="2"><br><a name="N100F1"><b>图7</b></a><br><img alt="图7" src="http://www-128.ibm.com/developerworks/cn/java/l-j2eesecurity2/7.gif" height="335" width="485">

      <br></font>

        


      <p><font size="2"><a name="N100FE"><span class="smalltitle">5．	部署应用</span></a></font></p>


      <p><font size="2">选中Web Context属性页，在Context Root文本框中填写test,右键点击左导航栏的应用test，在弹出菜单中选择deploy完成应用程序的发布。至此我们完成了第一个例子的全部步骤。</font></p>

      <p>

        <font size="2"><b>部署描述文件</b></font>

      </p>

      <p><font size="2">这个例子使用了说明性的安全服务，因此我们不需要编写任何的安全相关的代码，而是完全通过配置组件的部署描述文件来实现的。下面是这个web组件的部署描述文件。</font></p>

 
      <table bgcolor="#eeeeee" border="1" cellpadding="5" cellspacing="0" width="100%"><tbody><tr><td><pre><font size="2"><code class="section"><br>&lt;?xml version="1.0" encoding="UTF-8"?&gt;<br>&lt;!DOCTYPE web-app PUBLIC <br>'-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN'<br>'http://java.sun.com/dtd/web-app_2_3.dtd'&gt;<br>&lt;web-app&gt;<br>  &lt;display-name&gt;WebAppTest&lt;/display-name&gt;     //Web组件名称<br>  &lt;servlet&gt;<br>    &lt;servlet-name&gt;webtest&lt;/servlet-name&gt;<br>    &lt;display-name&gt;webtest&lt;/display-name&gt;<br>    &lt;jsp-file&gt;/webtest.jsp&lt;/jsp-file&gt;              //组件中包含的jsp文件<br>  &lt;/servlet&gt;<br>  &lt;session-config&gt;<br>    &lt;session-timeout&gt;30&lt;/session-timeout&gt;<br>  &lt;/session-config&gt;<br>  &lt;security-constraint&gt;                                //安全约束部分<br>    &lt;web-resource-collection&gt;                         //受约束的web资源集<br>      &lt;web-resource-name&gt;WRCollection&lt;/web-resource-name&gt;  //资源集名<br>      &lt;url-pattern&gt;/webtest.jsp&lt;/url-pattern&gt;                  //资源的url表达式<br>      &lt;http-method&gt;GET&lt;/http-method&gt;                     //受约束的资源操作方法<br>      &lt;http-method&gt;POST&lt;/http-method&gt;<br>    &lt;/web-resource-collection&gt;<br>    &lt;auth-constraint&gt;                                    //对安全角色授权<br>      &lt;role-name&gt;user&lt;/role-name&gt;                        //安全角色名<br>    &lt;/auth-constraint&gt;<br>    &lt;user-data-constraint&gt;<br>      &lt;transport-guarantee&gt;NONE&lt;/transport-guarantee&gt;<br>    &lt;/user-data-constraint&gt;<br>  &lt;/security-constraint&gt;<br>  &lt;login-config&gt;                                        //验证方式设置<br>    &lt;auth-method&gt;BASIC&lt;/auth-method&gt;                   //使用基本的HTTP验证方式<br>    &lt;realm-name&gt;&lt;/realm-name&gt;<br>  &lt;/login-config&gt;<br>  &lt;security-role&gt;                                        //定义安全角色<br>    &lt;description&gt;this is a user&lt;/description&gt;                      <br>    &lt;role-name&gt;user&lt;/role-name&gt;<br>  &lt;/security-role&gt;<br>&lt;/web-app&gt;<br></code></font></pre></td></tr></tbody></table><font size="2"><br></font> 


      <p><font size="2">从部署描述文件可以知道这是
一个名为WebAppTest的web组件，包含一个名为webtest.jsp的文件，只有被赋予user安全角色的用户或用户组才有权对
webtest.jsp进行GET或POST操作。这里并没有包含安全角色对实际用户的映射，j2ee部署描述文件的DTD中并没有定义用于安全角色和实
际用户的映射的元素，因为实际环境中有多种不同的用户系统（如关系数据库，系统文件形式和LDAP系统等）。因此安全角色和实际用户的映射方式是由
j2ee产品厂商制定的。</font></p>

      <p>

        <font size="2"><b>测试运行结果</b></font>

      </p>

      <p><font size="2">打
开ie，在导航栏输入http://localhost:8000/test/webtest.jsp回车，会弹出验证对话框，要求用户提供用户名和密码
（见图8），输入用户名j2ee和密码j2ee。通过用户验证后执行jsp文件，webtest.jsp打印出"hello!"（见图9）。</font></p>


      

        <font size="2"><br><a name="N10122"><b>图8</b></a><br><img alt="图8" src="http://www-128.ibm.com/developerworks/cn/java/l-j2eesecurity2/8.gif" height="335" width="485">

      <br><br><a name="N10131"><b>图9</b></a><br><img alt="图9" src="http://www-128.ibm.com/developerworks/cn/java/l-j2eesecurity2/9.gif" height="347" width="521">

      <br></font>

        


      

        

        

      <p><font size="2">注释：在第一个例子中已经详细的描述了各个步骤，在接下来的例子中会有一些与第一个例子相同的操作，因此对下面的例子只描述与第一个例子不同的步骤。</font></p>

      <p>

        <font size="2"><b>基于表单的验证的例子</b></font>

      </p>

      <p><font size="2">基于表单的验证与基本的HTTP验证的唯一区别是基本的HTTP验证用浏览器提供的验证信息对话框收集用户验证信息，而基于表单的验证允许自定义登陆页面来收集用户验证信息。本例子与第一个例子的步骤基本相同，不同的地方在于此例子要提供登陆页面和出错页面。</font></p>

      <p><font size="2">登陆页面login.html</font></p>

 
      <table bgcolor="#eeeeee" border="1" cellpadding="5" cellspacing="0" width="100%"><tbody><tr><td><pre><font size="2"><code class="section"><br>&lt;form method="POST" action="j_security_check"&gt;<br>&lt;input type=text name="j_username"&gt;<br>&lt;input type=password name="j_password"&gt;<br>&lt;input type=submit name="login" value="login"&gt;<br>&lt;/form&gt;<br></code></font></pre></td></tr></tbody></table><font size="2"><br></font> 


      <p><font size="2">此文件有几个地方值得注意：</font></p>

      <ol><li><font size="2">Action的值必须为"j_security_check"</font></li><li><font size="2">获取用户名的域名必须是"j_username"</font></li><li><font size="2">获取用户密码的域必须是" j_password"</font></li></ol>

      <p><font size="2">出错页面 error.html</font></p>

 
      <table bgcolor="#eeeeee" border="1" cellpadding="5" cellspacing="0" width="100%"><tbody><tr><td><pre><font size="2"><code class="section"><br>&lt;html&gt;<br>用户名或密码不正确！<br>&lt;/html&gt;<br></code></font></pre></td></tr></tbody></table><font size="2"><br></font> 


      <p><font size="2">出错页面只是简单的显示出错信息。</font></p>

      <p>

        <font size="2"><b>配置基于表单的验证</b></font>

      </p>

      <p><font size="2">首先将login.html和error.html加入到WebAppTest组件中。
        <br>
然后见图10选择Security属性页，在User Authentication Method下拉框中选择Form
Based选项。点击Settings…弹出用户验证设置对话框，在Login Page下拉框选login.html,在Error
Page下拉框选error.html。 </font></p>


      

        <font size="2"><br><a name="N10177"><b>图10</b></a><br><img alt="图10" src="http://www-128.ibm.com/developerworks/cn/java/l-j2eesecurity2/10.gif" height="360" width="476">

      <br></font>

        

      <p><font size="2">重新部署应用，再一次访问http://localhost:8000/test/webtest.jsp 会出现login页面（见图11），如果用户名或密码错误，error.html将显示给用户。</font></p>


      

        <font size="2"><br><a name="N10189"><b>图11</b></a><br><img alt="图11" src="http://www-128.ibm.com/developerworks/cn/java/l-j2eesecurity2/11.gif" height="347" width="515">

      <br></font>

        

      <p>

        <font size="2"><b>部署描述文件</b></font>

      </p>

 
      <table bgcolor="#eeeeee" border="1" cellpadding="5" cellspacing="0" width="100%"><tbody><tr><td><pre><font size="2"><code class="section"><br>&lt;?xml version="1.0" encoding="UTF-8"?&gt;<br>&lt;!DOCTYPE web-app PUBLIC <br>'-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN'<br>'http://java.sun.com/dtd/web-app_2_3.dtd'&gt;<br>&lt;web-app&gt;<br>  &lt;display-name&gt;WebAppTest&lt;/display-name&gt;     <br>                 .<br>                 .<br>                 .<br>&lt;transport-guarantee&gt;NONE&lt;/transport-guarantee&gt;<br>    &lt;/user-data-constraint&gt;<br>  &lt;/security-constraint&gt;<br>&lt;login-config&gt;<br>    &lt;auth-method&gt;FORM&lt;/auth-method&gt;                   //使用基于表单的验证方式<br>    &lt;realm-name&gt;Default&lt;/realm-name&gt;                    //使用缺省的安全域<br>    &lt;form-login-config&gt;<br>      &lt;form-login-page&gt;/login.html&lt;/form-login-page&gt;        //定义登陆页面<br>      &lt;form-error-page&gt;/error.html&lt;/form-error-page&gt;       //定义出错页面<br>    &lt;/form-login-config&gt;<br>  &lt;/login-config&gt;<br>&lt;security-role&gt;                                        <br>    &lt;description&gt;this is a user&lt;/description&gt;                      <br>    &lt;role-name&gt;user&lt;/role-name&gt;<br>  &lt;/security-role&gt;<br>&lt;/web-app&gt;<br></code></font></pre></td></tr></tbody></table><font size="2"><br><br></font> 

      <table border="0" cellpadding="0" cellspacing="0" width="100%"><tbody><tr><td><font size="2"><img src="http://www.ibm.com/i/v14/rules/blue_rule.gif" alt="" height="1" width="100%"></font></td></tr></tbody></table><table class="no-print" align="right" cellpadding="0" cellspacing="0"><tbody><tr align="right"><td><table border="0" cellpadding="0" cellspacing="0"><tbody><tr><td valign="middle"><font size="2"><img src="http://www.ibm.com/i/v14/icons/u_bold.gif" alt="" border="0" height="16" width="16"><br></font></td><td align="right" valign="top"><font size="2"><a href="http://www-128.ibm.com/developerworks/cn/java/l-j2eesecurity2/#main" class="fbox"><b>回页首</b></a></font></td></tr></tbody></table></td></tr></tbody></table><font size="2"><br><br></font><p><font size="2"><a name="2"><span class="atitle">ejb方法授权的例子</span></a></font></p>


      <p>

      </p><p><font size="2">从j2ee1.3
开始便提供了对ejb的方法进行授权的安全服务，这种授权服务由ejb容器实现。当调用者调用ejb的方法时，ejb容器用调用者的身份来查找授予此调用
者的访问权限条目，如果调用者调用的方法属于授权条目，那么ejb容器调用方法。否则，ejb容器拒绝调用此方法，并向调用者返回拒绝访问异常。可以对远
程方法和home接口方法进行授权。本例中我们将对一个远程方法和一个home接口方法进行授权。</font></p>

 
      <table bgcolor="#eeeeee" border="1" cellpadding="5" cellspacing="0" width="100%"><tbody><tr><td><pre><font size="2"><code class="section"><br>首先创建一个session bean CountEjb<br>远程接口 Count.java<br>import javax.ejb.*;<br>import java.rmi.RemoteException;<br><br>public interface Count extends EJBObject {<br><br>  /**<br>   * 远程方法count<br>   */<br>  public int count() throws RemoteException;<br>}<br><br>Home接口 CountHome.java<br>import javax.ejb.*;<br>import java.rmi.RemoteException;<br><br>/**<br> * This is the home interface for CountBean.  <br> * One create() method is in this Home Interface, which<br> * corresponds to the ejbCreate() method in the CountBean file.<br> */<br>public interface CountHome extends EJBHome {<br><br>   /*<br>    * This method creates the EJB Object.<br>    *<br>    * @param val Value to initialize counter to<br>    *<br>    * @return The newly created EJB Object.<br>    */<br>  Count create(int val) throws RemoteException, CreateException;<br>}<br><br>实现类 CountBean.java<br>import javax.ejb.*;<br>import java.security.Principal;<br><br>/**<br> <br>public class CountBean implements SessionBean {<br>	<br>	// The current counter is our conversational state.<br>	public int val;<br>    private SessionContext sessionCtx;<br>        <br>	//<br>	// 远程商业方法实现<br>	public int count() {<br>		System.out.println("count()");<br>          <br>		return ++val;<br>	}<br><br>	//<br>	// home接口Create方法的实现<br>	//<br><br>	public void ejbCreate(int val) throws CreateException {<br>		this.val = val;<br>		System.out.println("ejbCreate()");<br>	}<br><br>	public void ejbRemove() {<br>		System.out.println("ejbRemove()");<br>	}<br><br>	public void ejbActivate() {<br>		System.out.println("ejbActivate()");<br>	}<br><br>	public void ejbPassivate() {<br>		System.out.println("ejbPassivate()");<br>	}<br><br>	public void setSessionContext(SessionContext ctx) {<br>            sessionCtx=ctx;<br>	}<br>}<br>客户端程序 CountClient.java<br>import javax.ejb.*;<br>import javax.naming.*;<br>import java.util.Properties;<br><br>/**<br> * This class is a simple example of client code.<br> */<br>public class CountClient {<br><br>	public static void main(String[] args) {<br><br>		try {<br>			<br>            InitialContext ctx = new InitialContext();<br>                        <br>			CountHome home = (CountHome)<br>			  javax.rmi.PortableRemoteObject.narrow(<br>			    ctx.lookup("java:comp/env/CountHome"), CountHome.class);<br>			   <br><br>			int countVal = 0;<br>			Count count=null;<br><br>			/*<br>			创建并执行远程方法			<br>*/<br>			System.out.println("Instantiating beans...");<br>			<br>				 <br>				count = home.create(countVal);<br>                               <br>				countVal = count.count();<br><br>				System.out.println(countVal);<br><br>			/*<br>			 remove Count对象 <br>			 */<br>			count.remove();<br>		<br>		} catch (Exception e) {<br>            System.out.println(e.getMessage());<br>			e.printStackTrace();<br>		}<br>	}<br>}<br></code></font></pre></td></tr></tbody></table><font size="2"><br></font> 

      <p><font size="2">这个ejb包括一个远程商业方法count()，我们将将此方法授权给某个安全角色。此外还将home接口的Create()方法授权给安全角色。</font></p>

      <p>

        <font size="2"><b>步骤1</b></font>

      </p>

      <p><font size="2">编译以上源程序并用j2sdkee1.3.1的组装发布工具（deploytool.bat）进行组装（如图12）。</font></p>


      

        <font size="2"><br><a name="N101BD"><b>图12</b></a><br><img alt="图12" src="http://www-128.ibm.com/developerworks/cn/java/l-j2eesecurity2/12.gif" height="347" width="525">

      <br></font>

        

      <p>

        <font size="2"><b>步骤2：配置安全角色</b></font>

      </p>

      <p><font size="2">在对方法进行授权前，必须创建将被授权的安全角色。创建安全角色的步骤前边已经介绍过了，此处不再重复。本例中我们创建名为admin的安全角色。</font></p>

      <p>

        <font size="2"><b>步骤3：方法授权</b></font>

      </p>

      <p><font size="2">方法授权的过程是确定那些安全角色可以访问特定方法的过程。方法授权一般是应用程序组装或应用程序部署者的责任。他们根据企业特定的需求创建不同的安全角色，并授予这些安全角色特定的访问权限。</font></p>

      <p><font size="2">用
鼠标选中CountBean，在右端的窗口选择Security属性页（如图13），在Security Identity选项中选择Use
Caller ID,这意味着ejb容器将用方法调用者的身份来验证方法调用权限。
Run As Specified
Role选项将在"传播调用者身份标识的例子"进行介绍。由于在前面创建了admin安全角色，因此你可以看到Method
Permissions栏中出现admin列。首先对远程方法count()进行授权。选择Remote选项，并在count()方法的
Availability列中选择Sel
Roles，然后选中count()方法的admin列。到此为止我们已对远程方法count()进行了授权。接下来对home接口的create()方
法进行授权。在Show栏中选择Remote
Home，剩下的步骤与count()方法授权相同。我们已经将count()方法和create()方法授权给了admin安全角色。但安全角色这是一
个逻辑的集合，并不代表具体的用户或用户组，因此结下来我们要做的就是将安全角色与实际的用户映射起来。</font></p>

      <p>

        <font size="2"><b>步骤4：角色映射</b></font>

      </p>

      <p><font size="2">首先我们需要在我们的j2ee环境中创建一个用户，用户起名为Tony，密码为1。</font></p>


      

        <font size="2"><br><a name="N101EA"><b>图13</b></a><br><img alt="图13" src="http://www-128.ibm.com/developerworks/cn/java/l-j2eesecurity2/13.gif" height="356" width="528">

      <br></font>

        

      <p><font size="2">这
里我们使用用户名和密码的方式进行身份验证。我们在default Realm中创建此用户。可以使用命令行方式："realmtool -add
Tony 1
eng"。详细的使用方法参见j2sdk1.3.1文档的工具部分。接下来映射安全角色到用户。选中ejb应用CountEjb，在右边窗口中选择
Security属性页（如图14），点击Edit
Roles按钮，选择安全角色admin。再点击Add按钮选择Tony用户。这样已经将安全角色admin和用户Tony映射起来了。</font></p>

      <p>

        <font size="2"><b>步骤5：部署应用</b></font>

      </p>

      <p><font size="2">部署应用到本地机，右键点击ejb应用CountEjb，选择弹出菜单的deploy项，按要求配置各项。</font></p>

      <p>

        <font size="2"><b>步骤6：创建客户端</b></font>

      </p>

      <p><font size="2">创建客户端将客户端程序的主类和其他辅助类打包。创建端将客的过程比较简单，这里就不作描述了。</font></p>

      <p>

        <font size="2"><b>步骤7：运行程序</b></font>

      </p>

      <p><font size="2">现在可以运行客户端程序来验证方法的授权了。通过命令runclient.bat -client客户端jar包文件名 -name 主类名来执行客户端程序。客户端程序的容器将显示一个对话框来提示用户输入用户名和密码（如图15），填写用户名和密码，按OK。</font></p>


      

        <font size="2"><br><a name="N10217"><b>图15</b></a><br><img alt="图15" src="http://www-128.ibm.com/developerworks/cn/java/l-j2eesecurity2/15.gif" height="142" width="359">

      <br></font>

        

      <p><font size="2">若用户名或密码与授权的方法不符，则会抛出没有权限异常（如图16）。</font></p>


      

        <font size="2"><br><a name="N10229"><b>图16</b></a><br><img alt="图16" src="http://www-128.ibm.com/developerworks/cn/java/l-j2eesecurity2/16.gif" height="423" width="630">

      <br><br></font>

        

      <table border="0" cellpadding="0" cellspacing="0" width="100%"><tbody><tr><td><font size="2"><img src="http://www.ibm.com/i/v14/rules/blue_rule.gif" alt="" height="1" width="100%"></font></td></tr></tbody></table><table class="no-print" align="right" cellpadding="0" cellspacing="0"><tbody><tr align="right"><td><table border="0" cellpadding="0" cellspacing="0"><tbody><tr><td valign="middle"><font size="2"><img src="http://www.ibm.com/i/v14/icons/u_bold.gif" alt="" border="0" height="16" width="16"><br></font></td><td align="right" valign="top"><font size="2"><a href="http://www-128.ibm.com/developerworks/cn/java/l-j2eesecurity2/#main" class="fbox"><b>回页首</b></a></font></td></tr></tbody></table></td></tr></tbody></table><font size="2"><br><br></font><p><font size="2"><a name="3"><span class="atitle">Countejb的部署描述文件ejb.xml</span></a></font></p>

      <p>

 
      </p><table bgcolor="#eeeeee" border="1" cellpadding="5" cellspacing="0" width="100%"><tbody><tr><td><pre><font size="2"><code class="section"><br>&lt;?xml version="1.0" encoding="UTF-8"?&gt;<br><br>&lt;!DOCTYPE ejb-jar PUBLIC <br>'-//Sun Microsystems, Inc.//DTD Enterprise JavaBeans 2.0//EN' <br>'http://java.sun.com/dtd/ejb-jar_2_0.dtd'&gt;<br><br>&lt;ejb-jar&gt;<br>  &lt;display-name&gt;count&lt;/display-name&gt;     <br>  &lt;enterprise-beans&gt;<br>    &lt;session&gt;                          // CountBean属于session bean<br>      &lt;display-name&gt;CountBean&lt;/display-name&gt;  //ejb组件的显示名<br>      &lt;ejb-name&gt;CountBean&lt;/ejb-name&gt;         //ejb组件名<br><br>      &lt;home&gt;CountHome&lt;/home&gt;          //Home接口<br>      &lt;remote&gt;Count&lt;/remote&gt;             //远程接口<br>      &lt;ejb-class&gt;CountBean&lt;/ejb-class&gt;      //实现类<br>      &lt;session-type&gt;Stateful&lt;/session-type&gt;   // CountBean属于Stateful Bean<br>      &lt;transaction-type&gt;Container&lt;/transaction-type&gt;    //CountBean事务类型为容器管理的<br>      &lt;security-identity&gt;                           //安全标识<br>        &lt;description&gt;&lt;/description&gt;<br>        &lt;use-caller-identity&gt;&lt;/use-caller-identity&gt;		//CountBean使用调用者的身份标识<br>      &lt;/security-identity&gt;<br>    &lt;/session&gt;<br>  &lt;/enterprise-beans&gt;<br>  &lt;assembly-descriptor&gt;                           <br>    &lt;security-role&gt;<br>      &lt;role-name&gt;admin&lt;/role-name&gt;             //定义安全角色admin<br>    &lt;/security-role&gt;<br>    &lt;method-permission&gt;                //将方法count和remove授权给安全角色admin<br>      &lt;role-name&gt;admin&lt;/role-name&gt;<br><br>      &lt;method&gt;                       //方法定义<br>        &lt;ejb-name&gt;CountBean&lt;/ejb-name&gt;<br>        &lt;method-intf&gt;Remote&lt;/method-intf&gt;<br>        &lt;method-name&gt;count&lt;/method-name&gt;<br>        &lt;method-params /&gt;<br>      &lt;/method&gt;<br>      &lt;method&gt;<br>        &lt;ejb-name&gt;CountBean&lt;/ejb-name&gt;<br>        &lt;method-intf&gt;Home&lt;/method-intf&gt;<br>        &lt;method-name&gt;remove&lt;/method-name&gt;<br>        &lt;method-params&gt;<br>          &lt;method-param&gt;java.lang.Object&lt;/method-param&gt;<br>        &lt;/method-params&gt;<br>      &lt;/method&gt;<br>    &lt;/method-permission&gt;<br>    &lt;method-permission&gt;<br>      &lt;unchecked /&gt;              //不检查以下方法的授权<br>      &lt;method&gt;<br>        &lt;ejb-name&gt;CountBean&lt;/ejb-name&gt;<br>        &lt;method-intf&gt;Remote&lt;/method-intf&gt;<br>        &lt;method-name&gt;getHandle&lt;/method-name&gt;<br>        &lt;method-params /&gt;<br>      &lt;/method&gt;<br>          .<br>          .<br>          .<br>          .<br>      &lt;method&gt;<br>        &lt;ejb-name&gt;CountBean&lt;/ejb-name&gt;<br>        &lt;method-intf&gt;Remote&lt;/method-intf&gt;<br>        &lt;method-name&gt;getEJBHome&lt;/method-name&gt;<br>        &lt;method-params /&gt;<br>      &lt;/method&gt;<br>    &lt;/method-permission&gt;<br>    &lt;container-transaction&gt;        // CountBean的事务属性<br>      &lt;method&gt;<br>        &lt;ejb-name&gt;CountBean&lt;/ejb-name&gt;<br>        &lt;method-intf&gt;Remote&lt;/method-intf&gt;<br>        &lt;method-name&gt;count&lt;/method-name&gt;<br>        &lt;method-params /&gt;<br>      &lt;/method&gt;<br>      &lt;trans-attribute&gt;Required&lt;/trans-attribute&gt;<br>    &lt;/container-transaction&gt;<br>  &lt;/assembly-descriptor&gt;<br>&lt;/ejb-jar&gt;<br></code></font></pre></td></tr></tbody></table><font size="2"><br><br></font> 

      <table border="0" cellpadding="0" cellspacing="0" width="100%"><tbody><tr><td><font size="2"><img src="http://www.ibm.com/i/v14/rules/blue_rule.gif" alt="" height="1" width="100%"></font></td></tr></tbody></table><table class="no-print" align="right" cellpadding="0" cellspacing="0"><tbody><tr align="right"><td><table border="0" cellpadding="0" cellspacing="0"><tbody><tr><td valign="middle"><font size="2"><img src="http://www.ibm.com/i/v14/icons/u_bold.gif" alt="" border="0" height="16" width="16"><br></font></td><td align="right" valign="top"><font size="2"><a href="http://www-128.ibm.com/developerworks/cn/java/l-j2eesecurity2/#main" class="fbox"><b>回页首</b></a></font></td></tr></tbody></table></td></tr></tbody></table><font size="2"><br><br></font><p><font size="2"><a name="4"><span class="atitle">可编程安全性和传播调用者身份标识的例子</span></a></font></p>


      <p>

      </p><p><font size="2">此例程包括两个部分，分别演示可编程安全性和调用者身份传播。</font></p>


      <p><font size="2"><a name="N1024D"><span class="smalltitle">可编程安全性例程</span></a></font></p>


      <p><font size="2">可
编程安全性可应用在web层和EJB层，分别是通过javax.servlet.http.HttpServletRequest接口的
isUserInRole ()、getUserPrincipal
()方法和javax.ejb.EJBContext接口的isCallerInRole ()、getCallerPrincipal
()方法来实现的。
public boolean isUserInRole(java.lang.String role)方法
此方法用来判断调用者是否属于某一特定的安全角色，如果属于返回true，否则返回false。
参数role指定某一安全角色。通过此方法开发者可以在程序代码中加入自己的安全逻辑判断，从而增强了J2EE在安全方面的灵活性。</font></p>

      <p><font size="2">public
java.security.Principal getUserPrincipal()方法
调用此方法可以得到一个java.security.Principal对象，此对象包含了调用者的用户名，通过Principal.getName()
方法可以得到用户名。通过调用getUserPrincipal()方法开发者可以得到调用者的用户名，然后对调用者的用户名进行特定的逻辑判断。</font></p>

      <p><font size="2">public java.security.Principal getCallerPrincipal()方法 和public boolean isCallerInRole(java.lang.String roleName)方法的作用和方法同上。</font></p>

      <p><font size="2">下面我们通过例程来演示这些方法的用法
        <br>
程序清单：
        <br>
webtest.jsp
      </font></p>

 
      <table bgcolor="#eeeeee" border="1" cellpadding="5" cellspacing="0" width="100%"><tbody><tr><td><pre><font size="2"><code class="section"><br>&lt;%@page contentType="text/html"%&gt;<br>&lt;html&gt;<br>&lt;head&gt;&lt;title&gt;JSP Page&lt;/title&gt;&lt;/head&gt;<br>&lt;body&gt;<br><br>&lt;%-- &lt;jsp:useBean id="beanInstanceName" scope="session" class="package.class" /&gt; --%&gt;<br>&lt;%-- &lt;jsp:getProperty name="beanInstanceName"  property="propertyName" /&gt; --%&gt;<br>Hello!<br>the caller is &lt;%=request.getUserPrincipal().getName()%&gt;&lt;br/&gt; &lt;%--得到调用者的用户名--％&gt;<br>&lt;% if (request.isUserInRole("admin")){%&gt;  &lt;%--判断调用者是否属于"admin"安全角色--%&gt;<br>the caller is admin Role;<br>&lt;%} %&gt;<br>&lt;/body&gt;<br>&lt;/html&gt;<br><br>web.xml<br>&lt;?xml version="1.0" encoding="UTF-8"?&gt;<br>&lt;!DOCTYPE web-app PUBLIC <br>'-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN' <br>'http://java.sun.com/dtd/web-app_2_3.dtd'&gt;<br>&lt;web-app&gt;<br>  &lt;display-name&gt;WebApp&lt;/display-name&gt;<br>  &lt;servlet&gt;<br>    &lt;servlet-name&gt;webtest&lt;/servlet-name&gt;<br>    &lt;display-name&gt;webtest&lt;/display-name&gt;<br>    &lt;jsp-file&gt;/webtest.jsp&lt;/jsp-file&gt;<br>    &lt;security-role-ref&gt;<br>      &lt;role-name&gt;adminref&lt;/role-name&gt;<br>      &lt;role-link&gt;admin&lt;/role-link&gt;<br>    &lt;/security-role-ref&gt;<br>    &lt;security-role-ref&gt;<br>      &lt;role-name&gt;guestref&lt;/role-name&gt;<br>      &lt;role-link&gt;guest&lt;/role-link&gt;<br>    &lt;/security-role-ref&gt;<br>  &lt;/servlet&gt;<br>  &lt;session-config&gt;<br>    &lt;session-timeout&gt;30&lt;/session-timeout&gt;<br>  &lt;/session-config&gt;<br>  &lt;welcome-file-list&gt;<br>    &lt;welcome-file&gt;webtest.jsp&lt;/welcome-file&gt;<br>  &lt;/welcome-file-list&gt;<br>  &lt;security-constraint&gt;<br>    &lt;web-resource-collection&gt;<br>      &lt;web-resource-name&gt;WRCollection&lt;/web-resource-name&gt;<br>      &lt;url-pattern&gt;/webtest.jsp&lt;/url-pattern&gt;<br>      &lt;http-method&gt;GET&lt;/http-method&gt;<br>      &lt;http-method&gt;POST&lt;/http-method&gt;<br>    &lt;/web-resource-collection&gt;<br>    &lt;auth-constraint&gt;<br>      &lt;role-name&gt;admin&lt;/role-name&gt;<br>      &lt;role-name&gt;guest&lt;/role-name&gt;<br>    &lt;/auth-constraint&gt;<br>    &lt;user-data-constraint&gt;<br>      &lt;transport-guarantee&gt;NONE&lt;/transport-guarantee&gt;<br>    &lt;/user-data-constraint&gt;<br>  &lt;/security-constraint&gt;<br>  &lt;login-config&gt;<br>    &lt;auth-method&gt;BASIC&lt;/auth-method&gt;<br>    &lt;realm-name&gt;&lt;/realm-name&gt;<br>  &lt;/login-config&gt;<br>  &lt;security-role&gt;<br>    &lt;role-name&gt;admin&lt;/role-name&gt;<br>  &lt;/security-role&gt;<br>  &lt;security-role&gt;<br>    &lt;role-name&gt;guest&lt;/role-name&gt;<br>  &lt;/security-role&gt;<br>&lt;/web-app&gt;<br></code></font></pre></td></tr></tbody></table><font size="2"><br></font> 


      <p><font size="2">从web.xml文件的内容可以看出，只有安全角色为"admin"和"guest"的用户才有权对webtest.jsp文件进行POST和GET操作。</font></p>

      <p>

        <font size="2"><b>运行结果：</b></font>

      </p>

      <p><font size="2">创
建一个web应用，将webtest.jsp作为一个web组件，并按照web.xml的内容配置web应用，在运行环境中将用户j2ee分配为
admin安全角色，将用户Tony分配为guest角色。发布web应用到本地j2ee Server上。用ie访问webtest.jsp
如图17用用户j2ee的身份进行验证，用户j2ee属于admin安全角色。显示结果见图18</font></p>


      

        <font size="2"><br><a name="N10275"><b>图17</b></a><br><img alt="图17" src="http://www-128.ibm.com/developerworks/cn/java/l-j2eesecurity2/17.gif" height="292" width="565">

      <br><br><a name="N10284"><b>图18</b></a><br><img alt="图18" src="http://www-128.ibm.com/developerworks/cn/java/l-j2eesecurity2/18.gif" height="344" width="547">

      <br></font>

        


      

        

        

      <p><font size="2">如果用用户Tony进行验证，结果见图19</font></p>


      

        <font size="2"><br><a name="N10296"><b>图19</b></a><br><img alt="图19" src="http://www-128.ibm.com/developerworks/cn/java/l-j2eesecurity2/19.gif" height="360" width="558">

      <br></font>

        

      <p><font size="2">ejb中应用可编程的安全性与在web中的方法相似，本文不再进行介绍</font></p>

      <font size="2"><br></font><table border="0" cellpadding="0" cellspacing="0" width="100%"><tbody><tr><td><font size="2"><img src="http://www.ibm.com/i/v14/rules/blue_rule.gif" alt="" height="1" width="100%"></font></td></tr></tbody></table><table class="no-print" align="right" cellpadding="0" cellspacing="0"><tbody><tr align="right"><td><table border="0" cellpadding="0" cellspacing="0"><tbody><tr><td valign="middle"><font size="2"><img src="http://www.ibm.com/i/v14/icons/u_bold.gif" alt="" border="0" height="16" width="16"><br></font></td><td align="right" valign="top"><font size="2"><a href="http://www-128.ibm.com/developerworks/cn/java/l-j2eesecurity2/#main" class="fbox"><b>回页首</b></a></font></td></tr></tbody></table></td></tr></tbody></table><font size="2"><br><br></font><p><font size="2"><a name="5"><span class="atitle">传播调用者身份标识例程</span></a></font></p>

      <p>
      </p><p><font size="2">本例程将演示调用者身份标识如何在调用链中传递的，并且介绍如何应用"Run As"来实现在调用链中更改调用者的身份。本例将用一个web组件（一个jsp文件）和两个ejb组件来形成一个调用链。
        <br>
程序清单：
        <br>
webtest.jsp
      </font></p>

 
      <table bgcolor="#eeeeee" border="1" cellpadding="5" cellspacing="0" width="100%"><tbody><tr><td><pre><font size="2"><code class="section"><br>&lt;%@page contentType="text/html"%&gt;<br>&lt;%@page import="andy.*"%&gt;<br>&lt;%@page import="javax.naming.*"%&gt;<br>&lt;html&gt;<br>&lt;head&gt;&lt;title&gt;JSP Page&lt;/title&gt;&lt;/head&gt;<br>&lt;body&gt;<br><br>&lt;%-- &lt;jsp:useBean id="beanInstanceName" scope="session" class="package.class" /&gt; --%&gt;<br>&lt;%-- &lt;jsp:getProperty name="beanInstanceName"  property="propertyName" /&gt; --%&gt;<br>Hello!<br>the caller is &lt;%=request.getUserPrincipal().getName()%&gt; &lt;br/&gt;<br>&lt;% if (request.isUserInRole("admin")){%&gt;<br>the caller is admin Role;<br>&lt;%} %&gt;<br>&lt;%  <br>    try {<br>    Context ctx = new InitialContext();<br>    andy.CountHome home = <br>(andy.CountHome)javax.rmi.PortableRemoteObject.narrow(<br>ctx.lookup("java:comp/env/CountHome"), andy.CountHome.class);<br>    andy.Count count = home.create(1);<br>    count.count();<br>    }catch (Exception e) <br>    {<br>	e.printStackTrace();<br>    }<br>%&gt;<br>&lt;/body&gt;<br>&lt;/html&gt;<br></code></font></pre></td></tr></tbody></table><font size="2"><br></font> 


      <p>

        <font size="2"><b>CountBean.java</b></font>

      </p>

 
      <table bgcolor="#eeeeee" border="1" cellpadding="5" cellspacing="0" width="100%"><tbody><tr><td><pre><font size="2"><code class="section"><br>package andy;<br>import javax.ejb.*;<br>import javax.naming.*;<br>public class CountBean implements SessionBean {<br>	public int val;<br>    private SessionContext EjbCxt = null;<br>	public int count()<br>{<br>        int temp = 0;<br>		System.out.println("CountBean.count()");<br>		//打印调用者名<br>        System.out.println("the caller is "+EjbCxt.getCallerPrincipal().getName());<br>		//判断调用者的安全角色<br>        if(EjbCxt.isCallerInRole("adminref")) // adminref为安全角色admin的引用名<br>        {<br>            System.out.println("the caller is admin Role");<br>         }<br>         if(EjbCxt.isCallerInRole("guestref")) // guestref为安全角色guest的引用名<br>         {<br>              System.out.println("the caller is guest Role");<br>          }<br>         if(EjbCxt.isCallerInRole("userref")) // userref为安全角色user的引用名<br>         {<br>              System.out.println("the caller is user Role");<br>          }<br>         //调用另一个ejb的远程方法<br>          try {<br>                   Context ctx = new InitialContext();<br>                   CountHome1 home = <br>(CountHome1)javax.rmi.PortableRemoteObject.narrow(<br>ctx.lookup("java:comp/env/CountHome1"), CountHome1.class);<br>          Count1 count = home.create(1);<br>          temp = count.count();<br>          }catch (Exception e) <br>                {<br>                      e.printStackTrace();<br>                 }<br>		return ++temp;<br>	}<br>	public void ejbCreate(int val) throws CreateException {<br>		this.val = val;<br>	}<br>	public void ejbRemove() {<br>	}<br>	public void ejbActivate() {<br>	}<br>	public void ejbPassivate() {<br>	}<br>	public void setSessionContext(SessionContext ctx) {<br>            EjbCxt = ctx;  //获取EjbContext对象<br>	}<br>}<br><br>CountBean1.java<br>package andy;<br>import javax.ejb.*;<br><br>public class CountBean1 implements SessionBean {<br>	public int val;<br>    private SessionContext EjbCxt = null; <br>	public int count() {<br>		System.out.println("CountBean1.count()");<br>        System.out.println("the caller is "+EjbCxt.getCallerPrincipal().getName());<br>                if(EjbCxt.isCallerInRole("adminref"))<br>                {<br>                    System.out.println("the caller is admin Role");<br>                }<br>                if(EjbCxt.isCallerInRole("guestref"))<br>                {<br>                    System.out.println("the caller is guest Role");<br>                }<br>                if(EjbCxt.isCallerInRole("userref"))<br>                {<br>                    System.out.println("the caller is user Role");<br>                }<br>                <br>		return ++val;<br>	}<br>	public void ejbCreate(int val) throws CreateException {<br>		this.val = val;<br>	}<br>	public void ejbRemove() {<br>	}<br>	public void ejbActivate() {<br>	}<br>	public void ejbPassivate() {<br>	}<br>	public void setSessionContext(SessionContext ctx) {<br>            EjbCxt = ctx;<br>	}<br>}<br></code></font></pre></td></tr></tbody></table><font size="2"><br></font> 


      <p><font size="2">以上的三个文件分别是一个
web组件和两个ejb组件的源代码,这三个组件构成了一个调用链.webtest.jsp中,首先通过HttpServletRequest..
getUserPrincipal
()方法来得到调用webtest.jsp的用户的Principal对象,在通过Principal.getName()方法得到调用者的用户名.
然后通过HttpServletRequest..isUserInRole()方法来判断调用这是否属于特定的安全角色.CountBean是一个
stateful
SessoinBean,它拥有一个count()远程方法,在这个远程方法中写了用于得到调用者用户名和判断调用这安全角色的代码,还包括调用
CountBean1对象的代码,用于展示调用者身份标识是如何在调用链中传递和调用者的安全角色如何被改变的.CountBean1也是一个
stateful SessoinBean,它的代码内容与CountBean基本相同,只不过它不包含调用其他Bean的代码.</font></p>

      <p><font size="2">现
在我们应该配置各组件的安全属性了.我们在组件webtest中创建安全角色admin,引用名为adminref,在组件CountBean和
CountBean1中分别创建安全角色admin和user,
引用名分别为adminref和userref.将webtest组件配置为"HTTP Basic
Authentication",给安全角色admin赋予访问webtest.jsp的权限.把CountBean设置为Run As
Specified
Role,选择user安全角色.在运行环境中将用户j2ee赋予admin安全角色和user安全角色.然后发布应用到服务器.用用户j2ee访问
webtest.jsp.</font></p>

      <p>

        <font size="2"><b>执行结果:</b>
        <br>
客户端见图20
      </font></p>


      

        <font size="2"><br><a name="N102D3"><b>图20</b></a><br><img alt="图20" src="http://www-128.ibm.com/developerworks/cn/java/l-j2eesecurity2/20.gif" height="292" width="493">

      <br></font>

        

      <p><font size="2">服务器端:
        <br>
CountBean.count()
        <br>
the caller is j2ee
        <br>
the caller is admin Role
        <br>
CountBean1.count()
        <br>
the caller is j2ee
        <br>
the caller is user Role
      </font></p>

      <p><font size="2">从
运行结果看,访问webtest.jsp的用户为j2ee,其安全角色为admin,访问CountBean的用户名为j2ee,安全角色为admin.
可以看到用户身份标识从web容器传递到了ejb容器.再看组件CountBean1的输出结果,由于CountBean被设置成了Run As
Specified
Role,因此在CountBean向下调用其他组件对象时,用户安全角色已经被改为指定的安全角色,这里是user.所以我们会看到,调用组件
CountBean1的用户名为j2ee,安全角色为user.j2ee的这种安全特性满足了同一用户在不同应用中具有不同安全角色的需求.开发人员也可
以利用这种特性进行灵活的安全逻辑判断.</font></p><font size="2"><br></font><p>
      </p><font size="2"><br></font><img src ="http://www.blogjava.net/cmd/aggbug/30939.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/cmd/" target="_blank">静夜思</a> 2006-02-16 11:03 <a href="http://www.blogjava.net/cmd/articles/30939.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>走出 JNDI 迷宫</title><link>http://www.blogjava.net/cmd/articles/30925.html</link><dc:creator>静夜思</dc:creator><author>静夜思</author><pubDate>Thu, 16 Feb 2006 02:32:00 GMT</pubDate><guid>http://www.blogjava.net/cmd/articles/30925.html</guid><wfw:comment>http://www.blogjava.net/cmd/comments/30925.html</wfw:comment><comments>http://www.blogjava.net/cmd/articles/30925.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/cmd/comments/commentRss/30925.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/cmd/services/trackbacks/30925.html</trackback:ping><description><![CDATA[<blockquote><font size="2">从单机编程转向 EJB 技术和分布式计算这些更复杂领域的 Java 开发人员常常会陷入困境：编写成功地游历 JDNI
迷宫的代码会很困难，多计算机和配置也增加了出错的可能性。在本文中，EJB开发人员 Daniel Would 解释了如何编写可以成功地找到在
JNDI 名称空间中发布的 EJB
组件的客户代码。他向您展示了使处理更容易的各种编程选项，并提供了一些可以在您自己的应用程序中作为实用工具类使用的代码。</font></blockquote>
			
      <p><font size="2">如果您在让客户机应用程序看到 EJB 组件这一方面从来都没有任何问题，并且认为在 Java 平台的不同安装上、或者在完全不同的计算机上运行您的客户机和
bean 一点也不复杂，那么本文可能不会吸引您。不过，如果您才刚刚开始，并且在试图用真实的配置做任何事情时看见弹出了许多奇怪和意义不明的错误消息，那么就请读下去。</font></p>
			
      <p><font size="2"><a name="1"><span class="atitle">EJB 错误？不要慌！</span></a></font></p>
			
      <p><font size="2">
您已经在自己所钟爱的 Java 书籍中读过了关于企业 Javabean 技术的那一章，也已经练习过了简单的 HelloWorld bean，并遵循所建议的部署过程发布了它。现在您得编写一个客户机，以便通过这个客户机来调用这个杰作。因此您写出了类似清单
1 中的代码：</font></p>
			
      <font size="2"><br><a name="listing1"><b>清单 1. 一个调用 bean 的非常简单的客户机</b></a><br></font>
			
      <table bgcolor="#eeeeee" border="1" cellpadding="5" cellspacing="0" width="500"><tbody><tr><td><pre><font size="2"><code class="section"><br>InitialContext ic = new InitialContext();<br>Object or = ic.lookup("ejb/HelloWorldHome");<br>if (or != null) {<br>  // Narrow the return object to the Home class type<br>    HelloWorldHome home = <br>      (HelloWorldHome)PortableRemoteObject.narrow(or, <br>        HelloWorldHome.class);<br>  // Create an EJB object instance using the home interface.<br>    HelloWorld hw = home.create();<br>  // Invoke the method<br>    System.out.Println(hw.hello());<br>}<br></code></font></pre></td></tr></tbody></table><font size="2"><br></font>
			
      <p><font size="2"> 在命令行中运行这个客户机，使用手头最方便的一个 Java 安装 ―― 即应用服务器使用的那一个。所有事情都很完美！带着成功的喜悦，您转移到第二台计算机上运行您的客户机。这回，您得到了一个可怕的错误消息。首先，您可能得到
 
        <code>java.lang.NoClassDefFoundError: javax/ejb/EJBObject</code> ，然后是一大堆其他的
 
        <code>NoClassDefFoundError</code> s，因为您忘记提交一个带有必需的 stub 和 tie 的 JAR 文件，并且没有提供或者考虑到其他各种
EJB 相关的内容。不过最终，您的客户机运行到了第一行有意思的代码（ 
        <code>InitialContext ic = new InitialContext();</code> ）。在到达这一行时得到的异常
―― 您几乎肯定会得到一个异常 ―― 将会根据您所选择的特定
        <i>上下文 provider</i>而有所不同。
      </font></p>
			
      <font size="2"><br></font><table border="0" cellpadding="0" cellspacing="0" width="100%"><tbody><tr><td><font size="2"><img src="http://www.ibm.com/i/v14/rules/blue_rule.gif" alt="" height="1" width="100%"></font></td></tr></tbody></table><table class="no-print" align="right" cellpadding="0" cellspacing="0"><tbody><tr align="right"><td><table border="0" cellpadding="0" cellspacing="0"><tbody><tr><td valign="middle"><font size="2"><img src="http://www.ibm.com/i/v14/icons/u_bold.gif" alt="" border="0" height="16" width="16"><br></font></td><td align="right" valign="top"><font size="2"><a href="http://www-128.ibm.com/developerworks/cn/java/j-namespace/#main" class="fbox"><b>回页首</b></a></font></td></tr></tbody></table></td></tr></tbody></table><font size="2"><br><br></font><p><font size="2"><a name="2"><span class="atitle">解释这些术语</span></a></font></p>
			
      <p><font size="2">

在我们继续往下之前，定义几个术语会很有帮助。计算世界使用的都是一些奇怪的术语、时髦的语汇和首字母缩写词，Java 技术也不例外（也许这应该是
 
        <code>JavaIsNoException</code> ？）。如果您遇到了上面所说的问题，那么这里面的术语可能会让您感到有些无所适从。所以让我们讨论在本文中将会遇到的术语，搞明白它们的意思是一个好主意。
      </font></p>
			
      <font size="2"><b>名称空间、上下文、初始上下文和子上下文</b>
这些术语都是有关位置的 ―― 是从客户机的角度看时 EJB 组件所在的概念性的位置。将一个 
      <i>名称空间</i> 想像为一个城镇，城镇中的商店由
EJB home接口（我们将在稍后讨论它）表示。
      <i>上下文</i>是城镇中的一个位置。
      <i>初始上下文</i> 是您开始时所在的位置
―― 就像它是到城镇的道路。而 
      <i>子上下文</i>是街道名。
      <br><b>home接口（home interface）和远程接口（remote interface）</b>
企业 JavaBean 组件有三个部分。首先是 bean 代码本身。然后是 
      <i>home接口</i>，它定义了创建您自己的 EJB
bean 的方法。home接口是在名称空间中发布的。当您有了home接口后，就可以调用 
      <code>Create()</code> 以从应用服务器获得远程接口。获得了远程接口后，就可以调用构成实际的
EJB 代码的方法了。
      <br>

如何将这些术语应用到您的城镇模拟中去呢？到达正确的城镇并找到正确的地址后，您需要走进商店或者按铃（调用 
      <code>Create()</code> ）。这个过程对于您要去的所有商店都是一样的，不过，您所收到的响应取决于是由谁来提供服务
―― 比如是一位屠夫、一位面包师还是一位烛台制作者。这个响应代表了
      <i>远程接口</i>。每个人都是不同的并且可以要求他提供不同的东西。您必须知道与您交谈的人（即
bean）的职业才能提出正确的问题(即调用正确的方法) ―― 向一位屠夫要一条面包可不妥当。
      <br><b>CosNaming、LDAP 和 JNDI</b>
Java 命名和目录接口（Java Naming and Directory Interface 
      <i>JNDI</i>）提供了一个标准接口，它指明您需要如何与名称空间交互。我们所提到的
      <i>
LDAP</i>和
      <i>CosNaming</i> 就是 JDNI 名称空间类型。现在扩展我们的比喻：JNDI 是城镇的模板，而
CosNaming 和 LDAP 是特定的城镇。它们以相似的方式操作，但是有不同的布局。



      <br></font>
			
      
			
      <table border="0" cellpadding="0" cellspacing="0" width="100%"><tbody><tr><td><font size="2"><img src="http://www.ibm.com/i/v14/rules/blue_rule.gif" alt="" height="1" width="100%"></font></td></tr></tbody></table><table class="no-print" align="right" cellpadding="0" cellspacing="0"><tbody><tr align="right"><td><table border="0" cellpadding="0" cellspacing="0"><tbody><tr><td valign="middle"><font size="2"><img src="http://www.ibm.com/i/v14/icons/u_bold.gif" alt="" border="0" height="16" width="16"><br></font></td><td align="right" valign="top"><font size="2"><a href="http://www-128.ibm.com/developerworks/cn/java/j-namespace/#main" class="fbox"><b>回页首</b></a></font></td></tr></tbody></table></td></tr></tbody></table><font size="2"><br><br></font><p><font size="2"><a name="3"><span class="atitle">属性提供了一个映射</span></a></font></p>
			
      <p><font size="2">
让我们看一看如何使用所有这些元素以成功地从远程计算机上调用我们的 EJB 组件上的方法。为了让客户程序连接到您精心打造的 EJB 组件，需要几样东西。首先，它需要客户代码的所有
JAR 文件、一般性的 EJB 相关 JAR 文件如 J2EE.jar 以及在部署 bean 时生成的 stub 和 tie。这些文件让您的客户机可以一直到达初始上下文。</font></p>
			
      <p><font size="2">接下来您的客户机需要的信息是一些属性的值。首先，您将需要几个 
        <code>java.naming.factory.initial</code> 
的值。该属性指向一个提供初始上下文工厂的类。该属性的一个典型值是 
        <code>com.sun.jndi.cosnaming.CNCtxFactory</code> ，这也是我们在这里的几个例子中所使用的值。这个类存在于
 
        <code>rt.jar</code> 中，因而它是基本 JVM 的一部分。工厂是由 CosNaming 命名服务器所使用的，但是 JVM
还包括一个 LDAP 工厂。我们在后面将会看到，不同的应用服务器提供它们自己的初始上下文工厂。
      </font></p>
			
      <p><font size="2">这个类连同命名服务器 URL 和端口号的详细信息，用于生成与名称空间交互的 
        <code>InitialContext</code> 类。不过，如果没有
provider URL，那么它将连接到 
        <code>localhost</code> 的 900 端口（或者您的上下文工厂的其他默认端口）。要连接到远程服务器，您需要有属性
 
        <code>java.naming.provider.url</code> 的一个值。
      </font></p>
			
      <p><font size="2">新程序员对于所有这些觉得很难理解的原因是：不管您在应用服务器本地运行任何东西，这东西通常都会听话地工作。这是由于环境照管了一切，当您要求一个 
        <code>InitialContext</code> 时，环境就会给您提供您想要的那个。但是当您将客户即转移到不同的计算机上时，就得靠自己了。您需要知道拷贝哪一个 JAR 文件，以及要做哪些设置。我知道有些人为使他们的客户机正确工作，将应用服务器上的所有 JAR 文件都拷贝到第二台计算机上！
      </font></p>
			
      <p><font size="2">在默认情况下， 
        <code>InitialContext</code> 工厂是在 
        <code>jndi.properties</code> 
中定义的，这个工厂类有默认的服务器 URL 和端口号默认值。这个文件在类路径中（这一般意味着在本地目录）或者在您的类路径中的任何 JAR 中。不同的应用服务器可能在不同的
JAR 文件中提供它们的默认值，WebSphere Application Server 在 
        <code>namingclient.jar</code> 
中储存一个默认副本。要指定您自己的默认值，只需要编辑在类路径中的第一个副本。这是配置属性的一种方法，如果缺少命令行或者代码驱动的设置，那么客户机将使用
 
        <code>jndi.properties</code> 中的值。不过，虽然这可能适合于简单的设置，但是如果处理多个服务器和名称空间，那么您可能希望一个客户一个客户地进行配置。
      </font></p>
			
      <p><font size="2">这些属性是如何根据我们要使用的名称空间而使用不同的值的呢？正如前面提到的，有两种形式的 JNDI 名称空间：CosNaming 和 LDAP。其中每一个都有与之相关联的传输：分别是
IIOP 和 LDAP。一个 LDAP 名称空间使用 LDAP 传输（您将用一个像 
        <code>ldap://myldapnameserver</code> 
这样的 URL 连接到它），而 CosNaming 使用一个 IIOP 传输（您将用一个像 
        <code>iiop://mycosnamingserver</code> 
这样的 URL 连接到它）。CosNaming 的默认端口号是 900，而 LDAP 的默认端口号是 389。不过，任何给定的名称空间服务器实现使用的默认值可能是不同的。
      </font></p>
			
      <p><font size="2"><a name="N100F0"><span class="smalltitle">用命令行配置属性</span></a></font></p>
			
      <p><font size="2">
让我们看一下如何用命令行配置属性。如果您要在家里自己练习，进入 JDK 安装中的 
        <code>bin</code> 目录。在这个文件夹中，可以找到一个名为
 
        <code>tnameserv.exe</code> 的程序（对于 Windows）或者只是 
        <code>tnameserv</code> 
（对于基于 UNIX 的系统）。通过执行这个程序将会在端口 900 启动一个示例 CosNmaing 命名服务器。
      </font></p>
			
      <p><font size="2">现在正好可以用一个可以查看 CosNaming 名称空间的实用工具来装备您自己。我本人使用 Eclipse 作为开发环境，我在下面的
        <a href="http://www-128.ibm.com/developerworks/cn/java/j-namespace/#resources">参考资料</a>
部分中提供了到 JNDI 浏览器插件的链接。理论上，您应该可以将一个名称空间浏览器指向自己计算机的端口
900，并看到一个非常无聊的空名称空间（尽管一些应用服务器在默认情况下会用很多不同的内容填充名称空间）。为了丰富我们的名称空间，我们现在将编写一
个简单的程序以在它里面放一些内容，如清单
2 所示： </font></p>
			
      <font size="2"><br><a name="listing2"><b>清单 2. 一个简单的 cosNaming 名称空间交互</b></a><br></font>
			
      <table bgcolor="#eeeeee" border="1" cellpadding="5" cellspacing="0" width="100%"><tbody><tr><td><pre><font size="2"><code class="section"><br>package example.publisher;<br><br>import javax.naming.InitialContext;<br><br>public class Publish {<br><br>    public static void main(String[] args) {<br>        //<br>        //This example creates a subcontext in a namespace<br>        //<br>        try{<br>            InitialContext ic = new InitialContext();<br>            ic.createSubcontext("Test");<br>        }catch(Exception e){<br>            System.out.println(e);<br>            e.printStackTrace();<br>            <br>        }<br>    }<br>}<br></code></font></pre></td></tr></tbody></table><font size="2"><br></font>
			
      <p><font size="2">这个应用程序将假定为得到正确的初始上下文件所需的所有属性都是可用的。所以现在可以从命令行运行它并在运行时提供这些属性（其中 URL 要根据您的环境作调整）：</font></p>
			
      <table bgcolor="#eeeeee" border="1" cellpadding="5" cellspacing="0" width="100%"><tbody><tr><td><pre><font size="2"><code class="section"><br>java -Djava.naming.factory.initial=com.sun.jndi.cosnaming.CNCtxFactory <br>     -Djava.naming.provider.url=iiop://mymachine:900 <br>       example.publisher.Publish<br></code></font></pre></td></tr></tbody></table><font size="2"><br></font>
			
      <p><font size="2"> 一切正常，我们的客户会找到示例名称空间的上下文并创建名为 
        <code>Test</code> 
的子上下文。您可以用名称空间浏览器确认这一点。
      </font></p>
			
      <p><font size="2">现
在试着在一台计算机上运行命名服务器，用同一个命令行（当然，对 URL 再次做了调整）在另一台计算机上运行清单 2
中的应用程序。它运行起来应该没有问题（您可能需要修改这个例子以改变所限定的内容，甚至删除子上下文而不是创建它，这样在第二次运行时您就可以确信它已
经起过作用了）。</font></p>
			
      <p><font size="2"><a name="N10127"><span class="smalltitle">在应用程序中配置属性</span></a></font></p>
			
      <p><font size="2">
那么，如果不希望在命令行中设置这些属性怎么办？还有另外一个方法。可以在程序中显式地声明这些属性。这意味着您不需要为 
        <code>java</code> 
命令提供特殊的选项。改变清单 2 中的代码以显式地设置所需要的属性后，它看起来与清单 3 中的代码一样：
      </font></p>
			
      <font size="2"><br><a name="listing3"><b>清单 3. 简单的 cosNaming 名称空间交互，在应用程序代码中设置属性</b></a><br></font>
			
      <table bgcolor="#eeeeee" border="1" cellpadding="5" cellspacing="0" width="100%"><tbody><tr><td><pre><font size="2"><code class="section"><br>package example.publisher;<br><br>import javax.naming.InitialContext;<br><br>public class Publish {<br><br>    public static void main(String[] args) {<br>        //<br>        //This example creates a subcontext in a namespace<br>        //<br>        try{<br>            Properties prop = new Properties();<br>            prop.setProperty("java.naming.factory.initial",<br>              "com.sun.jndi.cosnaming.CNCtxFactory");<br>            prop.setProperty("java.naming.provider.url",<br>              "iiop://mymachine:900");<br>            InitialContext ic = new InitialContext(prop);<br>            ic.createSubcontext("Test");<br>        }catch(Exception e){<br>            System.out.println(e);<br>            e.printStackTrace();<br>            <br>        }<br>    }<br>} <br></code></font></pre></td></tr></tbody></table><font size="2"><br></font>
			
      <p><font size="2">现在这个程序不再需要长长的命令行配置，不过要记住，以这种方式编写的应用程序硬编码了这些设置。</font></p>
			
      <font size="2"><br></font><table border="0" cellpadding="0" cellspacing="0" width="100%"><tbody><tr><td><font size="2"><img src="http://www.ibm.com/i/v14/rules/blue_rule.gif" alt="" height="1" width="100%"></font></td></tr></tbody></table><table class="no-print" align="right" cellpadding="0" cellspacing="0"><tbody><tr align="right"><td><table border="0" cellpadding="0" cellspacing="0"><tbody><tr><td valign="middle"><font size="2"><img src="http://www.ibm.com/i/v14/icons/u_bold.gif" alt="" border="0" height="16" width="16"><br></font></td><td align="right" valign="top"><font size="2"><a href="http://www-128.ibm.com/developerworks/cn/java/j-namespace/#main" class="fbox"><b>回页首</b></a></font></td></tr></tbody></table></td></tr></tbody></table><font size="2"><br><br></font><p><font size="2"><a name="4"><span class="atitle">寻找通往 bean 的道路</span></a></font></p>
			
      <p><font size="2">
到目前为止，我们已经看到了几个可以证明我们已连接到远程名称空间并完成一些任务的例子，尽管这些任务是相当无聊的 ―― 创建一个子上下文。在实际中，一般是由工具来为您完成所有的创建和发布工作，您 
        <i>真正</i> 需要的做是查找一个对象。在这一节，我们将在
CosNaming 名称空间中获得已发布的 HelloWorld bean 的 
        <code>Home</code> 接口。然后我们再看一下如何在
LDAP 名称空间中找到它的 
        <code>Home</code> 接口。
      </font></p>
			
      <p><font size="2">为了说明问题，我们假设您已经部署了 HelloWorld bean，它的home接口 
        <code>HelloWorldHome</code> 
发布在 
        <code>example/HelloWorldHome</code> （如果您只想试一试，但是又不想自己创建一个 HellowWorld
bean，那么在 
        <a href="http://www-128.ibm.com/developerworks/cn/java/j-namespace/#resources">参考资料</a>中有一个下载预打包的 bean JAR 文件的链接以及一个使用它的客户机的文件）。
      </font></p>
			
      <table align="right" border="0" cellpadding="0" cellspacing="0" width="40%"><tbody><tr><td width="10"><font size="2"><img alt="" src="http://www.ibm.com/i/c.gif" height="1" width="10"></font></td><td><table border="1" cellpadding="5" cellspacing="0" width="100%"><tbody><tr><td bgcolor="#eeeeee">
				
        <font size="2"><a name="sidebar1"><b>一个上下文技巧</b></a><br></font>
				
        <p><font size="2">  在名称空间 URL 格式中，您可以设置自己的初始上下文，使之从树上比默认值更高的位置开始。例如，如果在我们的例子中对于 provider
URL 使用 
          <code>iiop://mymachine:900/example</code> ，那么您只需要查询 
          <code>HelloWorldHome</code> ，而不是
 
          <code>example/HelloWorldHome</code> ，初始上下文将在 
          <code>example</code> 内。如果您在一个名称空间中在同一个结构下进行几次查询，那么这将会有所帮助，这样，如果改变了设置，代码中要改变的惟一部分只是 provider
URL。

        </font></p>
			
      </td></tr></tbody></table></td></tr></tbody></table>
			
      <p><font size="2">在上一节，我们进行了连接到命名服务器的艰苦工作，现在我们所需要的就只是查询 EJB 组件了。这需要我们向查询方法传递一个字符串，它表示从 
        <code>InitialContext</code> 
（您在城镇中的出发点）到想要去的 
        <code>HomeInterface</code> （房屋或者商店）的方向。听起来简单 ―― 但是这里您所选择的特定上下文工厂就要产生影响了。像
WebSphere 这样的应用服务器所带的工厂类并不总是把您放到名称空间的根上。所以我们为了查询 
        <code>HomeInterface</code> 
而需要的字符串会根据 
        <code>InitialContext</code> 将您所放到城镇中的位置而变化。并且，在本地服务器上，上下文工厂可能将您放到与在远程服务器上不同的起始位置。
      </font></p>
			
      <p><font size="2">因
为这个原因，我建议您不要像在清单 3
中那样硬编码所使用的查询字符串，而是用命令行或者属性文件传递。特别是对于具有多步的体系结构更应如此。例如，您可能有一个调用一个
EJB 组件的客户机，这个 bean 可能又需要调用也许是在不同的服务器上的第二个 EJB
组件！在这种情况下，属性应该在每一步中传递。这为反复实验（trial-and-error）查询提供了一种简单的机制，并且只需要相对较少的改变就可
以得到最终应用程序的灵活性。因此让我们看一个示例查询应用程序。在清单
4 中，属性是在程序中设置的，但是它又以命令行值为依据。这样命令行与在我们前一个例子中使用的稍有不同，如我们在下面所看到的。</font></p>
			
      <font size="2"><br><a name="listing4"><b>清单 4. 查询一个home接口</b></a><br></font>
			
      <table bgcolor="#eeeeee" border="1" cellpadding="5" cellspacing="0" width="100%"><tbody><tr><td><pre><font size="2"><code class="section"><br>package example.lookup;<br>import java.util.Properties;<br>import javax.naming.InitialContext;<br>import javax.rmi.PortableRemoteObject;<br><br>import example.HelloWorld;<br>import example.HelloWorldBean;<br>import example.HelloWorldHome;<br>import javax.naming.InitialContext;<br><br>public class Lookup {<br><br>    public static void main(String[] args) {<br>        //<br>        //This example looks up the home interface of an EJB to a namespace<br>        //<br>        try{<br>            Properties prop = new Properties();<br>            prop.setProperty("java.naming.factory.initial",args[0]);<br>            prop.setProperty("java.naming.provider.url",args[1]);<br>            InitialContext ic = new InitialContext(prop);<br>            Object or = ic.lookup(args[2]);<br>            if (or != null) {<br>                // Narrow the return object to the Home class type<br>                HelloWorldHome home = <br>                  (HelloWorldHome)PortableRemoteObject.narrow(or, <br>                      HelloWorldHome.class);<br>                // Create an EJB object instance using the home interface.<br>                HelloWorld hw = home.create();<br>                // Invoke the method<br>                System.out.Println(hw.hello());<br>            }<br>        }catch(Exception e){<br>            System.out.println(e);<br>            e.printStackTrace();<br>            <br>        }<br>    }<br>}<br></code></font></pre></td></tr></tbody></table><font size="2"><br></font>
			
      <p><font size="2">这个程序是用三个参数调用的：要使用的上下文工厂、provider URL 和包含要查询的名字的字符串。我们已经知道前两个是什么，那么第三个呢？</font></p>
			
      <p><font size="2">如果您仍然使用 
        <code>tnameserv</code> 作为命名服务器，那么您很可能将 bean 直接发布到 
        <code>/example/HelloWorldHome</code> 。在这种情况下，只要将
 
        <code>/example/HelloWorldHome</code> 作为第三个参数传递就可以进行成功的查询。不过，如果您使用的命名服务器有一个更复杂的命名空间，那么可能会存在由所使用的部署工具增加的额外的层。例如，WebSphere
在默认情况下将 JavaBean 部署到 
        <code>ejb/</code> ，但是这不是名称空间的根，并且只有当使用 WebSphere 的上下文工厂时，通过传入字符串
 
        <code>/ejb/example/HelloWorldHome</code> 才会使您处于名称空间中的正确位置。 如果您使用一个与应用服务器提供的不同的上下文工厂（例如在一台只有标准
Java 安装的计算机上运行客户机时就需要这样做）时，这个问题会更加恶化。不过，应用服务器的命名服务器文档应当说明在查询 EJB 组件时将会从名称空间的什么地方开始。看一下文档中的例子，再用浏览器查看名称空间以确定其客户机的
 
        <code>InitialContext</code> 会将它们放到什么地方。名称空间往往会循环，这样您就可以试着沿着一个分枝到无限。这意味着从最开始的上下文可以找到一条回家的道路。
      </font></p>
			
      <p><font size="2">总之，下面是传递相应参数给清单 4 中的应用程序的命令行：</font></p>
			
      <table bgcolor="#eeeeee" border="1" cellpadding="5" cellspacing="0" width="100%"><tbody><tr><td><pre><font size="2"><code class="section"><br>java Lookup com.sun.jndi.cosnaming.CNCtxFactory <br>  iiop://mymachine:900 example/HelloWorldHome<br></code></font></pre></td></tr></tbody></table><font size="2"><br></font>
			
      <p><font size="2">在 CosNaming 中，名称空间子上下文由斜线(/)字符分隔，这与标准 URL 一样。LDAP 的语法则不同，我们在下面将会看到。</font></p>
			
      <p><font size="2"><a name="N101C8"><span class="smalltitle">介绍 LDAP</span></a></font></p>
			
      <p><font size="2">
现在让我们再加上 LDAP。就本文的内容来讲，LDAP 是另一个 JNDI 名称空间，但是它的结构的表示方法与 CosNaming 名称空间的表示方法截然不同。它还需要一个不同的上下文工厂
―― 但是这不成问题，因为我们总会在命令行指定正确的工厂（在我们的例子中，我们将使用属于基本 JVM 一部分的工厂，但是要记住不同的应用服务器可能有自己的工厂）。并且它需要一个指向不同命名服务器的指针
―― 并且，幸运的是，我们也是在命令行中指定它的。当然，表示home接口位置的字符串是不同的，但是您猜如何？是的，我们还是在命令行中指定它。您可以看到使用这些命令行调用的好处：所有要做的只是改变调用我们的测试程序的方式，理论上我们可以到达任何
JDNI 命名服务器，甚至可以顺利地从 CosNaming 转移到 LDAP 而不用改变任何代码。是的，这是只是理论，当然无论如何，关键是要有正确的参数。</font></p>
			
      <p><font size="2">一些命名服务器会保护部分命名空间，这意味着只能发布到允许的区域。假设您有一个运行的 LDAP 服务器，它的细节如下：</font></p>
			
      <ul><li><font size="2">URL: 
          <code>ldap://mymachine:1389</code></font>
				
        </li><li><font size="2">BaseDN: 
          <code>c=myldap</code></font>
				
        </li></ul>
			
      <p><font size="2">LDAP 名称空间中的树结构一般来说像下面这样：</font></p>
			
      <table bgcolor="#eeeeee" border="1" cellpadding="5" cellspacing="0" width="100%"><tbody><tr><td><pre><font size="2"><code class="section"><br>ibm-wsnName=MyServer,ibm-wsnName=HelloWorldHome<br></code></font></pre></td></tr></tbody></table><font size="2"><br></font>
			
      <p><font size="2">不过，当我们将这个字符串传递给程序时，我们需要反转它（不要问我为什么）。所以我们使用的字符串看起来是这样的：</font></p>
			
      <table bgcolor="#eeeeee" border="1" cellpadding="5" cellspacing="0" width="100%"><tbody><tr><td><pre><font size="2"><code class="section"><br>ibm-wsnName=HelloWorldHome,ibm-wsnName=MyServer,<br></code></font></pre></td></tr></tbody></table><font size="2"><br></font>
			
      <p><font size="2">BaseDN 表示在名称空间中您希望开始的位置。对于给定的 LDAP 命名服务器来说这可以是很多位置，这取决它是如何构造的。在这个例子中，我们直接到
 
        <code>c=myldap</code> 的根。但是如果我们希望跳到名称空间中的一个树，那么可以指定 
        <code>ibm-wsnTree=myTree,c=myldap</code> 
作为 BaseDN 而不是跳到那一点。
      </font></p>
			
      <p><font size="2">这样，我们将传递给程序的命令行参数就像下面这样：</font></p>
			
      <table bgcolor="#eeeeee" border="1" cellpadding="5" cellspacing="0" width="100%"><tbody><tr><td><pre><font size="2"><code class="section"><br>java Lookup com.sun.jndi.ldap.LdapCtxFactory ldap://mymachine:1389/c=myldap/ <br>     ibm-wsnName=HelloWorldHome,ibm-wsnName=MyServer<br></code></font></pre></td></tr></tbody></table><font size="2"><br></font>
			
      <table align="right" border="0" cellpadding="0" cellspacing="0" width="40%"><tbody><tr><td width="10"><font size="2"><img alt="" src="http://www.ibm.com/i/c.gif" height="1" width="10"></font></td><td><table border="1" cellpadding="5" cellspacing="0" width="100%"><tbody><tr><td bgcolor="#eeeeee">
				
        <font size="2"><a name="sidebar2"><b>可能出现的错误消息</b></a><br></font>
				
        <p><font size="2">
无论如何，您毫无疑问会熟悉在部署 EJB 组件时出现的各种异常消息。下面是您将会经常看到的几个异常消息（反正我常见到它们），以及您遇到它们的原因：


          </font></p><ul><li>
							
              <font size="2"><code>CORBA.OBJECT_NOT_EXIST</code> : 您在错误的位置上查找 EJB。
              <br></font>
						
            </li><li>
							
              <font size="2"><code>CORBA.MARSHALL _ 
                <i>something</i>
							
              </code> : CORBA marshall
异常经常发生。它们有不同的细节，但是它们基本上都表明同一件事情：您得到一些数据，但这不是您的客户机所期待的。也许您查询了错误的东西，也许您的客户机所知道的
EJB 组件类的版本与实际部署的不一样。或者出现了问题，客户机 ORB 不能理解服务器 ORB 所发送的内容。
              <br></font>
						
            </li><li>
							
              <font size="2"><code>javax.naming.NoInitialContextException</code></font> : 噢！您没有指定上下文工厂或者
provider URL，也许命名服务器没有运行。
            </li></ul>
				
        
			
      </td></tr></tbody></table></td></tr></tbody></table>
			
      <p><font size="2">这里我们指定一个 LDAP 上下文工厂 ，然后传递 LDAP 服务器的名字以及我们想要开始的位置。然后是到要查询的 EJB 组件的反转的路径。我们可以用这个命令行调用在
CosNaming 例子中使用的同一段代码（
        <a href="http://www-128.ibm.com/developerworks/cn/java/j-namespace/#listing4">清单 4</a>）。
      </font></p>
			
      <p><font size="2">当然，本文中使用的代码没有理由不能构成一个助手类的一个方法 ―― 它带三个参数，并且返回 
        <code>Object or</code> ，这个对象是在试图做任何事情之前调用
 
        <code>ic.lookup(args[2])</code> 时返回的。然后，当您需要进行一次查询时，只需使用这个助手类，向它传递适合于当前情况的适当参数，取回您所需要的对象引用，并准备将它窄化到实际的类。（
        <b>注意</b>：我不保证这种类的性能，而只是提供这段原本就是如此的代码，我或者
IBM 对此不作任何保证。）当然，可以通过反射实现一种完全通用的方式，但这会使事情复杂得多，也超出了本文的范围。
      </font></p>
			
      <p><font size="2">在
我们结束之前还有最后一件事要考虑。您可以编写一个结合了我们在清单 3 和 4
中使用的技术的客户机。它会检查命令行中是否给出了一个值；如果有，它就设置这些值，如果没有，它就使用硬编码的值。这样，在程序中可以有有意义的默认
值，但是如果需要，也可以用命令行选项覆盖它们。只需要对代码进行微不足道的更改。</font></p>
			
      <font size="2"><br></font><table border="0" cellpadding="0" cellspacing="0" width="100%"><tbody><tr><td><font size="2"><img src="http://www.ibm.com/i/v14/rules/blue_rule.gif" alt="" height="1" width="100%"></font></td></tr></tbody></table><table class="no-print" align="right" cellpadding="0" cellspacing="0"><tbody><tr align="right"><td><table border="0" cellpadding="0" cellspacing="0"><tbody><tr><td valign="middle"><font size="2"><img src="http://www.ibm.com/i/v14/icons/u_bold.gif" alt="" border="0" height="16" width="16"><br></font></td><td align="right" valign="top"><font size="2"><a href="http://www-128.ibm.com/developerworks/cn/java/j-namespace/#main" class="fbox"><b>回页首</b></a></font></td></tr></tbody></table></td></tr></tbody></table><font size="2"><br><br></font><p><font size="2"><a name="5"><span class="atitle">安全到家</span></a></font></p>
			
      <p><font size="2">
作为回顾：下面是在有多个 EJB 查询和多个应用服务器的情况下，要使任何系统运行而应该有或者应该知道的最重要的四件事情：</font></p>
			
      <ul><li><font size="2">在任何给定阶段，下一阶段的所有 stub 和 tie 都必须在类路径上。除非环境知道这个类是什么样的，否则您不能窄化一个对象以使用它。

</font></li><li><font size="2">每一阶段都需要与 EJB 相关的一般性 JAR 文件，如 
          <code>J2EE.jar</code> 。
          <br></font>
				
        </li><li><font size="2">以参数的形式传递上下文工厂类型、命名服务器名和 JDNI 查询字符串。以便能够轻松顺应变化。

</font></li><li><font size="2">知道您的名称空间。记住您的 JDNI 查询字符串需要将您从在名称空间中开始的位置移动到您的对象所储存的位置。但是您并不总是在同一位置开始！用一个工具浏览名称空间，并了解在本地和远程查询中是从什么位置开始的。

</font></li></ul>
			
      <p><font size="2">对于习惯于编写全部在同一台计算机上执行的代码的开发人员来说，浏览远程名称空间可能是一个困难的过程。希望本文的提示和代码可以帮助您设置并运行您的分布式
EJB 应用程序。当您掌握了 JNDI 名称空间后，再去看一下 developerWorks 上由 Brett McLaughlin 所写的

        <i>EJB 最佳实践</i>系列（请参阅
        <a href="http://www-128.ibm.com/developerworks/cn/java/j-namespace/#resources">参考资料</a>），以获得用于优化代码的一些很棒的技巧。
      </font></p>
		
    <font size="2"><br></font><table border="0" cellpadding="0" cellspacing="0" width="100%"><tbody><tr><td><font size="2"><img src="http://www.ibm.com/i/v14/rules/blue_rule.gif" alt="" height="1" width="100%"></font></td></tr></tbody></table><table class="no-print" align="right" cellpadding="0" cellspacing="0"><tbody><tr align="right"><td><table border="0" cellpadding="0" cellspacing="0"><tbody><tr><td valign="middle"><font size="2"><img src="http://www.ibm.com/i/v14/icons/u_bold.gif" alt="" border="0" height="16" width="16"><br></font></td><td align="right" valign="top"><font size="2"><a href="http://www-128.ibm.com/developerworks/cn/java/j-namespace/#main" class="fbox"><b>回页首</b></a></font></td></tr></tbody></table></td></tr></tbody></table><font size="2"><br><br></font><p><font size="2"><a name="resources"><span class="atitle">参考资料 </span></a></font></p>
			
      <ul><li><font size="2">您可以参阅本文在 developerWorks 全球站点上的
          <a href="http://www.ibm.com/developerworks/library/j-namespace.html">英文原文</a>.
        <br><br></font></li><li><font size="2">下载
          <a href="ftp://www6.software.ibm.com/software/developer/library/j-namespace/helloworldejb.jar">helloworldejb.jar</a>，这是我们的例子中使用的简单
bean。可以用 
          <a href="ftp://www6.software.ibm.com/software/developer/library/j-namespace/HWClient.jar">HWClient.jar</a>
作为客户机。
          <br><br><br></font>
				
        </li><li><font size="2">如果您使用 Eclipse 或者 WebSphere Studio Application Developer，那么可以使用
          <a href="http://renaud.waldura.com/software/java/com.waldura.eclipse.jndibrowser/">JNDI
Explorer</a>插件浏览名称空间。
          <br><br><br></font>
				
        </li><li><font size="2">了解更多关于
          <a href="http://www.eclipse.org/">Eclipse</a>和
          <a href="http://www7b.software.ibm.com/wsdd/zones/studio/">WebSphere
Application Developer Studio</a> 开发环境的内容。WebSphere Studio 是建立于 Eclipse
2.1 之上的。
          <br><br><br></font>
				
        </li><li><font size="2">使用正确的工具会使您轻松得多。alphaWorks 有
          <a href="http://www.alphaworks.ibm.com/eclipse">几个用于
Eclipse 的插件</a>。
          <br><br><br></font>
				
        </li><li><font size="2">从 WebSphere Application Server 库的“
          <a href="http://publib7b.boulder.ibm.com/wasinfo1/en/info/aes/ae/tnam_develop_naming.html">Developing
applications that use JNDI</a>”一文中，可以了解如何开发使用 WebSphere Application Server 的 JNDI 的应用程序。
          <br><br><br></font>
				
        </li><li><font size="2">Brett McLaughlin 在 developerWorks 的
          <i>
					
          <a href="http://www-128.ibm.com/developerworks/cn/java/j-ejb-col/index.shtml">
						
            <i>EJB 最佳实践</i>
					
          </a>专栏对于那些开始进行 EJB 编程的人来说是一个很好的资源。
          <br>
				
        <br><br></i></font></li><li><font size="2"><i>如果您确实希望了解 EJB 编程的细节，那么 Joe Sam Shirah 的综合教程“
          <a href="http://www.ibm.com/developerworks/cn/cnedu.nsf/java-onlinecourse-bytitle/BFCFA8BEE9A4216948256A7700308457?OpenDocument">Enterprise JavaBean 基础</a>”（
          <i>developerWorks</i>，2003
年 4 月）可以为您提供坚实的基础。
          <br>
				
        <br><br></i></font></li><li><font size="2"><i>在
          <a href="http://www-128.ibm.com/developerworks/cn/java/">
						
            <i>developerWorks
Java</i>技术专区
          </a>，可以找到数百篇有关 Java 编程各方面的文章。
        <br></i></font></li></ul>
<font size="2"><i>		
    <br></i></font><table border="0" cellpadding="0" cellspacing="0" width="100%"><tbody><tr><td><font size="2"><img src="http://www.ibm.com/i/v14/rules/blue_rule.gif" alt="" height="1" width="100%"></font></td></tr></tbody></table><table class="no-print" align="right" cellpadding="0" cellspacing="0"><tbody><tr align="right"><td><table border="0" cellpadding="0" cellspacing="0"><tbody><tr><td valign="middle"><font size="2"><img src="http://www.ibm.com/i/v14/icons/u_bold.gif" alt="" border="0" height="16" width="16"><br></font></td><td align="right" valign="top"><font size="2"><a href="http://www-128.ibm.com/developerworks/cn/java/j-namespace/#main" class="fbox"><b>回页首</b></a></font></td></tr></tbody></table></td></tr></tbody></table><font size="2"><i><br><br></i></font><p><font size="2"><i><a name="author"><span class="atitle">关于作者</span></a></i></font></p><table border="0" cellpadding="0" cellspacing="0" width="100%"><tbody><tr><td colspan="2"><font size="2"><img alt="" src="http://www.ibm.com/i/c.gif" height="5" width="100%"></font></td></tr><tr align="left" valign="top"><td><font size="2"><br></font></td><td><p><font size="2"> Daniel Would 于 2001 年加入 IBM。他做了一年 CTG 系统测试员，之后成为 CICS 系统测试员一直到现在。在
IBM 时，Daniel 的工作集中于 Java 技术和 EJB 组件。可以通过 
        <a href="mailto:wouldd@uk.ibm.com">wouldd@uk.ibm.com</a>
与 Daniel 联系。

      </font></p></td></tr></tbody></table><img src ="http://www.blogjava.net/cmd/aggbug/30925.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/cmd/" target="_blank">静夜思</a> 2006-02-16 10:32 <a href="http://www.blogjava.net/cmd/articles/30925.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>EJB 调用原理分析</title><link>http://www.blogjava.net/cmd/articles/30918.html</link><dc:creator>静夜思</dc:creator><author>静夜思</author><pubDate>Thu, 16 Feb 2006 02:19:00 GMT</pubDate><guid>http://www.blogjava.net/cmd/articles/30918.html</guid><wfw:comment>http://www.blogjava.net/cmd/comments/30918.html</wfw:comment><comments>http://www.blogjava.net/cmd/articles/30918.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/cmd/comments/commentRss/30918.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/cmd/services/trackbacks/30918.html</trackback:ping><description><![CDATA[<p><font size="2">一个远程对象至少要包括4个class文件：远程对象；远程对象的接口；实现远程接口的对象的stub；对象的skeleton这4个class文件。</font></p>
<p><font size="2">在EJB中则至少要包括10个class：</font></p>
<p><font size="2">Bean类，特定App Server的Bean实现类</font></p>
<p><font size="2">Bean的remote接口，特定App Server的remote接口实现类，特定App Server的remote接口的实现类的stub类和skeleton类</font></p>
<p><font size="2">Bean的home接口，特定App Server的home接口实现类，特定App Server的home接口的实现类的stub类和skeleton类</font></p>
<p><font size="2">和RMI不同的是，EJB中这10个class真正需要用户编写的只有3个，分别是Bean类和它的remote接口，home接口，至于其它的7
个class到底是怎么生成，被打包在什么地方，或者是否需要更多的类文件，会根据不同的App Server表现出比较大的差异，不能一概而论。</font></p>
<p><font size="2">拿我最熟悉的Weblogic的来说吧，Weblogic的Bean实现类，以及两个接口的Weblogic的实现类是在ejbc的时候被打包到
EJB的jar包里面的，这3个class文件可以看到。而home接口和remote接口的Weblogic的实现类的stub类和skeleton类
是在EJB被部署到Weblogic的时候，由Weblogic动态生成stub类和Skeleton类的字节码，因此看不到这4个类文件。</font></p>
<p><font size="2">对于一次客户端远程调用EJB，要经过两个远程对象的多次RMI循环。首先是通过JNDI查找Home接口，获得Home接口的实现类，这个过程其
实相当复杂，首先是找到Home接口的Weblogic实现类，然后创建一个Home接口的Weblogic实现类的stub类的对象实例，将它序列化传
送给客户端（注意stub类的实例是在第1次RMI循环中，由服务器动态发送给客户端的，因此不需要客户端保存Home接口的Weblogic实现类的
stub类），最后客户端获得该stub类的对象实例（普通的RMI需要在客户端保存stub类，而EJB不需要，因为服务器会把stub类的对象实例发
送给客户端）。</font></p>
<p><font size="2">客户端拿到服务器给它的Home接口的Weblogic实现类的stub类对象实例以后，调用stub类的create方法，(在代码上就是
home.create()，但是后台要做很多事情),于是经过第2次RMI循环，在服务器端，Home接口的Weblogic实现类的skeleton
类收到stub类的调用信息后，由它再去调用Home接口的Weblogic实现类的create方法。</font></p>
<p><font size="2">在服务端，Home接口的Weblogic实现类的create方法再去调用Bean类的Weblogic实现类的ejbCreate方法，在服务
端创建或者分配一个EJB实例，然后将这个EJB实例的远程接口的Weblogic实现类的stub类对象实例序列化发送给客户端。</font></p>
<p><font size="2">客户端收到remote接口的Weblogic实现类的stub类的对象实例，对该对象实例的方法调用（在客户端代码中实际上就是对remote接
口的调用），将传送给服务器端remote接口的Weblogic实现类的skeleton类对象，而skeleton类对象再调用相应的remote接
口的Weblogic实现类，然后remote接口的Weblogic实现类再去调用Bean类的Weblogic实现类，如此就完成一次EJB对象的远
程调用。</font></p>
<p><font size="2">看了一遍帖子，感觉还是没有说太清楚，既然写了帖子，就想彻底把它说清楚。</font></p>
<p><font size="2">先拿普通RMI来说，有4个class，分别是远程对象，对象的接口，对象的stub类和skeleton类。而对象本身和对象的stub类同时都实现了接口类。而我们在客户端代码调用远程对象的时候，虽然在代码中操纵接口，实质上是在操纵stub类，例如：</font></p>
<p><font size="2">接口类：Hello</font></p>
<p><font size="2">远程对象：Hello_Server </font></p>
<p><font size="2">stub类：Hello_Stub</font></p>
<p><font size="2">skeleton类：Hello_Skeleton</font></p>
<p><font size="2">客户端代码要这样写：</font></p>
<p><font size="2">Hello h = new Hello_Stub();<br>h.getString();</font></p>
<p><font size="2">我们不会这样写：</font></p>
<p><font size="2">Hello_Stub h = new Hello_Stub();<br>h.getString();</font></p>
<p><font size="2">因为使用接口适用性更广，就算更换了接口实现类，也不需要更改代码。因此客户端需要Hello.class和Hello_Stub.class这两
个文件。但是对于EJB来说，就不需要Hello_Stub.class，因为服务器会发送给它，但是Hello.class文件客户端是省不了的，必须
有。表面上我们的客户端代码在操纵Hello，但别忘记了Hello只是一个接口，抽象的，实质上是在操纵Hello_Stub。</font></p>
<p><font size="2">拿Weblogic上的EJB举例子，10个class分别是：</font></p>
<p><font size="2">Bean类：HelloBean （用户编写）<br>Bean类的Weblogic实现类：HelloBean_Impl （EJBC生成）<br>Home接口：HelloHome （用户编写）<br>Home接口的Weblogic实现类 ((Hello Bean))_HomeImpl（EJBC生成）<br>Home接口的Weblogic实现类的stub类 ((Hello Bean))_HomeImpl_WLStub（部署的时候动态生成字节码）<br>Home接口的Weblogic实现类的skeleton类 ((Hello Bean))_HomeImpl_WLSkeleton（部署的时候动态生成字节码）<br>Remote接口：Hello （用户编写）<br>Remote接口的Weblogic实现类 ((Hello Bean))_EOImpl（EJBC生成）<br>Remote接口的Weblogic实现类的stub类 ((Hello Bean))_EOImpl_WLStub（部署的时候动态生成字节码）<br>Remote接口的Weblogic实现类的skeleton类 ((Hello Bean))_EOImpl_WLSkeleton（部署的时候动态生成字节码）</font></p>
<p><font size="2">客户端只需要Hello.class和HelloHome.class这两个文件。</font></p>
<p><font size="2">((Hello Home)) home = (Home) ((Portable Remote Object)).narrow(ctx.lookup("Hello"), ((Hello Home)).class);</font></p>
<p><font size="2">这一行代码是从JNDI获得Home接口，但是请记住！接口是抽象的，那么home这个对象到底是什么类的对象实例呢？很简单，用toString()输出看一下就明白了，下面一行是输出结果：</font></p>
<p><font size="2">((Hello Bean))_HomeImpl_WLStub@18c458</font></p>
<p><font size="2">这表明home这个通过从服务器的JNDI树上查找获得的对象实际上是HelloBean_HomeImpl_WLStub类的一个实例。</font></p>
<p><font size="2">接下来客户端代码：</font></p>
<p><font size="2">Hello h = home.create()</font></p>
<p><font size="2">同样Hello只是一个抽象的接口，那么h对象是什么东西呢？打印一下：</font></p>
<p><font size="2">((Hello Bean))_EOImpl_WLStub@8fa0d1</font></p>
<p><font size="2">原来是HelloBean_EOImpl_WLStub的一个对象实例。</font></p>
<p><font size="2">用这个例子来简述一遍EJB调用过程：</font></p>
<p><font size="2">首先客户端JNDI查询，服务端JNDI树上Hello这个名字实际上绑定的对象是HelloBean_HomeImpl_WLStub，所以服务端将创建HelloBean_HomeImpl_WLStub的一个对象实例，序列化返回给客户端。</font></p>
<p><font size="2">于是客户端得到home对象，表面上是得到HelloHome接口的实例，实际上是进行了一次远程调用得到了HelloBean_HomeImpl_WLStub类的对象实例，别忘记了HelloBean_HomeImpl_WLStub也实现了HelloHome接口。</font></p>
<p><font size="2">然后home.create()实质上就是HelloBean_HomeImpl_WLStub.create()，该方法将发送信息给
HelloBean_HomeImpl_WLSkeleton，而HelloBean_HomeImpl_WLSkeleton接受到信息后，再去调用
HelloBean_HomeImpl的create方法，至此完成第1次完整的RMI循环。</font></p>
<p><font size="2">注意在这次RMI循环过程中，远程对象是HelloBean_HomeImpl，远程对象的接口是HelloHome，对象的stub是
HelloBean_HomeImpl_WLStub，对象的skeleton是HelloBean_HomeImpl_WLSkeleton。</font></p>
<p><font size="2">然后HelloBean_HomeImpl再去调用HelloBean_Impl的ejbCreate方法，而HelloBean_Impl的
ejbCreate方法将负责创建或者分配一个Bean实例，并且创建一个HelloBean_EOImpl_WLStub的对象实例。</font></p>
<p><font size="2">这一步比较有趣的是，在前一步RMI循环中，远程对象HelloBean_HomeImpl在客户端有一个代理类
HelloBean_HomeImpl_WLStub，但在这一步，HelloBean_HomeImpl自己却充当了HelloBean_Impl的代
理类，只不过HelloBean_HomeImpl不在客户端，而是在服务端，因此不进行RMI。</font></p>
<p><font size="2">然后HelloBean_EOImpl_WLStub的对象实例序列化返回给客户端，这一步也很有趣，上次RMI过程，主角是
HelloBean_HomeImpl和它的代理类HelloBean_HomeImpl_WLStub，但这这一次换成了
HelloBean_EOImpl和它的代理类HelloBean_EOImpl_WLStub来玩了。</font></p>
<p><font size="2">Hello h = home.create();h.helloWorld();</font></p>
<p><font size="2">假设Hello接口有一个helloWorld远程方法，那么表面上是在调用Hello接口的helloWorld方法，实际上是在调用HelloBean_EOImpl_WLStub的helloWorld方法。</font></p>
<p><font size="2">然后HelloBean_EOImpl_WLStub的helloWorld方法将发送信息给服务器上的
HelloBean_EOImpl_WLSkeleton，而HelloBean_EOImpl_WLSkeleton收到信息以后，再去调用
HelloBean_EOImpl的helloWorld方法。至此，完成第2次完整的RMI循环过程。</font></p>
<p><font size="2">在刚才HelloBean_EOImpl是作为远程对象被调用的，它的代理类是HelloBean_EOImpl_WLStub，但现在
HelloBean_EOImpl要作为HelloBean_Impl的代理类了。现在HelloBean_EOImpl去调用
HelloBean_Impl的helloWorld方法。注意！HelloBean_Impl继承了HelloBean，而HelloBean中的
helloWorld方法是我们亲自编写的代码，现在终于调用到了我们编写的代码了！</font></p>
<font size="2">至此，一次EJB调用过程终于完成。在整个过程中，服务端主要要调用的类是HelloBean_Impl， Hello
Bean?_HomeImpl，HelloBean_HomeImpl_WLSkeleton，HelloBean_EOImpl，
HelloBean_EOImpl_WLSkeleton。客户端主要调用的类是HelloBean_HomeImpl_WLStub，
HelloBean_EOImpl_WLStub，这两个类在客户端代码中并不会直接出现，出现在代码中的类是他们的接口HelloHome和
Hello，因此客户端需要这两个接口文件，而Stub是服务器传送给他们的。</font><img src ="http://www.blogjava.net/cmd/aggbug/30918.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/cmd/" target="_blank">静夜思</a> 2006-02-16 10:19 <a href="http://www.blogjava.net/cmd/articles/30918.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>RMI简要开发</title><link>http://www.blogjava.net/cmd/articles/30916.html</link><dc:creator>静夜思</dc:creator><author>静夜思</author><pubDate>Thu, 16 Feb 2006 02:13:00 GMT</pubDate><guid>http://www.blogjava.net/cmd/articles/30916.html</guid><wfw:comment>http://www.blogjava.net/cmd/comments/30916.html</wfw:comment><comments>http://www.blogjava.net/cmd/articles/30916.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/cmd/comments/commentRss/30916.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/cmd/services/trackbacks/30916.html</trackback:ping><description><![CDATA[<font size="2"><b>&nbsp;&nbsp; 简介</b><br><br>　　RMI是远程方法调用的简称，象其名称暗示的那样，它能够帮助我们查找并执行远程对象的方法。通俗地说，远程调用就象将一个class放在A机器上，然后在B机器中调用这个class的方法。<br><br>　　我个人认为，尽管RMI不是唯一的企业级远程对象访问方案，但它却是最容易实现的。与能够使不同编程语言开发的CORBA不同的是，RMI是一种纯Java解决方案。在RMI中，程序的所有部分都由Java编写。<br><br>　　在看本篇文章时，我假定读者都已经具备了较扎实的Java基础知识，在这方面有欠缺的读者请自行阅读有关资料。<br><br>　　<b>概念</b><br><br>　　我在前面已经提到，RMI是一种远程方法调用机制，其过程对于最终用户是透明的：在进行现场演示时，如果我不说它使用了RNI，其他人不可能知道调用的方法存储在其他机器上。当然了，二台机器上必须都安装有Java虚拟机（<a href="http://www.yesky.com/key/4024/179024.html" class="bluekey" target="_blank">JVM</a>）。<br><br>　　其他机器需要调用的对象必须被导出到<a href="http://www.yesky.com/key/1300/186300.html" class="bluekey" target="_blank">远程注册</a>服
务器，这样才能被其他机器调用。因此，如果机器A要调用机器B上的方法，则机器B必须将该对象导出到其远程注册服务器。注册服务器是服务器上运行的一种服
务，它帮助客户端远程地查找和访问服务器上的对象。一个对象只有导出来后，然后才能实现RMI包中的远程接口。例如，如果想使机器A中的Xyz对象能够被
远程调用，它就必须实现远程接口。<br><br>　　RMI需要使用占位程序和框架，占位程序在客户端，框架在服务器端。在调用远程方法时，我们无需直接面对存储有该方法的机器。<br><br>　　在进行数据通讯前，还必须做一些准备工作。占位程序就象客户端机器上的一个本机对象，它就象服务器上的对象的代理，向客户端提供能够被服务器调用的方法。然后，Stub就会向服务器端的Skeleton发送方法调用，Skeleton就会在服务器端执行接收到的方法。<br><br>　　Stub和Skeleton之间通过远程调用层进行相互通讯，远程调用层遵循TCP/IP协议收发数据。下面我们来大致了解一种称为为“绑定”的技术。<br><br>　
　客户端无论何时要调用服务器端的对象，你可曾想过他是如何告诉服务器他想创建什么样的对象吗？这正是“绑定”的的用武之地。在服务器端，我们将一个字符
串变量与一个对象联系在一起（可以通过方法来实现），客户端通过将那个字符串传递给服务器来告诉服务器它要创建的对象，这样服务器就可以准确地知道客户端
需要使用哪一个对象了。所有这些字符串和对象都存储在的远程注册服务器中。 <br><br>　　<b>在编程中需要解决的问题</b><br><br>　　在研究代码之前，我们来看看必须编写哪些代码：<br><br>　　·<b>远程对象：</b>这个接口只定义了一个方法。我们应当明白的是，这个接口并非总是不包括方法的代码而只包括方法的定义。远程对象包含要导出的每个方法的定义，它还实现Java.rmi中的远程接口。<br><br>　　·<b>远程对象实现：</b>这是一个实现远程对象的类。如果实现了远程对象，就能够覆盖该对象中的所有方法，因此，远程对象的实现类将真正包含我们希望导出的方法的代码。<br><br>　　·</font><font size="2"><b><a href="http://www.yesky.com/key/597/265597.html" class="bluekey" target="_blank">远程服务器</a></b></font><font size="2"><b>：</b>这是一个作为服务器使用的类，它是相对于要访问远程方法的客户端而言的。它存储着绑定的字符串和对象。<br><br>　　·<b>远程客户端：</b>这是一个帮助我们访问远程方法提供帮助的类，它也是最终用户。我们将使用查找和调用远程方法的方法在该类中调用远程方法。<br><b>编程</b><br><br>　　我们将首先编写远程对象，并将代码保存为名字为AddServer.Java的文件：<br></font>

<table bgcolor="#ffffff" width="100%">
<tbody>
<tr>
<td><font size="2">import Java.rmi.*; <br><br>public interface AddServer extends Remote { <br><br>public int AddNumbers(int firstnumber,int secondnumber) throws RemoteException; <br><br>} <br></font></td></tr></tbody></table>
<p><font size="2">　　我们来看看上面的代码。首先，为了使用其内容，我们导入rmi包。然后，我们创建一个扩展了Java.rmi中远程接口的接口。所有的远程对象
必须扩展该远程接口，我们将该远程接口称为AddServer。在该远程对象中，有一个名字为AddNumbers的方法，客户端可以调用这一方法。我们
必须记住的是，所有的远程方法都需要启动RemoteException方法，有错误发生时就会调用该方法。<br><br>　　下面我们开始编写远程对象的实现。这是一个实现远程对象并包含有所有方法代码的类，将下面的代码保存为名字为AddServerImpl.Java的文件：<br></font></p>
<table bgcolor="#ffffff" width="100%">
<tbody>
<tr>
<td><font size="2">import Java.rmi.*; <br><br>public class AddServerImpl extends UnicastRemoteObject implements AddServer { <br>public AddServerImpl() { <br>super(); <br>} <br>public int AddNumbers(int firstnumber,int secondnumber) throws RemoteException { <br>return firstnumber + secondnumber; <br>} <br>} </font></td></tr></tbody></table>
<p><font size="2">　　首先，我们导入rmi包，然后创建一个扩展UnicastRemoteObject和实现创建的远程对象的类；其次，我们可以为类创建一个缺省
的构建器。我们还了解了AddNumbers方法的代码，它启动RemoteException。这样我们就覆盖了创建的远程对象中的方法。
AddNumbers方法的代码非常好理解，它接受2个整型参数，然后相加并返回它们的和。<br><br>　　至此，我们已经有了二个Java文件：远程对象和远程对象的实现。下面我们将使用Javac命令编译这二个文件：<br><br>　　编译远程对象：<br></font></p>
<table bgcolor="#ffffff" width="100%">
<tbody>
<tr>
<td><font size="2">C:\jdk\bin\Javac workingdir\AddServer.Java </font></td></tr></tbody></table>
<p><font size="2">　　编译远程对象实现：<br></font></p>
<table bgcolor="#ffffff" width="100%">
<tbody>
<tr>
<td><font size="2">C:\jdk\bin\Javac workingdir\AddServerImpl.Java </font></td></tr></tbody></table>
<p><font size="2">　　这样，就会达到二个Java文件和二个类文件，下面我们将创建stub和skeleton。为了创建stub和skeleton文件，我们必须使用rmic编译器编译远程对象实现文件。<br><br>　　用Rmic编译远程对象实现文件：<br></font></p>
<table bgcolor="#ffffff" width="100%">
<tbody>
<tr>
<td><font size="2">C:\jdk\bin\rmic workingdir\AddServerImpl.Java </font></td></tr></tbody></table>
<p><font size="2">　　然后，我们就会发现多了2个新建的类文件，它们分别是AddServerImpl_Stub.class 和AddServerImpl_Skel.class 。<br><br>　　The Coding (Contd.) <br><br>　　我们已经编译了所有的源代码，下面我们来创建客户端和服务器端，将下面的代码保存为名字为RmiServer.Java的文件：<br></font></p>
<table bgcolor="#ffffff" width="100%">
<tbody>
<tr>
<td><font size="2">import Java.rmi.*; <br>import Java.net.*; <br><br>public class RmiServer { <br>public static void main (String args[]) throws RemoteException, MalformedURLException { <br>AddServerImpl add = new AddServerImpl(); <br>Naming.rebind("addnumbers",add); <br>} <br>} <br></font></td></tr></tbody></table>
<p><font size="2">　　首先，我们导入Java.rmi包和Java.net包。另外，我们还使用throws从句捕获任何异常。我们从对象中得出远程对象实现，使用rebind方法将字符串addnumbers与该对象绑定。下面的例子显示了绑定的含义：<br>从现在开始，无论何时客户端要调用远程对象，使用字符串addnumbers就可以实现。rebind方法有二个参数：第一个参数是字符串变量，第二个参数是远程对象实现类的对象。<br><br>　　下面我们来创建客户端，将下面的代码保存为名字为RmiClient.Java的文件：<br></font></p>
<table bgcolor="#ffffff" width="100%">
<tbody>
<tr>
<td><font size="2">import Java.rmi.*; <br>import Java.net.*; <br><br>public class RmiClient { <br>public static void main(String args[]) throws RemoteException, MalformedURLException { <br>String url="rmi://127.0.0.1/addnumbers"; <br>AddServer add; <br>add = (AddServer)Naming.lookup(url); <br>int result = add.AddNumbers(10,5); <br>System.out.println(result); <br>} <br>} <br></font></td></tr></tbody></table>
<p><font size="2">　　首先，我们导入Java.rmi包和Java.net包，并使用throws从句捕获所有必要的异常。然后通过利用Naming类中的静态lookup方法从远程对象中得到一个对象。（这也是我们无需从Naming类中得到一个对象并调用它。而只使用类名字的原因。）<br><br>　
　lookup方法接受远程对象的完整的URL名字，该URL由完整的机器IP地址以及与对象绑定的字符串（也誻对象的绑定名）组成。在调用远程对象时，
我们使用了RMI协议。lookup方法向我们返回一个对象，在能够使用它前，我们必须将它的数据类型转换为与远程对象的数据类型一致。<br></font></p>
<table bgcolor="#ffffff" width="100%">
<tbody>
<tr>
<td><font size="2">Since we have both our server and client source ready, let's compile them both: </font></td></tr></tbody></table>
<p><font size="2">　　至此，我们已经有了服务器端和客户端的源代码，下面我们来编译这二个源文件：<br><br>　　编译远程服务器：<br></font></p>
<table bgcolor="#ffffff" width="100%">
<tbody>
<tr>
<td><font size="2">C:\jdk\bin\Javac workingdir\RmiServer.Java </font></td></tr></tbody></table>
<p><font size="2">　　编译远程客户端：<br></font></p>
<table bgcolor="#ffffff" width="100%">
<tbody>
<tr>
<td><font size="2">C:\jdk\bin\Javac workingdir\RmiClient.Java </font></td></tr></tbody></table>
<p><font size="2">　　在对我们的代码进行测试前，还必须首先启动RMI Registry。RMI Registry存储有所有绑定的数据，没有它，RMI就不能正常地运行！<br><br>　　启动Rmi Registry服务器：<br></font></p>
<table bgcolor="#ffffff" width="100%">
<tbody>
<tr>
<td><font size="2">C:\jdk\bin\start rmiregistry </font></td></tr></tbody></table>
<p><font size="2">　　我们会注意到，这时会出现一个空白的DOS提示符窗口，这表明Rmi Registry服务器在运行，注意不要关闭该窗口。然后，我们首先在一个DOS提示符窗口中运行Rmi服务器，然后在另一个DOS提示符窗口中运行Rmi客户端。<br><br>　　启动RMI服务器： </font></p>
<table bgcolor="#ffffff" width="100%">
<tbody>
<tr>
<td><font size="2">C:\jdk\bin\Java workingdir\RmiServer </font></td></tr></tbody></table>
<p><font size="2">　　启动RMI客户端：<br></font></p>
<table bgcolor="#ffffff" width="100%">
<tbody>
<tr>
<td><font size="2">C:\jdk\bin\Java workingdir\RmiClient </font></td></tr></tbody></table>
<font size="2">　　如果一切正常，我们应该能够得到15这个输出。我们向AddNumbers方法输入10和5二个数字，该方法将这二者加起来，并将其和15返回
给我们。如果得到了15这个输出，说明我们已经成功地执行了一个远程方法。当然，在这里，我们并没有执行真正意义上的远程方法，因为我们的计算机既是服务
器，又是客户机。如果有计算机网络，我们就可以方便地进行执行远程方法的试验了。</font><br><img src ="http://www.blogjava.net/cmd/aggbug/30916.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/cmd/" target="_blank">静夜思</a> 2006-02-16 10:13 <a href="http://www.blogjava.net/cmd/articles/30916.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>