﻿<?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-beauty_beast</title><link>http://www.blogjava.net/beauty_beast/</link><description>上善若水   厚德载物 
</description><language>zh-cn</language><lastBuildDate>Sat, 04 Apr 2026 08:58:55 GMT</lastBuildDate><pubDate>Sat, 04 Apr 2026 08:58:55 GMT</pubDate><ttl>60</ttl><item><title>(转载)WebLogic Server 之安全(架构篇) </title><link>http://www.blogjava.net/beauty_beast/archive/2006/10/11/74500.html</link><dc:creator>柳随风</dc:creator><author>柳随风</author><pubDate>Wed, 11 Oct 2006 02:50:00 GMT</pubDate><guid>http://www.blogjava.net/beauty_beast/archive/2006/10/11/74500.html</guid><wfw:comment>http://www.blogjava.net/beauty_beast/comments/74500.html</wfw:comment><comments>http://www.blogjava.net/beauty_beast/archive/2006/10/11/74500.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/beauty_beast/comments/commentRss/74500.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/beauty_beast/services/trackbacks/74500.html</trackback:ping><description><![CDATA[
		<p align="center">WebLogic Server 之安全(架构篇) <br /></p>本文来自<a href="http://www.chinaitpower.com/2005Nov/2005-11-23/209281.html">http://www.chinaitpower.com/2005Nov/2005-11-23/209281.html</a><br />安全的变化可是WebLogic Server近期版本(7.0,8.1)最大的变化之一，全新的安全体系架构，既易于客户化安全解决方案的变化，又能实现细节和应用基础架构分离，使安全更易于部署，管理，维护和根据需求变化.同时也是支持最新的J2EE的标准和规范。本文试图从架构方面详细讲解WebLogic Server 7.0的安全体系及其相关细节，希望读者能对其机制有个框架层次和深度上的了解，文档参照了WebLogic Server 7.0的英文版手册，表述不当的地方，希望大家一起探讨。BTW,新出的WebLogic Server 8.1的安全架构完全和WebLogic Server 7.0一致。<b><br /><br />WebLogic Security的基本特征</b><p>开放，灵活的架构。<br />1. 基于标准，易于理解的设计<br />2. 基于WebLogic Server应用的端对端的安全（从主机到浏览器）<br />3. 能与已存在的安全规则(Scheme)进行集成，从而有利于保护企业投资。<br />4. 安全的工具集成到一个灵活，统一架构的系统以便于在企业级进行安全管理<br />5. 通过将公司的业务规则和安全policies的对应，能方便实现根据业务需求客户化应用安全<br />6. 易于Security Policy的更新。<br />7. 易于客户化安全解决方案的变化。<br />8. 因为具有模块化的架构，因此安全架构能根据不同公司的特殊需求不断变化<br />9. 能够配置多个Security Provider<br />10. 安全实现细节和应用基础架构分离，使安全更易于部署，管理，维护和根据需求变化<br />11. 当前的WebLogic Security providers提供了和应用无关的可行的安全规则。（Scheme）<br />12. 使用WebLogic custom security provider进行客户化工作<br />13. 通过管理控制台进行统一的安全规则(Rule),Security Policy和Security Provider的统一管理<br />14. 对最新的J2EE安全技术的支持，包括JAAS(Java Authentication and Authorization Service),JSSE(java Secure Sockets Extensions),and JCE(java Cryptography Extensions)<br /></p><br /><table cellspacing="2" cellpadding="2" width="95%" border="0"><tbody><tr><td bgcolor="#cccccc"><b>易于使用：</b>对最终用户而言，只需要登陆一次，就能访问所有资源（SSO），（限制在WebLogic Domain）<br /><b>可管理：</b>提供WebLogic security Provider,当前的Provider支持所有需要的安全功能，Security Data存放在WebLogic Server提供的LDAP Server中<br /><b>客户化:</b> WebLogic Security API,JAAS,JSSE custom security provider through SSPIs(WebLogic Server Security Service Provider Interfaces)</td></tr></tbody></table><b><br />WebLogic Server Security从6.x到7.0的变化: <br /><br /></b><table cellspacing="1" cellpadding="2" width="95%" border="1"><tbody><tr><td bgcolor="#c0c0c0">WebLogic Server 6.x </td><td bgcolor="#c0c0c0">WebLogic Server70 </td></tr><tr><td>安全的API </td><td>非常多的已存在API不推荐使用，BEA建议使用基于J2EE规范的标准接口实现应用 </td></tr><tr><td>JAAS认证 </td><td>JAAS认证增强已提供对于IIOP和t3客户端访问的LoginModules </td></tr><tr><td>审计 </td><td>无需实现WebLogic.security.Audit接口已在你的应用中加入审计功能，WebLogic Auditing provider已提供。 </td></tr><tr><td>定义weblogic.xml和weblogic-ejb-jar.xml和weblogic-ra.xml中的安全需求 </td><td>增强可以通过管理控制台定制 </td></tr><tr><td>系统密码 </td><td>没有指定的系统账号 </td></tr><tr><td>ACL </td><td>ACL已经不推荐使用，并由security policy代替 </td></tr><tr><td>用户和组 </td><td>仍然存在，并通过security policy将weblogic resouce赋予user,group或security role </td></tr><tr><td>6.x安全Realm </td><td>不推荐使用，但仍存在Realm Adapter当你进行安全的转换时 </td></tr><tr><td></td><td>支持多个Security Provider </td></tr><tr><td>SSL </td><td>支持JSSE标准和TLS v1(Transport Layer Security)协议 </td></tr><tr><td></td><td>支持J2EE JKS keystores </td></tr></tbody></table><br /><b>安全基础</b><br />Audit(审计):<br />提供对操作请求和请求输出的搜集，存储和发布，提供了对计算机行为的电子纪录。<br />在Security operations happens的前后，WebLogic调用Auditing Provider对特殊事件（基于audit criteria or serverity level）进行纪录同时写入LDAP Server,数据库或文件系统中。 
<p>Authentication(认证)<br />通过使用username/password组合来判断用户是否是一个系统的合法用户，主要回答"Who Are You?"的问题。<br /></p><b>1. Principal &amp; Subject<br /></b><table cellspacing="1" cellpadding="2" width="95%" border="1"><tbody><tr><td>Principal认证通过后赋予用户或组的身份（Identity）Subject JAAS 要求通过subject，Container知道客户信息，包括pricipals</td></tr><tr><td>·Relationships Among Users, Groups, Principals and Subjects</td></tr></tbody></table><br /><span lang="EN-US"><img height="329" alt="" src="http://dev2dev.bea.com.cn/images/article/server030602/image001.gif" width="410" v:shapes="_x0000_i1027" /></span><br /><br />成功的认证后，principal签名同时存在一个subject中，principal validation provider签名，并且Authentication Provider的LoginModule存放pricipal到subject中，当caller试图访问存在subject中的pricipal时，principal validation provider认证pricipal自签名后没有改变，然后principal回给caller. 
<p>2. JAAS<br />JAAS实现了java版本的PAM模块，允许应用和后层采用的认证技术，同时允许再不更改应用的情况下采用更新的或者变化了的安全认证技术。<br />WebLogic sever使用JAAS在胖客户端认证和内部认证,因此，只有客户化Authentication Provider的开发者和远程胖客户端需要直接使用JAAS, Thin 客户端和在container内的胖客户端(EJB throgh servelt)不需要直接使用JAAS.</p><p>·JAAS LoginModules<br />认证用户，发布Subject.不用于perimeter authentication</p><p>·JAAS Control Flags<br />Multiple Authentication provider configured<br />REQUIRED : 必须成功，如果失败，authentication继续其他的LoginModule(当前)<br />REQUISITE:必须成功，成功继续，不成功，将返回应用。<br />SUFFICIENT：不需要成功，如果成功，返回应用，如果失败，继续LoginModule List<br />OPTIONAL:允许成功或失败，但必须通过一个LoginModule</p><p>·CallBackHandlers<br />CallbackHandler 是为将参数或复杂对象传入方法中的一种高度灵活的规范。包括NameCallback:return username<br />PasswordCallback:return password<br />TextInputCallback: return 用户在Login form中输入的数据。<br />Security service将不同的Callback传入CallbackHandler要求获取不同类型的信息，CallbackHandler的实现决定如何根据不同的Callback获取和显示。</p><p>·Mutual Authentication<br />客户端和服务端都需要相互认证，WebLogic Server支持2-way SSL</p><p>·Identity Assertion Providers and LoginModules<br />当和LoginModules一起使用时，Identity Assertion provider支持Sigle-sign-on,LoginModule包含：<br /></p>→ 客户化开发的Authentication Provider<br />→ BEA 开发的WebLogic Authentication Provider<br />→ 第三方的Authentication Provider<br /><b><br /></b>·Identity Assertion and Tokens<br />Identity Assertion Provider支持用户名的mapping,对应到weblogic server中的一个正确的token,能够开发Identitty Assertion Provider已支持多种token类型，但 只有一种为Active状态，同时只有一种Identity Assertion provider最终执行这一mapping的操作。 <br /><br />Authentication类型<br />WebLogic authentication provider支持的认证类型有：<br /><table cellspacing="1" cellpadding="2" width="95%" border="1"><tbody><tr><td>用户名/口令认证:SSL,HTTPS<br />CA(Certification Authentication):当SSL或HTTPS客户请求发起时，WebLogic Server回应客户一个数字认证（digital certification），客户证实这个数字认证从而建立SSL联结，数字认证是由仍证机构CA发放。<br />Perimeter Authentication<br />主要用于应用服务器外的远程用户的认证过程<br />远程用户指定assert identity和相应得材料来完成。<br />Authentication agent能采用各种格式，VPN,Firewall,企业级认证服务。<br />Authentication agent处理认证过程同时导致artifact和token产生。Identity assertion的概联使weblogic server能使用perimeter authentication schemes提供的认证机制，比如Checkpoint's OPSEC,the emerging Security Assertion Markup Language(SAML), enhancement of Common Secure Interoperability(CSI)v2完成功能。</td></tr></tbody></table><span lang="EN-US"><br /><img height="249" alt="" src="http://dev2dev.bea.com.cn/images/article/server030602/image002.gif" width="554" v:shapes="_x0000_i1028" /></span><br /><br />3. Authorization<br /><br />Authorization在用户和weblogic 控制的资源中交互的进程，基于用户身份或其他信息。<br />Authorization主要回答的问题是"What can you access" 
<p>WebLogic resource:包含<br />管理资源<br />应用资源<br />COM资源<br />EIS资源<br />EJB<br />JDBC<br />JMS<br />JNDI<br />Server资源<br />URL资源<br />Web Service资源</p><p>·Security Policy<br />代替ACL,主要回答的问题是：Who have access to WebLogic resource?<br />当你将weblogic resource和用户，组，security role相关联时，Security Policy产生。<br />Security Policy存储在Authorization Provider的数据库中，当前的WebLogic Authorization provider存在LDAP Server中<br />BEA推荐在security role上建立security policy.</p><p>·ContextHandlers<br />从resource container中获取context和有关container的信息，为security provider做出访问或权限mapping决定的提供信息。<br /></p>·Access Decisions<br />回答了"is access allowed?"的问题.Access Decision结果为PERMIT,DENY,ABSTAIN 
<p>·Adjudication<br />解决了当多个Authorization Provider提供授权时产生的冲突问题。Adjudication Server权衡多个Access Decision的结果，并且决定最终permit或者deny的结果。当唯一的Authorization provider返回结果为ABSTAIN时，Adjudication provider决定怎样处理。</p><p>·SSL<br />当前WebLogic Server支持1-way方式的SSL认证，使用管理控制台，能够配置2-way的SSL.<br />需要配置privite key,包括public key的数字证书(digital certificate)，和至少由一个CA认证的数字证书(digital certificate),有可能需要安装root trusted CA's digital certificate.</p><p>为获取数字证书，自己生成public key,private key和认证签名请求(CSR)(包含public key),同时将CSR发到认证机构已得到签名的证书。<br />Public key存放在WebLogic domain目录下的文件系统中<br />Private key和通过认证的证书能存放在文件系统或通过WebLogic keystore provider提供的keystore中。</p><p>SSL提供<br />1） 应用间沟通的机制便于认证双方的身份<br />2） 应用间交换数据的加密。</p><p>Server authentication:WebLogic Server使用经过CA签名的数字证书，向客户认证。当客户不需要向Server证明自己的数字证书时，这种联接的方式叫1-way SSL认证。<br />Client Identity Verification:可选的，客户必须向WebLogic Server出示数字证书,WebLogic Server证实数字证书由CA发放同时建立SSL联结，这种方式叫2-way SSL认证。<br />Confidentiality所有客户的请求和Server的相应在网络上的传输都加密。<br />数据完整性:客户端和服务端传输的数据受保护。</p><p>SSL Tunneling<br />SSL是在基于IP协议上Tuneled.意味着每个SSL纪录被封装同时有使它在其他协议上发送的header来包装。</p><p>One-way/Two-way SSL认证<br />在One-way情况下，客户端通过两种方式检查数字认证<br />1． 检查数字认证在它的可信任认证机构中(CA)<br />2． 检查认证中的主机名和Server一致。<br /><br /><span lang="EN-US"><img height="292" alt="" src="http://dev2dev.bea.com.cn/images/article/server030602/image003.gif" width="553" v:shapes="_x0000_i1029" /></span></p>Domestic SSL和Exportable SSL <br /><table cellspacing="1" cellpadding="2" width="95%" border="1"><tbody><tr><td>Exportable SSL支持512位证书和40-或50-位数据加密（标准支持）<br />Domestic SSL支持768位和 1024位证书和128位数据加密(Ask BEA Sales)</td></tr></tbody></table><br /><b>Security Realm（安全域） </b><br />Security Realm 包含整套保护WebLogic资源的机制，由一系列配置好的Security Provider,用户，组，security role,和security policy构成<br /><br /><span lang="EN-US"><img height="237" alt="" src="http://dev2dev.bea.com.cn/images/article/server030602/image004.gif" width="554" v:shapes="_x0000_i1025" /></span><br /><br />·用户 <br />当用户访问WebLogic Server时，通过JAAS LoginModule到认证提供者，如果通过认证，weblogic server通过一个线程将principal和用户相联，在用户线程开始执行代码前，WebLogic Server检查WebLogic resource的policy和principal以确认用户取得相应的要求的权限。 
<p>当定义一个用户时，WebLogic 对用户的密码进行加密，同时当WebLogic接收到客户请求时，客户端请求的密码被hash加密,同时和以存在的加密密码比较以判断是否符合。</p>·组<br />组是用户的逻辑组合。在一个Security Realm中，所有的用户和组必须唯一。 
<p>·Security Role<br />动态赋予用户和组，根据用户名，组的成员或一天中的某个时间。 <br />能在一个WebLogic server的域中，针对单个应用定义资源。</p><p>在weblogic 6.x中，security role只能赋予web application 和 EJB, 在 70版本中，用户的security role能扩展到所有的WebLogic资源。</p>·Security Policy <br />Policy是WebLogic资源和用户，组，和Security Role的关联。用以保护WebLogic资源不收不被授权的用户访问。它代替了6.x中用的ACL 
<p>·Security Provider<br />对应用提供安全服务的模块，用以保护WebLogic资源，客户能够直接使用WebLogic Server直接提供的security provider,也能购买第三方的产品进行集成，或者开发自己的security provider.</p><p>Security Provider数据库<br />Security Provider数据库包含用户，组，role和policy,以及有些安全提供者使用的密码。比如Authentication provider要求用户和组的信息。Authorization provider要求安全policy方面的信息，Role Mapping Provider要求安全Role方面的信息，Credential Mapping provider要求resource Adapter用于联结后台的EIS系统的密码信息。这些信息都存放在数据库中。</p><p>Security provider的数据库在递一次使用security provider时候被初始化，所做的工作是<br />当WebLogic实例重起。 <br />当一个security provider的Mbean被调用。</p><p>当你在一个security realm中配置了多个security provider,那么这些security provider将是用同一个security provider数据库。当你在不同的security realm中配置两个security provider或两个相同的security provider,他们将使用不同的provider database.,在一个时间只有一个security Realm处在激活状态。<br /><br />·Embedded LDAP Server<br />Embedded LDAP Server被当前的WebLogic security provider使用作为数据库存储用户，组，role和policy. 是一个完整的LDAP Server,支持的操作有：<br />在LDAP Server中访问和修改<br />使用LDAP browser导入或导出安全的数据<br />被WebLogic security provider读写访问。<br />WebLogic Server不支持在Embedded LDAP Server中加入属性(Attribute)<br /></p><table cellspacing="2" cellpadding="2" width="95%" border="1"><tbody><tr><td bgcolor="#c0c0c0">WebLogic Security Provider </td><td bgcolor="#c0c0c0">Embedded LDAP Server Usage </td></tr><tr><td>Authentication </td><td>Stores user and group information. </td></tr><tr><td>Identity Assertion </td><td>Stores user and group information. </td></tr><tr><td>Authorization </td><td>Stores security roles and security policies. </td></tr><tr><td>Adjudication </td><td>None. </td></tr><tr><td>Role Mapping </td><td>Supports dynamic role associations by obtaining a computed set of roles granted to a requestor for a given WebLogic resource. </td></tr><tr><td>Auditing </td><td>None. </td></tr><tr><td>Credential Mapping </td><td>Stores Username-Password credential mapping information. </td></tr></tbody></table><p>·Security Provider的类型<br />Authentication Provider<br />主要用于:<br />用户名/密码的认证<br />直接和WebLogic Server基于证书的认证<br />通过外部的web server基于HTTP证书的认证代理</p><p>Identity Assertion Provider主要用于处理基于perimeter的认证和多个安全另牌(token)类型和协议。</p><p>在安全域中必须至少有一个Authentication provider,同时你可以配置多个Authentication Provider,意味着你有多个LoginModules,每个处理不同的认证。管理员配置不同的provider以便于当用户登陆系统时多个LoginModules怎样被调用。<br />Identity Assertion Provider<br />使用客户提供的可能存在于请求外的Token识别客户的身份。<br /></p><p><b>WebLogic Security Service Architecture(架构) <br /><br /></b>Security Framwork <br /><br /><span lang="EN-US"><img height="374" alt="" src="http://dev2dev.bea.com.cn/images/article/server030602/image005.gif" width="552" v:shapes="_x0000_i1030" /></span><br /><br />security framework提供的应用API被security和应用开发者定义安全服务使用，同时做为weblogic Container(Web和EJB),资源 Container和安全提供者间的中间层。</p><p>下面详细描述各层间的交互<br /></p>·认证过程 <br /><br /><span lang="EN-US"><img height="257" alt="" src="http://dev2dev.bea.com.cn/images/article/server030602/image007.gif" width="553" v:shapes="_x0000_i1026" /></span><p>当用户通过username/passwd组和登陆系统时，WebLogic建立信任同时对于每个JAAS请求回送包含principal的subject。</p><p>通过认证后，一个authentication context建立。</p><p><br />·Identity Assertion Process<br /><br /><span lang="EN-US"><img height="262" alt="" src="http://dev2dev.bea.com.cn/images/article/server030602/image009.gif" width="553" v:shapes="_x0000_i1031" /></span><br /></p><p>主要用于perimeter authentication过程，当使用perimeter authentication时，外部系统传入token给Identity Assertion Provider.如果通过，Identity Assertion provider将token对应到相应的username,并将回送username到weblogic server,然后由WebLogic Server将username送回到JAAS的CallbackHandler并传递到每个配置的Authentication Provider的LoginModule模块，最后LoginModule能产生包含正确principal的Subject.<br /><br />·Principal Validation Process <br /><br /><span lang="EN-US"><img height="257" alt="" src="http://dev2dev.bea.com.cn/images/article/server030602/image010.gif" width="553" v:shapes="_x0000_i1032" /></span><br /><br />WebLogic将subject传递给principal validation Provider,后者将对principal进行签名并通过WebLogic Server把他回送客户的应用。以后principal validation provider将用来证明经过签名的subject中的principal没有被别人改过。</p><br />Authorization process<br /><br /><span lang="EN-US"><img height="236" alt="" src="http://dev2dev.bea.com.cn/images/article/server030602/image011.gif" width="551" v:shapes="_x0000_i1033" /></span><br /><br />Authenorization process最初由用户或系统要求使用WebLogic Server资源时触发。Resource Container处理被请求的资源。 Resource Container调用WebLogic Security Framwork同时传递给它相应的请求subject和请求resource的信息。WebLogic framework调用配置的role mapping provider,同时将传递的信息转换成相应的格式，由Role Mapping provider回送role的列表给WebLogic security framework, Authorization provider决定subject是否有访问相关资源的权限，如果配有多个Authorization provider,WebLogic security framework决定任何有冲突的访问判断传递给Adjudication Provider,最后由Adjudication Provider决定最终的访问许可。 <br /><p>·Role Mapping Process<br /><br /><span lang="EN-US"><img height="236" alt="" src="http://dev2dev.bea.com.cn/images/article/server030602/image012.gif" width="551" v:shapes="_x0000_i1034" /></span><br /><br />WebLogic security framework调用每个role mapping provider以得到请求的role list，如果security policy指定请求赋予相应的role,role就被加入到subject可用的role list中，同时role list 返回给WebLogic security framwork做为下一步访问控制的判断。<br /><br />·Auditing Process <br /><br /><span lang="EN-US"><img height="236" alt="" src="http://dev2dev.bea.com.cn/images/article/server030602/image013.gif" width="551" v:shapes="_x0000_i1035" /></span><br /><br />Authentication Provider除了提供认证服务外，需要发送audit事件，初始化AuditEvent对象（包含audit的event类型和audit的级别）,Authentication Provider随后调用Audit Provider,同时传递AuditEvent对象，当AuditEvent和Auditing Provider配置的需要Audit的信息相同时，将被记录到文件系统，数据库或其他存储介质中。<br /><br />·Credential Mapping Process <br /><br /><span lang="EN-US"><img height="247" alt="" src="http://dev2dev.bea.com.cn/images/article/server030602/image014.gif" width="551" v:shapes="_x0000_i1036" /></span><br /><br />最初由应用组件（包括jsp,ejb,resource adapter）访问EIS系统（包括Oracle,SQL Server或其他）调用 WebLogic security framework时引发。应用组件传递subject(who),weblogic resource(what)和需要的credential。WebLogic security framework将请求传递给credential mapping provider,credential mapping provider处理请求，并将数据库中的credential回送weblogic security framework,最后将credential返回给组件层，组件层使用credential以访问EIS系统。<br /><br />·weblogic security provider</p><p>缺省自带的security provider,如果不能满足你的需求，可以自己开发客户化的security provider.<br />→ 从weblogic.security.spi包中实现正确的SSPIs(security service provider interface)以生成security provider.<br />→ 建立Mbean的定义文件（MDF）,使用Weblogic MbeanMaker工具生成Mbean类型。<br /><br /><span lang="EN-US"><img height="377" alt="" src="http://dev2dev.bea.com.cn/images/article/server030602/image015.gif" width="553" v:shapes="_x0000_i1037" /></span></p><p>weblogic authentication provider</p><p>代替了6.xFile realm的功能，支持用户名/密码认证，利用embedded LDAP Server存储用户和组的信息。</p><p>支持LDAP Authentication访问外部的LDAP 存储（Open LDAP,Netscape iPlanet,Microsoft Active Directory,Novell NDS）</p><p>WebLogic Identity Assertion Provider</p><p>支持X.509认证和CSIv2(CORBA Common Secure Interoperability version 2)身份认证。<br />WebLogic Identity Assertion provider验证token 的类型，然后将X.509数字认证和x.501的单一的名字和WebLogic 的用户名相对应。同样指定一信任的客户principal列表便于CSIv2的身份确认。统配符*用于指定所有的信任的principal.如果客户没有列入到信任的principal中，CSIv2 Identity Assertion将失败。<br />支持的token类型有<br /></p>→ AU_TYPE -- WebLogic authenticationdUser<br />→ X509_TYPE X.509客户端<br />→ CSI_PRINCIPAL_TYPE CSIV2 principal name<br />→ CSI_ANONYMOUS_TYPE CSIV2 anonymous identity<br />CSI_X509_CERTCHAIN_TYPE CSIv2 X.509 certificate chain identity<br />→ CSI_DISTINGUISHED_NAME_TYPE CSIv2 distinguished name identity<br /><p>WebLogic principal validation provider<br />WebLogic authorization provider<br />WebLogic adjudication provider<br />WebLogic role mapping provider<br />·WebLogic auditing provider<br />WebLogic credential mapping provider<br />以上provider功能在前面已经做了基本介绍。在此省略。<br /><br />·Weblogic keystore provider<br />使用sun公司sdk提供的keystore实现，利用标准的JKS keystore类型，将keystore做为文件（每台机一个）来保存。有两种keystore 文件<br />→ 一个文件保存CA认证(CA certificates)<br />→ 另一个文件保存server的private key<br /><br />·WebLogic realm adapter provider<br />主要用来提供和6.x安全域的兼容。Realm Adapter提供<br />Authentication(include Identity Assertion Provider)<br />Authorization<br />Auditing<br />Adjudication</p><p>仅仅用于升级且不建议使用。<br /><!--文章其他信息--></p><div class="dot001"><img height="1" alt="" src="http://dev2dev.bea.com.cn/images/_.gif" width="100%" /></div><table cellspacing="0" cellpadding="3" width="100%" border="0"><tbody><tr valign="bottom"><td colspan="2" height="20"> <span class="h2b">作者简介</span></td></tr><tr><td valign="top" align="middle"></td><td>聂健是（dev2dev ID: ericnie） Horizon Software Ltd. 技术顾问</td></tr></tbody></table><img src ="http://www.blogjava.net/beauty_beast/aggbug/74500.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/beauty_beast/" target="_blank">柳随风</a> 2006-10-11 10:50 <a href="http://www.blogjava.net/beauty_beast/archive/2006/10/11/74500.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>（转载）自定义WebLogic LDAP Authentication Provider</title><link>http://www.blogjava.net/beauty_beast/archive/2006/10/11/74495.html</link><dc:creator>柳随风</dc:creator><author>柳随风</author><pubDate>Wed, 11 Oct 2006 02:45:00 GMT</pubDate><guid>http://www.blogjava.net/beauty_beast/archive/2006/10/11/74495.html</guid><wfw:comment>http://www.blogjava.net/beauty_beast/comments/74495.html</wfw:comment><comments>http://www.blogjava.net/beauty_beast/archive/2006/10/11/74495.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/beauty_beast/comments/commentRss/74495.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/beauty_beast/services/trackbacks/74495.html</trackback:ping><description><![CDATA[
		<p align="center">
				<span class="h1b">
						<strong>自定义WebLogic LDAP Authentication Provider</strong>
						<br />
				</span>
				<br />
				<br />
		</p>
		<table cellspacing="0" cellpadding="0" width="100%" border="0">
				<tbody>
						<tr>
								<td height="64">时间：2005-04-20<br />作者：<a href="http://dev2dev.bea.com.cn/author/55.html">马晓强</a><br />浏览次数：
<script language="JavaScript" src="http://203.81.25.103/cgi-bin/beadevcount.cgi?d_id=502" type="text/JavaScript"></script>
 152611 <br />本文关键字：</td>
								<td>
										<table class="box_content" cellspacing="0" cellpadding="0" border="0">
												<tbody>
														<tr>
																<td>
																</td>
														</tr>
												</tbody>
										</table>
								</td>
						</tr>
				</tbody>
		</table>
		<!-- 提取技术文章 -->
		<div class="beas">
				<img height="1" alt="" src="http://dev2dev.bea.com.cn/images/dot6B6B6B.gif" width="100%" />
		</div>目录 
<p><a href="http://dev2dev.bea.com.cn/techdoc/200504502.html#1">1 J2EE Security 和 LDAP Security</a><br /><a href="http://dev2dev.bea.com.cn/techdoc/200504502.html#2">2 JAAS和WebLogic Security Framework</a><br /><a href="http://dev2dev.bea.com.cn/techdoc/200504502.html#3">3 了解WebLogic LDAP Authentication Provider</a><br /><a href="http://dev2dev.bea.com.cn/techdoc/200504502.html#4">4 定制自己的Custom LDAP Authentication Provider</a><br /><a href="http://dev2dev.bea.com.cn/techdoc/200504502.html#5">5 部署中的注意事项</a><br /><a href="http://dev2dev.bea.com.cn/techdoc/200504502.html#6">6 结束语</a><br /><a href="http://dev2dev.bea.com.cn/techdoc/200504502.html#7">7 参考资料</a></p><p>　　从WebLogic Server 7.0开始，WebLogic Server的安全机制有了全面的改变，实现了一个更加规范的基于JAAS的Security Framework，以及提供了一系列设计良好的Security Service Provider Interface。这样我们可以根据自己的具体需求，通过Custom Security Authentication Provider来实现安全上的定制功能。</p><p>　　本文将以WebLogic（WebLogic Server 8.1） Security和 LDAP为基础，介绍Custom LDAP Authentication Provider如何给我们带来更多的灵活性，和系统安全设计上更多的空间；以及讨论如何实现一个Custom LDAP Authentication Provider和部署过程中的一些良好经验。</p><p>　　由于本文涉及到的范围太广，不可能一一详细讨论；为了使没有相关基础的读者也能够阅读理解本文，因此我将在文章前半部分，试图通过最简洁扼要的描述，来使大家对于J2EE Security，WebLogic Security Framework以及LDAP 等有一个初步的清晰认识；进而可以开发出自己的LDAP Authentication Provider。因此很多地方做了比较有限的描述或者介绍，更多详细的内容可以参考文后附带的参考资料或者文中给出的链接。<a id="1" name="1"></a><br /><br /><b>1 J2EE Security 和 LDAP Security</b><br />　　Sun J2EE推出以来，其安全部分的规范就一直倍受关注。我们最常见到安全规范的两个方面分别是Servlet Security 和 EJB Security。目前绝大多数的Servlet容器，J2EE容器都能很好的支持这些安全规范。</p><p>　　WebLogic Server作为业界领先的J2EE服务器对J2EE Security的支持是非常优秀的。我们这里将结合WebLogic Security和使用越来越广泛的LDAP做一个简要的介绍，这些是设计开发Custom LDAP Authentication Provider的技术基础。</p><p><b>1.1 Authentication 和Authorization<br /></b>　　这里需要大家先明确安全上的两个重要名词：一个是认证（Authentication），一个是授权（Authorization）。认证是回答这个人是谁的问题，即完成用户名和密码的匹配校验；授权是回答这个人能做什么的问题。我们讨论的J2EE Security包括Declarative Authorization和Programmatic Authorization，即一个是通过web.xml，ejb-jar.xml等部署描述符中的安全声明通过容器提供的服务来完成权限控制的；一个是通过HttpServletRequest.isUserInRole()和EJBContext.isCallerInRole()这样的编程接口在应用中自己完成权限控制的。</p><p><b>1.2 资源（Resource）和Security Role<br />　　</b>资源原本只包括 Web Resource和EJB Resource，但在WebLogic Security中扩展到几乎任何一个WebLogic Platform中的资源，具体可以参考<a href="http://e-docs.bea.com/wls/docs81/secwlres/types.html#1213777" target="_blank">http://e-docs.bea.com/wls/docs81/secwlres/types.html#1213777</a>。授权就是针对资源的访问控制。</p><p>　　J2EE Security是基于Security Role的。我们可以将一组资源与一个Security Role进行关联来达到控制的目的——只有拥有该Role权限的用户才能够访问这些资源。简单的说，我们可以通过给用户分配不同的Security Role来完成权限的控制。复杂的情况下包括用户/用户组，以及Principal和Role的映射关系等等。下面是一个声明性安全在web application（war包中WEB-INF/web.xml）中的示例：</p><table cellspacing="0" cellpadding="4" width="98%" align="center" bgcolor="#e8e8e8" border="0"><tbody><tr><td>&lt;web-app&gt;<br />　&lt;security-constraint&gt;<br />　&lt;web-resource-collection&gt;<br />　　&lt;web-resource-name&gt;Success&lt;/web-resource-name&gt; <br />　　&lt;url-pattern&gt;/welcome.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;webuser&lt;/role-name&gt; <br />　&lt;/auth-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;default&lt;/realm-name&gt; <br />　&lt;/login-config&gt; <br />　&lt;security-role&gt; <br />　　&lt;role-name&gt;webuser&lt;/role-name&gt; <br />　&lt;/security-role&gt;<br />&lt;/web-app&gt;</td></tr></tbody></table><p>　　只有拥有角色webuser的用户才能够访问welcome.jsp页面，否则容器会返回401无权访问的错误。更多信息请参考<a href="http://e-docs.bea.com/wls/docs81/security/index.html" target="_blank">http://e-docs.bea.com/wls/docs81/security/index.html</a>。</p><p>　　同时我们需要在weblogic.xml（war包中WEB-INF/weblogic.xml）中对security role和principal进行映射关系的配置：</p><table cellspacing="0" cellpadding="4" width="98%" align="center" bgcolor="#e8e8e8" border="0"><tbody><tr><td>&lt;weblogic-web-app&gt; <br />　&lt;security-role-assignment&gt; <br />　　&lt;role-name&gt;PayrollAdmin&lt;/role-name&gt; <br />　　&lt;principal-name&gt;Tanya&lt;/principal-name&gt; <br />　&lt;/security-role-assignment&gt;<br />&lt;/weblogic-web-app&gt;</td></tr></tbody></table><p>　　这样拥有Principal “Tanya”的用户（Principal将封装到Subject中，用户将和Subject关联）将会拥有PayrollAdmin的权限。</p><p>　　<i>注意：一般情况下为了简化设计，本文中将假设security role即是principal name（如果不配置security-role-assignment，WebLogic会默认做此假设）。即上例中Principal-name也为PayrollAdmin。</i></p><p><b>1.3 LDAP Security<br />　　</b>LDAP是轻量级目录服务（Lightweight Directory Access Protocol）。越来越多的应用开始采用LDAP作为后端用户存储。在安全上，LDAP Security是基于ACL（Access Control List）的，它通过给一个用户组分配LDAP 操作资源（比如对一个子树的查询，修改等）来最终完成权限的控制。因此在LDAP中，授权工作是以用户组为单位进行的。一个用户组一般来说是拥有如下一组属性的LDAP Entry：</p><p align="center"><img height="165" src="http://dev2dev.bea.com.cn/images/webser/image2005041901.gif" width="500" /><br />图1-3-1</p><p>　　其中objectclass可以为groupOfUniqueNames或者groupOfNames，它们对应的组成员属性分别是uniquemember和member。如果是动态组，objectclass为groupOfURLs。动态组一般应用在成员可以通过某种业务逻辑运算来决定的情况下。比如，经理为ZHANGSAN的全部员工。下面是一个典型的动态组，memberURL属性定义了哪些entry属于该组：</p><p align="center"><img height="130" src="http://dev2dev.bea.com.cn/images/webser/image2005041902.gif" width="550" /><br />图1-3-2</p><p><b>　　</b>从图1-3-1中我们可以看出，用户WANTXIAOMING，ZHANGSAN，LISI属于组HR Managers。这种组和成员的关系是通过属性uniquemember来决定的。同时LADP Group 支持嵌套，即一个组可以是另外一个组的成员，比如我们将Accounting Managers组分配给HR Managers组作为其成员：</p><p align="center"><img height="185" src="http://dev2dev.bea.com.cn/images/webser/image2005041903.gif" width="500" /><br />图1-3-3</p><p><b>　　</b>这样将表示Accounting Managers中的成员，同时也是组HR Managers的成员。通过这种层级关系可以使权限分配变的更加灵活。</p><p><b>　　</b>下面是一些名词的解释，希望大家对LDAP有更好的理解：<br /><b>　　</b>a) Objectclass —— LDAP对象类，抽象上的概念类似与一般我们理解的class。根据不同的objectclass，我们可以判断这个entry是否属于某一个类型。比如我们需要找出LDAP中的全部用户：(objectclass=person)再比如我们需要查询全部的LDAP组：(objectclass=groupOfUniqueNames)<br /><br /><b>　　</b>b) Entry —— entry可以被称为条目，或者节点，是LDAP中一个基本的存储单元；可以被看作是一个DN和一组属性的集合。 属性可以定义为多值或者单值。<br /><br /><b>　　</b>c) DN —— Distinguished Name，LDAP中entry的唯一辨别名，一般有如下的形式：uid=ZHANGSAN, ou=staff, ou=people, o=examples。LDAP中的entry只有DN是由LDAP Server来保证唯一的。</p><p><b>　　</b>d) LDAP Search filter ——使用filter对LDAP进行搜索。 Filter一般由 (attribute=value) 这样的单元组成，比如：(&amp;(uid=ZHANGSAN)(objectclass=person)) 表示搜索用户中，uid为ZHANGSAN的LDAP Entry．再比如：(&amp;(|(uid= ZHANGSAN)(uid=LISI))(objectclass=person))，表示搜索uid为ZHANGSAN, 或者LISI的用户；也可以使用*来表示任意一个值， 比如(uid=ZHANG*SAN)，搜索uid值以 ZHANG开头SAN结尾的Entry。更进一步，根据不同的LDAP属性匹配规则，可以有如下的Filter： (&amp;（createtimestamp&gt;=20050301000000）(createtimestamp&lt;=20050302000000))，表示搜索创建时间在20050301000000和20050302000000之间的entry。<br /><b>　　</b>Filter中 “&amp;” 表示“与”；“!”表示“非”；“|”表示“或”。根据不同的匹配规则，我们可以使用“=”，“~=”，“&gt;=”以及“&lt;=”，更多关于LDAP Filter读者可以参考LDAP相关协议：<a href="http://www.ietf.org/rfc/rfc2254.txt" target="_blank">http://www.ietf.org/rfc/rfc2254.txt</a>。</p><p><b>　　</b>e) Base DN —— 执行LDAP Search时一般要指定basedn，由于LDAP是树状数据结构，指定basedn后，搜索将从BaseDN开始，我们可以指定Search Scope为：只搜索basedn（base），basedn直接下级（one level），和basedn全部下级（sub tree level）。</p><p><b>　　</b>下面是一个典型的LDAP Tree结构，右侧显示Entry uid=ZHANGSAN, ou=staff, ou=people, o=examples的属性，该entry代表了一个名字叫张三的用户：</p><p align="center"><img height="328" src="http://dev2dev.bea.com.cn/images/webser/image2005041904.gif" width="600" /><br />图1-3-4<a id="2" name="2"></a></p><p><b>2 JAAS和WebLogic Security Framework</b><br /><b>　　</b>现在越来越多的人开始了解JAAS，使用JAAS。WebLogic Security Framework就是基于JAAS的。因此我们需要对此有一个非常准确的理解才能够设计开发Custom Authentication Provider。</p><p><b>　　</b>下面我们从几个名词入手，了解JAAS和 WebLogic Security Framework的关键之处：</p><p><b>2.1 Principal，Subject和LoginModule<br />　　</b>a) Principal <br /><b>　　</b>当用户成功验证后，系统将会生成与该用户关联的各种Principal。我们这里将Principal进行简化的设计，认为一个Principal就是用户登录帐号和它所属于的组（LDAP Group）。这样当用户登录成功后，我们将会在LDAP中执行搜索，找出用户属于哪些组，并将这些组的名字，或者其标识作为Principal返回。这样，当用户在LDAP中属于某一个组，并且这个组的名字对应到 web.xml （或者ejb-jar.xml）中的Security role，那么这个用户就可以看作拥有访问这个Security Role定义的资源的权限。<br /><b>　　</b>在WebLogic Security Framework中，这个LDAP Group的名字（Principal）和Security Role的映射关系，可以通过一个 Role Mapping Provider来实现动态的匹配，即用户的动态权限控制。比如在运行时根据某一个业务逻辑来决定用户是否拥有某一个权限。关于Role Mapping Provider，读者可以参考下面链接的内容：<a href="http://e-docs.bea.com/wls/docs81/dvspisec/rm.html#1145542" target="_blank">http://e-docs.bea.com/wls/docs81/dvspisec/rm.html#1145542</a>。</p><p><b>　　</b>b) Subject<br /><b>　　</b>JAAS规定由Subject封装用户以及用户认证信息，其中包括Principals。下面是WebLogic Security Framework中Subject的组成图示：</p><p align="center"><img height="385" src="http://dev2dev.bea.com.cn/images/webser/image2005041905.gif" width="500" /><br />图2-1-1</p><p><b>　　</b>这样当用户试图访问一个受限的J2EE资源时，比如一个web URL，或者一个 EJB Method（可以在web.xml或者ejb-jar.xml中定义，由Security Role控制），WebLogic Security Framework将会通过 Authorization Provider检查用户当前的Subject中是否包含有是否可以访问受限资源的Principals。由于Principals将和J2EE Security Role在weblogic.xml中定义一个映射关系（或者通过其他业务逻辑来确定这种关系），因此通过这样的关系，可以最终知道用户是否有某一个J2EE Resource的访问权限。</p><p><b>　　</b>c) LoginModule<br /><b>　　</b>JAAS LoginModule是一个Authentication Provider必须的组成部分。LoginModule是认证的核心引擎，它负责对用户身份进行验证，同时将返回与用户关联的Principals（用户登录帐号，以及LDAP Groups），然后放入Subject中，供后续的访问控制使用。<br /><b>　　</b>我们将在LoginModules中完成LDAP的相关认证，查询操作，将用户在LDAP中所属于的组搜索出来，作为认证后的结果封装到Subject中返回。</p><p><b>2.2 WebLogic Authentication认证过程</b><br /><b>　　</b>下面我们了解一下WebLogic的认证过程。以下图片来自<a href="http://e-docs.bea.com/wls/docs81/dvspisec/atn.html" target="_blank">http://e-docs.bea.com/wls/docs81/dvspisec/atn.html</a> 我将其中主要部分进行说明。</p><p align="center"><img height="452" src="http://dev2dev.bea.com.cn/images/webser/image2005041906.gif" width="600" /><br />图2-2-1</p><p><b>　　</b>Security Framework在WebLogic Server启动时初始化Authentication Provider（5）。当有认证请求进入时，Security Framework首先将通过AuthenticationProvider.getLoginModuleConfiguration()来获取一个AppConfigurationEntry对象。通过AppConfigurationEntry（详见<a href="http://java.sun.com/security/jaas/apidoc/javax/security/auth/login/AppConfigurationEntry.html" target="_blank">http://java.sun.com/security/jaas/apidoc/javax/security/auth/login/AppConfigurationEntry.html</a> ）可以初始化一个LoginModule。初始化LoginModule的方法为：public void initialize(Subject subject, CallbackHandler callbackHandler, Map sharedState, Map options)，可以看到里面有Subject, CallbackHandler等等重要参数。<br /><b>　　</b>被实例化的JAAS LoginModule将完成用户的一系列验证任务。验证完成后，在（6a）中principals将被Principal Validation Provider签名，在（ 6b）中存放到与用户关联的Subject里面。Security Framework最后将拿着该用户的Subject去完成其他权限控制等任务。<a id="3" name="3"></a><br /><br /><b>3 了解WebLogic LDAP Authentication Provider</b><br /><b>　　</b>现在我们有信心了解一下WebLogic LDAP Authentication Provider的工作原理了。这里将以WebLogic提供的iPlanet Authentication Provider的配置为例进行说明。在这里也需要明确说明一下，为了方便进行描述，我们将实际属于LoginModule的行为也一并归结到Provider中。没有单独将两个的行为分开，目的是为了突出整个完整的过程。</p><p><b>3.1 iPlanet Authentication Provider配置</b></p><p align="center"><img height="454" src="http://dev2dev.bea.com.cn/images/webser/image2005041907.gif" width="600" /><br />图3-1-1</p><p><b>　　</b>从上图可以看出我们需要指定LDAP服务器的地址，端口，连接LDAP使用的Principal（不同于前面讨论的Principal，这个Principal实际是一个连接LDAP的用户，也就是一个LDAP 中用户Entry的 DN，它必须要有相关的LDAP 搜索等权限）和Credential（一般来说就是口令）。<br /><b>　　</b>再看下面关于Users的配置：</p><p align="center"><img height="454" src="http://dev2dev.bea.com.cn/images/webser/image2005041908.gif" width="600" /><br />图3-1-2</p><p><b>3.1.1 User Object Class </b>—— 前面已经对objectclass进行过说明，指明LDAP Entry属于哪一类<br /><b>3.1.2 User Name Attribute </b>—— 用户登录帐号在LDAP Entry中的属性，一般为UID或者cn<br /><b>3.1.3 User Base DN</b> —— 所有的用户将会放置到这个子树下面，因此在Provider中对用户进行的搜索将会从这个basedn开始<br /><b>3.1.4 User Search Scope </b>—— 指定搜索范围为Basedn的直接一级或者全部下级<br /><b>3.1.5 User From Name Filter</b> —— 使用这个filter可以搜索出用户信息。其中%u将会被用户输入的登录帐号替换，从而查询中LDAP中的用户信息</p><p align="center"><img height="452" src="http://dev2dev.bea.com.cn/images/webser/image2005041909.gif" width="600" /><br />图3-1-3</p><p><b>3.1.6 Group Base DN </b>—— 从该Base DN开始搜索用户组<br /><b>3.1.7 Group From Name Filter</b> —— %g将会被组的名字替换，通过该filter可以搜索出符合条件的LDAP Group<br /><b>3.1.8 Static group name attribute</b> —— 组名字的属性，属性cn对应的值就是组的名字</p><p align="center"><img height="469" src="http://dev2dev.bea.com.cn/images/webser/image2005041910.gif" width="600" /><br />图3-1-4</p><p><b>3.1.9 Static Member DN Attribute</b> —— 静态成员属性，通过该属性可以判断一个Entry是否属于一个组<br /><b>3.1.10 Static Group DNs From Member DN Filter</b> —— 通过该filter可以找出用户属于哪些组<br /><b>3.1.11 Dynamic Group</b> —— 动态组是在运行时根据某种业务逻辑，来决定成员隶属关系的LDAP Group</p><p><b>3.2 iPlanet Authentication Provider的工作原理</b><br /><b>　　</b>从上面配置的介绍中可以看出，后端存储为LDAP的情况下，在Console中我们需要配置的参数已经清楚的表明它所要完成工作的内容。<br /><b>　　</b>首先，它使用配置的User BaseDN和Filter，来根据用户输入的登录帐号进行搜索，找出存放在LDAP中的用户Entry。如果找到一个用户，那么Provider就使用该用户的DN和用户输入的口令，进行验证。验证可以使用LDAP Bind和LDAP Compare，这需要根据不同LDAP的特点来进行选择。<br /><br /><b><i>　　</i></b><i>A. LDAP Bind —— 将当前的LDAP Connection绑定到一个用户身份上。这样后续的使用该Connection的LDAP Operation都将以该身份进行。LDAP Bind需要两个重要参数，一个是用户Entry的DN，一个是该用户的口令。</i></p><p><i><b>　　</b>B. LDAP Compare —— LDAP Compare是一个为兼容X.500的古老操作，它用于检查一个属性值是否包含在指定Entry中的属性里。这样我们可以在知道用户password存放在哪个属性的前提下，对该属性进行compare。如果成功，表明口令正确。如果属性值为散列后的口令（绝大多数情况），有的LDAP Server支持这样的验证，有的不支持，比如iPlanet LDAP Server 5。</i></p><p><b>　　</b>验证成功后，Provider将使用Console中关于Groups和Memberships中的配置，查找用户属于哪些LDAP Group，而且由于这些组本身可能会有一些嵌套，因此对于搜索到的组还需要进行查询。即使用filter： (&amp;（uniquemember=uid=ZHANGSAN,ou=staff,ou=people,o=examples）(objectclass=groupOfUniqueNames))从Group Base DN开始搜索，将返回用户所属的第一层次Group；然后对于这些返回的组DN，仍然需要使用上面的Filter进行搜索（uniquemember值替换为组的DN），找出嵌套关系，直到查询完成没有组嵌套为止（此处需要防止陷入嵌套的循环中，比如Group A 包含了Group B； Group B又包含了Group A，有的LDAP Server可以自动检测出，有的需要我们程序来判断）。<br /><br /><b>　　</b>然后将用户登录的帐号，用户所属组的名字（属性CN的值或其他），放入Subject中。最后调用Principal Validator Provider，对Subject中的principals进行签名，来表明该Subject是这个Provider生成的。这样防止其他攻击者伪造Subject以及Principal进行欺骗。此处也可以解释为何在不同的WLS Domain间不能够传递Subject，我们可以通过设置域信任来完成这种Subject的传递。设置域信任使用的Credential就是签名用的Key。<br /><br /><b>　　</b>图3-1-5表明了如何设置WebLogic Domain Credential，默认情况下WebLogic Server会在启动的时候随即生成一个Credentials（在WLS6.1时，这个值就是system用户的口令）：</p><p align="center"><img height="307" src="http://dev2dev.bea.com.cn/images/webser/image2005041911.gif" width="600" /></p><p><b>　　</b>可以想见如果我们实现自己的Principal Validator Provider，让它去一个集中的验证服务器中对Subject进行签名，或者验证Subject，这样就可以实现域信任，进而完成Application（EJB Tier）层的SSO。</p><p><b>　　</b>通过以上的讨论，我们对于实现自己的LDAP Authentication Provider是不是又增加了一份信心？<a id="4" name="4"></a><br /><b><br />4 定制自己的Custom LDAP Authentication Provider</b><br /><b>　　</b>为何要定制自己的Authentication Provider? 由于WebLogic Server已经提供了很多默认的Authentication Provider在一般情况下我们确实没有必要实现自己的Provider。但是面对某些针对安全方面的复杂需求时，WebLogic Server提供的Provider很有可能不满足这些需求，此时就需要我们定制自己的Provider。</p><p><b>　　</b>在这一章的开头部分中我需要简要讨论关于WebLogic MBean Types，以及WebLogic Console扩展等内容，目的在于让读者了解到我们通过WebLogic Console可以完成对Custom Security Provider的配置和部署，我将以WebLogic 提供的Sample Security Provider为示例进行说明。详细的信息可以参考以下的一些资源：</p><p><a href="http://e-docs.bea.com/wls/docs81/dvspisec/atn.html#1106272" target="_blank">http://e-docs.bea.com/wls/docs81/dvspisec/atn.html#1106272</a><br /><a href="http://e-docs.bea.com/wls/docs81/dvspisec/atn.html#1106241" target="_blank">http://e-docs.bea.com/wls/docs81/dvspisec/atn.html#1106241</a></p><p><b>　　</b>上面两个链接描述了如何创建MBean Types以及在控制台上配置Custom Authentication Provider.下面这个链接中专门介绍了WebLogic Console的扩展：</p><p><a href="http://dev2dev.bea.com.cn/techdoc/2005012102.html" target="_blank">/techdoc/2005012102.html</a></p><p><b>　　</b>读者可以从<a href="http://dev2dev.bea.com/codelibrary/code/security_prov81.jsp" target="_blank">http://dev2dev.bea.com/codelibrary/code/security_prov81.jsp</a>下载WLS提供的Sample Security Provider。</p><p><b>4.1 MBean Types和WebLogic Console</b><br /><b>　　</b>一般情况下，人们可能更习惯通过WebLogic Console对Security Provider进行配置。这里我将简要描述这个过程，以及可以到达的一个效果。限于篇幅就不详细讨论了。</p><p><b>　　</b>从weblogic.management.security.authentication.Authenticator扩展MBean Types。 MBean Types是MBean（http://java.sun.com/products/JavaManagement/wp/）的工厂，我们扩展SampleSecurityProviders81 包中的SimpleSampleAuthenticator.xml（MBean Definition File），增加一个我们自定义的参数LDAP Server IP：</p><table cellspacing="0" cellpadding="4" width="98%" align="center" bgcolor="#e8e8e8" border="0"><tbody><tr><td>&lt;MBeanAttribute<br /><b>　　</b>Name = "LDAPServerIP" <br /><b>　　</b>Type = "java.lang.String" <br /><b>　　</b>Writeable = "true" <br /><b>　　</b>Default = "&amp;quot;127.0.0.1&amp;quot;" <br />/&gt;</td></tr></tbody></table><p><b>　　</b>这样在Provider中我们将通过MbeanMaker(WLS提供)生成的SimpleSampleAuthenticatorMBean中取到这个属性：SimpleSampleAuthenticatorMBean.getLDAPServerIP()。MBean将在初始化Provider的时候作为参数传入。这样我们就可以通过MBean中的参数控制Provider的行为。</p><p><b>　　</b>当然这个参数是可以在WebLogic Console中设置的，通过对MBean Types的扩展，在WebLogic Console上看到的画面如下：</p><p align="center"><img height="210" src="http://dev2dev.bea.com.cn/images/webser/image2005041912.gif" width="600" /><br />图4-1-1</p><p><b>　　</b>这样我们可以通过Console修改配置参数（修改的Security Provider参数将保存在config.xml中，默认的值将保存在MBean Jar File中）。</p><p><b>4.2 为何定制LDAP Authentication Provider<br />　　</b>当我们面临越来越复杂的安全方面的业务需求时，或者面临较高的性能要求，需要根据目标LDAP做针对性的优化时，或者需要将我们已有的认证，或授权模块集成到WebLogic平台时，WebLogic提供的现成的Provider往往不能满足我们的需求。</p><p><b>4.2.1 复杂的业务需求<br /></b><b>　　</b>当系统要求用户不仅仅输入用户名（j_username），口令（j_password），还需要输入其他信息，比如登录的地点，系统的名字，用户的类型等等。如果是采用基于J2EE Form的验证方式， 登录信息需要提交到j_security_check（Servlet规范定义由容器负责实现的Servlet），导致我们没法处理更多的信息。</p><p><b>　　</b>这个时候，如果能够实现我们自己的 Authentication Provider，那么我们就可以通过TextInputCallback来获取登录表单中更多的信息了；进而通过这些信息在Provider中完成符合我们需要的处理。</p><p><b>　　</b>比如搜狐的登录页面上需要选择用户的类型：</p><p align="center"><img height="176" src="http://dev2dev.bea.com.cn/images/webser/image2005041913.gif" width="158" /><br />图4-2-1</p><p><b>4.2.2 性能需求或者调优</b><br />　　有时，有的用户会比较困惑为何WebLogic LDAP Security Provider在压力测试中的表现不很理想，用户需要较长时间的等待，才能够登录到系统中。由于这些Provider是 WebLogic产品的一部分，因此缺乏对不同目标LDAP Server的有针对性的优化。这样就使得我们无法充分发挥具体LDAP Server的性能调优。</p><p>　　比如，有的LDAP Server支持动态组（LDAP Dynamic Group，成员关系是运行时根据ldap server，basedn，filter等动态决定的），可以使用如下的Filter查询用户属于哪些动态组：</p><p>　　(uniquemember=uid=MAXQ,ou=staff,ou=people,o=examples)</p><p>　　有的LDAP Server虽然支持动态组，但是支持的有限，不能使用上面的Filter获取用户属于哪些动态组。在WebLogic iPlanet Authentication Provider的实现中，它先是搜索出全部的动态组，然后再遍历这些动态组，依次去LDAP中检查用户是否属于一个组；很明显，这样虽然最大程度的满足了不同LDAP Server的要求（从产品的角度讲可能是必须的），但是与LDAP交互的次数大增，并发用户量一大性能下降的比较明显。</p><p>　　此时，如果系统中的LDAP支持上面的Filter或者有更好的搜索方式，那么完全可以通过定制Provider完成对性能的优化。</p><p><b>4.2.3 已有权限控制的集成</b><br />　　如果系统中已经存在了现成的满足需求的认证模块，并且已经很好的工作；在系统转向J2EE架构，并使用WebLogic Server做J2EE容器时，我们可能会更愿意直接在Provider中加入这个认证模块。</p><p>　　综上，我只是列举了一些可以驱动我们开发自己Provider的需求，相信在读者实际工作中可能会面临更复杂的情况，开发自己的Provider将是一个非常好的选择。</p><p><b>4.3 LDAP Authentication Provider实现</b><br />　　本文之前为了表述的方便没有单独提到LoginModule，认为LoginModule的行为就是LDAP Authentication Provider的行为。到了目前的具体实现阶段，我们必须分开Authentication Provider和JAAS LoginModule。最终部署到WebLogic上的实际只是LDAP AuthenticationProvider Implements。</p><p>　　WebLogic SecurityFramework通过Authentication Provider获取具体的JAAS LoginModule。通过LoginModule完成最终登录的工作。因此我们必须先实现一个AuthenticationProvider。</p><p>　　我们一般通过weblogic.security.spi.AuthenticationProvider 来实现自己的AuthenticationProvider。这里介绍其中的几个重要方法：</p><p>　　a) public void initialize(ProviderMBean mbean, SecurityServices services)<br />初始化一个Provider。通过参数MBean我们可以获取到在WebLogic Console中配置的各项参数。进而初始化我们的Provider，然后通过Provider传递到LoginModule中。</p><p>　　b) public void shutdown()<br />释放一些与Provider，LoginModule等相关的资源。</p><p>　　c) public AppConfigurationEntry getLoginModuleConfiguration()<br />这个方法非常重要，通过该方法，WebLogic Security Framework可以获取用于初始化LoginModule的AppConfigurationEntry。AppConfigurationEntry中存放了LoginModule的类名等信息，比如使用如下代码返回一个AppConfigurationEntry:</p><table cellspacing="0" cellpadding="4" width="98%" align="center" bgcolor="#e8e8e8" border="0"><tbody><tr><td>　　return new AppConfigurationEntry(<br />　　　　"examples.security.providers.authentication.SampleLoginModuleImpl",<br />　　　　controlFlag,<br />　　options);</td></tr></tbody></table><p>　　其中LoginModule Name就</p><p>是"examples.security.providers.authentication.SampleLoginModuleImpl"，我们通过它就可以实例化一个LoginModule并通过LoginModule.initialize()方法进行初始化。</p><p>　　d) public AppConfigurationEntry getAssertionModuleConfiguration()<br />该方法将返回一个与Identity Assertion Provider关联的LoginModule。这个Assertion LoginModule，将只会验证用户是否存在，以及如果存在返回用户的Principals。 该方法也比较重要，需要正确实现，比如我们使用CLIENT-CERT这种WEB认证方式，该方法就会被调用。</p><p>　　Provider的实现比较简单，读者可以在<a href="http://dev2dev.bea.com/codelibrary/code/security_prov81.jsp" target="_blank">http://dev2dev.bea.com/codelibrary/code/security_prov81.jsp</a>下载WebLogic提供的Samples，查看SampleAuthenticationProviderImpl的代码。</p><p></p><p><b>4.4 LDAP LoginModule 逻辑流程<br /></b>　　实现了Provider后，必须拥有我们自己的LDAP LoginModule。下面是一个简单的用于演示的验证逻辑流程图。实际的一个LoginModule由于不同的业务需求，情况可能会复杂得多。这里只是描述了最核心最基本的逻辑，使读者能有一个清晰的思路。后面我将以这个流程为例进行实现。</p><p align="center"><img height="743" src="http://dev2dev.bea.com.cn/images/webser/image2005041914.gif" width="356" /></p><p><b>4.5 LoginModule代码示例和讲解<br /></b>　　这里我将使用Netscape LDAP SDK for java作为开发工具实现LDAP相关的操作，读者可以到<a href="http://docs.sun.com/db/doc/816-6402-10" target="_blank">http://docs.sun.com/db/doc/816-6402-10</a> 下载开发手册，从<a href="http://www.mozilla.org/directory/" target="_blank">http://www.mozilla.org/directory/</a> 下载SDK 包。一般来说还可以通过JNDI来操作LDAP，我个人认为Sun LDAP JNDI Provider中关于Connection Pool的实现非常优秀。但不管使用哪种SDK，对LDAP的编程原理上基本都是相同的（因为基于同样的LDAP协议），不同的可能仅仅是接口，类的名字而已。</p><p><b>4.5.1 初始化Connection Pool</b><br />　　为了有效使用宝贵，并且有限的LDAP连接，必须使用连接池。下面的代码初始化了一个LDAP连接池：</p><table cellspacing="0" cellpadding="4" width="98%" align="center" bgcolor="#e8e8e8" border="0"><tbody><tr><td>/** <br />* <br />*/ <br />static ConnectionPool pool; <br />/** * * LDAPException <br />*/ public LdapClient() <br />　　throws LDAPException <br />　　　　{ <br />　　　　　　pool= new ConnectionPool( <br />　　　　　　　　1, 150, "127.0.0.1", 389, "cn=Directory Manager", "88888888"); <br />}</td></tr></tbody></table><p>　　Sun JNDI LDAP Service Provider（JDK1.4）中可以通过在环境变量中设置具体的参数来启用连接池，达到复用连接的目的。具体可以参考链接：<a href="http://java.sun.com/products/jndi/tutorial/ldap/connect/index.html" target="_blank">http://java.sun.com/products/jndi/tutorial/ldap/connect/index.html</a>，下面是示例代码：<br /><br />Hashtable env= new Hashtable();<br />…<br />env.put("com.sun.jndi.ldap.connect.pool", "true");<br />DirContext ctx= new InitialDirContext( env);</p><p><b>4.5.2 根据用户输入的登录帐号，搜索用户Entry<br /></b>　　下面这个方法实现了从LDAP中搜索用户Entry DN的最简单的过程。实际上我们可以在其中实现很多定制的功能。比如允许用户使用多于一个的帐号登录，只要这些帐号能够通过LDAP Searche最终返回一个唯一的用户DN即可。</p><table cellspacing="0" cellpadding="4" width="98%" align="center" bgcolor="#e8e8e8" border="0"><tbody><tr><td>  * <br />   * LDAPException <br />   */<br />  public String getUserDN( String uid) throws LDAPException{ <br />    LDAPConnection conn= pool.getConnection();<br />    try {<br />      String[] attrs= new String[]<br />{}; <br />      LDAPSearchResults sr= conn.search("o=examples", 2, "(uid="+ uid +")", attrs, false);<br />      if ( sr.hasMoreElements()) { <br />        LDAPEntry entry= sr.next();<br />        return entry.getDN();<br />}<br />    throw new LDAPException("No Such Object:"+ uid, <br />        LDAPException.NO_SUCH_OBJECT);<br />    }catch ( LDAPException ex) { <br />      throw ex;<br />    }finally {<br />      try {<br />        if (conn!= null) pool.close(conn);<br />      }catch ( Exception ex)<br />{}<br />    }<br />  }</td></tr></tbody></table><p>　　首先需要从池中获取一个LDAP连接，然后使用LDAPConnection.search方法进行搜索。我这里以一个典型的LDAP Search接口为例进行说明，其他API比如JNDI等，基于同样的LDAP协议，接口中同样参数的含义都是相同的。</p><table cellspacing="0" cellpadding="4" width="98%" align="center" bgcolor="#e8e8e8" border="0"><tbody><tr><td>public LDAPSearchResults search(java.lang.String base,<br />　　int scope,<br />　　java.lang.String filter,<br />　　java.lang.String[] attrs,<br />　　boolean attrsOnly)</td></tr></tbody></table><p>　　1) Base: 表明从该Basedn开始搜索，可以通过MBean获取</p><p>　　2) Scope: 搜索的范围：</p><p>　　　　a) LDAPv2.SCOPE_BASE， 只搜索basedn指定的entry<br />　　　　b) LDAPv2.SCOPE_ONE, 在basedn的下一级entry中搜索，不包括basedn<br />　　　　c) LDAPv3.SCOPE_SUB，在basedn的全部下级entry中搜索，包括basedn</p><p>　　3) Filter：过虑条件。比如uid=ZHANGSAN，搜索UID为ZHANGSAN的用户；再比如uid=ZHANG*，搜索 ZHANG开头的帐号；或者uid=Z*S，搜索以Z开头S结尾的帐号。前面已经介绍过这里就不多说了。</p><p>　　4) attrs：返回该attrs数组中指定的属性，比如new String[]{“uid”}，只返回属性uid，其他属性将会不在结果中返回。 一般来说我们会要求开发人员只将需要的属性返回，这样避免返回无用的属性，降低网络和Server等方面的资源开销；而且如果存在一个有较大属性集合的Entry，并且你并不使用到这个较大的属性集合。举个实际例子来说，比如你的系统中拥有很多成员数目超过2万或者更多的LDAP Groups，并且你希望通过LDAP Search找出某一个用户属于的组CN，那么搜索结果只返回组的CN已经可以满足你的要求了，这时就没有必要将全部member属性也返回了。这在后面我会有代码来说明。</p><p>　　这里还有一点很重要的细节，相信对读者会有帮助。比如，你希望搜索到modifytimestamp等Operational Attributes。这些属性（LDAP Server自己负责维护的，用户无法修改的）必须要在参数attrs中指定，LDAP Server才会返回给客户端。如果用户希望返回全部User Attributes的同时，返回指定的 Operational Attributes那该怎么办？不在attrs列表中的属性将不会被返回，一旦我指定了attrs，是否需要将全部的属性都列在attrs参数中？实际上此时只需要传入 new String[]{ “*”, “createtimestamp”}这样的参数即可；“*”号即代表了全部的User Attributes。在Sun JNDI LDAP Service Provider中也是这样，尽管找遍Sun关于JNDI的文档也找不到这样的说明。LDAP协议对此的说明在 <a href="http://www.ietf.org/rfc/rfc2251.txt" target="_blank">http://www.ietf.org/rfc/rfc2251.txt</a> 第28页。</p><p>　　5) attrsOnly：只返回属性名称，不包含值。我们一般设置为false。</p><p>　　我们下面看一下Sun JNDI中关于LDAP Search的接口：</p><p>public NamingEnumeration search(String name,<br />　　String filter,<br />　　SearchControls cons)<br />　　throws NamingException<br />name参数就是上面的basedn，SearchControls中封装更多的设置，比如：SearchControls. setSearchScope(int scope)其中的scope对应上面的2)； SearchControls.setReturningAttributes(String[] attrs)，设置指定返回的属性，对应上面的4)。</p><p>　　其他的参数还包括Size Limit，即限制搜索结果的数目（LDAP返回的Entry一般情况下是没有排序的，除非使用一些Sort Control），当你的搜索可能会返回成千上万的Entry时，限制搜索的数目是非常明智也是必须的。关于这些，读者可以参考API文档或者LDAP协议。</p><p><b>4.5.3 根据用户的Entry DN，和用户输入的口令完成身份验证<br />　　</b>下面的方法实现了到LDAP的身份验证。LDAP中的用户实际上就是一个LDAP Entry，一次验证实际是将Connection与这个Entry进行绑定，因此验证时我们需要两个必须的参数，一个是Entry的DN，一个是口令。LDAP的身份验证方式有多种，包括SASL以及基于证书的验证等，我们这里只介绍Simple Authentication。关于SASL更多信息读者可以参考：<a href="http://www.ietf.org/rfc/rfc2222.txt" target="_blank">http://www.ietf.org/rfc/rfc2222.txt</a>。</p><table cellspacing="0" cellpadding="4" width="98%" align="center" bgcolor="#e8e8e8" border="0"><tbody><tr><td>  /**<br />   * <br />   * dn<br />   * pwd<br />   * <br />   * LDAPException<br />   */<br />  public boolean authentication( String dn, String pwd) throws LDAPException {<br />    LDAPConnection conn= pool.getConnection();<br />    try { <br />      conn.authenticate( dn, pwd);<br />      return true;<br />    }catch ( LDAPException ex) { <br />      if ( ex.getLDAPResultCode()== LDAPException.INVALID_CREDENTIALS) { <br />        return false; // 用户口令错误 }<br />      if ( ex.getLDAPResultCode()== LDAPException.NO_SUCH_OBJECT) { <br />        return false; // 用户不存在 }<br />      throw ex; <br />    }finally { <br />      try {<br />        if ( conn!= null) pool.close(conn);<br />      }catch ( Exception ex) {<br />      } <br />    }<br />  } </td></tr></tbody></table><p><br /><b>　　</b>我们这里使用 LDAP Bind操作完成对用户的身份验证。本文前面曾提到也可以使用LDAP Compare，通过比较口令属性（userpassword）中的值来完成验证。我们需要根据不同的LDAP Server选择合适的验证方法。</p><p><b>　　</b>比如iPlanet LDAP Server 5，只能通过Bind完成认证；而Oracle Internet Directory可以通过LDAP Compare完成验证，而且还支持口令策略。</p><p><b>　　</b>如果我们使用了连接池，连接池中的连接一般都是使用权限较大的用户初始化的，这样这些连接才可以完成对LDAP的搜索操作；而当通过这些连接对普通用户进行身份验证时，如果通过验证，连接的身份将被改变为普通的用户（或称为与普通用户的身份关联）。普通用户很可能没有除了bind以外的任何权限，所以在连接被放入池中前，我们必须要恢复连接的身份。</p><p><b>　　</b>这样我们必须执行两次LDAP Bind，一次用于对普通用户验证身份；一次用于恢复连接的较大权限的用户身份。我们看到这样效率是比较低的，可能你在LDAP Server端统计有2万次bind请求，实际上只有1万人次登录。</p><p><b>　　</b>对于特定的LDAP Server，比如 Oracle Internet Directory，可以通过LDAP Compare对用户身份进行验证，并且不会改变连接关联的用户身份。这样我在使用池的情况下只需要一次LDAP Compare即可。效率有很大提高。如果不通过定制LDAP Authentication Provider，这样的调优是没法实现的。</p><p><b>　　</b>Netscape LDAP SDK的ConnectionPool的实现中，在连接放入池中前会检查连接的身份，如果身份被改变，那么会重新进行bind。所以我们没有必要在代码中再做bind。</p><p><b>4.5.4 根据用户的DN搜索用户属于的组列表<br />　　</b>有了上面的基础后，这个方法就很容易理解了。下面我们来看看如何返回用户所属的组。</p><p></p><table cellspacing="0" cellpadding="4" width="98%" align="center" bgcolor="#e8e8e8" border="0"><tbody><tr><td>/**<br />   * <br />   * groupbasedn<br />   * memberDn<br />   * <br />   * LDAPException<br />   */<br />  public List getGroupMembership( String groupbasedn, String memberDn) throws LDAPException {<br />    LDAPConnection conn= pool.getConnection(); <br />    try { <br />      LDAPSearchResults sr= conn.search(<br />          groupbasedn,<br />          2,<br />          "(uniquemember="+ memberDn +")", <br />          new String[] {"cn"},<br />          false);<br />      List groups= new java.util.ArrayList();<br />      while ( sr.hasMoreElements()) {<br />        LDAPEntry entry= sr.next();<br />        LDAPAttribute attr= entry.getAttribute("cn");<br />        if ( attr!= null) {<br />          String[] values= attr.getStringValueArray(); <br />          if ( values!= null &amp;&amp; values.length&gt;0) groups.add( values[0]);<br />        }<br />      }<br />      return groups;<br />    }catch ( LDAPException ex) {<br />      throw ex;<br />    }finally { <br />      try { <br />        if ( conn!= null) pool.close(conn);<br />      }catch ( Exception ex) {<br />      }<br />    }<br />  } </td></tr></tbody></table><p><b>　　</b>成员和组的membership主要是通过uniquemember或者 member属性来定义的。成员不仅仅可以使用用户，也可以是组，因为组可以嵌套。<br /><br /><b>　　</b>a) 上面的方法中只实现了一个层次的搜索，即用户——组的搜索，而组间的嵌套搜索没有实现。读者可以根据系统内的具体情况，在此处也可以做一些优化。</p><p><b>　　</b>b) 组成员的数量可能比较大，为了避免不必要的开销，我们指定只返回组的cn属性。</p><p><b>　　</b>c) 动态组的优化。在WebLogic默认的实现中，Provider（实际为LoginModule）会不断的拿动态组中定义的URL中的filter和用户的DN去LDAP做搜索，来看用户是否属于该组。本文前面也讨论了，这样效率很低，完全可以放在Provider本地实现URL和Entry的匹配。</p><p><b>4.5.5 LoginModule中的login()方法实现<br />　　</b>一切准备就绪后，我们就可以完成LoginModule.login()这个最核心的方法了。下面我根据代码中的注释逐条说明。</p><table cellspacing="0" cellpadding="4" width="98%" align="center" bgcolor="#e8e8e8" border="0"><tbody><tr><td>  // <b>A</b><br />  boolean loginSucceeded;<br />  // <b>B</b><br />  List principals= new java.util.ArrayList();<br />  /**<br />   *<br />   * <br />   * LoginException <br />   */<br />  public boolean login() throws LoginException { <br />    // <b>C</b><br />    Callback[] callbacks= new Callback[] { <br />        new NameCallback("username: "),<br />        new PasswordCallback("password: ",false)};<br />    try { <br />      callbackHandler.handle( callbacks);<br />    }catch (IOException e) {<br />      throw new LoginException(e.toString());<br />    }catch (UnsupportedCallbackException e) {<br />      throw new LoginException(e.toString() + " " +e.getCallback().toString());     }<br />    //<br />    String userName = ((NameCallback)callbacks[0]).getName(); <br />    if ( userName== null || userName.length()== 0) {<br />      throw new LoginException("User login name is empty!");<br />    }<br />    // <br />    PasswordCallback passwordCallback= (PasswordCallback)callbacks[1];     char[] password = passwordCallback.getPassword();<br />    passwordCallback.clearPassword();<br />    if ( password== null || password.length== 0) {<br />      throw new LoginException("User password is empty!");<br />    }<br />    try {<br />      // <b>D</b><br />      String dn= this.getUserDN( userName);<br />      if ( dn== null) {<br />        throw new LoginException("User "+ userName +" doesn't exist."); <br />      }<br />      // <b>E</b><br />      boolean authResult= this.authentication( dn, String.valueOf( password));       if ( authResult== false) {<br />        throw new FailedLoginException("User login failed.");<br />      }<br />      // <b>F</b><br />      principals.add( new WLSUserImpl( userName));<br />      // <b>G</b><br />      List groups= this.getGroupMembership( "ou=groups,o=examples", dn);       for ( int i=0, n=groups.size(); i&lt;n; i++) {<br />        String cn= String.valueOf(groups.get(i));<br />        // <b>H</b><br />        principals.add( new WLSGroupImpl(cn)); <br />      }<br />    }catch ( LDAPException ex) { <br />      java.io.StringWriter sw = new java.io.StringWriter();  <br />     ex.printStackTrace(new java.io.PrintWriter(sw));<br />      sw.flush();<br />      throw new LoginException( sw.toString());<br />    } <br />       return loginSucceeded= true;<br />  } </td></tr></tbody></table><p><b>　　</b>a) 用于保存验证结果，在后续方法（commit等）中使用<br /><br /><b>　　</b>b) 用于保存和用户关联的Principal，在后续方法（commit等）中使用<br /><br /><b>　　</b>c) 在JAAS中通过CallbackHandler来完成用户和底层安全认证系统间信息的交换，这里我们通过CallbackHandler获取用户输入的登录帐号和口令。CallbackHandler将在初始化LoginModule时由WebLogic Security Framework传入。读者可能会问，我是否可以在此要求WebLogic Security Framework传入我自己实现的CallbackHandler，进而获取更多的信息，支持更多的Callback？很遗憾，不可以。只有在一种情况下（本文前面也提到）有一个变通，就是在Web应用中，通过Form Based这种认证方式进行用户身份验证时，可以获取更多的登录表单中提交的cgi变量。</p><p><b>　　</b>下面的代码将演示这种技术：</p><table cellspacing="0" cellpadding="4" width="98%" align="center" bgcolor="#e8e8e8" border="0"><tbody><tr><td>    Callback[] callbacks= new Callback[] { <br />        new NameCallback("username: "),<br />        new PasswordCallback("password: ",false),<br />        new TextInputCallback("my_hidden_field")};<br />    try { <br />      callbackHandler.handle( callbacks); <br />    }catch (IOException e) {<br />      throw new LoginException(e.toString());<br />    }catch (UnsupportedCallbackException e) {<br />      throw new LoginException(e.toString() + " " +e.getCallback().toString());     } <br />    String value= ((TextInputCallback)callbacks[2]).getText(); </td></tr></tbody></table><p><b>　　</b>这样我们通过((TextInputCallback)callbacks[2]).getText()来获取表单中更多的信息。其中TextInputCallback的Prompt必须要和表单中对应域的名字一致，否则取不到信息。如果有多个域，则需要传入多个TextInputCallback。</p><p><b>　　</b>同时如果登录表单中没有这些对应TextInputCallback的域，或者使用JNDI方式验证，那么会抛出UnsupportedCallbackException。</p><p><b>　　</b>下面是一个符合Servlet安全规范的登录表单，其中域my_hidden_field的值将通过CallbackHandler传递到的TextInputCallback中：</p><table cellspacing="0" cellpadding="4" width="98%" align="center" bgcolor="#e8e8e8" border="0"><tbody><tr><td>&lt;html&gt;<br /><b>　　</b>&lt;form method=”POST” action=”j_security_check”&gt;<br /><b>　　</b><b>　　</b>&lt;input type=”text” name=”j_username”&gt;<br /><b>　　</b><b>　　</b>&lt;input type=”password” name=”j_password”&gt;<br /><b>　　</b><b>　　</b>&lt;input type=”hidden” name=”my_hidden_field”value=”Hello Callback!”&gt;<br /><b>　　</b>&lt;/form&gt;<br />&lt;/html&gt;</td></tr></tbody></table><p><b>　　</b>d) 生成一个用于存放Principal的集合对象<br /><b>　　</b>e) 通过登录帐号获取LDAP中的Entry DN<br /><b>　　</b>f) 通过DN和用户口令进行身份验证<br /><b>　　</b>g) 将通过验证的用户名加入到Principal列表中，这些以后都将代表用户的一定权限<br /><b>　　</b>h) 查找用户属于哪些组<br /><b>　　</b>i) 将这些组的名字加入到Principal列表中</p><p><b>4.5.6 LoginModule中的commit()和abort()方法实现<br />　　</b>完成了以上的验证后，我们需要通过LoginModule.commit()方法提交验证结果；或者abort()方法取消验证结果。</p><table cellspacing="0" cellpadding="4" width="98%" align="center" bgcolor="#e8e8e8" border="0"><tbody><tr><td>  // <b>A</b><br />  private Subject subject;<br />    /**<br />   *<br />   * <br />   * LoginException<br />   */<br />  public boolean commit() throws LoginException { if (loginSucceeded) { <br />  // <b>B</b><br />      subject.getPrincipals().addAll(principals);<br />      return true;<br />    }else { <br />      return false;<br />    } <br />  }<br />    /**<br />   * <br />   * <br />   * LoginException<br />   */<br />    public boolean abort() throws LoginException {<br />    // <b>C</b><br />    subject.getPrincipals().removeAll(principals); <br />    return true;<br />  }    </td></tr></tbody></table><p> </p><p><b>　　</b>我这里仍然通过代码中的注释进行相应的说明：</p><p><b>　　</b>a) JAAS Subject，在LoginModule初始化时，由WebLogic Security Framework负责传入，用户的权限信息（Principals等）都将封装到该Subject中。同时Principal Validator会对Subject中的Principals进行签名，防止被黑客纂改。</p><p><b>　　</b>b) 用户认证成功的情况下，将用户关联的Principals加入到Subject中。这样用户在进行后续的访问时，WebLogic Security Framework会检查与用户关联的Subject中是否有可以访问受限资源的Principal。</p><p><b>　　</b>c) 用户登录失败的情况下（有可能是其他的LoginModule导致的整体登录失败，具体参考JAAS Control Flag），我们在commit中添加的Principals必须从Subject中删除。</p><p><br /><b>　　</b>通过上面的描述和讨论，相信读者已经对如何实现一个LDAP AuthenticationProvider/LoginModule有了比较清楚的了解。限于篇幅省略了很多细节，对此有需要的读者可以根据文中给出的相关链接做进一步的了解；或者与我交流。从这里我们也可以看到核心的逻辑还是比较简单的。其中将LDAP Group作为用户的Principal，是J2EE Security与LDAP Security结合的非常重要的一步。通过这种结合，我们可以将LDAP作为后端，设计出基于J2EE，JAAS的用户身份验证，权限管理等系统。<a id="5" name="5"></a><br /><br /><b>5 部署中的注意事项</b><br /><b>　　</b>开发完成LDAP Authentication Provider后，需要将通过WebLogic MBean Maker生成的MBean Jar File放到/server/lib/mbeantypes目录下。由于WebLogic Server在启动时会加载这些Provider，因此在一个分布式的环境中，WebLogic Domain内的全部WLServer（包括Managed Server）均需要部署该Jar包。</p><p><b>　　</b>同时由于使用了Provider MBean，因此，当你删除了MBean Types中定义的，并且通过修改保存在config.xml（WebLogic Domain配置文件）中的属性时，重新部署的Jar包会导致WebLogic Server无法正常启动，因为它没有办法按照config.xml中配置的属性初始化MBean。此时需要手工修改config.xml，删除相关的属性即可。</p><p>&lt;examples.security.providers.authentication.simple.SimpleSampleAuthenticator<br /><b>　　</b>ControlFlag="OPTIONAL"<br /><b>　　</b>Name="Security:Name=myrealmSimpleSampleAuthenticator"<br /><b>　　</b>UserBaseDN="ou=people, o=examples" Realm="Security:Name=myrealm"/&gt;</p><p><b>　　</b>比如UserBaseDN已经不需要通过MBean来管理，并且在MBean Types中已经删除，那么直接删除这里的UserBaseDN="ou=people, o=examples" 就可以了。<a id="6" name="6"></a><br /><br /><b>6 结束语</b><br /><b>　　</b>本文所讨论的内容有些过于广泛，从我个人角度讲，写起来也比较困难，很多技术问题没有进行深入的讨论。其实只是希望通过本文，能够帮助读者对WebLogic Security，J2EE Security，LDAP Security以及JAAS有一个初步的认识；能够给希望实现LDAP Authentication Provider，或者被WebLogic LDAP Authentication Provider困惑的读者一些启示。只要能够对读者有所帮助，本文的目的就算达到了。</p><p><b>　　</b>限于篇幅，本文很多地方的表述可能并不清晰，也可能会有一些错误，如果在阅读过程中产生任何疑问或者有任何看法都可以通过E-mail来与我交流，我也非常希望能和大家交流。<a id="7" name="7"></a><br /><br /><b>7 参考资料</b><br />a) <a href="http://e-docs.bea.com/wls/docs81/secwlres/types.html#1213777" target="_blank">http://e-docs.bea.com/wls/docs81/secwlres/types.html#1213777</a> 关于WebLogic Resource的更多说明<br />b) <a href="http://e-docs.bea.com/wls/docs81/security/index.html" target="_blank">http://e-docs.bea.com/wls/docs81/security/index.html</a> 关于WebLogic Security的全部内容<br />c) <a href="http://e-docs.bea.com/wls/docs81/dvspisec/rm.html#1145542" target="_blank">http://e-docs.bea.com/wls/docs81/dvspisec/rm.html#1145542</a> 关于Role Mapping Provider的更多内容<br />d) <a href="http://dev2dev.bea.com.cn/techdoc/2005012102.html" target="_blank">http://dev2dev.bea.com.cn/techdoc/2005012102.html</a> 关于WebLogic Console的扩展<br />e) <a href="http://dev2dev.bea.com/codelibrary/code/security_prov81.jsp" target="_blank">http://dev2dev.bea.com/codelibrary/code/security_prov81.jsp</a> Custom Seuciryt Provider的Samples<br />f) <a href="http://java.sun.com/products/JavaManagement/wp/" target="_blank">http://java.sun.com/products/JavaManagement/wp/</a> 更多关于Sun JMX<br />g) <a href="http://java.sun.com/products/jndi/tutorial/ldap/connect/index.html" target="_blank">http://java.sun.com/products/jndi/tutorial/ldap/connect/index.html</a> 关于如何使用Sun JNDI LDAP Service Provider中提供的连接池<br />h) <a href="http://www.ietf.org/rfc/rfc2254.txt" target="_blank">http://www.ietf.org/rfc/rfc2254.txt</a> 更多关于LDAP Filter<br />i) <a href="http://www.ietf.org/rfc/rfc2251.txt" target="_blank">http://www.ietf.org/rfc/rfc2251.txt</a> LDAP V3协议<br />j) <a href="http://www.ietf.org/rfc/rfc2222.txt" target="_blank">http://www.ietf.org/rfc/rfc2222.txt</a> 更多关于SASL</p><!--文章其他信息--><img src ="http://www.blogjava.net/beauty_beast/aggbug/74495.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/beauty_beast/" target="_blank">柳随风</a> 2006-10-11 10:45 <a href="http://www.blogjava.net/beauty_beast/archive/2006/10/11/74495.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>URLClassLoader加载class到当前线程类加载器</title><link>http://www.blogjava.net/beauty_beast/archive/2006/09/26/71981.html</link><dc:creator>柳随风</dc:creator><author>柳随风</author><pubDate>Tue, 26 Sep 2006 07:14:00 GMT</pubDate><guid>http://www.blogjava.net/beauty_beast/archive/2006/09/26/71981.html</guid><wfw:comment>http://www.blogjava.net/beauty_beast/comments/71981.html</wfw:comment><comments>http://www.blogjava.net/beauty_beast/archive/2006/09/26/71981.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/beauty_beast/comments/commentRss/71981.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/beauty_beast/services/trackbacks/71981.html</trackback:ping><description><![CDATA[
		<p>本文完全转自<a href="/hiswing/archive/2006/08/25/65764.html">http://www.blogjava.net/hiswing/archive/2006/08/25/65764.html</a><br />　我们知道，Java利用ClassLoader将类载入内存，并且在同一应用中，可以有很多个ClassLoader，通过委派机制，把装载的任务传递给上级的装载器的，依次类推，直到启动类装载器（没有上级类装载器）。如果启动类装载器能够装载这个类，那么它会首先装载。如果不能，则往下传递。当父类为null时，JVM内置的类(称为:bootstrap class loader)就会充当父类。想想眼下的越来越多用XML文件做配置文件或者是描述符、部署符。其实这些通过XML文档描述的配置信息最终都要变成Java类，基实都是通过ClassLoader来完成的。URLClassLoader是ClassLoader的子类，它用于从指向 JAR 文件和目录的 URL 的搜索路径加载类和资源。也就是说，通过URLClassLoader就可以加载指定jar中的class到内存中。</p>
		<p>下面来看一个例子，在该例子中，我们要完成的工作是利用URLClassLoader加载jar并运行其中的类的某个方法。</p>
		<p>首先我们定义一个接口，使所有继承它的类都必须实现action方法，如下：</p>
		<div style="BORDER-RIGHT: #cccccc 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: #cccccc 1px solid; PADDING-LEFT: 4px; FONT-SIZE: 13px; PADDING-BOTTOM: 4px; BORDER-LEFT: #cccccc 1px solid; WIDTH: 98%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: #cccccc 1px solid; BACKGROUND-COLOR: #eeeeee">
				<img id="Codehighlighter1_33_63_Open_Image" onclick="this.style.display='none'; Codehighlighter1_33_63_Open_Text.style.display='none'; Codehighlighter1_33_63_Closed_Image.style.display='inline'; Codehighlighter1_33_63_Closed_Text.style.display='inline';" src="http://www.blogjava.net/images/OutliningIndicators/ExpandedBlockStart.gif" align="top" />
				<img id="Codehighlighter1_33_63_Closed_Image" style="DISPLAY: none" onclick="this.style.display='none'; Codehighlighter1_33_63_Closed_Text.style.display='none'; Codehighlighter1_33_63_Open_Image.style.display='inline'; Codehighlighter1_33_63_Open_Text.style.display='inline';" src="http://www.blogjava.net/images/OutliningIndicators/ContractedBlock.gif" align="top" />
				<span style="COLOR: #0000ff">public</span>
				<span style="COLOR: #000000"> </span>
				<span style="COLOR: #0000ff">interface</span>
				<span style="COLOR: #000000"> ActionInterface </span>
				<span id="Codehighlighter1_33_63_Closed_Text" style="BORDER-RIGHT: #808080 1px solid; BORDER-TOP: #808080 1px solid; DISPLAY: none; BORDER-LEFT: #808080 1px solid; BORDER-BOTTOM: #808080 1px solid; BACKGROUND-COLOR: #ffffff">
						<img src="http://www.blogjava.net/images/dot.gif" />
				</span>
				<span id="Codehighlighter1_33_63_Open_Text">
						<span style="COLOR: #000000">{<br /><img src="http://www.blogjava.net/images/OutliningIndicators/InBlock.gif" align="top" />    </span>
						<span style="COLOR: #0000ff">public</span>
						<span style="COLOR: #000000"> String action();<br /><img src="http://www.blogjava.net/images/OutliningIndicators/ExpandedBlockEnd.gif" align="top" />}</span>
				</span>
		</div>
		<p>完成后将其打包为testInterface.jar文件。</p>
		<p>接下来新建一工程，为了编译通过，引入之前打好的testInterface.jar包。并创建TestAction类，使它实现ActionInterface接口。如下：</p>
		<p>
		</p>
		<div style="BORDER-RIGHT: #cccccc 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: #cccccc 1px solid; PADDING-LEFT: 4px; FONT-SIZE: 13px; PADDING-BOTTOM: 4px; BORDER-LEFT: #cccccc 1px solid; WIDTH: 98%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: #cccccc 1px solid; BACKGROUND-COLOR: #eeeeee">
				<img id="Codehighlighter1_51_135_Open_Image" onclick="this.style.display='none'; Codehighlighter1_51_135_Open_Text.style.display='none'; Codehighlighter1_51_135_Closed_Image.style.display='inline'; Codehighlighter1_51_135_Closed_Text.style.display='inline';" src="http://www.blogjava.net/images/OutliningIndicators/ExpandedBlockStart.gif" align="top" />
				<img id="Codehighlighter1_51_135_Closed_Image" style="DISPLAY: none" onclick="this.style.display='none'; Codehighlighter1_51_135_Closed_Text.style.display='none'; Codehighlighter1_51_135_Open_Image.style.display='inline'; Codehighlighter1_51_135_Open_Text.style.display='inline';" src="http://www.blogjava.net/images/OutliningIndicators/ContractedBlock.gif" align="top" />
				<span style="COLOR: #0000ff">public</span>
				<span style="COLOR: #000000"> </span>
				<span style="COLOR: #0000ff">class</span>
				<span style="COLOR: #000000"> TestAction </span>
				<span style="COLOR: #0000ff">implements</span>
				<span style="COLOR: #000000"> ActionInterface </span>
				<span id="Codehighlighter1_51_135_Closed_Text" style="BORDER-RIGHT: #808080 1px solid; BORDER-TOP: #808080 1px solid; DISPLAY: none; BORDER-LEFT: #808080 1px solid; BORDER-BOTTOM: #808080 1px solid; BACKGROUND-COLOR: #ffffff">
						<img src="http://www.blogjava.net/images/dot.gif" />
				</span>
				<span id="Codehighlighter1_51_135_Open_Text">
						<span style="COLOR: #000000">{<br /><img id="Codehighlighter1_80_133_Open_Image" onclick="this.style.display='none'; Codehighlighter1_80_133_Open_Text.style.display='none'; Codehighlighter1_80_133_Closed_Image.style.display='inline'; Codehighlighter1_80_133_Closed_Text.style.display='inline';" src="http://www.blogjava.net/images/OutliningIndicators/ExpandedSubBlockStart.gif" align="top" /><img id="Codehighlighter1_80_133_Closed_Image" style="DISPLAY: none" onclick="this.style.display='none'; Codehighlighter1_80_133_Closed_Text.style.display='none'; Codehighlighter1_80_133_Open_Image.style.display='inline'; Codehighlighter1_80_133_Open_Text.style.display='inline';" src="http://www.blogjava.net/images/OutliningIndicators/ContractedSubBlock.gif" align="top" />    </span>
						<span style="COLOR: #0000ff">public</span>
						<span style="COLOR: #000000"> String action() </span>
						<span id="Codehighlighter1_80_133_Closed_Text" style="BORDER-RIGHT: #808080 1px solid; BORDER-TOP: #808080 1px solid; DISPLAY: none; BORDER-LEFT: #808080 1px solid; BORDER-BOTTOM: #808080 1px solid; BACKGROUND-COLOR: #ffffff">
								<img src="http://www.blogjava.net/images/dot.gif" />
						</span>
						<span id="Codehighlighter1_80_133_Open_Text">
								<span style="COLOR: #000000">{<br /><img src="http://www.blogjava.net/images/OutliningIndicators/InBlock.gif" align="top" />        </span>
								<span style="COLOR: #0000ff">return</span>
								<span style="COLOR: #000000"> </span>
								<span style="COLOR: #000000">"</span>
								<span style="COLOR: #000000">com.mxjava.TestAction.action</span>
								<span style="COLOR: #000000">"</span>
								<span style="COLOR: #000000">;<br /><img src="http://www.blogjava.net/images/OutliningIndicators/ExpandedSubBlockEnd.gif" align="top" />    }</span>
						</span>
						<span style="COLOR: #000000">
								<br />
								<img src="http://www.blogjava.net/images/OutliningIndicators/ExpandedBlockEnd.gif" align="top" />}</span>
				</span>
				<span style="COLOR: #000000">
						<br />
						<img src="http://www.blogjava.net/images/OutliningIndicators/None.gif" align="top" />
				</span>
		</div>
		<p>完成后将其打包为test.jar，放在c盘根目录下。下面要做的就是利用URLClassLoader加载并运行TestAction的action方法，并将返回的值打印在控制台上。</p>
		<p>新建一工程，引入testInterface.jar包。并创建一可执行类（main方法），在其中加入如下代码：</p>
		<div style="BORDER-RIGHT: #cccccc 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: #cccccc 1px solid; PADDING-LEFT: 4px; FONT-SIZE: 13px; PADDING-BOTTOM: 4px; BORDER-LEFT: #cccccc 1px solid; WIDTH: 98%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: #cccccc 1px solid; BACKGROUND-COLOR: #eeeeee">
				<img src="http://www.blogjava.net/images/OutliningIndicators/None.gif" align="top" />
				<span style="COLOR: #000000">URL url </span>
				<span style="COLOR: #000000">=</span>
				<span style="COLOR: #000000"> </span>
				<span style="COLOR: #0000ff">new</span>
				<span style="COLOR: #000000"> URL(“file:C:</span>
				<span style="COLOR: #000000">/</span>
				<span style="COLOR: #000000">test.jar”);<br /><img id="Codehighlighter1_99_105_Open_Image" onclick="this.style.display='none'; Codehighlighter1_99_105_Open_Text.style.display='none'; Codehighlighter1_99_105_Closed_Image.style.display='inline'; Codehighlighter1_99_105_Closed_Text.style.display='inline';" src="http://www.blogjava.net/images/OutliningIndicators/ExpandedBlockStart.gif" align="top" /><img id="Codehighlighter1_99_105_Closed_Image" style="DISPLAY: none" onclick="this.style.display='none'; Codehighlighter1_99_105_Closed_Text.style.display='none'; Codehighlighter1_99_105_Open_Image.style.display='inline'; Codehighlighter1_99_105_Open_Text.style.display='inline';" src="http://www.blogjava.net/images/OutliningIndicators/ContractedBlock.gif" align="top" />URLClassLoader myClassLoader </span>
				<span style="COLOR: #000000">=</span>
				<span style="COLOR: #000000"> </span>
				<span style="COLOR: #0000ff">new</span>
				<span style="COLOR: #000000"> URLClassLoader(</span>
				<span style="COLOR: #0000ff">new</span>
				<span style="COLOR: #000000"> URL[] </span>
				<span id="Codehighlighter1_99_105_Closed_Text" style="BORDER-RIGHT: #808080 1px solid; BORDER-TOP: #808080 1px solid; DISPLAY: none; BORDER-LEFT: #808080 1px solid; BORDER-BOTTOM: #808080 1px solid; BACKGROUND-COLOR: #ffffff">
						<img src="http://www.blogjava.net/images/dot.gif" />
				</span>
				<span id="Codehighlighter1_99_105_Open_Text">
						<span style="COLOR: #000000">{ url }</span>
				</span>
				<span style="COLOR: #000000">);<br /><img src="http://www.blogjava.net/images/OutliningIndicators/None.gif" align="top" />Class myClass </span>
				<span style="COLOR: #000000">=</span>
				<span style="COLOR: #000000"> myClassLoader.loadClass(“com.mxjava.TestAction”);<br /><img src="http://www.blogjava.net/images/OutliningIndicators/None.gif" align="top" />ActionInterface action </span>
				<span style="COLOR: #000000">=</span>
				<span style="COLOR: #000000"> (ActionInterface)myClass.newInstance();<br /><img src="http://www.blogjava.net/images/OutliningIndicators/None.gif" align="top" />System.out.println(action.action());</span>
		</div>
		<p>　　在上面的例子中，首先利用URLClassLoader加载了C:\test.jar包，将其中的com.mxjava.TestAction类载入内存，将其强制转型为testInterface包中的ActionInterface类型，最后调用其action方法，并打印到控制台中。</p>
		<p>　　执行程序后，在控制台上如期打印出我们想要的内容。但是，事情并没有那么简单，当我们将该代码移动web应用中时，就会抛出异常。原来，Java为我们提供了三种可选择的ClassLoader：<br />1. 系统类加载器或叫作应用类加载器 (system classloader or application classloader)<br />2. 当前类加载器<br />3. 当前线程类加载器</p>
		<p>　　在上例中我们使用javac命令来运行该程序，这时候使用的是系统类加载器 (system classloader)。这个类加载器处理 -classpath下的类加载工作，可以通过ClassLoader.getSystemClassLoader()方法调用。 ClassLoader 下所有的 getSystemXXX()的静态方法都是通过这个方法定义的。在代码中，应该尽量少地调用这个方法，以其它的类加载器作为代理。否则代码将只能工作在简单的命令行应用中。当在web应用中时，服务器也是利用ClassLoader来加载class的，由于ClassLoader的不同，所以在强制转型时JVM认定不是同一类型。（在JAVA中，一个类用其完全匹配类名(fully qualified class name)作为标识，这里指的完全匹配类名包括包名和类名。但在JVM中一个类用其全名和一个加载类ClassLoader的实例作为唯一标识。因此，如果一个名为Pg的包中，有一个名为Cl的类，被类加载器KlassLoader的一个实例kl1加载，Cl的实例，即C1.class在JVM中表示为(Cl, Pg, kl1)。这意味着两个类加载器的实例(Cl, Pg, kl1) 和 (Cl, Pg, kl2)是不同的，被它们所加载的类也因此完全不同，互不兼容的。）为了能够使程序正确运行，我们首要解决的问题就是，如何将URLClassLoader加载的类，同当前ClassLoader保持在同一类加载器中。解决方法很简单，利用java提供的第三种ClassLoader—当前线程类加载器即可。jdk api文档就会发现，URLClassLoader提供了三种构造方式：</p>
		<div style="BORDER-RIGHT: #cccccc 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: #cccccc 1px solid; PADDING-LEFT: 4px; FONT-SIZE: 13px; PADDING-BOTTOM: 4px; BORDER-LEFT: #cccccc 1px solid; WIDTH: 98%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: #cccccc 1px solid; BACKGROUND-COLOR: #eeeeee">
				<img src="http://www.blogjava.net/images/OutliningIndicators/None.gif" align="top" />
				<span style="COLOR: #008000">//</span>
				<span style="COLOR: #008000">使用默认的委托父 ClassLoader 为指定的 URL 构造一个新 URLClassLoader。 </span>
				<span style="COLOR: #008000">
						<br />
						<img src="http://www.blogjava.net/images/OutliningIndicators/None.gif" align="top" />
				</span>
				<span style="COLOR: #000000">URLClassLoader(URL[] urls) <br /><img src="http://www.blogjava.net/images/OutliningIndicators/None.gif" align="top" /></span>
				<span style="COLOR: #008000">//</span>
				<span style="COLOR: #008000">为给定的 URL 构造新 URLClassLoader。 </span>
				<span style="COLOR: #008000">
						<br />
						<img src="http://www.blogjava.net/images/OutliningIndicators/None.gif" align="top" />
				</span>
				<span style="COLOR: #000000">URLClassLoader(URL[] urls, ClassLoader parent) <br /><img src="http://www.blogjava.net/images/OutliningIndicators/None.gif" align="top" /></span>
				<span style="COLOR: #008000">//</span>
				<span style="COLOR: #008000">为指定的 URL、父类加载器和 URLStreamHandlerFactory 创建新 URLClassLoader。</span>
				<span style="COLOR: #008000">
						<br />
						<img src="http://www.blogjava.net/images/OutliningIndicators/None.gif" align="top" />
				</span>
				<span style="COLOR: #000000">URLClassLoader(URL[] urls, ClassLoader parent, URLStreamHandlerFactory factory) </span>
		</div>
		<p>接下来要做的就是，在构造URLClassLoader时，将当前线程类加载器置入即可。如下：</p>
		<div style="BORDER-RIGHT: #cccccc 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: #cccccc 1px solid; PADDING-LEFT: 4px; FONT-SIZE: 13px; PADDING-BOTTOM: 4px; BORDER-LEFT: #cccccc 1px solid; WIDTH: 98%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: #cccccc 1px solid; BACKGROUND-COLOR: #eeeeee">
				<img id="Codehighlighter1_60_66_Open_Image" onclick="this.style.display='none'; Codehighlighter1_60_66_Open_Text.style.display='none'; Codehighlighter1_60_66_Closed_Image.style.display='inline'; Codehighlighter1_60_66_Closed_Text.style.display='inline';" src="http://www.blogjava.net/images/OutliningIndicators/ExpandedBlockStart.gif" align="top" />
				<img id="Codehighlighter1_60_66_Closed_Image" style="DISPLAY: none" onclick="this.style.display='none'; Codehighlighter1_60_66_Closed_Text.style.display='none'; Codehighlighter1_60_66_Open_Image.style.display='inline'; Codehighlighter1_60_66_Open_Text.style.display='inline';" src="http://www.blogjava.net/images/OutliningIndicators/ContractedBlock.gif" align="top" />
				<span style="COLOR: #000000">URLClassLoader myClassLoader </span>
				<span style="COLOR: #000000">=</span>
				<span style="COLOR: #000000"> </span>
				<span style="COLOR: #0000ff">new</span>
				<span style="COLOR: #000000"> URLClassLoader(</span>
				<span style="COLOR: #0000ff">new</span>
				<span style="COLOR: #000000"> URL[] </span>
				<span id="Codehighlighter1_60_66_Closed_Text" style="BORDER-RIGHT: #808080 1px solid; BORDER-TOP: #808080 1px solid; DISPLAY: none; BORDER-LEFT: #808080 1px solid; BORDER-BOTTOM: #808080 1px solid; BACKGROUND-COLOR: #ffffff">
						<img src="http://www.blogjava.net/images/dot.gif" />
				</span>
				<span id="Codehighlighter1_60_66_Open_Text">
						<span style="COLOR: #000000">{ url }</span>
				</span>
				<span style="COLOR: #000000">, Thread.currentThread().getContextClassLoader());</span>
		</div>
		<p>
				<strong>总结：<br />　　</strong>Java是利用ClassLoader来加载类到内存的，ClassLoader本身是用java语言写的，所以我们可以扩展自己的ClassLoader。利用URLClassLoader可以加载指定jar包中的类到内存。在命行上利用URLClassLoader加载jar时，是使用系统类加载器来加载class的，所以在web环境下，就会出错。这是因为JVM中一个类用其全名和一个加载类ClassLoader的实例作为唯一标识的。我们只要利用URLClassLoader的第二种构造方法并传入当前线程类加载器即可解决。<br /><br /><em>参考：<br /></em><a href="/sharajava/archive/2006/07/25/59946.html"><em>http://www.blogjava.net/sharajava/archive/2006/07/25/59946.html</em></a><br /><a href="http://kb.csdn.net/java/Articles/200510/a1843d60-05b1-456f-9f72-811cb45ea4ae.html"><em>http://kb.csdn.net/java/Articles/200510/a1843d60-05b1-456f-9f72-811cb45ea4ae.html</em></a><br /></p>
<img src ="http://www.blogjava.net/beauty_beast/aggbug/71981.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/beauty_beast/" target="_blank">柳随风</a> 2006-09-26 15:14 <a href="http://www.blogjava.net/beauty_beast/archive/2006/09/26/71981.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>oracle 9i 应用系统优化</title><link>http://www.blogjava.net/beauty_beast/archive/2006/09/22/71250.html</link><dc:creator>柳随风</dc:creator><author>柳随风</author><pubDate>Fri, 22 Sep 2006 02:01:00 GMT</pubDate><guid>http://www.blogjava.net/beauty_beast/archive/2006/09/22/71250.html</guid><wfw:comment>http://www.blogjava.net/beauty_beast/comments/71250.html</wfw:comment><comments>http://www.blogjava.net/beauty_beast/archive/2006/09/22/71250.html#Feedback</comments><slash:comments>2</slash:comments><wfw:commentRss>http://www.blogjava.net/beauty_beast/comments/commentRss/71250.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/beauty_beast/services/trackbacks/71250.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: 最近单位要求写技术总结，我根据《ORACLE9i_优化设计与系统调整.》以及网上相关文章，主要结合自己的实际工作经历，写了一篇《oracle 9i 应用系统优化》,本人水平有限，如果有误的话请多多指正，避免误导他人，同时也希望大家共享相关的经验，让我也学习学习。写这篇文章还是花了不少时间，如果转载的话请注明出处。Oracle9i 应用系统优化1、优化前提应用系统方案制定准确，对应用系统运行环境分析...&nbsp;&nbsp;<a href='http://www.blogjava.net/beauty_beast/archive/2006/09/22/71250.html'>阅读全文</a><img src ="http://www.blogjava.net/beauty_beast/aggbug/71250.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/beauty_beast/" target="_blank">柳随风</a> 2006-09-22 10:01 <a href="http://www.blogjava.net/beauty_beast/archive/2006/09/22/71250.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>oracle 优化器</title><link>http://www.blogjava.net/beauty_beast/archive/2006/07/07/57125.html</link><dc:creator>柳随风</dc:creator><author>柳随风</author><pubDate>Fri, 07 Jul 2006 06:19:00 GMT</pubDate><guid>http://www.blogjava.net/beauty_beast/archive/2006/07/07/57125.html</guid><wfw:comment>http://www.blogjava.net/beauty_beast/comments/57125.html</wfw:comment><comments>http://www.blogjava.net/beauty_beast/archive/2006/07/07/57125.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/beauty_beast/comments/commentRss/57125.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/beauty_beast/services/trackbacks/57125.html</trackback:ping><description><![CDATA[
		<table cellspacing="0" cellpadding="0" width="100%" border="0">
				<tbody>
						<tr>
								<td align="middle" bgcolor="#f7fbfe" colspan="2" height="10">－－从<font color="#ff0000"><a href="http://www.oradb.net/">http://www.oradb.net/</a>转载过来</font></td>
						</tr>
						<tr>
								<td valign="top" align="left" width="98%" bgcolor="#f7fbfe" height="142">
										<span class="top11">
												<p class="style35">Oracle在执行一个SQL之前,首先要分析一下语句的执行计划,然后再按执行计划去执行。分析语句的执行计划的工作是由优化器(Optimizer)来完成的。不同的情况,一条SQL可能有多种执行计划,但在某一时点,一定只有一种执行计划是最优的,花费时间是最少的。相信你一定会用Pl/sql Developer、Toad等工具去看一个语句的执行计划,不过你可能对Rule、Choose、First rows、All rows这几项有疑问,因为我当初也是这样的,那时我也疑惑为什么选了以上的不同的项,执行计划就变了? </p>
												<p class="style35">
														<strong>1、优化器的优化方式 </strong>
												</p>
												<p class="style35">Oracle的优化器共有两种的优化方式,即基于规则的优化方式(Rule-Based Optimization,简称为RBO)和基于代价的优化方式(Cost-Based Optimization,简称为CBO)。 </p>
												<p class="style35">A、RBO方式：优化器在分析SQL语句时,所遵循的是Oracle内部预定的一些规则。比如我们常见的,当一个where子句中的一列有索引时去走索引。 </p>
												<p class="style35">B、CBO方式：依词义可知,它是看语句的代价(Cost)了,这里的代价主要指Cpu和内存。优化器在判断是否用这种方式时,主要参照的是表及索引的统计信息。统计信息给出表的大小 、有少行、每行的长度等信息。这些统计信息起初在库内是没有的,是你在做analyze后才出现的,很多的时侯过期统计信息会令优化器做出一个错误的执行计划,因些我们应及时更新这些信息。在Oracle8及以后的版本,Oracle列推荐用CBO的方式。 </p>
												<p class="style35">我们要明了,不一定走索引就是优的 ,比如一个表只有两行数据,一次IO就可以完成全表的检索,而此时走索引时则需要两次IO,这时对这个表做全表扫描(full table scan)是最好的。 </p>
												<p class="style35">
														<strong>2、优化器的优化模式(Optermizer Mode) </strong>
												</p>
												<p class="style35">优化模式包括Rule,Choose,First rows,All rows这四种方式,也就是我们以上所提及的。如下我解释一下： </p>
												<p class="style35">Rule:不用多说,即走基于规则的方式。 </p>
												<p class="style35">Choolse:这是我们应观注的,默认的情况下Oracle用的便是这种方式。指的是当一个表或或索引有统计信息,则走CBO的方式,如果表或索引没统计信息,表又不是特别的小,而且相应的列有索引时,那么就走索引,走RBO的方式。 </p>
												<p class="style35">First Rows:它与Choose方式是类似的,所不同的是当一个表有统计信息时,它将是以最快的方式返回查询的最先的几行,从总体上减少了响应时间。 </p>
												<p class="style35">All Rows:也就是我们所说的Cost的方式,当一个表有统计信息时,它将以最快的方式返回表的所有的行,从总体上提高查询的吞吐量。没有统计信息则走基于规则的方式。 </p>
												<p class="style35">
														<strong>3、如何设定选用哪种优化模式 </strong>
												</p>
												<p class="style35">a、Instance级别 </p>
												<p class="style35">我们可以通过在init&lt;SID&gt;.ora文件中设定OPTIMIZER_MODE=RULE、OPTIMIZER_MODE=CHOOSE、OPTIMIZER_MODE=FIRST_ROWS、OPTIMIZER_MODE=ALL_ROWS去选用3所提的四种方式,如果你没设定OPTIMIZER_MODE参数则默认用的是Choose这种方式。 </p>
												<p class="style35">B、Sessions级别 </p>
												<p class="style35">通过SQL&gt; ALTER SESSION SET OPTIMIZER_MODE=&lt;Mode&gt;;来设定。 <br /><br />C、语句级别 </p>
												<p class="style35">这些需要用到Hint,比如: <br /><br />SQL&gt; SELECT /*+ RULE */ a.userid, <br /><br />2 b.name, <br /><br />3 b.depart_name <br /><br />4 FROM tf_f_yhda a, <br /><br />5 tf_f_depart b <br /><br />6 WHERE a.userid=b.userid; </p>
												<p class="style35">
														<strong>4、为什么有时一个表的某个字段明明有索引,当观察一些语的执行计划确不走索引呢？如何解决呢 ？ </strong>
												</p>
												<p class="style35">A、不走索引大体有以下几个原因 <br /><br />♀你在Instance级别所用的是all_rows的方式 <br /><br />♀你的表的统计信息(最可能的原因) <br /><br />♀你的表很小,上文提到过的,Oracle的优化器认为不值得走索引。 <br /><br />B、解决方法 <br /><br />♀可以修改init&lt;SID&gt;.ora中的OPTIMIZER_MODE这个参数,把它改为Rule或Choose,重起数据库。也可以使用4中所提的Hint. <br /><br />♀删除统计信息 <br /><br />SQL&gt;analyze table table_name delete statistics; <br /><br />♀表小不走索引是对的,不用调的。 </p>
												<p class="style35">
														<strong>5、其它相关 </strong>
												</p>
												<p class="style35">A、如何看一个表或索引是否是统计信息 </p>
												<p class="style35">SQL&gt;SELECT * FROM user_tables <br /><br />2 WHERE table_name=&lt;table_name&gt; <br /><br />3 AND num_rows is not null; </p>
												<p class="style35">SQL&gt;SELECT * FROM user_indexes <br /><br />2 WHERE table_name=&lt;table_name&gt; <br /><br />3 AND num_rows is not null; </p>
												<p class="style35">b、如果我们先用CBO的方式,我们应及时去更新表和索引的统计信息,以免生形不切合实的执行计划。 <br /><br />SQL&gt; ANALYZE TABLE table_name COMPUTE STATISTICS; <br /><br />SQL&gt; ANALYZE INDEX index_name ESTIMATE STATISTICS; </p>
												<p class="style35">具体的ANALYZE语句请参照Oracle8i/9i 的refrence文档。 <br /><br /><br /><br /></p>
										</span>
								</td>
						</tr>
				</tbody>
		</table>
<img src ="http://www.blogjava.net/beauty_beast/aggbug/57125.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/beauty_beast/" target="_blank">柳随风</a> 2006-07-07 14:19 <a href="http://www.blogjava.net/beauty_beast/archive/2006/07/07/57125.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Spring学习笔记二----Web MVC</title><link>http://www.blogjava.net/beauty_beast/archive/2006/06/24/54840.html</link><dc:creator>柳随风</dc:creator><author>柳随风</author><pubDate>Sat, 24 Jun 2006 02:59:00 GMT</pubDate><guid>http://www.blogjava.net/beauty_beast/archive/2006/06/24/54840.html</guid><wfw:comment>http://www.blogjava.net/beauty_beast/comments/54840.html</wfw:comment><comments>http://www.blogjava.net/beauty_beast/archive/2006/06/24/54840.html#Feedback</comments><slash:comments>1</slash:comments><wfw:commentRss>http://www.blogjava.net/beauty_beast/comments/commentRss/54840.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/beauty_beast/services/trackbacks/54840.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: 花了两个星期学习				Spring WebMVC				，				总体感觉收获不少，感受比较深的是				Spring				框架的确解决了在				j2EE				开发中经常遇到的问题				,				自己也写了一个覆盖框架主要功能的简单例子，和大家一起交流、分享一下，如有错误，欢迎大家多指正。								－、相对于WebWork的特性	...&nbsp;&nbsp;<a href='http://www.blogjava.net/beauty_beast/archive/2006/06/24/54840.html'>阅读全文</a><img src ="http://www.blogjava.net/beauty_beast/aggbug/54840.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/beauty_beast/" target="_blank">柳随风</a> 2006-06-24 10:59 <a href="http://www.blogjava.net/beauty_beast/archive/2006/06/24/54840.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>盖茨写给大学生的人生建议</title><link>http://www.blogjava.net/beauty_beast/archive/2006/06/20/53935.html</link><dc:creator>柳随风</dc:creator><author>柳随风</author><pubDate>Tue, 20 Jun 2006 03:44:00 GMT</pubDate><guid>http://www.blogjava.net/beauty_beast/archive/2006/06/20/53935.html</guid><wfw:comment>http://www.blogjava.net/beauty_beast/comments/53935.html</wfw:comment><comments>http://www.blogjava.net/beauty_beast/archive/2006/06/20/53935.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/beauty_beast/comments/commentRss/53935.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/beauty_beast/services/trackbacks/53935.html</trackback:ping><description><![CDATA[本文是转载，个人工作五年多了，还是比较认可他的建议，对刚毕业的大学生还是挺有指导意义的。<br />1. Life is not fair, get used to it. <br />1． 生活是不公平的；要去适应它。 <br /><br />2. The world won't care about your selfesteem. The world will expect you to accomplish something before you feel good about yourself. <br />2．这世界并不会在意你的自尊。这世界指望你在自我感觉良好之前先要有所成就。 <br /><br />3. You will not make 40 thousand dollars a year right out of high school. You won't be a vice president with a car phone, until you earn both. <br />3．高中刚毕业你不会一年挣4万美元。你不会成为一个公司的副总裁，并拥有一部装有电话的汽车，直到你将此职位和汽车电话都挣到手。 <br /><br />4. If you think your teacher is tough, wait till you get a boss. He doesn't have tenure. <br />4．如果你认为你的老师严厉，等你有了老板再这样想。老板可是没有任期限制的。 <br /><br />5. Flipping burgers is not beneath your dignity. Your grandparents had a different word for burger flipping; they called it opportunity. <br />5．烙牛肉饼并不有损你的尊严。你的祖父母对烙牛肉饼可有不同的定义；他们称它为机遇。 <br /><br />6. If you mess up, it's not your parents' fault, so don't whine about our mistakes, learn from them. <br />6．如果你陷入困境，那不是你父母的过错，所以不要尖声抱怨我们的错误，要从中吸取教训。 <br /><br />7. Before you were born, your parents weren't as boring as they are now. They got that way from paying your bills, cleaning your clothes and listening to you talk about how cool you are. So before you save the rain forest from the parasites of your parents' generation, try "delousing" the closet in your own room. <br />7．在你出生之前，你的父母并非像他们现在这样乏味。他们变成今天这个样子是因为这些年来他们一直在为你付账单，给你洗衣服，听你大谈你是如何的酷。所以，如果你想消灭你父母那一辈中的"寄生虫"来拯救雨林的话，还是先去清除你房间衣柜里的虫子吧。 <br /><br />8. Your school may have done away with winners and losers, but life has not. In some schools they have abolished failing grades; they'll give you as many times as you want to get the right answer. This doesn't bear the slightest resemblance to anything in real life. <br />8．你的学校也许已经不再分优等生和劣等生，但生活却仍在作出类似区分。在某些学校已经废除不及格分；只要你想找到正确答案，学校就会给你无数的机会。这和现实生活中的任何事情没有一点相似之处。 <br /><br />9. Life is not divided into semesters. You don't get summers off and very few employers are interested in helping you find yourself. Do that on your own time. <br />9．生活不分学期。你并没有暑假可以休息，也没有几位雇主乐于帮你发现自我。自己找时间做吧。 <br /><br />10. Television is NOT real life. In real life people actually have to leave the coffee shop and go to jobs. <br />10．电视并不是真实的生活。在现实生活中，人们实际上得离开咖啡屋去干自己的工作。 <br /><br />11. Be nice to nerds. Chances are you'll end up working for one. <br />11．善待你厌恶的人。有可能到头来你会为一个你厌恶的人工作<br /><br /><img src ="http://www.blogjava.net/beauty_beast/aggbug/53935.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/beauty_beast/" target="_blank">柳随风</a> 2006-06-20 11:44 <a href="http://www.blogjava.net/beauty_beast/archive/2006/06/20/53935.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Spring框架学习二篇外----在WEBMVC学习遇到的问题</title><link>http://www.blogjava.net/beauty_beast/archive/2006/05/25/48086.html</link><dc:creator>柳随风</dc:creator><author>柳随风</author><pubDate>Thu, 25 May 2006 10:10:00 GMT</pubDate><guid>http://www.blogjava.net/beauty_beast/archive/2006/05/25/48086.html</guid><wfw:comment>http://www.blogjava.net/beauty_beast/comments/48086.html</wfw:comment><comments>http://www.blogjava.net/beauty_beast/archive/2006/05/25/48086.html#Feedback</comments><slash:comments>3</slash:comments><wfw:commentRss>http://www.blogjava.net/beauty_beast/comments/commentRss/48086.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/beauty_beast/services/trackbacks/48086.html</trackback:ping><description><![CDATA[
		<p>这两天在学习SpringMVC遇到两个比较郁闷的问题，估计新学者很容易遇到，和大家分享一下，避免出现类似的问题。<br />1、 No request handling method with name 'insert' in class  "ClassName"，页面显示为404错误<br />这个问题出现在使用多操作控制器情况下，相关的操作方法中对应的方法参数前两位必须是request,response对象，必须要有，否则会报如上异常。<br />2、这个问题困惑了我半天，在网上也有类似的问题，但没有正确解决方法，异常如下：<br />javax.servlet.ServletException: ModelAndView [ModelAndView: materialized View is [null]<br />这个问题可能出现的场景很多，我所描述的只是其中之一，没有相关解决方法，只有查看相关源代码，开源就是有这个好处。<br />异常抛出代码为：<br />        at org.springframework.web.servlet.DispatcherServlet.render(DispatcherServlet.java:924)<br />查看了相关源代码，一层一层看下去<br />首先在ModelAndView 类实例是在DispatcherServlet类中的doDispatch方法中创建的，<br />再跟踪doDispatch方法中相关代码行<br /></p>
		<div style="BORDER-RIGHT: #cccccc 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: #cccccc 1px solid; PADDING-LEFT: 4px; FONT-SIZE: 13px; PADDING-BOTTOM: 4px; BORDER-LEFT: #cccccc 1px solid; WIDTH: 98%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: #cccccc 1px solid; BACKGROUND-COLOR: #eeeeee">
				<img src="http://www.blogjava.net/images/OutliningIndicators/None.gif" align="top" />
				<span style="COLOR: #000000">HandlerAdapter ha </span>
				<span style="COLOR: #000000">=</span>
				<span style="COLOR: #000000"> getHandlerAdapter(mappedHandler.getHandler());<br /><img src="http://www.blogjava.net/images/OutliningIndicators/None.gif" align="top" />mv </span>
				<span style="COLOR: #000000">=</span>
				<span style="COLOR: #000000"> ha.handle(processedRequest, response, mappedHandler.getHandler());<br /><img src="http://www.blogjava.net/images/OutliningIndicators/None.gif" align="top" /></span>
		</div>
		<p>ha是一个接口实现类，在该场景下，对应的接口实现类为：<br />org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter<br /><br />SimpleControllerHandlerAdapter类中对应的实现代码为：</p>
		<div style="BORDER-RIGHT: #cccccc 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: #cccccc 1px solid; PADDING-LEFT: 4px; FONT-SIZE: 13px; PADDING-BOTTOM: 4px; BORDER-LEFT: #cccccc 1px solid; WIDTH: 98%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: #cccccc 1px solid; BACKGROUND-COLOR: #eeeeee">
				<img src="http://www.blogjava.net/images/OutliningIndicators/None.gif" align="top" />
				<span style="COLOR: #000000">((Controller) handler).handleRequest(request, response)</span>
		</div>
		<p>调用的是对应的Controller接口中方法，当前Controller对应的接口实现类为我们配置的自定义控制类，一般继承于org.springframework.web.servlet.mvc.SimpleFormController;一层一层再跟踪发现：<br />SimpleFormController继层于同包AbstractFormController类，而<br />AbstractFormController继承于同包AbstractController类，对应的<br />handleRequest(request,response)在AbstractController类中实现，最终调用代码如下：<br /></p>
		<div style="BORDER-RIGHT: #cccccc 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: #cccccc 1px solid; PADDING-LEFT: 4px; FONT-SIZE: 13px; PADDING-BOTTOM: 4px; BORDER-LEFT: #cccccc 1px solid; WIDTH: 98%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: #cccccc 1px solid; BACKGROUND-COLOR: #eeeeee">
				<img src="http://www.blogjava.net/images/OutliningIndicators/None.gif" align="top" />
				<span style="COLOR: #0000ff">return</span>
				<span style="COLOR: #000000"> handleRequestInternal(request, response)</span>
		</div>
		<p>handleRequest方法为一个抽象方法，在AbstractFormController类中实现，终于找到原因了，呵呵<br /></p>
		<div style="BORDER-RIGHT: #cccccc 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: #cccccc 1px solid; PADDING-LEFT: 4px; FONT-SIZE: 13px; PADDING-BOTTOM: 4px; BORDER-LEFT: #cccccc 1px solid; WIDTH: 98%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: #cccccc 1px solid; BACKGROUND-COLOR: #eeeeee">
				<img src="http://www.blogjava.net/images/OutliningIndicators/None.gif" align="top" />
				<span style="COLOR: #0000ff">protected</span>
				<span style="COLOR: #000000"> </span>
				<span style="COLOR: #0000ff">final</span>
				<span style="COLOR: #000000"> ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response)<br /><img id="Codehighlighter1_129_1027_Open_Image" onclick="this.style.display='none'; Codehighlighter1_129_1027_Open_Text.style.display='none'; Codehighlighter1_129_1027_Closed_Image.style.display='inline'; Codehighlighter1_129_1027_Closed_Text.style.display='inline';" src="http://www.blogjava.net/images/OutliningIndicators/ExpandedBlockStart.gif" align="top" /><img id="Codehighlighter1_129_1027_Closed_Image" style="DISPLAY: none" onclick="this.style.display='none'; Codehighlighter1_129_1027_Closed_Text.style.display='none'; Codehighlighter1_129_1027_Open_Image.style.display='inline'; Codehighlighter1_129_1027_Open_Text.style.display='inline';" src="http://www.blogjava.net/images/OutliningIndicators/ContractedBlock.gif" align="top" />            </span>
				<span style="COLOR: #0000ff">throws</span>
				<span style="COLOR: #000000"> Exception </span>
				<span id="Codehighlighter1_129_1027_Closed_Text" style="BORDER-RIGHT: #808080 1px solid; BORDER-TOP: #808080 1px solid; DISPLAY: none; BORDER-LEFT: #808080 1px solid; BORDER-BOTTOM: #808080 1px solid; BACKGROUND-COLOR: #ffffff">
						<img src="http://www.blogjava.net/images/dot.gif" />
				</span>
				<span id="Codehighlighter1_129_1027_Open_Text">
						<span style="COLOR: #000000">{<br /><img src="http://www.blogjava.net/images/OutliningIndicators/InBlock.gif" align="top" /><br /><img src="http://www.blogjava.net/images/OutliningIndicators/InBlock.gif" align="top" />        </span>
						<span style="COLOR: #008000">//</span>
						<span style="COLOR: #008000"> Form submission or new form to show?</span>
						<span style="COLOR: #008000">
								<br />
								<img id="Codehighlighter1_207_926_Open_Image" onclick="this.style.display='none'; Codehighlighter1_207_926_Open_Text.style.display='none'; Codehighlighter1_207_926_Closed_Image.style.display='inline'; Codehighlighter1_207_926_Closed_Text.style.display='inline';" src="http://www.blogjava.net/images/OutliningIndicators/ExpandedSubBlockStart.gif" align="top" />
								<img id="Codehighlighter1_207_926_Closed_Image" style="DISPLAY: none" onclick="this.style.display='none'; Codehighlighter1_207_926_Closed_Text.style.display='none'; Codehighlighter1_207_926_Open_Image.style.display='inline'; Codehighlighter1_207_926_Open_Text.style.display='inline';" src="http://www.blogjava.net/images/OutliningIndicators/ContractedSubBlock.gif" align="top" />
						</span>
						<span style="COLOR: #000000">        </span>
						<span style="COLOR: #0000ff">if</span>
						<span style="COLOR: #000000"> (isFormSubmission(request)) </span>
						<span id="Codehighlighter1_207_926_Closed_Text" style="BORDER-RIGHT: #808080 1px solid; BORDER-TOP: #808080 1px solid; DISPLAY: none; BORDER-LEFT: #808080 1px solid; BORDER-BOTTOM: #808080 1px solid; BACKGROUND-COLOR: #ffffff">
								<img src="http://www.blogjava.net/images/dot.gif" />
						</span>
						<span id="Codehighlighter1_207_926_Open_Text">
								<span style="COLOR: #000000">{<br /><img src="http://www.blogjava.net/images/OutliningIndicators/InBlock.gif" align="top" /><br /><img src="http://www.blogjava.net/images/OutliningIndicators/InBlock.gif" align="top" />            </span>
								<span style="COLOR: #008000">//</span>
								<span style="COLOR: #008000"> Form submission: in session-form mode, we need to find<br /><img src="http://www.blogjava.net/images/OutliningIndicators/InBlock.gif" align="top" />            </span>
								<span style="COLOR: #008000">//</span>
								<span style="COLOR: #008000"> the form object in the HTTP session.</span>
								<span style="COLOR: #008000">
										<br />
										<img id="Codehighlighter1_338_625_Open_Image" onclick="this.style.display='none'; Codehighlighter1_338_625_Open_Text.style.display='none'; Codehighlighter1_338_625_Closed_Image.style.display='inline'; Codehighlighter1_338_625_Closed_Text.style.display='inline';" src="http://www.blogjava.net/images/OutliningIndicators/ExpandedSubBlockStart.gif" align="top" />
										<img id="Codehighlighter1_338_625_Closed_Image" style="DISPLAY: none" onclick="this.style.display='none'; Codehighlighter1_338_625_Closed_Text.style.display='none'; Codehighlighter1_338_625_Open_Image.style.display='inline'; Codehighlighter1_338_625_Open_Text.style.display='inline';" src="http://www.blogjava.net/images/OutliningIndicators/ContractedSubBlock.gif" align="top" />
								</span>
								<span style="COLOR: #000000">            </span>
								<span style="COLOR: #0000ff">if</span>
								<span style="COLOR: #000000"> (isSessionForm()) </span>
								<span id="Codehighlighter1_338_625_Closed_Text" style="BORDER-RIGHT: #808080 1px solid; BORDER-TOP: #808080 1px solid; DISPLAY: none; BORDER-LEFT: #808080 1px solid; BORDER-BOTTOM: #808080 1px solid; BACKGROUND-COLOR: #ffffff">
										<img src="http://www.blogjava.net/images/dot.gif" />
								</span>
								<span id="Codehighlighter1_338_625_Open_Text">
										<span style="COLOR: #000000">{<br /><img src="http://www.blogjava.net/images/OutliningIndicators/InBlock.gif" align="top" />                HttpSession session </span>
										<span style="COLOR: #000000">=</span>
										<span style="COLOR: #000000"> request.getSession(</span>
										<span style="COLOR: #0000ff">false</span>
										<span style="COLOR: #000000">);<br /><img id="Codehighlighter1_488_620_Open_Image" onclick="this.style.display='none'; Codehighlighter1_488_620_Open_Text.style.display='none'; Codehighlighter1_488_620_Closed_Image.style.display='inline'; Codehighlighter1_488_620_Closed_Text.style.display='inline';" src="http://www.blogjava.net/images/OutliningIndicators/ExpandedSubBlockStart.gif" align="top" /><img id="Codehighlighter1_488_620_Closed_Image" style="DISPLAY: none" onclick="this.style.display='none'; Codehighlighter1_488_620_Closed_Text.style.display='none'; Codehighlighter1_488_620_Open_Image.style.display='inline'; Codehighlighter1_488_620_Open_Text.style.display='inline';" src="http://www.blogjava.net/images/OutliningIndicators/ContractedSubBlock.gif" align="top" />                </span>
										<span style="COLOR: #0000ff">if</span>
										<span style="COLOR: #000000"> (session </span>
										<span style="COLOR: #000000">==</span>
										<span style="COLOR: #000000"> </span>
										<span style="COLOR: #0000ff">null</span>
										<span style="COLOR: #000000"> </span>
										<span style="COLOR: #000000">||</span>
										<span style="COLOR: #000000"> session.getAttribute(getFormSessionAttributeName(request)) </span>
										<span style="COLOR: #000000">==</span>
										<span style="COLOR: #000000"> </span>
										<span style="COLOR: #0000ff">null</span>
										<span style="COLOR: #000000">) </span>
										<span id="Codehighlighter1_488_620_Closed_Text" style="BORDER-RIGHT: #808080 1px solid; BORDER-TOP: #808080 1px solid; DISPLAY: none; BORDER-LEFT: #808080 1px solid; BORDER-BOTTOM: #808080 1px solid; BACKGROUND-COLOR: #ffffff">
												<img src="http://www.blogjava.net/images/dot.gif" />
										</span>
										<span id="Codehighlighter1_488_620_Open_Text">
												<span style="COLOR: #000000">{<br /><img src="http://www.blogjava.net/images/OutliningIndicators/InBlock.gif" align="top" />                    </span>
												<span style="COLOR: #008000">//</span>
												<span style="COLOR: #008000"> Cannot submit a session form if no form object is in the session.</span>
												<span style="COLOR: #008000">
														<br />
														<img src="http://www.blogjava.net/images/OutliningIndicators/InBlock.gif" align="top" />
												</span>
												<span style="COLOR: #000000">                    </span>
												<span style="COLOR: #0000ff">return</span>
												<span style="COLOR: #000000"> handleInvalidSubmit(request, response);<br /><img src="http://www.blogjava.net/images/OutliningIndicators/ExpandedSubBlockEnd.gif" align="top" />                }</span>
										</span>
										<span style="COLOR: #000000">
												<br />
												<img src="http://www.blogjava.net/images/OutliningIndicators/ExpandedSubBlockEnd.gif" align="top" />            }</span>
								</span>
								<span style="COLOR: #000000">
										<br />
										<img src="http://www.blogjava.net/images/OutliningIndicators/InBlock.gif" align="top" />
										<br />
										<img src="http://www.blogjava.net/images/OutliningIndicators/InBlock.gif" align="top" />            </span>
								<span style="COLOR: #008000">//</span>
								<span style="COLOR: #008000"> Found form object in HTTP session: fetch form object,<br /><img src="http://www.blogjava.net/images/OutliningIndicators/InBlock.gif" align="top" />            </span>
								<span style="COLOR: #008000">//</span>
								<span style="COLOR: #008000"> bind, validate, process submission.</span>
								<span style="COLOR: #008000">
										<br />
										<img src="http://www.blogjava.net/images/OutliningIndicators/InBlock.gif" align="top" />
								</span>
								<span style="COLOR: #000000">            Object command </span>
								<span style="COLOR: #000000">=</span>
								<span style="COLOR: #000000"> getCommand(request);<br /><img src="http://www.blogjava.net/images/OutliningIndicators/InBlock.gif" align="top" />            ServletRequestDataBinder binder </span>
								<span style="COLOR: #000000">=</span>
								<span style="COLOR: #000000"> bindAndValidate(request, command);<br /><img src="http://www.blogjava.net/images/OutliningIndicators/InBlock.gif" align="top" />            </span>
								<span style="COLOR: #0000ff">return</span>
								<span style="COLOR: #000000"> processFormSubmission(request, response, command, binder.getErrors());<br /><img src="http://www.blogjava.net/images/OutliningIndicators/ExpandedSubBlockEnd.gif" align="top" />        }</span>
						</span>
						<span style="COLOR: #000000">
								<br />
								<img src="http://www.blogjava.net/images/OutliningIndicators/InBlock.gif" align="top" />
								<br />
								<img id="Codehighlighter1_936_1024_Open_Image" onclick="this.style.display='none'; Codehighlighter1_936_1024_Open_Text.style.display='none'; Codehighlighter1_936_1024_Closed_Image.style.display='inline'; Codehighlighter1_936_1024_Closed_Text.style.display='inline';" src="http://www.blogjava.net/images/OutliningIndicators/ExpandedSubBlockStart.gif" align="top" />
								<img id="Codehighlighter1_936_1024_Closed_Image" style="DISPLAY: none" onclick="this.style.display='none'; Codehighlighter1_936_1024_Closed_Text.style.display='none'; Codehighlighter1_936_1024_Open_Image.style.display='inline'; Codehighlighter1_936_1024_Open_Text.style.display='inline';" src="http://www.blogjava.net/images/OutliningIndicators/ContractedSubBlock.gif" align="top" />        </span>
						<span style="COLOR: #0000ff">else</span>
						<span style="COLOR: #000000"> </span>
						<span id="Codehighlighter1_936_1024_Closed_Text" style="BORDER-RIGHT: #808080 1px solid; BORDER-TOP: #808080 1px solid; DISPLAY: none; BORDER-LEFT: #808080 1px solid; BORDER-BOTTOM: #808080 1px solid; BACKGROUND-COLOR: #ffffff">
								<img src="http://www.blogjava.net/images/dot.gif" />
						</span>
						<span id="Codehighlighter1_936_1024_Open_Text">
								<span style="COLOR: #000000">{<br /><img src="http://www.blogjava.net/images/OutliningIndicators/InBlock.gif" align="top" />            </span>
								<span style="COLOR: #008000">//</span>
								<span style="COLOR: #008000"> New form to show: render form view.</span>
								<span style="COLOR: #008000">
										<br />
										<img src="http://www.blogjava.net/images/OutliningIndicators/InBlock.gif" align="top" />
								</span>
								<span style="COLOR: #000000">            </span>
								<span style="COLOR: #0000ff">return</span>
								<span style="COLOR: #000000"> showNewForm(request, response);<br /><img src="http://www.blogjava.net/images/OutliningIndicators/ExpandedSubBlockEnd.gif" align="top" />        }</span>
						</span>
						<span style="COLOR: #000000">
								<br />
								<img src="http://www.blogjava.net/images/OutliningIndicators/ExpandedBlockEnd.gif" align="top" />    }</span>
				</span>
		</div>
		<p>原因实际很简单，就因为我在要提交的表单中没有采用post方法，呵呵<br />而isFormSubmission(request)就是根据此项判断，所以其实际执行的代码为：<br />return showNewForm(request, response);<br />而我在对应的配置属性中没有配置对应属性 formView值，因为我本来就不是要展现一个新表单。<br />故最后返回的ModelAndView为空。<br /><br />问题都解决了，只是没想到对提交表单这么严格，其他web框架是没有这种限制，不过也没多大关系，在实际开发中我们大都是采用post方式提交表单的。<br /><br /></p>
<img src ="http://www.blogjava.net/beauty_beast/aggbug/48086.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/beauty_beast/" target="_blank">柳随风</a> 2006-05-25 18:10 <a href="http://www.blogjava.net/beauty_beast/archive/2006/05/25/48086.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Spring框架学习一----基本配置</title><link>http://www.blogjava.net/beauty_beast/archive/2006/05/25/48064.html</link><dc:creator>柳随风</dc:creator><author>柳随风</author><pubDate>Thu, 25 May 2006 08:39:00 GMT</pubDate><guid>http://www.blogjava.net/beauty_beast/archive/2006/05/25/48064.html</guid><wfw:comment>http://www.blogjava.net/beauty_beast/comments/48064.html</wfw:comment><comments>http://www.blogjava.net/beauty_beast/archive/2006/05/25/48064.html#Feedback</comments><slash:comments>6</slash:comments><wfw:commentRss>http://www.blogjava.net/beauty_beast/comments/commentRss/48064.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/beauty_beast/services/trackbacks/48064.html</trackback:ping><description><![CDATA[
		<p>         花了两三天看了相关文档，只是初步了解相关配置，没有做深入了解，才达到基本会使用配置的目的。<br /><br /><strong>个人理解：</strong><br />         Spring 最基本的的功能是提供通过配置管理任意类，提供了Bean管理容器。这也是Spring最基础，最核心的功能，在Spring框架中，一切皆为Bean,实际上在我们的日常开发中，往往更多的是和Bean相关的开发，并且都是短生命周期，除了一些缓存功能，其他很多重要的功能都是由开源框架完成。<br />        实现方式也是框架常用的反射机制,因此使用Spring前提在JVM启动参数中不能加安全管理配置。<br />        Spring中所有的Bean相关配置均为一个文件，非常集中，个人感觉这有利有弊，利大于弊。<br />        统一配置比较方便集中，我最近的项目就是有很多子系统，除了一些公用的配置文件，还有很多子系统的配置文件，每次迁移环境是非常累的事，如果只有一个配置文件，那工作就会轻松多了。<br />        缺点也就在于很集中，如果配置有误，整个系统不可用，这种情况往往发生在系统升级阶段，也比较少，即使出现问题也比较好定位。<br />         Spring提供了Bean管理容器，这个功能可以大大简化业务系统的配置开发工作。<br />         个人感觉将来的开发工作更多的是配置，编写业务相关的Bean以及业务方法,其他功能均会有很多优秀的开源框架实现，重要的工作是选择符合项目的框架、熟悉框架，最重要的还是苦修内功，深入学习java，因为万变不如其中，再说开源不是万能的，有时候还是要靠自己的。<br />         <br /></p>
		<p>
				<strong>学习心得</strong>：<br />Spring提供三种创建类实例方法:<br />    a、通过构造函数，访问属性可以是任意<br />    b、通过对应类的静态工厂方法，注意不能是私有的<br />     c、通过工厂类的实例工厂方法，是非静态的方法<br /><br />－、bean节点相关主要属性：<br />     id/name          实例名/别名<br />     class              必须、类全名<br />     singleton        是否为单实例，默认为true<br />     init-method     初始化方法<br />     depends-on    指定依赖Bean,实现在被依赖Bean实例创建前，对依赖的一个或多个Bean的实例化,  <br />                           init-method在对应Bean实例已经创建后调用<br />     destroy-method   释放回调方法，常用于资源释放<br />     dependency-check 依赖检查<br />     parent           指定父Bean实例，减少因继承关系的重复配置</p>
		<p>     说明除配置init-method,destory以外，可以在对应Bean中实现InitializingBean,DisposableBean两个接口方法<br />     Spring容器不负责管理非单实例的Bean</p>
		<p>配置bean事项：<br />1、注意在通过构造器参数匹配时，必须指定参数的序号，同时建议明确说明类型，<br />  &lt;constructor-arg index="0"&gt;&lt;value&gt;128&lt;/value&gt;&lt;/constructor-arg&gt;<br />  &lt;constructor-arg index="1"&gt;&lt;value&gt;teststring&lt;/value&gt;&lt;/constructor-arg&gt; <br />  最好说明相关参数类型类型  <br />  &lt;constructor-arg index="0" type="int"&gt;&lt;value&gt;128&lt;/value&gt;&lt;/constructor-arg&gt;<br />  &lt;constructor-arg index="1" type="java.lang.String"&gt;&lt;value&gt;teststring&lt;/value&gt;&lt;/constructor-arg&gt; <br />  可以使用简化配置<br />  &lt;constructor-arg index="0" type="int"  value="128"/&gt;<br />  &lt;constructor-arg index="1" type="java.lang.String" value="testString"/&gt;<br />    <br />2、属性为空<br />    &lt;property name="pro1&gt;&lt;null/&gt;&lt;/property&gt;<br />3、空串 <br />    &lt;property name="pro1&gt;&lt;value&gt;&lt;/value&gt;&lt;/property&gt;   </p>
		<p>二、常用elements<br />bean | ref | idref | value | null | list | set | map | props</p>
		<p>说明：<br />     bean      创建一个新的bean实例<br />     ref         引用已经创建的bean实例<br />     value      简单数据类型<br />     null         空指针<br />     list         创建java.util.ArrayList对象实例<br />     set         创建java.util.LinkedHashSet对象实例<br />     map       创建java.util.LinkedHashMap对象实例<br />     props     创建java.util.Properties实例    </p>
		<p>list,set,map可以多层嵌套配置</p>
		<p>
				<br />遗留问题：<br />idref 配置没有起作用，类似value配置，测试输出为字符串对象，是否配置不正确?<br /><br />上述配置范围基本覆盖了我们开发时的bean使用情况，详见配置可以参考dtd<br /><a href="http://www.springframework.org/dtd/spring-beans.dtd">http://www.springframework.org/dtd/spring-beans.dtd</a></p>
		<p>
				<br />ApplicationContext 继承于BeanFactory相关接口，针对企业应用级的，占有内存较多<br />BeanFactory     提供配置框架和基本功能，适用于对内存有限制的相关应用<br />功能：<br />1、提供消息访问，ApplicationContext 本身继承于MessageSource接口，<br />   ApplicationContext 加载时查找对应的MessageSource Bean, bean id 必须为messageSource<br />   Spring框架提供两个MessageResource实现：<br />    org.springframework.context.support.ResourceBundleMessageSource <br />    org.springframework.context.support.StaticMessageSource<br />2、事件传递<br />3、使用资源<br /><br /></p>
<img src ="http://www.blogjava.net/beauty_beast/aggbug/48064.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/beauty_beast/" target="_blank">柳随风</a> 2006-05-25 16:39 <a href="http://www.blogjava.net/beauty_beast/archive/2006/05/25/48064.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Spring框架学习前言---我的开源框架使用心得体会</title><link>http://www.blogjava.net/beauty_beast/archive/2006/05/16/46421.html</link><dc:creator>柳随风</dc:creator><author>柳随风</author><pubDate>Tue, 16 May 2006 07:45:00 GMT</pubDate><guid>http://www.blogjava.net/beauty_beast/archive/2006/05/16/46421.html</guid><wfw:comment>http://www.blogjava.net/beauty_beast/comments/46421.html</wfw:comment><comments>http://www.blogjava.net/beauty_beast/archive/2006/05/16/46421.html#Feedback</comments><slash:comments>4</slash:comments><wfw:commentRss>http://www.blogjava.net/beauty_beast/comments/commentRss/46421.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/beauty_beast/services/trackbacks/46421.html</trackback:ping><description><![CDATA[
		<p>
		</p>
		<p>
				<strong>目的：</strong>
				<br />在实际项目中出于稳定性以及团队开发技能我一直没考虑采用Spring框架，最近有时间就计划学习Spring框架，为以后的项目开发做相关的技术储备。</p>
		<p>
				<br />
				<strong>回顾：</strong>
				<br />开源框架在以前的项目中用过不少， struts,webwork,hibernate,前两者属于web框架，后者属于ORM框架，这些框架基本都是侧重于应用的某个层面，不能称之为J2EE全面的框架，往往是需要和其他框架相结合，我开发的项目采用过如下组合，有的根本就没有框架，只是自己做了设计封装<br />a、servlet+jdbc,<br />b、servlet+jsp+javabean+jdbc,<br />c、struts+BD+DAO ,<br />d、webwork+ejb+hibernate <br /><br /><strong>使用体会：</strong></p>
		<p> a、b就不要说了，那时候的代码结构真是天马行空，我当时初学，维护公司一个项目代码，1个jsp实现一个模块所有功能，我花了好长时间才 读懂代码。<br />  时代总是向前发展的，慢慢的系统结构层次上是越来越清晰，开发效率也越来越高，维护也越来越容易（基于一定的培训成本）。<br /> <br /><font color="#6666ff"> 相对而言出于设计理念来讲，个人更喜欢webwork+hibernate框架组合。</font> <br /><br /> 常有人讨论同等开发层面框架的优缺点，我所属产品的另外一个项目团队曾经为项目后续开发 应该采用struts还是webwork争论的不可开交， 其实个人认为struts,webwork都是非常好的web框架，都有自身的优缺点：<br /> struts开创web MVC框架之先河，2003－2004年开发的项目基本都是采用struts框架（当时招聘人的时候常问会不会struts，会基本就招了 ），实际MVC设计理念很早就提出了，在j2SE中有很多使用之处，但当时没有一个web框架开发中很好的贯彻该设计理念，直到struts 出现（可能有，只是没有apache更引人注意）。<br /><br /> struts优点使用用户多，相关技术文档比较全面，开发遇到的问题相关案例也比较多、比较容易解决，框架更加稳定(这一点非常重要）。<br /> <br /> webwork相对于struts来讲，的确有不少优异之处，（也是情理之中的事，后发布的框架如果再没有优点别人也不会使用）<br /> 个人认为在开发上主要有以下几点：<br /><br /> 1、页面数据封装成值对象功能比struts强大，webwork采用ognl类型转化。<br /> struts只能对简单类型的数据对象以及文件对象封装成值对象, 而webwork封装的值对象基本没有限制，值对象属性还可以是List,Map这些对象（这个特性在我们开发一些批量数据、复杂数据处理时非常方便），减少很多代码量，代码非常整洁。<br /> <br /> 2、拦截器功能<br /> 拦截器是webwork的一个亮点，实际上也是业界比较流行的AOP的一个体现，在实际开发中你可以配置默认的拦截器， 也可以为单独的模块指定特定的拦截器，并且可自定义拦截器，action执行前后拦截都可以。 <br /> <br /> 3、单元测试比较方便<br /> 最早使用struts框架开发测试时，相关的单元测试基本是不好做的，无法脱离web环境，测试都是只做集成测试、系统测试。<br /> 使用webwork可以简单对action相关属性赋值，可以相关的单元测试，当然前提是在对应action中没有引用web环境相关的对象。 <br /> 其实webwork框架核心还是xwork框架，最早框架使用在C/S结构下，webwork只是xwork的一个在web环境的实现，只是ActionContext 上下文发生了变化，所以说action能够做到脱离web环境也是情理之中的。</p>
		<p>
				<br />4、配置文件<br />webwork配置文件可以采用引用、继承其他配置文件方式，在团队开发一般都是分模块开发，这样比较方便，配置管理更容易，不会冲突。<br /><br />5、模板技术集成<br /> 我在实际应用项目中是采用velocity模板做视图展现，因为在对应版本的webwork框架中和velociy集成的相当好，比较方便， 比直接写jsp代码更整洁、同时利用velocity模板特性结合每个action的配置文件，可实现比较通用的页面查询、录入等视图的展现。<br /> 而struts是没有相关模板集成，不过struts的tag相对而言比webwork的tag好用，webwork如果视图是jsp类型，相关的tag真的比较麻烦, 虽然tag 库很丰富，这也是我为什么使用velocity做视图的原因 。 <br /> <br />6、 框架的效验功能<br /> 老实说，两者框架的效验功能都比较麻烦，相对而言webwork更加麻烦点，配置较多，验证接口实现太麻烦，我实际项目使用中还是 自定义了相关后台验证接口， 要验证的相关action只要实现相关接口即可，相关拦截器负责拦截验证， 大部分的效验根据配置数据以及html对象自定义属性通过javascript通用效验，实际上现在基于XmlHttp的ajax技术应用成熟的话, 后台验证接口的用途会逐渐淡化。 <br /> <br /><font style="BACKGROUND-COLOR: #ffffff" color="#6666ff"> 说明上述比较版本为 struts 1.0 /webwork 2.1.6，后续struts框架扩展很多功能，不是很了解，可能和上诉描述不一定很吻合。<br /></font> <br /> 现在两者已经合并，希望能结合两者的优势，发展出更好web框架。<br /> <br />7、关于Hibernate还是DAO模式，个人建议采用hibernate+DAO相集合的模式，hibernate占主导地位。<br /> 虽然DAO模式通过自动生成代码效率不会低，但如果需求变更就比较麻烦，维护修改代码较多，测试工作量也较大， 但Hibernate不是万能的，在一些必要的应用还是采用DAO模式，特别是性能相关的部分。 <br /> <br /> <br /> 废话一大堆，也算把我的工作回顾了一遍，呵呵，这两天简单了解Spring框架，个人感觉Spring更像一个全面的J2EE框架解决方案, 希望能够有时间系统的学习一把，大家有兴趣的请多多交流,我会将我的学习心得和大家分享。<br /> <br /> </p>
		<p>
				<br /> <br /> <br /> <br /> <br /> <br /> <br /> <br /> <br /> <br /> <br /> <br /> </p>
		<p>
				<br /> </p>
<img src ="http://www.blogjava.net/beauty_beast/aggbug/46421.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/beauty_beast/" target="_blank">柳随风</a> 2006-05-16 15:45 <a href="http://www.blogjava.net/beauty_beast/archive/2006/05/16/46421.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>