??xml version="1.0" encoding="utf-8" standalone="yes"?>亚洲色在线无码国产精品不卡,亚洲欧洲国产日韩精品,亚洲午夜一区二区电影院http://www.tkk7.com/cmd/category/7473.htmlzh-cnFri, 02 Mar 2007 03:13:19 GMTFri, 02 Mar 2007 03:13:19 GMT60在学习webwork案例中碰C静态的内部c?抄篇文章?/title><link>http://www.tkk7.com/cmd/articles/42248.html</link><dc:creator>静夜?/dc:creator><author>静夜?/author><pubDate>Thu, 20 Apr 2006 17:13:00 GMT</pubDate><guid>http://www.tkk7.com/cmd/articles/42248.html</guid><wfw:comment>http://www.tkk7.com/cmd/comments/42248.html</wfw:comment><comments>http://www.tkk7.com/cmd/articles/42248.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.tkk7.com/cmd/comments/commentRss/42248.html</wfw:commentRss><trackback:ping>http://www.tkk7.com/cmd/services/trackbacks/42248.html</trackback:ping><description><![CDATA[ <font size="2"> <span id="xufruan" class="javascript" id="text629"> Q一Q? <br /> Java1.1以后版本d了嵌套类QInner ClassQ嵌套类、内部类Q。嵌套类定义在类Q外部类Q里面? <br /> 嵌套cd以体现逻辑上的从属关系。同时对于其他类可以控制内部cM可见{? <br /> 外部cȝ成员变量作用域是整个外部c,包括嵌套cR但外部cM能访问嵌套类的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 /> Q二Q下面演CZ如何在外部类之外实例Z个public Innercd象? <br /><br /> cd前加上外部类cdQnew语句前加上外部类的引用变量。这很类似package的用? <br /> 例子Q? <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 /> Q三Q当内部cM外部cL同名变量Ӟ~省指本cM的变量? <br /> 例子Qpublic 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++; // 错误QOuter.size++因ؓsize不是静态变? <br /> } <br /> } <br /> public static void main(String args[]){ <br /> this.size++; //错误Q静态方法用非静态变量this <br /> } <br /> } <br /> Q四Q在Ҏ中定义一个嵌套类Q注意嵌套类不可以访问方法的局部变量? <br /> 因ؓҎ的局部变量只存在与方法作用期_故localvalue不可讉K。而final变量的生命周期超ZҎQ所以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 /> Q五Q? <br /> 嵌套cȝcd必须与包装它的外部类区别Q且嵌套cȝcd仅仅可以作用于定义范围中。方法中定义c,其类名名只能出现在Ҏ中? <br /> 定义在方法中的嵌套类只能使用定义为final的局部变量,不可以用方法中的非静态变量? <br /> 嵌套cd以用的变量U类包括Q类变量、实例变量、final局部变量? <br /> 嵌套cd样具有所有的讉K控制权限。高U类属性: <br /> 嵌套cd以是abstractc? <br /> 嵌套cd以是接口Q被其它嵌套cd现? <br /> 定义为static的内部类成为顶U类(top-level)。它们不依赖于外部类的对象而生成,所以不可以讉K外部cȝ对象成员? <br /> 非static内部cM能定义static成员 <br /> 当外部类~译Ӟ内部cM会编译,生成的类文g格式? <br /> OuterClass$InnerClassQ如Outer$Inner.class <br /><br /> 例子Q? <br /> class Outer { <br /> private int size =5; <br /> static class Inner{ <br /> int value=5; <br /> public void doStuff(){ size+=value; }// 错误Q不能访问非static 外部cȝ? <br /> } <br /> } <br /><br /> public class TestInner{ <br /> public static void main(String args[]){ <br /> Outer outer = new Outer(); <br /> Inner inner = new Inner();// q种实例也不? <br /> 应该 Out.Inner innner=new Outer.Inner() ; <br /> } <br /> } <br /><br /> 非static内部cM能定义static成员 Q? <br /> Q?Q定?static ?内部c, 如果成员变量也定义成 static ,那么外围cd以直接通过cd来访问? <br /> Q?Q定?static ?内部c, 如果成员变量不是 static ,那么外围c需要生成内部类的对象才能访问,否则直接讉KD~译出错Q! </span> </font> <img src ="http://www.tkk7.com/cmd/aggbug/42248.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.tkk7.com/cmd/" target="_blank">静夜?/a> 2006-04-21 01:13 <a href="http://www.tkk7.com/cmd/articles/42248.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>一个关于优化SQL的文?/title><link>http://www.tkk7.com/cmd/articles/36355.html</link><dc:creator>静夜?/dc:creator><author>静夜?/author><pubDate>Mon, 20 Mar 2006 08:08:00 GMT</pubDate><guid>http://www.tkk7.com/cmd/articles/36355.html</guid><wfw:comment>http://www.tkk7.com/cmd/comments/36355.html</wfw:comment><comments>http://www.tkk7.com/cmd/articles/36355.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.tkk7.com/cmd/comments/commentRss/36355.html</wfw:commentRss><trackback:ping>http://www.tkk7.com/cmd/services/trackbacks/36355.html</trackback:ping><description><![CDATA[ <font size="2"> 大家都在讨论关于数据库优化方面的东东Q刚好参与开发了一个数据仓库方面的目Q以下的一点东西算是数据库优化斚w的学?实战的一些心得体会了Q拿出来大家׃n。欢q批评指正阿Q?br /><br />SQL语句Q?br />是对数据?数据)q行操作的惟一途径Q?br />消耗了70%~90%的数据库资源Q独立于E序设计逻辑Q相对于对程序源代码的优化,对SQL语句的优化在旉成本和风险上的代价都很低Q?br />可以有不同的写法Q易学,隄通。?br /><br />SQL优化Q?br />固定的SQL书写习惯Q相同的查询量保持相同Q存储过E的效率较高。?br />应该~写与其格式一致的语句Q包括字母的大小写、标点符受换行的位置{都要一臾b?br /><br />ORACLE优化器: <br />在Q何可能的时候都会对表达式进行评伎ͼq且把特定的语法l构转换成等Ll构Q这么做的原因是 <br />要么l果表达式能够比源表辑ּh更快的速度 <br />要么源表辑ּ只是l果表达式的一个等仯义结构?br />不同的SQLl构有时h同样的操作(例如Q? ANY (subquery) and IN (subquery)Q,ORACLE会把他们映射C个单一的语义结构。?br /><br />1 帔R优化Q?br />帔R的计是在语句被优化时一ơ性完成,而不是在每次执行时。下面是索月薪大?000的的表达式: <br />sal > 24000/12 <br />sal > 2000 <br />sal*12 > 24000 <br />如果SQL语句包括W一U情况,优化器会单地把它转变成第二种。?br />优化器不会简化跨比较符的表辑ּQ例如第三条语句Q鉴于此Q应量写用帔R跟字D|较检索的表达式,而不要将字段|于表达式当中。否则没有办法优化,比如如果sal上有索引Q第一和第二就可以使用Q第三就难以使用。?br /><br />2 操作W优化: <br />优化器把使用LIKE操作W和一个没有通配W的表达式组成的索表辑ּ转换Z个?”操作符表达式。?br />例如Q优化器会把表达式ename LIKE 'SMITH'转换为ename = 'SMITH' <br />优化器只能{换涉及到可变长数据类型的表达式,前一个例子中Q如果ENAME字段的类型是CHAR(10)Q 那么优化器不做Q何{换。?br />一般来讲LIKE比较难以优化。?br /><br />其中Q?br />~~ IN 操作W优化: <br />优化器把使用IN比较W的索表辑ּ替换为等L使用?”和“OR”操作符的检索表辑ּ。?br />例如Q优化器会把表达式ename IN ('SMITH','KING','JONES')替换为?br />ename = 'SMITH' OR ename = 'KING' OR ename = 'JONES‘?br /><br />~~ ANY和SOME 操作W优? <br />优化器将跟随值列表的ANY和SOME索条件用{h的同{操作符和“OR”组成的表达式替换。?br />例如Q优化器如下所C的W一条语句用W二条语句替换: <br />sal > ANY (:first_sal, :second_sal) <br />sal > :first_sal OR sal > :second_sal <br />优化器将跟随子查询的ANY和SOME索条件{换成由“EXISTS”和一个相应的子查询组成的索表辑ּ。?br />例如Q优化器如下所C的W一条语句用W二条语句替换: <br />x > ANY (SELECT sal FROM emp WHERE job = 'ANALYST') <br />EXISTS (SELECT sal FROM emp WHERE job = 'ANALYST' AND x > sal) <br /><br />~~ ALL操作W优? <br />优化器将跟随值列表的ALL操作W用{h的?”和“AND”组成的表达式替换。例如: <br />sal > ALL (:first_sal, :second_sal)表达式会被替换ؓQ?br />sal > :first_sal AND sal > :second_sal <br />对于跟随子查询的ALL表达式,优化器用ANY和另外一个合适的比较W组成的表达式替换。例如?br />x > ALL (SELECT sal FROM emp WHERE deptno = 10) 替换为: <br />NOT (x <= ANY (SELECT sal FROM emp WHERE deptno = 10)) <br />接下来优化器会把W二个表辑ּ适用ANY表达式的转换规则转换Z面的表达式: <br />NOT EXISTS (SELECT sal FROM emp WHERE deptno = 10 AND x <= sal) <br /><br />~~ BETWEEN 操作W优? <br />优化器L用?gt;=”和?lt;=”比较符来等L代替BETWEEN操作W。?br />例如Q优化器会把表达式sal BETWEEN 2000 AND 3000用sal >= 2000 AND sal <= 3000来代ѝ?br /><br />~~ NOT 操作W优? <br />优化器L试图化检索条件以消除“NOT”逻辑操作W的影响Q这涉及到“NOT”操作符的消除以及代以相应的比较q算W。?br />例如Q优化器下面的W一条语句用W二条语句代替: <br />NOT deptno = (SELECT deptno FROM emp WHERE ename = 'TAYLOR') <br />deptno <> (SELECT deptno FROM emp WHERE ename = 'TAYLOR') <br />通常情况下一个含有NOT操作W的语句有很多不同的写法Q优化器的{换原则是低쀜NOT”操作符后边的子句尽可能的简单,即可能会ɾl果表达式包含了更多的“NOT”操作符。?br />例如Q优化器如下所C的W一条语句用W二条语句代替: <br />NOT (sal < 1000 OR comm IS NULL) <br />NOT sal < 1000 AND comm IS NOT NULL sal >= 1000 AND comm IS NOT NULL <br /><br />如何~写高效的SQL: <br />当然要考虑sql帔R的优化和操作W的优化啦,另外Q还需要: <br /><br />1 合理的烦引设计: <br />例:表record?20000行,试看在不同的索引下,下面几个SQL的运行情况: <br />语句A <br />SELECT count(*) FROM record <br />WHERE date >'19991201' and date < '19991214‘ and amount >2000 <br /><br />语句B <br />SELECT count(*) FROM record <br />WHERE date >'19990901' and place IN ('BJ','SH') <br /><br />语句C <br />SELECT date,sum(amount) FROM record <br />group by date <br />1 在date上徏有一个非聚集索引 <br />AQ?25U? <br />BQ?27U? <br />CQ?55U? <br />分析Q?br />date上有大量的重复|在非聚集索引下,数据在物理上随机存放在数据页上,在范围查找时Q必L行一ơ表扫描才能扑ֈq一范围内的全部行。?br />2 在date上的一个聚集烦引?br />AQ(14U) <br />BQ(14U) <br />CQ(28U) <br />分析Q?br />在聚集烦引下Q数据在物理上按序在数据页上,重复g排列在一P因而在范围查找Ӟ可以先找到这个范围的h点,且只在这个范围内扫描数据,避免了大范围扫描Q提高了查询速度。?br />3 在placeQdateQamount上的l合索引 <br />AQ(26U) <br />CQ(27U) <br />BQ(< 1U) <br />分析Q?br />q是一个不很合理的l合索引Q因为它的前导列是placeQ第一和第二条SQL没有引用placeQ因此也没有利用上烦引;W三个SQL使用了placeQ且引用的所有列都包含在l合索引中,形成了烦引覆盖,所以它的速度是非常快的。?br />4 在dateQplaceQamount上的l合索引 <br />AQ?< 1U? <br />BQ(< 1U) <br />CQ(11U) <br />分析Q?br />q是一个合理的l合索引。它date作ؓ前导列,使每个SQL都可以利用烦引,q且在第一和第三个SQL中Ş成了索引覆盖Q因而性能辑ֈ了最优。?br /><br />ȝ1 <br />~省情况下徏立的索引是非聚集索引Q但有时它ƈ不是最佳的Q合理的索引设计要徏立在对各U查询的分析和预上。一般来_ <br />有大量重复倹{且l常有范围查询(between, >,< Q?gt;=,< =Q和order by、group by发生的列Q考虑建立聚集索引Q?br />l? 常同时存取多列,且每列都含有重复值可考虑建立l合索引Q在条g表达式中l常用到的不同D多的列上建立索,在不同值少的列上不要徏立烦引。比如在雇员  表的“性别”列上只有“男”与“女”两个不同|因此无必要建立索引。如果徏立烦引不但不会提高查询效率,反而会严重降低更新速度。?br />l合索引要尽量关键查询形成索引覆盖Q其前导列一定是使用最频繁的列。?br /><br />2 避免使用不兼容的数据cdQ?br />例如float和INt、char和varchar、bINary和varbINary是不兼容的。数据类型的不兼容可能优化器无法执行一些本来可以进行的优化操作。例? <br />SELECT name FROM employee WHERE salary Q?0000 <br />在这条语句中,如salary字段是money型的,则优化器很难对其q行优化,因ؓ60000是个整型数。我们应当在~程时将整型转化成ؓ钱币?而不要等到运行时转化。?br /><br />3 IS NULL 与IS NOT NULLQ?br />不? 能用null作烦引,M包含null值的列都不会被包含在烦引中。即使烦引有多列q样的情况下Q只要这些列中有一列含有nullQ该列就会从索引中排  除。也是说如果某列存在空|即对该列徏索引也不会提高性能。Q何在WHERE子句中用is null或is not null的语句优化器是不 允 许使用索引的。?br /><br />4 IN和EXISTSQ?br />EXISTS要远比IN的效率高。里面关pdfull table scan和range scan。几乎将所有的IN操作W子查询改写Z用EXISTS的子查询。?br />例子Q?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中对empq行了full table scan,q是很浪Ҏ间的操作。而且1中没有用到emp的INdexQ?br />因ؓ没有WHERE子句。?中的语句对empq行的是range scan。?br /><br />5 IN、OR子句怼使用工作表,使烦引失效: <br />如果不生大量重复|可以考虑把子句拆开。拆开的子句中应该包含索引。?br /><br />6 避免或简化排序: <br />应当化或避免对大型表q行重复的排序。当能够利用索引自动以适当的次序生输出时Q优化器避免了排序的步骤。以下是一些媄响因素: <br />索引中不包括一个或几个待排序的列; <br />group by或order by子句中列的次序与索引的次序不一P <br />排序的列来自不同的表。?br />Z避免不必要的排序Q就要正地增徏索引Q合理地合ƈ数据库表Q尽有时可能媄响表的规范化Q但相对于效率的提高是值得的)。如果排序不可避免,那么应当试图化它Q如~小排序的列的范围等。?br /><br />7 消除对大型表行数据的序存取Q?br />在? 嵌套查询中,对表的顺序存取对查询效率可能产生致命的媄响。比如采用顺序存取策略,一个嵌?层的查询Q如果每层都查询1000行,那么q个查询p查询  10亿行数据。避免这U情늚主要Ҏ是对连接的列进行烦引。例如,两个表:学生表(学号、姓名、年??Q和选课表(学号、课E号、成l)。如果两 个 表要做q接Q就要在“学号”这个连接字D上建立索引。?br />q可以用ƈ集来避免序存取。尽在所有的查列上都有烦引,但某些Ş式的WHERE子句优化器用顺序存取。下面的查询强q对orders表执行顺序操作: <br />SELECT Q FROM orders WHERE (customer_num=104 AND order_num>1001) OR order_num=1008 <br />虽然在customer_num和order_num上徏有烦引,但是在上面的语句中优化器q是使用序存取路径扫描整个表。因个语句要索的是分ȝ行的集合Q所以应该改为如下语句: <br />SELECT Q FROM orders WHERE customer_num=104 AND order_num>1001 <br />UNION <br />SELECT Q FROM orders WHERE order_num=1008 <br />q样p利用索引路径处理查询。?br /><br />8 避免相关子查询: <br />一个列的标{֐时在L询和WHERE子句中的查询中出玎ͼ那么很可能当L询中的列值改变之后,子查询必重新查询一ơ。查询嵌套层ơ越多,效率低Q因此应当尽量避免子查询。如果子查询不可避免Q那么要在子查询中过滤掉可能多的行。?br /><br />9 避免困难的正规表辑ּQ?br />MATCHES和LIKE关键字支持通配W匹配,技术上叫正规表辑ּ。但q种匚w特别耗费旉。例如:SELECT Q FROM customer WHERE zipcode LIKE ?8_ _ _”?br />即在zipcode字段上徏立了索引Q在q种情况下也q是采用序扫描的方式。如果把语句改ؓSELECT Q FROM customer WHERE zipcode >?8000”,在执行查询时׃利用索引来查询,昄会大大提高速度。?br />另外Q还要避免非开始的子串。例如语句:SELECT Q FROM customer WHERE zipcode[2Q?] >?0”,在WHERE子句中采用了非开始子Ԍ因而这个语句也不会使用索引。?br /><br />10 不充份的q接条gQ?br />例:表card?896行,在card_no上有一个非聚集索引Q表account?91122行,在account_no上有一个非聚集索引Q试看在不同的表q接条g下,两个SQL的执行情况: <br />SELECT sum(a.amount) FROM account a,card b WHERE a.card_no = b.card_no <br />Q?0U) <br />SQL改ؓQ?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 />Q?lt; 1U) <br />分析Q?br />在第一个连接条件下Q最x询方案是account作外层表Qcard作内层表Q利用card上的索引Q其I/Oơ数可由以下公式估算为: <br />外层表account上的22541?Q外层表account?91122?内层表card上对应外层表W一行所要查扄3)=595907ơI/O <br />在第二个q接条g下,最x询方案是card作外层表Qaccount作内层表Q利用account上的索引Q其I/Oơ数可由以下公式估算为: <br />外层表card上的1944?Q外层表card?896?内层表account上对应外层表每一行所要查扄4)= 33528ơI/O <br />可见Q只有充份的q接条gQ真正的最x案才会被执行。?br />多表操作在被实际执行前,查询优化器会Ҏq接条gQ列出几l可能的q接Ҏq从中找出系l开销最的最x案。连接条件要充䆾考虑带有索引的表、行数多的表Q内外表的选择可由公式Q外层表中的匚w行数*内层表中每一ơ查扄ơ数定Q乘U最ؓ最x案。?br />不可优化的WHERE子句 <br />? <br />下列SQL条g语句中的列都建有恰当的烦引,但执行速度却非常慢Q?br />SELECT * FROM record WHERE substrINg(card_no,1,4)='5378' <br />(13U? <br />SELECT * FROM record WHERE amount/30< 1000 <br />Q?1U) <br />SELECT * FROM record WHERE convert(char(10),date,112)='19991201' <br />Q?0U) <br />分析Q?br />WHERE子句中对列的M操作l果都是在SQLq行旉列计算得到的,因此它不得不q行表搜索,而没有用该列上面的索引Q如果这些结果在查询~译时就能得刎ͼ那么可以被SQL优化器优化,使用索引Q避免表搜烦Q因此将SQL重写成下面这P <br />SELECT * FROM record WHERE card_no like '5378%' <br />Q?lt; 1U) <br />SELECT * FROM record WHERE amount< 1000*30 <br />Q?lt; 1U) <br />SELECT * FROM record WHERE date= '1999/12/01' <br />Q?lt; 1U) <br /><br />11 存储q程中,采用临时表优化查询: <br />例?br />1Q从parven表中按vendor_num的次序读数据Q?br />SELECT part_numQvendor_numQprice FROM parven ORDER BY vendor_num <br />INTO temp pv_by_vn <br />q个语句序读parvenQ?0)Q写一个时表Q?0)Qƈ排序。假定排序的开销?00,d?00c?br />2Q把临时表和vendor表连接,把结果输出到一个时表Qƈ按part_num排序Q?br />SELECT pv_by_vnQ* vendor.vendor_num FROM pv_by_vnQvendor <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 />q? 个查询读取pv_by_vn(50?Q它通过索引存取vendor?.5万次Q但׃按vendor_numơ序排列Q实际上只是通过索引序地读  vendor表(40Q?=42)Q输出的表每늺95行,?60c写q存取这些页引发5Q?60=800ơ的dQ烦引共d892c?br />3Q把输出和partq接得到最后的l果Q?br />SELECT pvvn_by_pn.Q,part.part_desc FROM pvvn_by_pnQpart <br />WHERE pvvn_by_pn.part_num=part.part_num <br />DROP TABLE pvvn_by_pn <br />q样Q查询顺序地读pvvn_by_pn(160?Q通过索引读part?.5万次Q由于徏有烦引,所以实际上q行1772ơ磁盘读写,优化比例?0?。?br /><br />好了Q搞定。?br />其实sql的优化,各种数据库之间都是互通的? </font> <img src ="http://www.tkk7.com/cmd/aggbug/36355.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.tkk7.com/cmd/" target="_blank">静夜?/a> 2006-03-20 16:08 <a href="http://www.tkk7.com/cmd/articles/36355.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>介绍一关于session的好文章,写的很详l?jsp-servlet 技?http://www.tkk7.com/cmd/articles/34646.html静夜?/dc:creator>静夜?/author>Fri, 10 Mar 2006 05:10:00 GMThttp://www.tkk7.com/cmd/articles/34646.htmlhttp://www.tkk7.com/cmd/comments/34646.htmlhttp://www.tkk7.com/cmd/articles/34646.html#Feedback0http://www.tkk7.com/cmd/comments/commentRss/34646.htmlhttp://www.tkk7.com/cmd/services/trackbacks/34646.html摘要Q虽然session机制在web应用E序中被采用已经很长旉了,但是仍然有很多h不清? session机制的本质,以至不能正确的应用这一技术。本文将详细讨论session的工作机制ƈ且对在Java web application中应 用session机制时常见的问题作出解答?br>
目录Q?br>一、术语session
二、HTTP协议与状态保?br>三、理解cookie机制
四、理解session机制
五、理解javax.servlet.http.HttpSession
六、HttpSession常见问题
七、跨应用E序的session׃n
八、ȝ
参考文?br>
一、术语session
在我的经验里Qsessionq个词被滥用的程度大概仅ơ于transactionQ更加有的是transaction与session在某些语境下的含义是相同的?br>
sessionQ? 中文l常译Z话,其本来的含义是指有始有终的一pd动作/消息Q比如打电话时从拿v电话拨号到挂断电话这中间的一pdq程可以UCZ?  session。有时候我们可以看到这L话“在一个浏览器会话期间Q?..”,q里的会话一词用的就是其本义Q是指从一个浏览器H口打开到关闭这个期 ?nbsp;①。最混ؕ的是“用P客户端)在一ơ会话期间”这样一句话Q它可能指用L一pd动作Q一般情况下是同某个具体目的相关的一pd动作Q比如从d? 选购商品到结账登样一个网上购物的q程Q有时候也被称Z个transactionQ,然而有时候也可能仅仅是指一ơ连接,也有可能是指含义①,其中 的差别只能靠上下文来推断②?br>
然而当session一词与|络协议相关联时Q它又往往隐含了“面向连接”和/或“保持状态”这样两个含 义, “面向连接”指的是在通信双方在通信之前要先建立一个通信的渠道,比如打电话,直到Ҏ接了电话通信才能开始,与此相对的是写信Q在你把信发出去? 时候你q不能确认对方的地址是否正确Q通信渠道不一定能建立Q但对发信h来说Q通信已经开始了。“保持状态”则是指通信的一方能够把一pd的消息关联v 来,使得消息之间可以互相依赖Q比如一个服务员能够认出再次光的老顾客ƈ且记得上ơ这个顾客还Ơ店里一块钱。这一cȝ例子有“一? TCP session”或?nbsp;“一个POP3 session”③?br>
而到了web服务器蓬勃发展的时代Qsession在web开发语 境下的语义又有了新的扩展Q它的含义是指一cȝ来在客户端与服务器之间保持状态的解决Ҏ④。有时候session也用来指q种解决Ҏ的存储结构,? “把xxx保存在session 里”⑤。由于各U用于web开发的语言在一定程度上都提供了对这U解x案的支持Q所以在某种特定语言的语境下Q? session也被用来指代该语a的解x案,比如l常把Java里提供的javax.servlet.http.HttpSessionUCؓ session⑥?br>
鉴于q种混ؕ已不可改变,本文中session一词的q用也会Ҏ上下文有不同的含义,请大家注意分辨?br>在本文中Q用中文“浏览器会话期间”来表达含义①,使用“session机制”来表达含义④,使用“session”表辑֐义⑤Q用具体的“HttpSession”来表达含义?br>
二、HTTP协议与状态保?br>HTTP  协议本n是无状态的Q这与HTTP协议本来的目的是相符的,客户端只需要简单的向服务器h下蝲某些文gQ无论是客户端还是服务器都没有必要纪录彼此过? 的行为,每一ơ请求之间都是独立的Q好比一个顾客和一个自动售货机或者一个普通的Q非会员Ӟ大卖Z间的关系一栗?br>
然而聪明(或者贪 心?Q的Z很快发现如果能够提供一些按需生成的动态信息会使web变得更加有用Q就像给有线电视加上Ҏ功能一栗这U需求一斚wqHTML逐步d 了表单、脚本、DOM{客L行ؓQ另一斚w在服务器端则出现了CGI规范以响应客L的动态请求,作ؓ传输载体的HTTP协议也添加了文g上蝲?  cookieq些Ҏ。其中cookie的作用就是ؓ了解决HTTP协议无状态的~陷所作出的努力。至于后来出现的session机制则是又一U在客户 端与服务器之间保持状态的解决Ҏ?br>
让我们用几个例子来描qC下cookie和session机制之间的区别与联系。笔者曾l常ȝ一家咖啡店有喝5杯咖啡免费赠一杯咖啡的优惠Q然而一ơ性消?杯咖啡的Z微乎其微Q这时就需要某U方式来U录某位֮的消Ҏ量。想象一下其实也无外乎下面的几种ҎQ?br>1、该店的店员很厉宻I能记住每位顾客的消费数量Q只要顾客一走进咖啡店,店员q道该怎么对待了。这U做法就是协议本w支持状态?br>2、发l顾客一张卡片,上面记录着消费的数量,一般还有个有效期限。每ơ消ҎQ如果顾客出C张卡片,则此ơ消费就会与以前或以后的消费相联pv来。这U做法就是在客户端保持状态?br>3、发l顾客一张会员卡Q除了卡号之外什么信息也不纪录,每次消费Ӟ如果֮出示该卡片,则店员在店里的纪录本上找到这个卡号对应的U录d一些消费信息。这U做法就是在服务器端保持状态?br>
? 于HTTP协议是无状态的Q而出于种U考虑也不希望使之成ؓ有状态的Q因此,后面两种Ҏ成为现实的选择。具体来说cookie机制采用的是在客L? 持状态的ҎQ而session机制采用的是在服务器端保持状态的Ҏ。同时我们也看到Q由于采用服务器端保持状态的Ҏ在客L也需要保存一个标识,所 以session机制可能需要借助于cookie机制来达C存标识的目的Q但实际上它q有其他选择?br>
三、理解cookie机制 
cookie机制的基本原理就如上面的例子一L单,但是q有几个问题需要解冻I“会员卡”如何分发;“会员卡”的内容Q以及客户如何用“会员卡”?br>
正统的cookie分发是通过扩展HTTP协议来实现的Q服务器通过在HTTP的响应头中加上一行特D的指示以提C浏览器按照指示生成相应的cookie。然而纯_的客户端脚本如JavaScript或者VBScript也可以生成cookie?br>
而cookie  的用是由浏览器按照一定的原则在后台自动发送给服务器的。浏览器查所有存储的cookieQ如果某个cookie所声明的作用范围大于等于将要请求的 资源所在的位置Q则把该cookie附在h资源的HTTPh头上发送给服务器。意思是麦当劳的会员卡只能在麦当劳的店里出示Q如果某家分店还发行了自 q会员卡,那么q这家店的时候除了要出示麦当劳的会员卡,q要出示q家店的会员卡?br>
cookie的内容主要包括:名字Q|q期旉Q\径和域?br>其中域可以指定某一个域比如.google.comQ相当于d招牌Q比如宝z公司,也可以指定一个域下的具体某台机器比如www.google.com或者froogle.google.comQ可以用飘柔来做比?br>路径是跟在域名后面的URL路径Q比?或?foo{等Q可以用某飘柔专柜做比?br>路径与域合在一起就构成了cookie的作用范围?br>? 果不讄q期旉Q则表示q个cookie的生命期为浏览器会话期间Q只要关闭浏览器H口Qcookie消׃。这U生命期为浏览器会话期的  cookie被称Z话cookie。会话cookie一般不存储在硬盘上而是保存在内存里Q当然这U行为ƈ不是规范规定的。如果设|了q期旉Q浏? 器就会把cookie保存到硬盘上Q关闭后再次打开览器,q些cookie仍然有效直到过讑֮的过期时间?br>
存储在硬盘上? cookie 可以在不同的览器进E间׃nQ比如两个IEH口。而对于保存在内存里的cookieQ不同的览器有不同的处理方式。对于IEQ在一个打 开的窗口上?nbsp;Ctrl-NQ或者从文g菜单Q打开的窗口可以与原窗口共享,而用其他方式新开的IEq程则不能共享已l打开的窗口的内存cookieQ? 对于 Mozilla Firefox0.8Q所有的q程和标{N都可以共享同Lcookie。一般来说是用javascript? window.open打开的窗口会与原H口׃n内存cookie。浏览器对于会话cookie的这U只认cookie不认人的处理方式l常l采? session机制的web应用E序开发者造成很大的困扰?br>
下面是一个goolge讄cookie的响应头的例?br>HTTP/1.1 302 Found
Location: http://www.google.com/intl/zh-CN/
Set-Cookie: PREF=ID=0565f77e132de138:NW=1:TM=1098082649:LM=1098082649:S=KaeaCFPo49RiA_d8;
expires=Sun, 17-Jan-2038 19:14:07 GMT; path=/; domain=.google.com
Content-Type: text/html




q是使用HTTPLookq个HTTP Sniffer软g来俘LHTTP通讯U录的一部分




览器在再次讉Kgoolge的资源时自动向外发送cookie




使用Firefox可以很容易的观察现有的cookie的?br>使用HTTPLook配合Firefox可以很容易的理解cookie的工作原理?br>



IE也可以设|在接受cookie前询?br>



q是一个询问接受cookie的对话框?br>
四、理解session机制
session机制是一U服务器端的机制Q服务器使用一U类g散列表的l构Q也可能是使用散列表)来保存信息?br>
? E序需要ؓ某个客户端的h创徏一个session的时候,服务器首先检查这个客L的请求里是否已包含了一个session标识 - UCؓ  session idQ如果已包含一个session id则说明以前已lؓ此客L创徏qsessionQ服务器按照session id把这?  session索出来用(如果索不刎ͼ可能会新Z个)Q如果客Lh不包含session idQ则为此客户端创Z个sessionq且? 成一个与此session相关联的session idQsession id的值应该是一个既不会重复Q又不容易被扑ֈ规律以仿造的字符Ԍq个  session id被在本ơ响应中q回l客L保存?br>
保存q个session id的方式可以采用cookieQ这样在交互q程? 览器可以自动的按照规则把这个标识发挥给服务器。一般这个cookie的名字都是类gSEEESIONIDQ而。比如weblogic对于web应用 E序生成的cookieQJSESSIONID=  ByOK3vjFD75aPnrF7C2HmdnV6QZcEbzWoWiBYEnLerjQ99zWpBng!-145788764Q它的名字就?  JSESSIONID?br>
׃cookie可以被h为的止Q必L其他机制以便在cookie被禁止时仍然能够把session id 传递回服务器。经常被使用的一U技术叫做URL重写Q就是把session id直接附加在URL路径的后面,附加方式也有两种Q一U是作ؓURL路径? 附加信息Q表现Ş式ؓhttp://...../xxx;jsessionid= ByOK3vjFD75aPnrF7C2HmdnV6QZcEbzWoWiBYEnLerjQ99zWpBng!-145788764
另一U是作ؓ查询字符串附加在URL后面Q表现Ş式ؓhttp://...../xxx?jsessionid=ByOK3vjFD75aPnrF7C2HmdnV6QZcEbzWoWiBYEnLerjQ99zWpBng!-145788764
q两U方式对于用h说是没有区别的,只是服务器在解析的时候处理的方式不同Q采用第一U方式也有利于把session id的信息和正常E序参数区分开来?br>Z在整个交互过E中始终保持状态,必d每个客户端可能请求的路径后面都包含这个session id?br>
另一U技术叫做表单隐藏字Dc就是服务器会自动修改表单,d一个隐藏字D,以便在表单提交时能够把session id传递回服务器。比如下面的表单
<form name="testform" action="/xxx">
<input type="text">
</form>
在被传递给客户端之前将被改写成
<form name="testform" action="/xxx">
<input type="hidden" name="jsessionid"
       value="ByOK3vjFD75aPnrF7C2HmdnV6QZcEbzWoWiBYEnLerjQ99zWpBng!-145788764">
<input type="text">
</form>
q种技术现在已较少应用Q笔者接触过的很古老的iPlanet6(SunONE应用服务器的前n)׃用了q种技术?br>实际上这U技术可以简单的用对action应用URL重写来代ѝ?br>
? 谈论session机制的时候,常常听到q样一U误解“只要关闭浏览器Qsession消׃”。其实可以想象一下会员卡的例子,除非֮d对店家提 出销卡,否则店家l对不会L删除֮的资料。对session来说也是一LQ除非程序通知服务器删除一个sessionQ否则服务器会一直保留,E序 一般都是在用户做log off的时候发个指令去删除session。然而浏览器从来不会d在关闭之前通知服务器它要关闭Q因此服务器Ҏ不会有机? 知道览器已l关闭,之所以会有这U错觉,是大部分session机制都用会话cookie来保存session idQ而关闭浏览器后这?  session id消׃Q再ơ连接服务器时也无法找到原来的session。如果服务器讄的cookie被保存到盘上,或者用某U手D| 写浏览器发出的HTTPh_把原来的session id发送给服务器,则再ơ打开览器仍然能够找到原来的session?br>
恰恰是由于关闭浏览器不会Dsession被删除,q服务器ؓseesion讄了一个失效时_当距dL上一ơ用session的时间超q这个失效时间时Q服务器可以认为客L已经停止了活动,才会把session删除以节省存储空间?br>
五、理解javax.servlet.http.HttpSession
HttpSession是Javaq_对session机制的实现规范,因ؓ它仅仅是个接口,具体到每个web应用服务器的提供商,除了对规范支持之外,仍然会有一些规范里没有规定的细微差异。这里我们以BEA的Weblogic Server8.1作ؓ例子来演C?br>
? 先,Weblogic Server提供了一pd的参数来控制它的HttpSession的实玎ͼ包括使用cookie的开关选项Q用URL重写的开? 选项Qsession持久化的讄Qsession失效旉的设|,以及针对cookie的各U设|,比如讄cookie的名字、\径、域Q?  cookie的生存时间等?br>
一般情况下Qsession都是存储在内存里Q当服务器进E被停止或者重启的时候,内存里的session 也会被清I,如果讄了session的持久化Ҏ,服务器就会把session保存到硬盘上Q当服务器进E重新启动或q些信息能够被再次使用Q?  Weblogic Server支持的持久性方式包括文件、数据库、客Lcookie保存和复制?br>
复制严格说来不算持久化保存,因ؓsession实际上还是保存在内存里,不过同样的信息被复制到各个cluster内的服务器进E中Q这样即使某个服务器q程停止工作也仍然可以从其他q程中取得session?br>
cookie生存旉的设|则会媄响浏览器生成的cookie是否是一个会话cookie。默认是使用会话cookie。有兴趣的可以用它来试验我们在第四节里提到的那个误解?br>
cookie的\径对于web应用E序来说是一个非帔R要的选项QWeblogic Server对这个选项的默认处理方式得它与其他服务器有明昄区别。后面我们会专题讨论?br>
关于session的设|参考[5] http://e-docs.bea.com/wls/docs70/webapp/weblogic_xml.html#1036869

六、HttpSession常见问题
Q在本小节中session的含义ؓ⑤和⑥的混合Q?br>

1、session在何时被创徏
一 个常见的误解是以为session在有客户端访问时p创徏Q然而事实是直到某server端程序调?  HttpServletRequest.getSession(true)q样的语句时才被创徏Q注意如果JSP没有昄的?nbsp;<%  @page session="false"%> 关闭sessionQ则JSP文g在编译成Servlet时将会自动加上这样一条语?  HttpSession session = HttpServletRequest.getSession(true);q也是JSP中隐含的  session对象的来历?br>
׃session会消耗内存资源,因此Q如果不打算使用sessionQ应该在所有的JSP中关闭它?br>
2、session何时被删?br>l合前面的讨论,session在下列情况下被删除a.E序调用HttpSession.invalidate();或b.距离上一ơ收到客L发送的session id旉间隔过了session的超时设|?或c.服务器进E被停止Q非持久sessionQ?br>
3、如何做到在览器关闭时删除session
严格的讲Q做不到q一炏V可以做一点努力的办法是在所有的客户端页面里使用javascript代码window.oncolose来监视浏览器的关闭动作,然后向服务器发送一个请求来删除session。但是对于浏览器崩溃或者强行杀死进E这些非常规手段仍然无能为力?br>
4、有个HttpSessionListener是怎么回事
? 可以创徏q样的listenerȝ控session的创建和销毁事Ӟ使得在发生这L事g时你可以做一些相应的工作。注意是session的创建和销 毁动作触发listenerQ而不是相反。类似的与HttpSession有关的listenerq有  HttpSessionBindingListenerQHttpSessionActivationListener?  HttpSessionAttributeListener?br>
5、存攑֜session中的对象必须是可序列化的?br>不是必需 的。要求对象可序列化只是ؓ了session能够在集中被复制或者能够持久保存或者在必要时server能够暂时把session交换出内存。在  Weblogic Server的session中放|一个不可序列化的对象在控制C会收C个警告。我所用过的某个iPlanet版本如果  session中有不可序列化的对象Q在session销毁时会有一个ExceptionQ很奇怪?br>
6、如何才能正的应付客户端禁止cookie的可能?br>Ҏ有的URL使用URL重写Q包括超链接Qform的actionQ和重定向的URLQ具体做法参见[6]
http://e-docs.bea.com/wls/docs70/webapp/sessions.html#100770

7、开两个览器窗口访问应用程序会使用同一个sessionq是不同的session
参见W三节对cookie的讨论,对session来说是只认id不认人,因此不同的浏览器Q不同的H口打开方式以及不同的cookie存储方式都会对这个问题的{案有媄响?br>
8、如何防止用h开两个览器窗口操作导致的session混ؕ
q? 个问题与防止表单多次提交是类似的Q可以通过讄客户端的令牌来解冟뀂就是在服务器每ơ生成一个不同的idq回l客LQ同时保存在session里,? L提交表单时必Lq个id也返回服务器Q程序首先比较返回的id与保存在session里的值是否一_如果不一致则说明本次操作已经被提交过了。可 以参看《J2EE核心模式》关于表C层模式的部分。需要注意的是对于用javascript window.open打开的窗口,一般不讄q个idQ? 或者用单独的idQ以防主H口无法操作Q徏议不要再window.open打开的窗口里做修Ҏ作,q样可以不用设|?br>
9、ؓ什么在Weblogic Server中改变session的值后要重新调用一ơsession.setValue
做这个动作主要是Z在集环境中提示Weblogic Server session中的值发生了改变Q需要向其他服务器进E复制新的session倹{?br>
10、ؓ什么session不见?br>? 除session正常失效的因素之外,服务器本w的可能性应该是微乎其微的,虽然W者在iPlanet6SP1加若q补丁的Solaris版本上倒也遇到 q;览器插件的可能性次之,W者也遇到q?721插g造成的问题;理论上防火墙或者代理服务器在cookie处理上也有可能会出现问题?br>出现q一问题的大部分原因都是E序的错误,最常见的就是在一个应用程序中去访问另外一个应用程序。我们在下一节讨个问题?br>
七、跨应用E序的session׃n

? 常有q样的情况,一个大目被分割成若干项目开发,Z能够互不q扰Q要求每个小目作ؓ一个单独的web应用E序开发,可是C最后突然发现某几个? 目之间需要共享一些信息,或者想使用session来实现SSO(single sign on)Q在session中保存login的用户信息,最? 然的要求是应用程序间能够讉K彼此的session?br>
然而按照Servlet规范Qsession的作用范围应该仅仅限于当前应用程? 下,不同的应用程序之间是不能够互相访问对方的session的。各个应用服务器从实际效果上都遵守了q一规范Q但是实现的l节却可能各有不同,因此解决 跨应用程序session׃n的方法也各不相同?br>
首先来看一下Tomcat是如何实现web应用E序之间session的隔ȝQ从  Tomcat讄的cookie路径来看Q它对不同的应用E序讄的cookie路径是不同的Q这样不同的应用E序所用的session id是不? 的,因此即在同一个浏览器H口里访问不同的应用E序Q发送给服务器的session id也可以是不同的?br>

  

Ҏq个Ҏ,我们可以推测Tomcat中session的内存结构大致如下?br>



W? 者以前用q的iPlanet也采用的是同L方式Q估计SunONE与iPlanet之间不会有太大的差别。对于这U方式的服务器,解决的思\很简单,? 际实行v来也不难。要么让所有的应用E序׃n一个session idQ要么让应用E序能够获得其他应用E序的session id?br>
iPlanet中有一U很单的Ҏ来实现共享一个session idQ那是把各个应用程序的cookie路径都设?Q实际上应该?NASAppQ对于应用程序来讲它的作用相当于根)?br><session-info>
<path>/NASApp</path>
</session-info>

需 要注意的是,操作׃n的session应该遵@一些编E约定,比如在session attribute名字的前面加上应用程序的前缀Q?  setAttribute("name", "neo")变成setAttribute("app1.name", "neo")Q以防止命名I间? H,D互相覆盖?br>

在Tomcat中则没有q么方便的选择。在Tomcat版本3上,我们q可以有一些手D|׃n session。对于版?以上的TomcatQ目前笔者尚未发现简单的办法。只能借助于第三方的力量,比如使用文g、数据库、JMS或者客L cookieQURL参数或者隐藏字D늭手段?br>
我们再看一下Weblogic Server是如何处理session的?br>

  

? 截屏画面上可以看到Weblogic ServerҎ有的应用E序讄的cookie的\径都?Q这是不是意味着在Weblogic Server? 默认的就可以׃nsession了呢Q然而一个小实验卛_证明即不同的应用程序用的是同一个sessionQ各个应用程序仍然只能访问自己所讄的那 些属性。这说明Weblogic Server中的session的内存结构可能如?br>



对于q样一U结构,?  session机制本n上来解决session׃n的问题应该是不可能的了。除了借助于第三方的力量,比如使用文g、数据库、JMS或者客L  cookieQURL参数或者隐藏字D늭手段Q还有一U较为方便的做法Q就是把一个应用程序的session攑ֈServletContext中,q样 另外一个应用程序就可以从ServletContext中取得前一个应用程序的引用。示例代码如下,

应用E序A
context.setAttribute("appA", session); 

应用E序B
contextA = context.getContext("/appA");
HttpSession sessionA = (HttpSession)contextA.getAttribute("appA"); 

值得注意的是q种用法不可ULQ因为根据ServletContext的JavaDocQ应用服务器可以处于安全的原因对于context.getContext("/appA");q回I|以上做法在Weblogic Server 8.1中通过?br>
? 么Weblogic ServerZ么要把所有的应用E序的cookie路径都设?呢?原来是ؓ了SSOQ凡是共享这个session的应用程序都? 以共享认证的信息。一个简单的实验可以证明这一点,修改首先d的那个应用程序的描述Wweblogic.xmlQ把cookie路径修改? /appA 讉K另外一个应用程序会重新要求dQ即使是反过来,先访问cookie路径?的应用程序,再访问修改过路径的这个,虽然不再提示dQ但 是登录的用户信息也会丢失。注意做q个实验时认证方式应该用FORMQ因为浏览器和web服务器对basic认证方式有其他的处理方式Q第二次h的认 证不是通过 session来实现的。具体请参看[7] secion 14.8 AuthorizationQ你可以修改所附的CZE序来做q些试验?br>
八、ȝ
session机制本nq不复杂Q然而其实现和配|上的灵zL却使得具体情况复杂多变。这也要求我们不能把仅仅某一ơ的l验或者某一个浏览器Q服务器的经验当作普遍适用的经验,而是始终需要具体情况具体分析?br>? 要:虽然session机制在web应用E序中被采用已经很长旉了,但是仍然有很多h不清楚session机制的本质,以至不能正确的应用这一技术。本 文将详细讨论session的工作机制ƈ且对在Java web application中应用session机制时常见的问题作出解答?/span>

]]>
J2EE 中的安全http://www.tkk7.com/cmd/articles/30939.html静夜?/dc:creator>静夜?/author>Thu, 16 Feb 2006 03:03:00 GMThttp://www.tkk7.com/cmd/articles/30939.htmlhttp://www.tkk7.com/cmd/comments/30939.htmlhttp://www.tkk7.com/cmd/articles/30939.html#Feedback1http://www.tkk7.com/cmd/comments/commentRss/30939.htmlhttp://www.tkk7.com/cmd/services/trackbacks/30939.htmlJ2EE 中的安全W一部分
现在来多的企业应用构建在j2eeq_?q得益于j2eeZ业应用的开发提供了良好的框架和服务的支?j2eeZ业应用提供了多方面的服务 (Security、Transaction、Naming{?.本文介lj2ee提供的安全服?作者首先介lj2ee中的安全概念和j2ee的安? 体系架构.然后l合具体的实例向读者展C如何在自己的程序中应用j2ee提供的安全特性?br>

一.?/span>

? 在越来越多的企业应用构徏在j2eeq_?q得益于j2eeZ业应用的开发提供了良好的框架和服务的支?j2eeZ业应用提供了多方面的服务 (Security、Transaction、Naming{?.本文介lj2ee提供的安全服?作者首先介lj2ee中的安全概念和j2ee的安? 体系架构.然后l合具体的实例向读者展C如何在自己的程序中应用j2ee提供的安全特性。本文所介绍的内ҎZj2ee1.3版本的?/font>

二.j2ee中的安全概念

MQPrincipalQ:? 体(PrincipalQ是被在企业安全服务验证了的实体。主体(PrincipalQ用M名作为它的标识,通过与主体相关的验证数据q行验证。通常? 况下M名就是用L登陆名,验证数据是登陆的密码。J2EE规范中ƈ没有限定J2EE 产品提供商用怎样的认证方法,因此M名和验证数据的内容和格式依不同的认证协议而不同?

安全{略域(Security Policy DomainQ:? U安全域Qsecurity domainQ或 realmQ它是一个逻辑范围或区域,在这一范围或区域中安全服务的管理员定义和实施通用的安全策略。它是从安全{略的角度划分的区域。比如可以将企业? 用系l划分ؓ企业员工、供应商、合作伙伴等不同的安全域Q对q些安全区域采用不同的安全策略?

安全技术域QSecurity Technology DomainQ:它是从安全技术的角度划分的区域,在一个安全技术域中用同L安全机制来执行安全策略。一个安全技术域可以包括多个安全{略域?

安全属性(Security AttributesQ:? 个主体(PrincipalQ都有一pd与之相关的安全属性。安全属性可用来讉K被保护的资源Q检查用Lw䆾和完成其他一些安全相关的用途。J2EE? 品提供商或具体的验证服务的实现来军_怎样安全属性与一个主体联pv来。J2EE规范q没有限定什么样的安全属性将与主体相联系?

凭证QCredentialQ:凭证包含或引用ؓJ2EE pȝ验证一个主体的验证信息Q安全属性)。如果成功的通过了验证,M获得一个包括安全属性的凭证。如果被允许的话Q一个主体也可能获取另一个主体的凭证。在q种情况下两个主体在同一安全域中h相同的安全属性?/font>

三.j2ee的安全体pȝ?/span>

1Q? Z容器的安?/span>

在j2ee 的环境中Q组件的安全是由他们各自的容器来负责的,lg的开发h员几乎可以不用或者很在lg中添加有兛_全的代码。这U安全逻辑和业务逻辑相对独立的架 构,使得企业U应用系l有更好的灵zL和扩展性。J2ee规范要求j2ee 产品必须为应用程序开发者提供两UŞ式的Z容器的安全性-说明性的安全性和可编E的安全性?/font>

a. 说明性的安全?/b>

? 明性的安全性通过安全l构描述的方式来代表应用E序的安全需求,安全l构一般包括安全角Ԍ讉K控制和验证要求等。在j2eeq_中部|描q符充当了说? 的安全性的主要工具。部|描q符是组件开发者和应用E序部v者或应用E序l装者之间的交流工具。应用程序的开发者用它来表示应用中的安全需求,应用E序? |者或应用E序l装者将安全角色与部|环境中的用户和l映v来?/font>

在程序运行时容器从部|描q符中提取出相应的安全策略,然后容器Ҏ安全{略执行安全验证。说明的安全性不需要开发h员编写Q何安全相关的代码Q一切都是通过配置部v描述W来完成的?/font>

b. 可编E的安全?/b>

? ~程的安全性在说明性的安全性的基础上,使安全敏感的应用可以通过调用被容器提供的API来对安全作出x。这在说明性的安全性不以满企业的安全模? 的情冉|非常有用的。J2ee在EJB EjbConext interface和servlet HttpServletRequest interface中各提供两个ҎQ?/font>

 
isCallerInRole (EJBContext)
getCallerPrincipal (EJBContext)
isUserInRole (HttpServletRequest)
getUserPrincipal (HttpServletRequest)

q些Ҏ允许lgҎ调用者或q程用户的安全角色来作出商业判断。在文章的后面部分将有这些方法的详细介绍和例E,以便读者更好的理解可编E的安全性的用途?/font>

2QJ2ee的验证模?/span>

w䆾验证是用hlg调用者向pȝ证明其n份的q程。用户通过某种方式向系l提交验证信息(通常是用户名和密码或者是用户的数字证书)Q系l用用户提供的验证信息和pȝ的安全策略来验证用户的n份?/font>


图一 初始验证q程
图一 初始验证q程
图一 初始验证q程
图二 验证URL
图二 验证URL

图三 验证EJBҎ调用
图三 验证EJBҎ调用

用户的验?/b>

用户的验证根据其客户端类型不同分ZU:Web 客户端的验证和Application客户端的验证

a. Web 客户端的验证

Web 客户端通常通过http协议来请求web服务器端的资源,q些web资源通常包括html|页、jspQjava server pageQ文件、java servlet和其他一些二q制或多媒体文g。在企业环境中,企业的某些资源往往要求只允许某些h讉KQ有些资源甚x机密的或安全敏感的。因此对企业? 各种web资源q行讉K控制是十分必要的。ؓ了满企业中的不同安全别和客户化的需求,j2ee提供了三U基于web客户端的验证方式Q?/font>

HTTP基本验证QHTTP Basic AuthenticationQ?/b>
HTTP基本验证 是HTTP协议所支持的验证机制。这U验证机制用用L用户名和密码作ؓ验证信息。Web客户端从用户获取用户名和密码Q然后传递他们给web服务器, web服务器在指定的区域(realmQ中验证用户。但需要注意的是,q种验证Ҏ是不够安全的。因U验证方法ƈ不对用户密码q行加密Q而只是对密码 q行基本的base64的编码。而且目标web服务器对用户来说也是非验证过的。不能保证用戯问到的web服务器就是用户希望访问的。可以采用一些安? 措施来克服这个弱炏V例如在传输层上应用SSL或者在|络层上使用IPSEC或VPN技术?

Z表单的验证(Form-Based AuthenticationQ?/b>
Z表单的验? 使系l开发者可以自定义用户的登陆页面和报错面。这U验证方法与基本HTTP的验证方法的唯一区别在于它可以Ҏ用户的要求制定登陆和出错面。基? 表单的验证方法同样具有与基本HTTP验证cM的不安全的弱炏V用户在表单中填写用户名和密码,而后密码以明文Ş式在|\中传递,如果在网路的某一节点? 此验证请求截P在经q反~码很容易就可以获取用户的密码。因此在使用基本HTTP的验证方式和Z表单的验证方法时Q一定确定这两种方式的弱点对你的? 用是可接受的?

Z客户端证书的验证QClient-Certificate AuthenticationQ?/b>
Z客户端证书的验证方式要比上面两种方式更安全。它通过HTTPSQHTTP over SSLQ来保证验证的安全性。安全套接层QSecure Sockets LayerQؓ验证q程提供了数据加密,服务器端认证Q信息真实性等斚w的安全保证。在此验证方式中Q客L必须提供一个公钥证书,你可以把q个公钥证书 看作是你的数字护照。公钥证书也U数字证书,它是被称作证书授权机构(CAQ-一个被信Q的组l颁发的。这个数字证书必ȝ合X509公钥体系l构 QPKIQ的标准。如果你指定了这U验证方式,Web服务器将使用客户端提供的数字证书来验证用Lw䆾?

b. 应用E序客户端的验证QApplication Client User AuthenticationQ?/b>
java客户端程序是执行在用h地java虚拟Z的javaE序Q它拥有mainҎQ通常q户可通过java.exe或javaw.exe直接? 动执行。J2ee应用E序客户端与java客户端程序相|也拥有mainҎQ但他们在运行时存在一定的差别。J2ee应用E序客户端和其他j2eel? 件一栯行在自己的容器中。用户通过容器来执行J2ee应用E序客户端。这样J2ee应用E序客户端容器就有机会在J2ee应用E序客户端被执行之前完成 用户w䆾的验证。J2ee提供了一U可自定义的方式来获取用L验证信息。可以选择使用容器提供的缺省的方式来获取j2ee应用客户端程序的用户的验证信 息,也可以选择自定义的方式来获取用L验证信息。当选择自定义方式时Q应用程序开发者必L供一个实C javax.security.auth.callback.CallbackHandler interfce的类,q且在j2ee部v描述文gapplication-client.xml中的元素callback-handler中加入这个类 的类名。这P当系l需要验证用戯n份时Q客LE序的容器将部v描述文g中的CallbackHandler实现cȝcd传递给pȝ的登陆模块(验证? 块)Q登陆模块再实例化这个实现类。这个类的实例负责收集用户验证信息,q将攉到的用户验证信息传递给登陆模块Q登陆模块用q些验证信息来验证用戗这 个实现类可以是具有用L面的Q或是通过要求用户输入来收集用户验证信息,也可以是通过命o行来获取用户验证信息Q还可能是通过d本地或在U的用户证书 库来获取用户的电子证书。选取哪种方式取决于验证信息的存储方式?

有些j2ee产品厂商把容器的验证服务和本地系l的验证服务或其他应用系l品的验证服务集成hQ从而在一定的应用pȝ的范围内实现单点登陆的能力?/font>

单点登陆 QSingle Sign-OnQ?/b>
单点M用户的视角是指用户在特定的逻辑安全区域中,只需q行一ơ登陆即可在讉K在此逻辑安全区域中不同应用系l中的被授权的资源,只有越了安全区域边 ~时才要求再ơ登陆。这U能力对多种IT应用pȝ共存的企业显得尤为有价倹{随着企业信息化徏讄度的不断提高Q企业中的应用系l也来多。在传统的应 用系l中Q各pȝ各自l护自己的安全策略,q些安全{略典型的包括组l结构定义,安全角色定义Q用戯n份验证,资源讉K控制{。由于各pȝ互相独立Q一? 用户在用每一应用pȝ之前Q都必须按照相应的系ln份进行系l登陆。这对于用户来说必须C每一个系l的用户名和密码Q给用户带来了不的ȝ。针对于 q种情况Q单点登陆的概念随之产生Qƈ不断的应用到企业的应用系l的集成当中。J2ee1.3也在规范中徏议j2ee产品应ؓ应用pȝ提供单点登陆的能 力。但j2ee1.3规范q没有规定j2ee产品应遵循何U标准,因此不同的厂商的产品在单点登陆上的实现和应用各不相同。有的j2ee产品实现了在本 品环境范围内的单点登陆,有的实现了特定系l环境之间的单点登陆Q如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中单点登陆是通过传递凭证(CredentialQ来实现?当用戯行系l登陆时Q客L容器Q包? WEB客户端和应用E序客户端)Ҏ用户的凭证(CredentialQؓ用户建立一个安全上下文Qsecurity ContextQ,安全上下文包含用于验证用L安全信息Q系l用q个安全上下文和安全{略来判断用h否有讉Kpȝ资源的权限。遗憄时j2ee规范q? 没有规定安全上下文的格式Q因此不能在不同厂商的j2ee产品之间传递安全上下文。到目前为止q很有在不同的j2ee产品间互相共享安全上下文Q因此在 不同j2ee产品间实现单点登陆只能通过W三方品(如LDAP server{)集成的方式?

惰性验证(Lazy AuthenticationQ?/b>
w䆾验是有代L。例如,一ơ验证过E也许包括多ơ通过|络信息交换。因此惰性验证就非常有用了。惰性验证当用戯问受保护的资源时才执行验证过E,而不是在用户W一ơ发赯求时执行验证过E?

3. J2ee的授权模?/span>

代码授权QCode AuthorizationQ?/b>
j2ee产品通过java 2 安全模型来限制特定J2SE的类和方法的执行Q以保护和确保操作系l的安全。详l描q请参阅《J2SE规范文》?

调用者授权(Caller AuthorizationQ?/b>
安全角色Q安全角色是h相同安全属性的逻辑l。它由应用程序的装配者(Application AssemblerQ或应用E序的部|者(Application DeployerQ分配的?

安全角色引用Q?/b>安全角色引用是应用程序提供者(Application ProviderQ用来引用安全角色的标识。应用程序提供者(Application ProviderQ可以用安全角色引用来ؓ安全角色分配资源讉K的权限。也在安全相关的E序代码中引用安全角艌Ӏ?

用户和组Q?/b>用户和组是在实际pȝ环境下的用户和用L集合。它们对应者现实当中的人和体?

讉K控制Q?/b>讉K控制可以保安全角色只能讉K已授予它安全权限的授权对象。授权对象包括EJB的远E方法、web资源Qhtml|页Qjsp/servlet和多媒体或二q制文gQ等。在j2ee中访问控制在应用E序描述文g中与安全角色兌h?

映射Q?/b>通过映射应用E序的系l管理员实际系l环境中的用户和角色与安全角色联pv来,从而是实际的用h有对企业资源讉K的适当授权?

被传播的调用者n份标识(Propagated Caller IdentitiesQ?/b>
在j2ee 1.3中可以选择用传播调用者标识作为weblg和ejblg调用者的标识来进行验证。在q种方式下,整个ejblg的调用链中interface EJBContext的方法getCallerPrincipalq回相同的主体名Qprincipal nameQ。如果调用链中的W一个ejb是被jsp/servlet调用的,interface EJBContext的方法getCallerPrincipalq回的主体名Qprincipal nameQ应与interface HttpServletRequest的方法getUserPrincipal的返回值相同。要注意的是在调用链中传递的是用L标识Q而不是凭? QcredentialsQ,q一炚w帔R要,因ؓ在调用链的每个节点上用户可能使用不同的安全属性?

Run As Identities
J2ee 1.3中提供了允许lg开发者和部vq来指定lg以什么n份运行的Ҏ。符合j2ee1.3规范的品会提供组件设|成Run As Identities方式的方法。如果Run As Identities方式被选中Q在q行中被讄为Run As Identities的组件的调用者不再是调用链中W一个节点的调用者了Q而是在部|时被指定的调用者。而调用链中随后节点的调用者也变ؓ与被讄? Run As Identities的组件的调用者相同?


囑֛ 用户标识传?/b>
囑֛ 用户标识传? src=

q一部分介绍了j2ee的安全概念,意在使读者能够对j2ee在安全方面有一定的了解Q后面还会有应用q些概念的具体例子?/font>



J2EE 中的安全W二部分--j2ee安全应用


在本pd文章的第一部分作者介l了j2ee的安全概c验证模型和授权模型Q这部分更偏重于理论的介l。本文的W二部分作者将通过具体的例子向读者展C如何在开发中应用j2ee提供的安全服务。本部分的重点在于应用与实践?/font>

? 释:本文的目的是介绍如何应用j2ee提供的安全服务,而ƈ不针对特定的产品。因此作者选择sun? j2ee参考实玎ͼj2sdkeeQ作为演C^台。因为j2sdkee是完全遵照j2ee规范开发的Q虽然它不像IBM WebSphere 、BEA WebLogic{j2ee产品那么产品化和商业化,但它l对是学习j2ee的理惛_^台。你可以通过 http://java.sun.com/j2ee/获取sun 的j2ee参考实现的最新版本。本文选择的是Sun的j2sdkee1.3.1?

本文包括以下内容:

  1. 一个采用HTTP基本的验证的例子
  2. 一个采用基于表单的验证的例?/font>
  3. 一个ejbҎ授权的例?/font>
  4. 一个可~程安全性和传播调用者n份标识的例子

采用HTTP基本的验证的例子

http基本验证是Web客户端验证的一U,它和pȝ的授权机制一h制受保护资源的访问?/font>

步骤Q?/font>

1Q?创徏一个j2ee应用

在应用程序部|工LFile菜单选中New子菜单中的Application菜单(见图1Q。会弹出新徏应用E序对话框。填写应用程序文件名和应用程序显C名Q见?Q?/font>


?
?

?
?

2Q?创徏一个weblg

? 应用E序部v工具的File菜单选中New子菜单中的Web Compent菜单?会弹出新建weblg向导对话?见图3)。选择Create New WAR File in Application,在下拉框中选择步骤1创徏的应用testQ在WAR Display Name框中填写WebAppTest.点击Content栏中 的Eidt按钮选择此Weblg包含的文件。在q个例子中只有一个webtest.jsp文g。然后点击NextQ进入下一个对话框Q见?Q。由于我? 的weblg是一个jsp文gQ因此在lgcd中选择JSP。然后一直按Next直到l束。此时我们已l创Z一个只包含一个jsp文g的weblg。接 下来是配|安全属性的步骤?/font>


?
?

?
?

3Q?配置安全属?/span>

3Q?创徏安全角色

在部|工L左导航栏中点中步?创徏的weblgWebAppTestQ在双的属性页中选择Roles属性页Q见?Q。点击Add按钮Q在Name栏中填写安全角色名user,Description栏填写描qC息。安全角色代表具有相同安全权限用L集合?/font>


?
?

3.2 配置安全{略

创徏了安全角色后Q应该对安全角色配置相应的安全策略。点击Security属性页Q见?Q?/font>


?
?

? 先选择你想用的验证方式Q从User Authentication Method下拉框中选择Basic。这意味着你将通过基本的HTTP验证方式验证用户。下面我们进行web资源的授权。点击Security Constraint栏中的Add按钮d一条安全约束,U束名可以自定。接下来对创建好的约束添加Web资源。首先在Web Resource Collections中添加资源集合,然后选取资源集合包含的资源。此例中WRCollection资源集合中包含webtest.jsp文gQ也可以 包含各种属于q个weblg的文件。接下来选择哪些web操作要收到约束,j2sdkee1.3.1中只包含两种操作QGET和POSTQ,不同的品支 持的操作有所不同Q在开发是应结合具体品提供的操作来选取。现在应该指定安全角色了Q点击Authorized Roles栏中的Edit按钮Q会弹出安全角色列表对话框,从中选取已定义的安全角色。本例中选择user。至此安全策略已l配|完毕,下面的步骤是实 际环境中的用户和用户l映与安全角色q行映射?/font>

4Q?映射

? 左导航栏中选中应用E序test在右辚w择Security属性页Q见?Q,在Role Name Reference栏中选中userQ点L下方的Add按钮Q会弹出用户和用L列表对话框,从中选择要映成安全角色user的用hl。此例中我们 用户j2ee映射为安全角色user。这L户J2ee具有ؓ安全角色user分配的访问授权?/font>


?
?

5Q?部v应用

选中Web Context属性页Q在Context Root文本框中填写test,右键点击左导航栏的应用testQ在弹出菜单中选择deploy完成应用E序的发布。至此我们完成了W一个例子的全部步骤?/font>

部v描述文g

q个例子使用了说明性的安全服务Q因此我们不需要编写Q何的安全相关的代码,而是完全通过配置lg的部|描q文件来实现的。下面是q个weblg的部|描q文件?/font>


<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE web-app PUBLIC
'-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN'
'http://java.sun.com/dtd/web-app_2_3.dtd'>
<web-app>
<display-name>WebAppTest</display-name> //Weblg名称
<servlet>
<servlet-name>webtest</servlet-name>
<display-name>webtest</display-name>
<jsp-file>/webtest.jsp</jsp-file> //lg中包含的jsp文g
</servlet>
<session-config>
<session-timeout>30</session-timeout>
</session-config>
<security-constraint> //安全U束部分
<web-resource-collection> //受约束的web资源?br> <web-resource-name>WRCollection</web-resource-name> //资源集名
<url-pattern>/webtest.jsp</url-pattern> //资源的url表达?br> <http-method>GET</http-method> //受约束的资源操作Ҏ
<http-method>POST</http-method>
</web-resource-collection>
<auth-constraint> //对安全角色授?br> <role-name>user</role-name> //安全角色?br> </auth-constraint>
<user-data-constraint>
<transport-guarantee>NONE</transport-guarantee>
</user-data-constraint>
</security-constraint>
<login-config> //验证方式讄
<auth-method>BASIC</auth-method> //使用基本的HTTP验证方式
<realm-name></realm-name>
</login-config>
<security-role> //定义安全角色
<description>this is a user</description>
<role-name>user</role-name>
</security-role>
</web-app>

从部|描q文件可以知道这? 一个名为WebAppTest的weblgQ包含一个名为webtest.jsp的文Ӟ只有被赋予user安全角色的用h用户l才有权? webtest.jspq行GET或POST操作。这里ƈ没有包含安全角色对实际用L映射Qj2ee部v描述文g的DTD中ƈ没有定义用于安全角色和实 际用L映射的元素,因ؓ实际环境中有多种不同的用Ll(如关pL据库Q系l文件Ş式和LDAPpȝ{)。因此安全角色和实际用户的映方式是? j2ee产品厂商制定的?/font>

试q行l果

? 开ieQ在D栏输入http://localhost:8000/test/webtest.jsp回RQ会弹出验证对话框,要求用户提供用户名和密码 Q见?Q,输入用户名j2ee和密码j2ee。通过用户验证后执行jsp文gQwebtest.jsp打印?hello!"Q见?Q?/font>


?
?

?
?

注释Q在W一个例子中已经详细的描qC各个步骤Q在接下来的例子中会有一些与W一个例子相同的操作Q因此对下面的例子只描述与第一个例子不同的步骤?/font>

Z表单的验证的例子

Z表单的验证与基本的HTTP验证的唯一区别是基本的HTTP验证用浏览器提供的验证信息对话框攉用户验证信息Q而基于表单的验证允许自定义登陆页面来攉用户验证信息。本例子与第一个例子的步骤基本相同Q不同的地方在于此例子要提供登陆面和出错页面?/font>

登陆面login.html


<form method="POST" action="j_security_check">
<input type=text name="j_username">
<input type=password name="j_password">
<input type=submit name="login" value="login">
</form>

此文件有几个地方值得注意Q?/font>

  1. Action的值必Mؓ"j_security_check"
  2. 获取用户名的域名必须?j_username"
  3. 获取用户密码的域必须? j_password"

出错面 error.html


<html>
用户名或密码不正!
</html>

出错面只是单的昄出错信息?/font>

配置Z表单的验?/b>

首先login.html和error.html加入到WebAppTestlg中?
然后见图10选择Security属性页Q在User Authentication Method下拉框中选择Form Based选项。点击Settings…弹出用户验证设|对话框Q在Login Page下拉框选login.html,在Error Page下拉框选error.html?


?0
?0

重新部v应用Q再一ơ访问http://localhost:8000/test/webtest.jsp 会出现login面Q见?1Q,如果用户名或密码错误Qerror.html显C给用户?/font>


?1
?1

部v描述文g


<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE web-app PUBLIC
'-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN'
'http://java.sun.com/dtd/web-app_2_3.dtd'>
<web-app>
<display-name>WebAppTest</display-name>
.
.
.
<transport-guarantee>NONE</transport-guarantee>
</user-data-constraint>
</security-constraint>
<login-config>
<auth-method>FORM</auth-method> //使用Z表单的验证方?br> <realm-name>Default</realm-name> //使用~省的安全域
<form-login-config>
<form-login-page>/login.html</form-login-page> //定义登陆面
<form-error-page>/error.html</form-error-page> //定义出错面
</form-login-config>
</login-config>
<security-role>
<description>this is a user</description>
<role-name>user</role-name>
</security-role>
</web-app>



回页?/b>


ejbҎ授权的例?/span>

从j2ee1.3 开始便提供了对ejb的方法进行授权的安全服务Q这U授权服务由ejb容器实现。当调用者调用ejb的方法时Qejb容器用调用者的w䆾来查找授予此调用 者的讉K权限条目Q如果调用者调用的Ҏ属于授权条目Q那么ejb容器调用Ҏ。否则,ejb容器拒绝调用此方法,q向调用者返回拒l访问异常。可以对q? E方法和home接口Ҏq行授权。本例中我们对一个远E方法和一个home接口Ҏq行授权?/font>


首先创徏一个session bean CountEjb
q程接口 Count.java
import javax.ejb.*;
import java.rmi.RemoteException;

public interface Count extends EJBObject {

/**
* q程Ҏcount
*/
public int count() throws RemoteException;
}

Home接口 CountHome.java
import javax.ejb.*;
import java.rmi.RemoteException;

/**
* This is the home interface for CountBean.
* One create() method is in this Home Interface, which
* corresponds to the ejbCreate() method in the CountBean file.
*/
public interface CountHome extends EJBHome {

/*
* This method creates the EJB Object.
*
* @param val Value to initialize counter to
*
* @return The newly created EJB Object.
*/
Count create(int val) throws RemoteException, CreateException;
}

实现c?CountBean.java
import javax.ejb.*;
import java.security.Principal;

/**

public class CountBean implements SessionBean {

// The current counter is our conversational state.
public int val;
private SessionContext sessionCtx;

//
// q程商业Ҏ实现
public int count() {
System.out.println("count()");

return ++val;
}

//
// home接口CreateҎ的实?br> //

public void ejbCreate(int val) throws CreateException {
this.val = val;
System.out.println("ejbCreate()");
}

public void ejbRemove() {
System.out.println("ejbRemove()");
}

public void ejbActivate() {
System.out.println("ejbActivate()");
}

public void ejbPassivate() {
System.out.println("ejbPassivate()");
}

public void setSessionContext(SessionContext ctx) {
sessionCtx=ctx;
}
}
客户端程?CountClient.java
import javax.ejb.*;
import javax.naming.*;
import java.util.Properties;

/**
* This class is a simple example of client code.
*/
public class CountClient {

public static void main(String[] args) {

try {

InitialContext ctx = new InitialContext();

CountHome home = (CountHome)
javax.rmi.PortableRemoteObject.narrow(
ctx.lookup("java:comp/env/CountHome"), CountHome.class);


int countVal = 0;
Count count=null;

/*
创徏q执行远E方?
*/
System.out.println("Instantiating beans...");


count = home.create(countVal);

countVal = count.count();

System.out.println(countVal);

/*
remove Count对象
*/
count.remove();

} catch (Exception e) {
System.out.println(e.getMessage());
e.printStackTrace();
}
}
}

q个ejb包括一个远E商业方法count()Q我们将此Ҏ授权l某个安全角艌Ӏ此外还home接口的Create()Ҏ授权l安全角艌Ӏ?/font>

步骤1

~译以上源程序ƈ用j2sdkee1.3.1的组装发布工Pdeploytool.batQ进行组装(如图12Q?/font>


?2
?2

步骤2Q配|安全角?/b>

在对Ҏq行授权前,必须创徏被授权的安全角艌Ӏ创建安全角色的步骤前边已经介绍q了Q此处不再重复。本例中我们创徏名ؓadmin的安全角艌Ӏ?/font>

步骤3Q方法授?/b>

Ҏ授权的过E是定那些安全角色可以讉K特定Ҏ的过E。方法授权一般是应用E序l装或应用程序部|者的责Q。他们根据企业特定的需求创Z同的安全角色Qƈ授予q些安全角色特定的访问权限?/font>

? 鼠标选中CountBeanQ在右端的窗口选择Security属性页Q如?3Q,在Security Identity选项中选择Use Caller ID,q意味着ejb容器用Ҏ调用者的w䆾来验证方法调用权限? Run As Specified Role选项在"传播调用者n份标识的例子"q行介绍。由于在前面创徏了admin安全角色Q因此你可以看到Method Permissions栏中出现admin列。首先对q程Ҏcount()q行授权。选择Remote选项Qƈ在count()Ҏ? Availability列中选择Sel RolesQ然后选中count()Ҏ的admin列。到此ؓ止我们已对远E方法count()q行了授权。接下来对home接口的create()? 法进行授权。在Show栏中选择Remote HomeQ剩下的步骤与count()Ҏ授权相同。我们已l将count()Ҏ和create()Ҏ授权l了admin安全角色。但安全角色q是一 个逻辑的集合,q不代表具体的用h用户l,因此l下来我们要做的是安全角色与实际的用hv来?/font>

步骤4Q角色映?/b>

首先我们需要在我们的j2ee环境中创Z个用P用户起名为TonyQ密码ؓ1?/font>


?3
?3

q? 里我们用用户名和密码的方式q行w䆾验证。我们在default Realm中创建此用户。可以用命令行方式Q?realmtool -add Tony 1 eng"。详l的使用Ҏ参见j2sdk1.3.1文档的工具部分。接下来映射安全角色到用戗选中ejb应用CountEjbQ在双H口中选择 Security属性页Q如?4Q,点击Edit Roles按钮Q选择安全角色admin。再点击Add按钮选择Tony用户。这样已l将安全角色admin和用户Tony映射h了?/font>

步骤5Q部|应?/b>

部v应用到本地机Q右键点击ejb应用CountEjbQ选择弹出菜单的deploy,按要求配|各V?/font>

步骤6Q创建客L

创徏客户端将客户端程序的ȝ和其他辅助类打包。创建端客的过E比较简单,q里׃作描qC?/font>

步骤7Q运行程?/b>

现在可以q行客户端程序来验证Ҏ的授权了。通过命orunclient.bat -client客户端jar包文件名 -name ȝ名来执行客户端程序。客LE序的容器将昄一个对话框来提C用戯入用户名和密码(如图15Q,填写用户名和密码Q按OK?/font>


?5
?5

若用户名或密码与授权的方法不W,则会抛出没有权限异常Q如?6Q?/font>


?6
?6


回页?/b>


Countejb的部|描q文件ejb.xml


<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE ejb-jar PUBLIC
'-//Sun Microsystems, Inc.//DTD Enterprise JavaBeans 2.0//EN'
'http://java.sun.com/dtd/ejb-jar_2_0.dtd'>

<ejb-jar>
<display-name>count</display-name>
<enterprise-beans>
<session> // CountBean属于session bean
<display-name>CountBean</display-name> //ejblg的显C名
<ejb-name>CountBean</ejb-name> //ejblg?br>
<home>CountHome</home> //Home接口
<remote>Count</remote> //q程接口
<ejb-class>CountBean</ejb-class> //实现c?br> <session-type>Stateful</session-type> // CountBean属于Stateful Bean
<transaction-type>Container</transaction-type> //CountBean事务cd为容器管理的
<security-identity> //安全标识
<description></description>
<use-caller-identity></use-caller-identity> //CountBean使用调用者的w䆾标识
</security-identity>
</session>
</enterprise-beans>
<assembly-descriptor>
<security-role>
<role-name>admin</role-name> //定义安全角色admin
</security-role>
<method-permission> //方法count和remove授权l安全角色admin
<role-name>admin</role-name>

<method> //Ҏ定义
<ejb-name>CountBean</ejb-name>
<method-intf>Remote</method-intf>
<method-name>count</method-name>
<method-params />
</method>
<method>
<ejb-name>CountBean</ejb-name>
<method-intf>Home</method-intf>
<method-name>remove</method-name>
<method-params>
<method-param>java.lang.Object</method-param>
</method-params>
</method>
</method-permission>
<method-permission>
<unchecked /> //不检查以下方法的授权
<method>
<ejb-name>CountBean</ejb-name>
<method-intf>Remote</method-intf>
<method-name>getHandle</method-name>
<method-params />
</method>
.
.
.
.
<method>
<ejb-name>CountBean</ejb-name>
<method-intf>Remote</method-intf>
<method-name>getEJBHome</method-name>
<method-params />
</method>
</method-permission>
<container-transaction> // CountBean的事务属?br> <method>
<ejb-name>CountBean</ejb-name>
<method-intf>Remote</method-intf>
<method-name>count</method-name>
<method-params />
</method>
<trans-attribute>Required</trans-attribute>
</container-transaction>
</assembly-descriptor>
</ejb-jar>



回页?/b>


可编E安全性和传播调用者n份标识的例子

此例E包括两个部分,分别演示可编E安全性和调用者n份传播?/font>

可编E安全性例E?/span>

? ~程安全性可应用在web层和EJB层,分别是通过javax.servlet.http.HttpServletRequest接口? isUserInRole ()、getUserPrincipal ()Ҏ和javax.ejb.EJBContext接口的isCallerInRole ()、getCallerPrincipal ()Ҏ来实现的? public boolean isUserInRole(java.lang.String role)Ҏ 此方法用来判断调用者是否属于某一特定的安全角Ԍ如果属于q回trueQ否则返回false? 参数role指定某一安全角色。通过此方法开发者可以在E序代码中加入自q安全逻辑判断Q从而增ZJ2EE在安全方面的灉|性?/font>

public java.security.Principal getUserPrincipal()Ҏ 调用此方法可以得C个java.security.Principal对象Q此对象包含了调用者的用户名,通过Principal.getName() Ҏ可以得到用户名。通过调用getUserPrincipal()Ҏ开发者可以得到调用者的用户名,然后对调用者的用户名进行特定的逻辑判断?/font>

public java.security.Principal getCallerPrincipal()Ҏ 和public boolean isCallerInRole(java.lang.String roleName)Ҏ的作用和Ҏ同上?/font>

下面我们通过例程来演C些方法的用法
E序清单Q?
webtest.jsp


<%@page contentType="text/html"%>
<html>
<head><title>JSP Page</title></head>
<body>

<%-- <jsp:useBean id="beanInstanceName" scope="session" class="package.class" /> --%>
<%-- <jsp:getProperty name="beanInstanceName" property="propertyName" /> --%>
Hello!
the caller is <%=request.getUserPrincipal().getName()%><br/> <%--得到调用者的用户?-Q?gt;
<% if (request.isUserInRole("admin")){%> <%--判断调用者是否属?admin"安全角色--%>
the caller is admin Role;
<%} %>
</body>
</html>

web.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE web-app PUBLIC
'-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN'
'http://java.sun.com/dtd/web-app_2_3.dtd'>
<web-app>
<display-name>WebApp</display-name>
<servlet>
<servlet-name>webtest</servlet-name>
<display-name>webtest</display-name>
<jsp-file>/webtest.jsp</jsp-file>
<security-role-ref>
<role-name>adminref</role-name>
<role-link>admin</role-link>
</security-role-ref>
<security-role-ref>
<role-name>guestref</role-name>
<role-link>guest</role-link>
</security-role-ref>
</servlet>
<session-config>
<session-timeout>30</session-timeout>
</session-config>
<welcome-file-list>
<welcome-file>webtest.jsp</welcome-file>
</welcome-file-list>
<security-constraint>
<web-resource-collection>
<web-resource-name>WRCollection</web-resource-name>
<url-pattern>/webtest.jsp</url-pattern>
<http-method>GET</http-method>
<http-method>POST</http-method>
</web-resource-collection>
<auth-constraint>
<role-name>admin</role-name>
<role-name>guest</role-name>
</auth-constraint>
<user-data-constraint>
<transport-guarantee>NONE</transport-guarantee>
</user-data-constraint>
</security-constraint>
<login-config>
<auth-method>BASIC</auth-method>
<realm-name></realm-name>
</login-config>
<security-role>
<role-name>admin</role-name>
</security-role>
<security-role>
<role-name>guest</role-name>
</security-role>
</web-app>

从web.xml文g的内容可以看出,只有安全角色?admin"?guest"的用h有权对webtest.jsp文gq行POST和GET操作?/font>

q行l果Q?/b>

? Z个web应用Q将webtest.jsp作ؓ一个weblgQƈ按照web.xml的内定w|web应用Q在q行环境中将用户j2ee分配? admin安全角色Q将用户Tony分配为guest角色。发布web应用到本地j2ee Server上。用ie讉Kwebtest.jsp 如图17用用户j2ee的n份进行验证,用户j2ee属于admin安全角色。显C结果见?8


?7
?7

?8
?8

如果用用户Tonyq行验证Q结果见?9


?9
?9

ejb中应用可~程的安全性与在web中的Ҏ怼Q本文不再进行介l?/font>



回页?/b>


传播调用者n份标识例E?/span>

本例E将演示调用者n份标识如何在调用链中传递的Qƈ且介l如何应?Run As"来实现在调用链中更改调用者的w䆾。本例将用一个weblgQ一个jsp文gQ和两个ejblg来Ş成一个调用链?
E序清单Q?
webtest.jsp


<%@page contentType="text/html"%>
<%@page import="andy.*"%>
<%@page import="javax.naming.*"%>
<html>
<head><title>JSP Page</title></head>
<body>

<%-- <jsp:useBean id="beanInstanceName" scope="session" class="package.class" /> --%>
<%-- <jsp:getProperty name="beanInstanceName" property="propertyName" /> --%>
Hello!
the caller is <%=request.getUserPrincipal().getName()%> <br/>
<% if (request.isUserInRole("admin")){%>
the caller is admin Role;
<%} %>
<%
try {
Context ctx = new InitialContext();
andy.CountHome home =
(andy.CountHome)javax.rmi.PortableRemoteObject.narrow(
ctx.lookup("java:comp/env/CountHome"), andy.CountHome.class);
andy.Count count = home.create(1);
count.count();
}catch (Exception e)
{
e.printStackTrace();
}
%>
</body>
</html>

CountBean.java


package andy;
import javax.ejb.*;
import javax.naming.*;
public class CountBean implements SessionBean {
public int val;
private SessionContext EjbCxt = null;
public int count()
{
int temp = 0;
System.out.println("CountBean.count()");
//打印调用者名
System.out.println("the caller is "+EjbCxt.getCallerPrincipal().getName());
//判断调用者的安全角色
if(EjbCxt.isCallerInRole("adminref")) // adminref为安全角色admin的引用名
{
System.out.println("the caller is admin Role");
}
if(EjbCxt.isCallerInRole("guestref")) // guestref为安全角色guest的引用名
{
System.out.println("the caller is guest Role");
}
if(EjbCxt.isCallerInRole("userref")) // userref为安全角色user的引用名
{
System.out.println("the caller is user Role");
}
//调用另一个ejb的远E方?br> try {
Context ctx = new InitialContext();
CountHome1 home =
(CountHome1)javax.rmi.PortableRemoteObject.narrow(
ctx.lookup("java:comp/env/CountHome1"), CountHome1.class);
Count1 count = home.create(1);
temp = count.count();
}catch (Exception e)
{
e.printStackTrace();
}
return ++temp;
}
public void ejbCreate(int val) throws CreateException {
this.val = val;
}
public void ejbRemove() {
}
public void ejbActivate() {
}
public void ejbPassivate() {
}
public void setSessionContext(SessionContext ctx) {
EjbCxt = ctx; //获取EjbContext对象
}
}

CountBean1.java
package andy;
import javax.ejb.*;

public class CountBean1 implements SessionBean {
public int val;
private SessionContext EjbCxt = null;
public int count() {
System.out.println("CountBean1.count()");
System.out.println("the caller is "+EjbCxt.getCallerPrincipal().getName());
if(EjbCxt.isCallerInRole("adminref"))
{
System.out.println("the caller is admin Role");
}
if(EjbCxt.isCallerInRole("guestref"))
{
System.out.println("the caller is guest Role");
}
if(EjbCxt.isCallerInRole("userref"))
{
System.out.println("the caller is user Role");
}

return ++val;
}
public void ejbCreate(int val) throws CreateException {
this.val = val;
}
public void ejbRemove() {
}
public void ejbActivate() {
}
public void ejbPassivate() {
}
public void setSessionContext(SessionContext ctx) {
EjbCxt = ctx;
}
}

以上的三个文件分别是一? weblg和两个ejblg的源代码,q三个组件构成了一个调用链.webtest.jsp?首先通过HttpServletRequest.. getUserPrincipal ()Ҏ来得到调用webtest.jsp的用LPrincipal对象,在通过Principal.getName()Ҏ得到调用者的用户? 然后通过HttpServletRequest..isUserInRole()Ҏ来判断调用这是否属于特定的安全角?CountBean是一? stateful SessoinBean,它拥有一个count()q程Ҏ,在这个远E方法中写了用于得到调用者用户名和判断调用这安全角色的代?q包括调? CountBean1对象的代?用于展示调用者n份标识是如何在调用链中传递和调用者的安全角色如何被改变的.CountBean1也是一? stateful SessoinBean,它的代码内容与CountBean基本相同,只不q它不包含调用其他Bean的代?

? 在我们应该配|各lg的安全属性了.我们在组件webtest中创建安全角色admin,引用名ؓadminref,在组件CountBean? CountBean1中分别创建安全角色admin和user, 引用名分别ؓadminref和userref.webtestlg配置?HTTP Basic Authentication",l安全角色admin赋予讉Kwebtest.jsp的权?把CountBean讄为Run As Specified Role,选择user安全角色.在运行环境中用户j2ee赋予admin安全角色和user安全角色.然后发布应用到服务器.用用户j2ee讉K webtest.jsp.

执行l果:
客户端见?0


?0
?0

服务器端:
CountBean.count()
the caller is j2ee
the caller is admin Role
CountBean1.count()
the caller is j2ee
the caller is user Role

? q行l果?讉Kwebtest.jsp的用户ؓj2ee,其安全角色ؓadmin,讉KCountBean的用户名为j2ee,安全角色为admin. 可以看到用户w䆾标识从web容器传递到了ejb容器.再看lgCountBean1的输出结?׃CountBean被设|成了Run As Specified Role,因此在CountBean向下调用其他lg对象?用户安全角色已经被改为指定的安全角色,q里是user.所以我们会看到,调用lg CountBean1的用户名为j2ee,安全角色为user.j2ee的这U安全特性满了同一用户在不同应用中h不同安全角色的需?开发h员也? 以利用这U特性进行灵zȝ安全逻辑判断.





]]>
走出 JNDI q宫http://www.tkk7.com/cmd/articles/30925.html静夜?/dc:creator>静夜?/author>Thu, 16 Feb 2006 02:32:00 GMThttp://www.tkk7.com/cmd/articles/30925.htmlhttp://www.tkk7.com/cmd/comments/30925.htmlhttp://www.tkk7.com/cmd/articles/30925.html#Feedback0http://www.tkk7.com/cmd/comments/commentRss/30925.htmlhttp://www.tkk7.com/cmd/services/trackbacks/30925.html从单机编E{?EJB 技术和分布式计这些更复杂领域?Java 开发h员常怼陷入困境Q编写成功地游历 JDNI q宫的代码会很困难,多计机和配|也增加了出错的可能性。在本文中,EJB开发h?Daniel Would 解释了如何编写可以成功地扑ֈ? JNDI 名称I间中发布的 EJB lg的客户代码。他向您展示了处理更容易的各种~程选项Qƈ提供了一些可以在您自q应用E序中作为实用工L使用的代码?/font>

如果您在让客h应用E序看到 EJB lgq一斚w从来都没有Q何问题,q且认ؓ?Java q_的不同安装上、或者在完全不同的计机上运行您的客h? bean 一点也不复杂,那么本文可能不会吸引您。不q,如果您才刚刚开始,q且在试囄真实的配|做M事情时看见弹Z许多奇怪和意义不明的错误消息,那么pM厅R?/font>

EJB 错误Q不要慌Q?/span>

您已l在自己所钟爱?Java 书籍中读q了关于企业 Javabean 技术的那一章,也已l练习过了简单的 HelloWorld beanQƈ遵@所的部|过E发布了它。现在您得编写一个客hQ以侉K过q个客户机来调用q个C。因此您写出了类似清? 1 中的代码Q?/font>


清单 1. 一个调?bean 的非常简单的客户?/b>

InitialContext ic = new InitialContext();
Object or = ic.lookup("ejb/HelloWorldHome");
if (or != null) {
// Narrow the return object to the Home class type
HelloWorldHome home =
(HelloWorldHome)PortableRemoteObject.narrow(or,
HelloWorldHome.class);
// Create an EJB object instance using the home interface.
HelloWorld hw = home.create();
// Invoke the method
System.out.Println(hw.hello());
}

在命令行中运行这个客hQ用手头最方便的一?Java 安装 ―?卛_用服务器使用的那一个。所有事情都很完!带着成功的喜悦,您{UdW二台计机上运行您的客h。这回,您得C一个可怕的错误消息。首先,您可能得? java.lang.NoClassDefFoundError: javax/ejb/EJBObject Q然后是一大堆其他? NoClassDefFoundError sQ因为您忘记提交一个带有必需?stub ?tie ?JAR 文gQƈ且没有提供或者考虑到其他各U? EJB 相关的内宏V不q最l,您的客户行到了第一行有意思的代码Q? InitialContext ic = new InitialContext(); Q。在到达q一行时得到的异? ―?您几乎肯定会得到一个异?―?会Ҏ您所选择的特? 上下?provider而有所不同?



回页?/b>


解释q些术语

在我们l往下之前,定义几个术语会很有帮助。计世界用的都是一些奇怪的术语、时髦的语汇和首字母~写词,Java 技术也不例外(也许q应该是 JavaIsNoException Q)。如果您遇到了上面所说的问题Q那么这里面的术语可能会让您感到有些无所适从。所以让我们讨论在本文中会遇到的术语,搞明白它们的意思是一个好L?

名称I间、上下文、初始上下文和子上下?/b> q些术语都是有关位置?―?是从客户机的角度看时 EJB lg所在的概念性的位置。将一? 名称I间 惛_Z个城镇,城镇中的商店? EJB home接口Q我们将在稍后讨论它Q表C? 上下?/i>是城镇中的一个位|? 初始上下?/i> 是您开始时所在的位置 ―?像它是到城镇的道\。? 子上下文是街道名?
home接口Qhome interfaceQ和q程接口Qremote interfaceQ?/b> 企业 JavaBean lg有三个部分。首先是 bean 代码本n。然后是 home接口Q它定义了创建您自己?EJB bean 的方法。home接口是在名称I间中发布的。当您有了home接口后,可以调? Create() 以从应用服务器获得远E接口。获得了q程接口后,可以调用构成实际的 EJB 代码的方法了?
如何这些术语应用到您的城镇模拟中去呢?到达正确的城镇ƈ扑ֈ正确的地址后,您需要走q商店或者按铃(调用 Create() Q。这个过E对于您要去的所有商店都是一LQ不q,您所收到的响应取决于是由谁来提供服务 ―?比如是一位屠夫、一位面包师q是一位烛台制作者。这个响应代表了 q程接口。每个h都是不同的ƈ且可以要求他提供不同的东ѝ您必须知道与您交谈的hQ即 beanQ的职业才能提出正确的问?卌用正的Ҏ) ―?向一位屠夫要一条面包可不妥当?
CosNaming、LDAP ?JNDI Java 命名和目录接口(Java Naming and Directory Interface JNDIQ提供了一个标准接口,它指明您需要如何与名称I间交互。我们所提到? LDAP? CosNaming 是 JDNI 名称I间cd。现在扩展我们的比喻QJNDI 是城镇的模板Q? CosNaming ?LDAP 是特定的城镇。它们以怼的方式操作,但是有不同的布局?

回页?/b>


属性提供了一个映?/span>

让我们看一看如何用所有这些元素以成功Cq程计算Z调用我们?EJB lg上的Ҏ。ؓ了让客户E序q接到您_ֿ打造的 EJB lgQ需要几样东ѝ首先,它需要客户代码的所? JAR 文g、一般性的 EJB 相关 JAR 文g?J2EE.jar 以及在部|?bean 时生成的 stub ?tie。这些文件让您的客户机可以一直到辑ֈ始上下文?/font>

接下来您的客h需要的信息是一些属性的倹{首先,您将需要几? java.naming.factory.initial 的倹{该属性指向一个提供初始上下文工厂的类。该属性的一个典型值是 com.sun.jndi.cosnaming.CNCtxFactory Q这也是我们在这里的几个例子中所使用的倹{这个类存在? rt.jar 中,因而它是基?JVM 的一部分。工厂是?CosNaming 命名服务器所使用的,但是 JVM q包括一?LDAP 工厂。我们在后面会看到Q不同的应用服务器提供它们自q初始上下文工厂?

q个c连同命名服务器 URL 和端口号的详l信息,用于生成与名U空间交互的 InitialContext cR不q,如果没有 provider URLQ那么它连接到 localhost ?900 端口Q或者您的上下文工厂的其他默认端口)。要q接到远E服务器Q您需要有属? java.naming.provider.url 的一个倹{?

新程序员对于所有这些觉得很隄解的原因是:不管您在应用服务器本地运行Q何东西,q东襉K常都会听话地工作。这是由于环境照了一切,当您要求一? InitialContext Ӟ环境׃l您提供您想要的那个。但是当您将客户卌{Ud不同的计机上时Q就得靠自己了。您需要知道拷贝哪一?JAR 文gQ以及要做哪些设|。我知道有些Zؓ使他们的客户机正工作,应用服务器上的所?JAR 文g都拷贝到W二台计机上!

在默认情况下Q? InitialContext 工厂是在 jndi.properties 中定义的Q这个工厂类有默认的服务?URL 和端口号默认倹{这个文件在c\径中Q这一般意味着在本地目录)或者在您的c\径中的Q?JAR 中。不同的应用服务器可能在不同? JAR 文g中提供它们的默认|WebSphere Application Server ? namingclient.jar 中储存一个默认副本。要指定您自q默认|只需要编辑在c\径中的第一个副本。这是配|属性的一U方法,如果~少命o行或者代码驱动的讄Q那么客h? jndi.properties 中的倹{不q,虽然q可能适合于简单的讄Q但是如果处理多个服务器和名U空_那么您可能希望一个客户一个客户地q行配置?

q些属性是如何Ҏ我们要用的名称I间而用不同的值的呢?正如前面提到的,有两UŞ式的 JNDI 名称I间QCosNaming ?LDAP。其中每一个都有与之相兌的传输:分别? IIOP ?LDAP。一?LDAP 名称I间使用 LDAP 传输Q您用一个像 ldap://myldapnameserver q样?URL q接到它Q,?CosNaming 使用一?IIOP 传输Q您用一个像 iiop://mycosnamingserver q样?URL q接到它Q。CosNaming 的默认端口号?900Q?LDAP 的默认端口号?389。不q,Ml定的名U空间服务器实现使用的默认值可能是不同的?

用命令行配置属?/span>

让我们看一下如何用命o行配|属性。如果您要在安自己l习Q进?JDK 安装中的 bin 目录。在q个文g夹中Q可以找C个名? tnameserv.exe 的程序(对于 WindowsQ或者只? tnameserv Q对于基?UNIX 的系l)。通过执行q个E序会在端?900 启动一个示?CosNmaing 命名服务器?

现在正好可以用一个可以查?CosNaming 名称I间的实用工h装备您自己。我本h使用 Eclipse 作ؓ开发环境,我在下面? 参考资?/a> 部分中提供了?JNDI 览器插件的链接。理ZQ您应该可以一个名U空间浏览器指向自己计算机的端口 900Qƈ看到一个非常无聊的I名U空_管一些应用服务器在默认情况下会用很多不同的内容填充名U空_。ؓ了丰富我们的名称I间Q我们现在将~写一 个简单的E序以在它里面放一些内容,如清? 2 所C:


清单 2. 一个简单的 cosNaming 名称I间交互

package example.publisher;

import javax.naming.InitialContext;

public class Publish {

public static void main(String[] args) {
//
//This example creates a subcontext in a namespace
//
try{
InitialContext ic = new InitialContext();
ic.createSubcontext("Test");
}catch(Exception e){
System.out.println(e);
e.printStackTrace();

}
}
}

q个应用E序假定ؓ得到正确的初始上下文件所需的所有属性都是可用的。所以现在可以从命o行运行它q在q行时提供这些属性(其中 URL 要根据您的环境作调整Q:


java -Djava.naming.factory.initial=com.sun.jndi.cosnaming.CNCtxFactory
-Djava.naming.provider.url=iiop://mymachine:900
example.publisher.Publish

一切正常,我们的客户会扑ֈCZ名称I间的上下文q创建名? Test 的子上下文。您可以用名U空间浏览器认q一炏V?

? 在试着在一台计机上运行命名服务器Q用同一个命令行Q当Ӟ?URL 再次做了调整Q在另一台计机上运行清?2 中的应用E序。它q行h应该没有问题Q您可能需要修改这个例子以改变所限定的内容,甚至删除子上下文而不是创建它Q这样在W二ơ运行时您就可以信它已 lvq作用了Q?/font>

在应用程序中配置属?/span>

那么Q如果不希望在命令行中设|这些属性怎么办?q有另外一个方法。可以在E序中显式地声明q些属性。这意味着您不需要ؓ java 命o提供Ҏ的选项。改变清?2 中的代码以显式地讄所需要的属性后Q它看v来与清单 3 中的代码一P


清单 3. 单的 cosNaming 名称I间交互Q在应用E序代码中设|属?/b>

package example.publisher;

import javax.naming.InitialContext;

public class Publish {

public static void main(String[] args) {
//
//This example creates a subcontext in a namespace
//
try{
Properties prop = new Properties();
prop.setProperty("java.naming.factory.initial",
"com.sun.jndi.cosnaming.CNCtxFactory");
prop.setProperty("java.naming.provider.url",
"iiop://mymachine:900");
InitialContext ic = new InitialContext(prop);
ic.createSubcontext("Test");
}catch(Exception e){
System.out.println(e);
e.printStackTrace();

}
}
}

现在q个E序不再需要长长的命o行配|,不过要记住,以这U方式编写的应用E序编码了q些讄?/font>



回页?/b>


L通往 bean 的道?/span>

到目前ؓ止,我们已经看到了几个可以证明我们已q接到远E名U空间ƈ完成一些Q务的例子Q尽这些Q务是相当无聊?―?创徏一个子上下文。在实际中,一般是由工h为您完成所有的创徏和发布工作,? 真正 需要的做是查找一个对象。在q一节,我们在 CosNaming 名称I间中获得已发布?HelloWorld bean ? Home 接口。然后我们再看一下如何在 LDAP 名称I间中找到它? Home 接口?

Z说明问题Q我们假设您已经部v?HelloWorld beanQ它的home接口 HelloWorldHome 发布? example/HelloWorldHome Q如果您只想试一试,但是又不惌己创Z?HellowWorld beanQ那么在 参考资?/a>中有一个下载预打包?bean JAR 文g的链接以及一个用它的客h的文Ӟ?

一个上下文技?/b>

在名U空?URL 格式中,您可以设|自q初始上下文,使之从树上比默认值更高的位置开始。例如,如果在我们的例子中对?provider URL 使用 iiop://mymachine:900/example Q那么您只需要查? HelloWorldHome Q而不? example/HelloWorldHome Q初始上下文在 example 内。如果您在一个名U空间中在同一个结构下q行几次查询Q那么这会有所帮助Q这P如果改变了设|,代码中要改变的惟一部分只是 provider URL?

在上一节,我们q行了连接到命名服务器的艰苦工作Q现在我们所需要的只是查?EJB lg了。这需要我们向查询Ҏ传递一个字W串Q它表示? InitialContext Q您在城镇中的出发点Q到惌ȝ HomeInterface Q房屋或者商店)的方向。听h?―?但是q里您所选择的特定上下文工厂p产生影响了。像 WebSphere q样的应用服务器所带的工厂cdƈ不L把您攑ֈ名称I间的根上。所以我们ؓ了查? HomeInterface 而需要的字符串会Ҏ InitialContext 您所攑ֈ城镇中的位置而变化。ƈ且,在本地服务器上,上下文工厂可能将您放C在远E服务器上不同的起始位置?

? 个原因,我徏议您不要像在清单 3 中那L~码所使用的查询字W串Q而是用命令行或者属性文件传递。特别是对于h多步的体pȝ构更应如此。例如,您可能有一个调用一? EJB lg的客hQ这?bean 可能又需要调用也许是在不同的服务器上的第二个 EJB lgQ在q种情况下,属性应该在每一步中传递。这为反复实验(trial-and-errorQ查询提供了一U简单的机制Qƈ且只需要相对较的改变可 以得到最l应用程序的灉|性。因此让我们看一个示例查询应用程序。在清单 4 中,属性是在程序中讄的,但是它又以命令行gؓ依据。这样命令行与在我们前一个例子中使用的稍有不同,如我们在下面所看到的?/font>


清单 4. 查询一个home接口

package example.lookup;
import java.util.Properties;
import javax.naming.InitialContext;
import javax.rmi.PortableRemoteObject;

import example.HelloWorld;
import example.HelloWorldBean;
import example.HelloWorldHome;
import javax.naming.InitialContext;

public class Lookup {

public static void main(String[] args) {
//
//This example looks up the home interface of an EJB to a namespace
//
try{
Properties prop = new Properties();
prop.setProperty("java.naming.factory.initial",args[0]);
prop.setProperty("java.naming.provider.url",args[1]);
InitialContext ic = new InitialContext(prop);
Object or = ic.lookup(args[2]);
if (or != null) {
// Narrow the return object to the Home class type
HelloWorldHome home =
(HelloWorldHome)PortableRemoteObject.narrow(or,
HelloWorldHome.class);
// Create an EJB object instance using the home interface.
HelloWorld hw = home.create();
// Invoke the method
System.out.Println(hw.hello());
}
}catch(Exception e){
System.out.println(e);
e.printStackTrace();

}
}
}

q个E序是用三个参数调用的:要用的上下文工厂、provider URL 和包含要查询的名字的字符丌Ӏ我们已l知道前两个是什么,那么W三个呢Q?/font>

如果您仍然? tnameserv 作ؓ命名服务器,那么您很可能?bean 直接发布? /example/HelloWorldHome 。在q种情况下,只要? /example/HelloWorldHome 作ؓW三个参C递就可以q行成功的查询。不q,如果您用的命名服务器有一个更复杂的命名空_那么可能会存在由所使用的部|工具增加的额外的层。例如,WebSphere 在默认情况下?JavaBean 部v? ejb/ Q但是这不是名称I间的根Qƈ且只有当使用 WebSphere 的上下文工厂Ӟ通过传入字符? /ejb/example/HelloWorldHome 才会使您处于名称I间中的正确位置?如果您用一个与应用服务器提供的不同的上下文工厂Q例如在一台只有标? Java 安装的计机上运行客h时就需要这样做Q时Q这个问题会更加恶化。不q,应用服务器的命名服务器文档应当说明在查询 EJB lg时将会从名称I间的什么地方开始。看一下文中的例子,再用览器查看名U空间以定其客h? InitialContext 会将它们攑ֈ什么地斏V名U空间往往会@环,q样您就可以试着沿着一个分枝到无限。这意味着从最开始的上下文可以找C条回家的道\?

MQ下面是传递相应参数给清单 4 中的应用E序的命令行Q?/font>


java Lookup com.sun.jndi.cosnaming.CNCtxFactory
iiop://mymachine:900 example/HelloWorldHome

?CosNaming 中,名称I间子上下文由斜U?/)字符分隔Q这与标?URL 一栗LDAP 的语法则不同Q我们在下面会看到?/font>

介绍 LDAP

现在让我们再加上 LDAP。就本文的内ҎԌLDAP 是另一?JNDI 名称I间Q但是它的结构的表示Ҏ?CosNaming 名称I间的表C方法截然不同。它q需要一个不同的上下文工? ―?但是q不成问题,因ؓ我们M在命令行指定正确的工厂(在我们的例子中,我们用属于基?JVM 一部分的工厂,但是要记住不同的应用服务器可能有自己的工厂)。ƈ且它需要一个指向不同命名服务器的指? ―?q且Q幸q的是,我们也是在命令行中指定它的。当Ӟ表示home接口位置的字W串是不同的Q但是您猜如何?是的Q我们还是在命o行中指定它。您可以看到使用q些命o行调用的好处Q所有要做的只是改变调用我们的测试程序的方式Q理Z我们可以到达M JDNI 命名服务器,甚至可以利C CosNaming 转移?LDAP 而不用改变Q何代码。是的,q是只是理论Q当然无论如何,关键是要有正的参数?/font>

一些命名服务器会保护部分命名空_q意味着只能发布到允许的区域。假设您有一个运行的 LDAP 服务器,它的l节如下Q?/font>

  • URL: ldap://mymachine:1389
  • BaseDN: c=myldap

LDAP 名称I间中的树结构一般来说像下面q样Q?/font>


ibm-wsnName=MyServer,ibm-wsnName=HelloWorldHome

不过Q当我们这个字W串传递给E序Ӟ我们需要反转它Q不要问我ؓ什么)。所以我们用的字符串看h是这LQ?/font>


ibm-wsnName=HelloWorldHome,ibm-wsnName=MyServer,

BaseDN 表示在名U空间中您希望开始的位置。对于给定的 LDAP 命名服务器来说这可以是很多位|,q取军_是如何构造的。在q个例子中,我们直接? c=myldap 的根。但是如果我们希望蟩到名U空间中的一个树Q那么可以指? ibm-wsnTree=myTree,c=myldap 作ؓ BaseDN 而不是蟩到那一炏V?

q样Q我们将传递给E序的命令行参数像下面q样Q?/font>


java Lookup com.sun.jndi.ldap.LdapCtxFactory ldap://mymachine:1389/c=myldap/
ibm-wsnName=HelloWorldHome,ibm-wsnName=MyServer

可能出现的错误消?/b>

无论如何Q您毫无疑问会熟悉在部v EJB lg时出现的各种异常消息。下面是您将会经常看到的几个异常消息Q反正我常见到它们)Q以及您遇到它们的原因:

  • CORBA.OBJECT_NOT_EXIST : 您在错误的位|上查找 EJB?
  • CORBA.MARSHALL _ something : CORBA marshall 异常l常发生。它们有不同的细节,但是它们基本上都表明同一件事情:您得C些数据,但这不是您的客户机所期待的。也许您查询了错误的东西Q也许您的客h所知道? EJB lgcȝ版本与实际部|的不一栗或者出C问题Q客h ORB 不能理解服务?ORB 所发送的内容?
  • javax.naming.NoInitialContextException : 噢!您没有指定上下文工厂或? provider URLQ也许命名服务器没有q行?

q里我们指定一?LDAP 上下文工?Q然后传?LDAP 服务器的名字以及我们惌开始的位置。然后是到要查询?EJB lg的反转的路径。我们可以用q个命o行调用在 CosNaming 例子中用的同一D代码( 清单 4Q?

当然Q本文中使用的代码没有理׃能构成一个助手类的一个方?―?它带三个参数Qƈ且返? Object or Q这个对象是在试囑ցM事情之前调用 ic.lookup(args[2]) 时返回的。然后,当您需要进行一ơ查询时Q只需使用q个助手c,向它传递适合于当前情늚适当参数Q取回您所需要的对象引用Qƈ准备它H化到实际的cR( 注意Q我不保证这U类的性能Q而只是提供这D原本就是如此的代码Q我或? IBM Ҏ不作M保证。)当然Q可以通过反射实现一U完全通用的方式,但这会事情复杂得多Q也出了本文的范围?

? 我们l束之前q有最后一件事要考虑。您可以~写一个结合了我们在清?3 ?4 中用的技术的客户机。它会检查命令行中是否给Z一个|如果有,它就讄q些|如果没有Q它׃用硬~码的倹{这P在程序中可以有有意义的默? |但是如果需要,也可以用命o行选项覆盖它们。只需要对代码q行微不道的更攏V?/font>



回页?/b>


安全到家

作ؓ回顾Q下面是在有多个 EJB 查询和多个应用服务器的情况下Q要使Q何系l运行而应该有或者应该知道的最重要的四件事情:

  • 在Q何给定阶D,下一阶段的所?stub ?tie 都必dc\径上。除非环境知道这个类是什么样的,否则您不能窄化一个对象以使用它?
  • 每一阶段都需要与 EJB 相关的一般?JAR 文gQ如 J2EE.jar ?
  • 以参数的形式传递上下文工厂cd、命名服务器名和 JDNI 查询字符丌Ӏ以便能够轻N应变化?
  • 知道您的名称I间。记住您?JDNI 查询字符串需要将您从在名U空间中开始的位置Ud到您的对象所储存的位|。但是您q不L在同一位置开始!用一个工h览名U空_q了解在本地和远E查询中是从什么位|开始的?

对于习惯于编写全部在同一台计机上执行的代码的开发h员来_览q程名称I间可能是一个困隄q程。希望本文的提示和代码可以帮助您讄q运行您的分布式 EJB 应用E序。当您掌握了 JNDI 名称I间后,再去看一?developerWorks 上由 Brett McLaughlin 所写的 EJB 最佛_?/i>pdQ请参阅 参考资?/a>Q,以获得用于优化代码的一些很的技巧?



回页?/b>


参考资?



回页?/b>


关于作?/span>


Daniel Would ?2001 q加?IBM。他做了一q?CTG pȝ试员,之后成ؓ CICS pȝ试员一直到现在。在 IBM ӞDaniel 的工作集中于 Java 技术和 EJB lg。可以通过 wouldd@uk.ibm.com ?Daniel 联系?



]]>
EJB 调用原理分析http://www.tkk7.com/cmd/articles/30918.html静夜?/dc:creator>静夜?/author>Thu, 16 Feb 2006 02:19:00 GMThttp://www.tkk7.com/cmd/articles/30918.htmlhttp://www.tkk7.com/cmd/comments/30918.htmlhttp://www.tkk7.com/cmd/articles/30918.html#Feedback0http://www.tkk7.com/cmd/comments/commentRss/30918.htmlhttp://www.tkk7.com/cmd/services/trackbacks/30918.html一个远E对象至要包括4个class文gQ远E对象;q程对象的接口;实现q程接口的对象的stubQ对象的skeletonq?个class文g?/font>

在EJB中则臛_要包?0个classQ?/font>

Beanc,特定App Server的Bean实现c?/font>

Bean的remote接口Q特定App Server的remote接口实现c,特定App Server的remote接口的实现类的stubcdskeletonc?/font>

Bean的home接口Q特定App Server的home接口实现c,特定App Server的home接口的实现类的stubcdskeletonc?/font>

和RMI不同的是QEJB中这10个class真正需要用L写的只有3个,分别是Beancd它的remote接口Qhome接口Q至于其它的7 个class到底是怎么生成Q被打包在什么地方,或者是否需要更多的cLӞ会根据不同的App Server表现出比较大的差异,不能一概而论?/font>

拿我最熟悉的Weblogic的来说吧QWeblogic的Bean实现c,以及两个接口的Weblogic的实现类是在ejbc的时候被打包? EJB的jar包里面的Q这3个class文g可以看到。而home接口和remote接口的Weblogic的实现类的stubcdskeletonc? 是在EJB被部|到Weblogic的时候,由Weblogic动态生成stubcdSkeletoncȝ字节码,因此看不到这4个类文g?/font>

对于一ơ客Lq程调用EJBQ要l过两个q程对象的多ơRMI循环。首先是通过JNDI查找Home接口Q获得Home接口的实现类Q这个过E其 实相当复杂,首先是找到Home接口的Weblogic实现c,然后创徏一个Home接口的Weblogic实现cȝstubcȝ对象实例Q将它序列化? 送给客户端(注意stubcȝ实例是在W?ơRMI循环中,由服务器动态发送给客户端的Q因此不需要客L保存Home接口的Weblogic实现cȝ stubc)Q最后客L获得该stubcȝ对象实例Q普通的RMI需要在客户端保存stubc,而EJB不需要,因ؓ服务器会把stubcȝ对象实例? 送给客户端)?/font>

客户端拿到服务器l它的Home接口的Weblogic实现cȝstubcd象实例以后,调用stubcȝcreateҎQ?在代码上是 home.create()Q但是后台要做很多事?,于是l过W?ơRMI循环Q在服务器端QHome接口的Weblogic实现cȝskeleton cL到stubcȝ调用信息后,由它再去调用Home接口的Weblogic实现cȝcreateҎ?/font>

在服务端QHome接口的Weblogic实现cȝcreateҎ再去调用BeancȝWeblogic实现cȝejbCreateҎQ在服务 端创建或者分配一个EJB实例Q然后将q个EJB实例的远E接口的Weblogic实现cȝstubcd象实例序列化发送给客户端?/font>

客户端收到remote接口的Weblogic实现cȝstubcȝ对象实例Q对该对象实例的Ҏ调用Q在客户端代码中实际上就是对remote? 口的调用Q,传送给服务器端remote接口的Weblogic实现cȝskeletoncd象,而skeletoncd象再调用相应的remote? 口的Weblogic实现c,然后remote接口的Weblogic实现cd去调用BeancȝWeblogic实现c,如此完成一ơEJB对象的远 E调用?/font>

看了一遍帖子,感觉q是没有说太清楚Q既然写了帖子,想d把它说清楚?/font>

先拿普通RMI来说Q有4个classQ分别是q程对象Q对象的接口Q对象的stubcdskeletoncR而对象本w和对象的stubcd旉实现了接口类。而我们在客户端代码调用远E对象的时候,虽然在代码中操纵接口Q实质上是在操纵stubc,例如Q?/font>

接口c:Hello

q程对象QHello_Server

stubc:Hello_Stub

skeletonc:Hello_Skeleton

客户端代码要q样写:

Hello h = new Hello_Stub();
h.getString();

我们不会q样写:

Hello_Stub h = new Hello_Stub();
h.getString();

因ؓ使用接口适用性更q,q更换了接口实现类Q也不需要更改代码。因此客L需要Hello.class和Hello_Stub.classq两 个文件。但是对于EJB来说Q就不需要Hello_Stub.classQ因为服务器会发送给它,但是Hello.class文g客户端是省不了的Q必? 有。表面上我们的客L代码在操UHelloQ但别忘CHello只是一个接口,抽象的,实质上是在操UHello_Stub?/font>

拿Weblogic上的EJB举例子,10个class分别是:

Beanc:HelloBean Q用L写)
BeancȝWeblogic实现c:HelloBean_Impl QEJBC生成Q?br>Home接口QHelloHome Q用L写)
Home接口的Weblogic实现c?((Hello Bean))_HomeImplQEJBC生成Q?br>Home接口的Weblogic实现cȝstubc?((Hello Bean))_HomeImpl_WLStubQ部|的时候动态生成字节码Q?br>Home接口的Weblogic实现cȝskeletonc?((Hello Bean))_HomeImpl_WLSkeletonQ部|的时候动态生成字节码Q?br>Remote接口QHello Q用L写)
Remote接口的Weblogic实现c?((Hello Bean))_EOImplQEJBC生成Q?br>Remote接口的Weblogic实现cȝstubc?((Hello Bean))_EOImpl_WLStubQ部|的时候动态生成字节码Q?br>Remote接口的Weblogic实现cȝskeletonc?((Hello Bean))_EOImpl_WLSkeletonQ部|的时候动态生成字节码Q?/font>

客户端只需要Hello.class和HelloHome.classq两个文件?/font>

((Hello Home)) home = (Home) ((Portable Remote Object)).narrow(ctx.lookup("Hello"), ((Hello Home)).class);

q一行代码是从JNDI获得Home接口Q但是请CQ接口是抽象的,那么homeq个对象到底是什么类的对象实例呢Q很单,用toString()输出看一下就明白了,下面一行是输出l果Q?/font>

((Hello Bean))_HomeImpl_WLStub@18c458

q表明homeq个通过从服务器的JNDI树上查找获得的对象实际上是HelloBean_HomeImpl_WLStubcȝ一个实例?/font>

接下来客L代码Q?/font>

Hello h = home.create()

同样Hello只是一个抽象的接口Q那么h对象是什么东西呢Q打C下:

((Hello Bean))_EOImpl_WLStub@8fa0d1

原来是HelloBean_EOImpl_WLStub的一个对象实例?/font>

用这个例子来qC遍EJB调用q程Q?/font>

首先客户端JNDI查询Q服务端JNDI树上Helloq个名字实际上绑定的对象是HelloBean_HomeImpl_WLStubQ所以服务端创建HelloBean_HomeImpl_WLStub的一个对象实例,序列化返回给客户端?/font>

于是客户端得到home对象Q表面上是得到HelloHome接口的实例,实际上是q行了一ơ远E调用得CHelloBean_HomeImpl_WLStubcȝ对象实例Q别忘记了HelloBean_HomeImpl_WLStub也实CHelloHome接口?/font>

然后home.create()实质上就是HelloBean_HomeImpl_WLStub.create()Q该Ҏ发送信息给 HelloBean_HomeImpl_WLSkeletonQ而HelloBean_HomeImpl_WLSkeleton接受C息后Q再去调? HelloBean_HomeImpl的createҎQ至此完成第1ơ完整的RMI循环?/font>

注意在这ơRMI循环q程中,q程对象是HelloBean_HomeImplQ远E对象的接口是HelloHomeQ对象的stub? HelloBean_HomeImpl_WLStubQ对象的skeleton是HelloBean_HomeImpl_WLSkeleton?/font>

然后HelloBean_HomeImpl再去调用HelloBean_Impl的ejbCreateҎQ而HelloBean_Impl? ejbCreateҎ负责创建或者分配一个Bean实例Qƈ且创Z个HelloBean_EOImpl_WLStub的对象实例?/font>

q一步比较有的是,在前一步RMI循环中,q程对象HelloBean_HomeImpl在客L有一个代理类 HelloBean_HomeImpl_WLStubQ但在这一步,HelloBean_HomeImpl自己却充当了HelloBean_Impl的代 理类Q只不过HelloBean_HomeImpl不在客户端,而是在服务端Q因此不q行RMI?/font>

然后HelloBean_EOImpl_WLStub的对象实例序列化q回l客LQ这一步也很有,上次RMIq程Q主角是 HelloBean_HomeImpl和它的代理类HelloBean_HomeImpl_WLStubQ但q这一ơ换成了 HelloBean_EOImpl和它的代理类HelloBean_EOImpl_WLStub来玩了?/font>

Hello h = home.create();h.helloWorld();

假设Hello接口有一个helloWorldq程ҎQ那么表面上是在调用Hello接口的helloWorldҎQ实际上是在调用HelloBean_EOImpl_WLStub的helloWorldҎ?/font>

然后HelloBean_EOImpl_WLStub的helloWorldҎ发送信息给服务器上? HelloBean_EOImpl_WLSkeletonQ而HelloBean_EOImpl_WLSkeleton收到信息以后Q再去调? HelloBean_EOImpl的helloWorldҎ。至此,完成W?ơ完整的RMI循环q程?/font>

在刚才HelloBean_EOImpl是作E对象被调用的,它的代理cLHelloBean_EOImpl_WLStubQ但现在 HelloBean_EOImpl要作为HelloBean_Impl的代理类了。现在HelloBean_EOImpl去调? HelloBean_Impl的helloWorldҎ。注意!HelloBean_Impll承了HelloBeanQ而HelloBean中的 helloWorldҎ是我们亲自编写的代码Q现在终于调用到了我们编写的代码了!

xQ一ơEJB调用q程l于完成。在整个q程中,服务端主要要调用的类是HelloBean_ImplQ?Hello Bean?_HomeImplQHelloBean_HomeImpl_WLSkeletonQHelloBean_EOImplQ? HelloBean_EOImpl_WLSkeleton。客L主要调用的类是HelloBean_HomeImpl_WLStubQ? HelloBean_EOImpl_WLStubQ这两个cd客户端代码中q不会直接出玎ͼ出现在代码中的类是他们的接口HelloHome? HelloQ因此客L需要这两个接口文gQ而Stub是服务器传送给他们的?/font>

]]>
RMI要开?/title><link>http://www.tkk7.com/cmd/articles/30916.html</link><dc:creator>静夜?/dc:creator><author>静夜?/author><pubDate>Thu, 16 Feb 2006 02:13:00 GMT</pubDate><guid>http://www.tkk7.com/cmd/articles/30916.html</guid><wfw:comment>http://www.tkk7.com/cmd/comments/30916.html</wfw:comment><comments>http://www.tkk7.com/cmd/articles/30916.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.tkk7.com/cmd/comments/commentRss/30916.html</wfw:commentRss><trackback:ping>http://www.tkk7.com/cmd/services/trackbacks/30916.html</trackback:ping><description><![CDATA[<font size="2"><b>   ?/b><br><br>  RMI是远E方法调用的Uͼ象其名称暗示的那P它能够帮助我们查扑ƈ执行q程对象的方法。通俗地说Q远E调用就象将一个class攑֜A机器上,然后在B机器中调用这个class的方法?br><br>  我个为,管RMI不是唯一的企业q程对象讉KҎQ但它却是最Ҏ实现的。与能够使不同编E语a开发的CORBA不同的是QRMI是一U纯Java解决Ҏ。在RMI中,E序的所有部分都由Java~写?br><br>  在看本篇文章Ӟ我假定读者都已经具备了较扎实的Java基础知识Q在q方面有Ơ缺的读者请自行阅读有关资料?br><br>  <b>概念</b><br><br>  我在前面已经提到QRMI是一U远E方法调用机Ӟ其过E对于最l用h透明的:在进行现场演C时Q如果我不说它用了RNIQ其他h不可能知道调用的Ҏ存储在其他机器上。当然了Q二台机器上必须都安装有Java虚拟机(<a class="bluekey" target="_blank">JVM</a>Q?br><br>  其他机器需要调用的对象必须被导出到<a class="bluekey" target="_blank">q程注册</a>? 务器Q这h能被其他机器调用。因此,如果机器A要调用机器B上的ҎQ则机器B必须该对象导出到其q程注册服务器。注册服务器是服务器上运行的一U服 务,它帮助客Lq程地查扑֒讉K服务器上的对象。一个对象只有导出来后,然后才能实现RMI包中的远E接口。例如,如果想机器A中的Xyz对象能够? q程调用Q它必d现远E接口?br><br>  RMI需要用占位程序和框架Q占位程序在客户端,框架在服务器端。在调用q程ҎӞ我们无需直接面对存储有该Ҏ的机器?br><br>  在进行数据通讯前,q必d一些准备工作。占位程序就象客L机器上的一个本机对象,它就象服务器上的对象的代理,向客L提供能够被服务器调用的方法。然后,Stub׃向服务器端的Skeleton发送方法调用,Skeleton׃在服务器端执行接收到的方法?br><br>  Stub和Skeleton之间通过q程调用层进行相互通讯Q远E调用层遵@TCP/IP协议收发数据。下面我们来大致了解一U称Zؓ“绑定”的技术?br><br>   客户端无Z时要调用服务器端的对象,你可曾想q他是如何告诉服务器他想创徏什么样的对象吗Q这正是“绑定”的的用武之地。在服务器端Q我们将一个字W? 串变量与一个对象联pd一P可以通过Ҏ来实玎ͼQ客L通过那个字W串传递给服务器来告诉服务器它要创建的对象Q这h务器可以准地知道客户? 需要用哪一个对象了。所有这些字W串和对象都存储在的q程注册服务器中?<br><br>  <b>在编E中需要解决的问题</b><br><br>  在研I代码之前,我们来看看必ȝ写哪些代码:<br><br>  ·<b>q程对象Q?/b>q个接口只定义了一个方法。我们应当明白的是,q个接口qL不包括方法的代码而只包括Ҏ的定义。远E对象包含要导出的每个方法的定义Q它q实现Java.rmi中的q程接口?br><br>  ·<b>q程对象实现Q?/b>q是一个实现远E对象的cR如果实Cq程对象Q就能够覆盖该对象中的所有方法,因此Q远E对象的实现cd真正包含我们希望导出的方法的代码?br><br>  ·</font><font size="2"><b><a class="bluekey" target="_blank">q程服务?/a></b></font><font size="2"><b>Q?/b>q是一个作为服务器使用的类Q它是相对于要访问远E方法的客户端而言的。它存储着l定的字W串和对象?br><br>  ·<b>q程客户端:</b>q是一个帮助我们访问远E方法提供帮助的c,它也是最l用戗我们将使用查找和调用远E方法的Ҏ在该cM调用q程Ҏ?br><b>~程</b><br><br>  我们首先编写远E对象,q将代码保存为名字ؓ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">  我们来看看上面的代码。首先,Z使用其内容,我们导入rmi包。然后,我们创徏一个扩展了Java.rmi中远E接口的接口。所有的q程对象 必须扩展该远E接口,我们该q程接口UCؓAddServer。在该远E对象中Q有一个名字ؓAddNumbers的方法,客户端可以调用这一Ҏ。我? 必须C的是Q所有的q程Ҏ都需要启动RemoteExceptionҎQ有错误发生时就会调用该Ҏ?br><br>  下面我们开始编写远E对象的实现。这是一个实现远E对象ƈ包含有所有方法代码的c,下面的代码保存为名字ؓ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">  首先Q我们导入rmi包,然后创徏一个扩展UnicastRemoteObject和实现创建的q程对象的类Q其ơ,我们可以为类创徏一个缺? 的构建器。我们还了解了AddNumbersҎ的代码,它启动RemoteException。这h们就覆盖了创建的q程对象中的Ҏ? AddNumbersҎ的代码非常好理解Q它接受2个整型参敎ͼ然后相加q返回它们的和?br><br>  xQ我们已l有了二个Java文gQ远E对象和q程对象的实现。下面我们将使用Javac命o~译q二个文Ӟ<br><br>  ~译q程对象Q?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">  ~译q程对象实现Q?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">  q样Q就会达C个Java文g和二个类文gQ下面我们将创徏stub和skeleton。ؓ了创建stub和skeleton文gQ我们必M用rmic~译器编译远E对象实现文件?br><br>  用Rmic~译q程对象实现文gQ?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">  然后Q我们就会发现多?个新建的cLӞ它们分别是AddServerImpl_Stub.class 和AddServerImpl_Skel.class ?br><br>  The Coding (Contd.) <br><br>  我们已经~译了所有的源代码,下面我们来创建客L和服务器端,下面的代码保存为名字ؓ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">  首先Q我们导入Java.rmi包和Java.net包。另外,我们q用throws从句捕获M异常。我们从对象中得E对象实玎ͼ使用rebindҎ字W串addnumbers与该对象l定。下面的例子昄了绑定的含义Q?br>从现在开始,无论何时客户端要调用q程对象Q用字W串addnumbers可以实现。rebindҎ有二个参敎ͼW一个参数是字符串变量,W二个参数是q程对象实现cȝ对象?br><br>  下面我们来创建客LQ将下面的代码保存ؓ名字为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">  首先Q我们导入Java.rmi包和Java.net包,q用throws从句捕获所有必要的异常。然后通过利用NamingcM的静态lookupҎ从远E对象中得到一个对象。(q也是我们无需从NamingcM得到一个对象ƈ调用它。而只使用cd字的原因。)<br><br>   lookupҎ接受q程对象的完整的URL名字Q该URL由完整的机器IP地址以及与对象绑定的字符Ԍ也誻对象的绑定名Q组成。在调用q程对象Ӟ 我们使用了RMI协议。lookupҎ向我们返回一个对象,在能够用它前,我们必须它的数据类型{换ؓ与远E对象的数据cd一致?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">  xQ我们已l有了服务器端和客户端的源代码,下面我们来编译这二个源文Ӟ<br><br>  ~译q程服务器:<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">  ~译q程客户端:<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">  在对我们的代码进行测试前Q还必须首先启动RMI Registry。RMI Registry存储有所有绑定的数据Q没有它QRMI׃能正常地q行Q?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">  我们会注意到Q这时会出现一个空白的DOS提示W窗口,q表明Rmi Registry服务器在q行Q注意不要关闭该H口。然后,我们首先在一个DOS提示W窗口中q行Rmi服务器,然后在另一个DOS提示W窗口中q行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">  如果一切正常,我们应该能够得到15q个输出。我们向AddNumbersҎ输入10?二个数字Q该Ҏ这二者加hQƈ其?5q回 l我们。如果得C15q个输出Q说明我们已l成功地执行了一个远E方法。当Ӟ在这里,我们q没有执行真正意义上的远E方法,因ؓ我们的计机既是服务 器,又是客户机。如果有计算机网l,我们可以方便地q行执行q程Ҏ的试验了?/font><br><img src ="http://www.tkk7.com/cmd/aggbug/30916.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.tkk7.com/cmd/" target="_blank">静夜?/a> 2006-02-16 10:13 <a href="http://www.tkk7.com/cmd/articles/30916.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss> <footer> <div class="friendship-link"> <p>лǵվܻԴȤ</p> <a href="http://www.tkk7.com/" title="亚洲av成人片在线观看">亚洲av成人片在线观看</a> <div class="friend-links"> </div> </div> </footer> վ֩ģ壺 <a href="http://cdhxfj.com" target="_blank">ѹۿͰŮƵ</a>| <a href="http://864007.com" target="_blank">Ƶ̫ˬ</a>| <a href="http://k67m.com" target="_blank">99þþþƷѹۿ</a>| <a href="http://gdporun.com" target="_blank">aԴ</a>| <a href="http://caopropp.com" target="_blank">av߲</a>| <a href="http://zzdyzj.com" target="_blank">Ʒ޾Ʒۿ</a>| <a href="http://sxhengshan.com" target="_blank">һһëƬ</a>| <a href="http://zjztauto.com" target="_blank">ĻӰԺww4164h</a>| <a href="http://lebaojj.com" target="_blank">һõþۺ</a>| <a href="http://xxxxyz.com" target="_blank">߹ۿ˳վ</a>| <a href="http://3hc88.com" target="_blank">ѵĻɫվ</a>| <a href="http://ge2hao.com" target="_blank">ѵҰսƵ</a>| <a href="http://cykj-tech.com" target="_blank">һAV</a>| <a href="http://91sebo.com" target="_blank">Ʒ޹</a>| <a href="http://987566.com" target="_blank">Ļѿ</a>| <a href="http://whspmd.com" target="_blank">ҹѸӰԺ</a>| <a href="http://vod8090.com" target="_blank">ŷ߹ۿ</a>| <a href="http://maomaots.com" target="_blank">պһѲ</a>| <a href="http://22youjizz.com" target="_blank">Ƶѹۿˬˬˬ</a>| <a href="http://qimiaodh.com" target="_blank">ۺ</a>| <a href="http://yyfass.com" target="_blank">߾Ʒһ </a>| <a href="http://8mav938.com" target="_blank">..ŷһ</a>| <a href="http://www-qwh.com" target="_blank">þþþƷҹѲ</a>| <a href="http://zhaosaohuo.com" target="_blank">ڵƷƵ</a>| <a href="http://456jjj.com" target="_blank">þþþ޾Ʒվ</a>| <a href="http://dzyong.com" target="_blank">ҹӰԺþþƷѿһ</a>| <a href="http://323799.com" target="_blank">91ۿ</a>| <a href="http://shguojing.com" target="_blank"> ۺ ŷ ˿</a>| <a href="http://zhnetbar.com" target="_blank">ƷAVһ</a>| <a href="http://zuche001.com" target="_blank">a߹ۿַȫ</a>| <a href="http://fense1.com " target="_blank">ƷŮ߹ۿ</a>| <a href="http://www-63228.com" target="_blank">޾ƷŮɫ</a>| <a href="http://81am.com" target="_blank">һһëѻƬ</a>| <a href="http://imfever.com" target="_blank">ƷþƵ</a>| <a href="http://jjwgzx.com" target="_blank">ձþһva</a>| <a href="http://dzhankong.com" target="_blank">Ƶ߹ۿ</a>| <a href="http://65123456.com" target="_blank">ľƷAVƬ</a>| <a href="http://gayhh.com" target="_blank">Avۺ辫Ʒ</a>| <a href="http://gedebai.com" target="_blank">ձһѸ</a>| <a href="http://tttui.com" target="_blank">޾Ʒѹۿ</a>| <a href="http://88109a.com" target="_blank">ձĻ</a>| <script> (function(){ var bp = document.createElement('script'); var curProtocol = window.location.protocol.split(':')[0]; if (curProtocol === 'https') { bp.src = 'https://zz.bdstatic.com/linksubmit/push.js'; } else { bp.src = 'http://push.zhanzhang.baidu.com/push.js'; } var s = document.getElementsByTagName("script")[0]; s.parentNode.insertBefore(bp, s); })(); </script> </body>