簡介
盡管WebSphere/WebLogic都是J2EE應(yīng)用服務(wù)器,但是由于J2EE標(biāo)準(zhǔn)本身的原因,以及不同應(yīng)用服務(wù)器提供不盡相同的特性,而程序員在開發(fā)應(yīng)用的時候又沒有考慮到應(yīng)用要兼容不同應(yīng)用服務(wù)器,這就出現(xiàn)了J2EE應(yīng)用在不同應(yīng)用服務(wù)器上的移植問題。
下面介紹我們在把J2EE Web應(yīng)用從WebLogic移植WebSphere應(yīng)用服務(wù)器過程中遇到的一些問題和解決辦法。
至于更詳細(xì)的系統(tǒng)環(huán)境準(zhǔn)備,移植步驟等細(xì)節(jié),請參考IBM紅皮書,如Migrating WebLogic Applications to WebSphere V5, REDP0448。
一、Servlet/JSP移植問題
WebSphere 4/5和WebLogic 6.1應(yīng)用服務(wù)器中的JSP編譯器對于空對象(null String, null object等)的處理是不同的。
在Welbogic 6.1當(dāng)中,如果字符串為null,或者對象為null,那么使用PrintWriter輸出該對象的時候,輸出的是長度為0的字符串"";而在WebSphere 4/5、Tomcat 4.1以及WebLogic 7.0當(dāng)中是輸出了長度為4的字符串"null"。
下面servlet/jsp的例子在WebLogic 6.1/WebSphere中的運行結(jié)果是截然不同的。
servlet測試代碼:
java.io.PrintWriter out = response.getWriter();
out.println(null);
jsp測試代碼:
<% String s = null; %>
<%=s%>
或者
<% Integer i = null; %>
<%=i%>
解決辦法1:
所有要在Servlet/JSP輸出的對象都有初始值,換言之就不會有輸出空對象的情況。這樣在servlet/jsp當(dāng)中通過PrintWriter輸出對象的時候就不會出現(xiàn)"null"字樣。
解決辦法2:
如果整個Web應(yīng)用已經(jīng)編寫完畢,沒有時間去修改包含業(yè)務(wù)邏輯的代碼,那么可以使用如下的類(java class)處理servlet/jsp的輸出。對于jsp頁面通常可以通過手工替換<%=為<%=NullObject.get(,替換%>為)%>。
package utils;
public class NullObject {
public static String get(String o) {
return (o == null) ? "" : o;
}
public static Integer get(Integer o) {
return (o == null) ? new Integer(0) : o;
}
public static Long get(Long o) {
return (o == null) ? new Long(0) : o;
}
public static Object get(Object o) {
return (o == null) ? "" : o;
}
}
比如jsp代碼片斷<%=s%>可以修改為<%=NullObject.get(s))%>
注:在Java Language Specification [sec 15.18.1.1 String Conversion]中寫到 If the reference is null, it is converted to the string "null" (four ASCII characters n, u, l, l). Otherwise, the conversion is performed as if by an invocation of the toString method of the referenced object with no arguments; but if the result of invoking the toString method is null, then the string "null" is used instead.
The toString method is defined by the primordial class Object; many classes override it, notably Boolean, Character, Integer, Long, Float, Double, and String.
二、JNDI移植問題
2.1 上下文工廠
J2EE程序在訪問不同J2EE應(yīng)用服務(wù)器名字空間的時候,需要指定相應(yīng)服務(wù)器的名字空間上下文的工廠class名稱,名字空間提供者的URL。
WebLogic相應(yīng)參數(shù)分別為weblogic.jndi.WLInitialContextFactory和t3://localhost:7001
WebLogic創(chuàng)建InitialContext的樣例代碼:
Properties h = new Properties();
h.put(Context.INITIAL_CONTEXT_FACTORY, "weblogic.jndi.WLInitialContextFactory");
h.put(Context.PROVIDER_URL,"t3://localhost:7001");
InitialContext ctx = new InitialContext(h);
WebSphere相應(yīng)參數(shù)分別為com.ibm.websphere.naming.WsnInitialContextFactory和iiop://localhost:2809/
WebSphere創(chuàng)建InitialContext的樣例代碼:
Properties h = new Properties();
h.put(Context.INITIAL_CONTEXT_FACTORY,
" com.ibm.websphere.naming.WsnInitialContextFactory ");
h.put(Context.PROVIDER_URL,"iiop://localhost:2809/");
InitialContext ctx = new InitialContext(h);
如果是訪問本地應(yīng)用服務(wù)器,可以采用缺省配置創(chuàng)建名字空間上下文,代碼如下:
InitialContext ctx = new InitialContext();
推薦應(yīng)用程序采用共同的類訪問JNDI名字空間,比如com.xxx.utils.Xxx,該類是singleton的,提供static方法來獲得名字空間上下文。這樣做的好處是
應(yīng)用中訪問名字空間的代碼都在這個類中,應(yīng)用程序的移植性好
采用singleton設(shè)計模式和static方法,可以減少對象的重復(fù)生成,減少JVM的垃圾回收
2.2 java.user.Transaction
在WebLogic 6.1中,javax.transaction.UserTransaction是通過名字javax.transaction.UserTransaction查找到的,而WebSphere 5中是通過名字jta/usertransaction查找到的。
WebLogic例子代碼如下:
import javax.transaction.UserTransaction;
(UserTransaction)getInitialContext().lookup("javax.transaction.UserTransaction");
WebSphere例子代碼如下:
(UserTransaction)getInitialContext().lookup("jta/usertransaction")
三、EJB訪問
EJB 1.0,java程序使用EJB部署后的JNDI名稱訪問EJB的例子代碼如下:
InitialContext ctx = new InitialContext();
Object home = ctx.lookup("ejb/account");
AccountHome accountHome =
(AccountHome) PortableRemoteObject.narrow(home, AccountHome.class);
EJB 1.1引入了EJB Refrence,java程序訪問EJB的例子代碼如下:
InitialContext ctx = new InitialContext();
Object home = ctx.lookup("java:comp/env/ejb/account");
AccountHome accountHome =
(AccountHome) PortableRemoteObject.narrow(home, AccountHome.class);
在EJB 2.0,引入了EJB local home interface,從此java客戶程序可以通過引用調(diào)用在一個JVM中的EJB。EJB Refrence,java程序訪問EJB的例子代碼如下:
InitialContext ctx = new InitialContext();
Object home = ctx.lookup("java:comp/env/ejb/account");
AccountHome accountHome = (AccountHome) home;
強烈推薦使用EJB Reference訪問EJB,使用EJB Refrence訪問EJB有很多好處。在程序移植方面,各種應(yīng)用服務(wù)器對local home的EJB在名字空間中部署是不一樣的,但是都可以通過EJB Refrence訪問到local home的EJB。
在WebLogic 6.1中,可以在EJB部署描述文件weblogic-ejb-jar.xml 中定義具有Local Home 的EJB的local jndi name。類如下面的部署描述符指定了具有l(wèi)ocal home的EJB Account的local jndi name為ejb/account,具有remote home的EJB AccountAccess的jndi name為ejb/accountaccess。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE weblogic-ejb-jar PUBLIC '-//BEA Systems, Inc.//DTD WebLogic 6.0.0 EJB//EN'
'http://www.bea.com/servers/wls600/dtd/weblogic-ejb-jar.dtd'>
<weblogic-ejb-jar>
<weblogic-enterprise-bean>
<ejb-name>Account</ejb-name>
<local-jndi-name>ejb/account</local-jndi-name>
</weblogic-enterprise-bean>
<weblogic-enterprise-bean>
<ejb-name>AccountAccess</ejb-name>
<jndi-name>ejb/accountaccess</jndi-name>
</weblogic-enterprise-bean>
</weblogic-ejb-jar>
在WebLogic中,java代碼中使用EJB部署后的JNDI名稱訪問local home/remote home的EJB例子代碼如下:
Properties h = new Properties();
h.put(Context.INITIAL_CONTEXT_FACTORY, "weblogic.jndi.WLInitialContextFactory");
h.put(Context.PROVIDER_URL,"t3://localhost:7001");
InitialContext ctx = new InitialContext(h);
AccountHome accountHome = (AccountHome) ctx.lookup("ejb/account");
AccountAccessHome accountAccessHome = (AccountAccessHome) ctx.lookup("ejb/accountaccess");
在WebSphere 5中,java代碼中使用EJB部署后的JNDI名稱訪問local home/remote home的EJB例子代碼如下:
InitialContext ctx = new InitialContext();
Context ctx_local = (Context) ctx.lookup("local:");
AccountHome accountHome = (AccountHome) ctx.lookup("ejb/" + "ejb/account");
AccountAccessHome accountAccessHome = (AccountAccessHome) ctx.lookup("ejb/accountaccess");
四、安全
4.1 在Web Application中,用戶使用Form方式登錄后無權(quán)訪問資源
如果一個用戶經(jīng)過用戶身份驗證(J2EE form based authentication)為合法用戶,但是該用戶試圖訪問其無權(quán)訪問的Web資源,WebLogic 6.1將返回給客戶該Web應(yīng)用的登陸頁面(比如login.jsp),而WebSphere 5.0將返回一個403的HTTP code給瀏覽器,在IE瀏覽器中將顯示 "You are not authorized to view this page...",或者"Error 403: AuthorizationFailed"(如果IE沒有打開缺省選項"顯示友好HTTP錯誤消息")。 J2EE form-based authentication的例子配置(web.xml片斷):
<login-config>
<auth-method>FORM</auth-method>
<realm-name>basicrealm</realm-name>
<form-login-config>
<form-login-page>/login.jsp</form-login-page>
<form-error-page>/failedlogin.jsp</form-error-page>
</form-login-config>
</login-config>
解決辦法:
在WEB-INF\web.xml文件中加入error-page指令,如果出現(xiàn)403錯誤,那么WebSphere將告訴瀏覽器出現(xiàn)403錯誤,同時返回/failedlogin.jsp頁面給瀏覽器。
<error-page>
<error-code>403</error-code>
<location>/failedlogin.jsp</location>
</error-page>
需要注意的是:IE缺省配置中打開了"顯示友好HTTP錯誤消息",如果web.xml文件中eror-page指定的頁面的內(nèi)容小于500個字節(jié),那么IE將忽略服務(wù)器返回的頁面,而"顯示友好HTTP錯誤消息",即顯示"You are not authorized to view this page..."。
4.2 HttpServletRequest.getRemoteUser()
在WebLogic 6.1中,用戶使用J2EE Form方式登陸以后,可以在任何servlet/jsp當(dāng)中使用HttpServletRequest對象的getRemoteUser()方法獲得登錄用戶的ID;而在WebSphere 5.0中只能在受保護的資源(servlet/jsp)當(dāng)中使用這個Servlet API獲得登錄用戶的ID,而且要求被授權(quán)訪問該資源的安全性角色(J2EE Role)在WebSphere中不能被映射為"每個用戶"(everyone)。注:在WebSphere 5中,每個安全性角色可以被映射為"每個用戶"(everyone), "所有已認(rèn)證的用戶"(all authenticated), "映射的用戶"(mapped users),"映射的組" (mapped groups)。
4.3 Webloigic LDAP Realm的移植
WebLogic提供了Realm API,java程序可以調(diào)用該API進行用戶的管理、組的管理。 SPC LDAP Realm API具有和WebLogic Realm相似的API代碼,在調(diào)用weblogic.security.acl.CachingRealm的java代碼中,把中weblogic.security替換為spc即可。
if(WebLogic) {
weblogic.security.acl.CachingRealm realm = (weblogic.security.acl.CachingRealm)
weblogic.security.acl.Security.getRealm();
weblogic.security.acl.User u = realm.newUser(clientId,password,null);
realm.getGroup("AdminGroup").addMember(u);
int ret = weblogic.servlet.security.ServletAuthentication.weak
(clientId,password,session);
if (ret != weblogic.servlet.security.ServletAuthentication.AUTHENTICATED){
System.out.print("Login failed!");
}
}
else {
spc.acl.CachingRealm realm = (spc.acl.CachingRealm)
spc.acl.Security.getRealm();
spc.acl.User u = realm.newUser(clientId,password,null);
realm.getGroup("HauiGroup").addMember(u);
try {
LoginContext lc =
new LoginContext("WSLogin",
newcom.ibm.websphere.security.auth.callback.WSCallbackHandlerImpl(clientId,
"realm", password));
lc.login();
lc.logout();
} catch (LoginException le) {
System.out.print("Login failed!");
return;
//throw new InvalidClientIdException(le.toString());
}
}
spc.acl.LdapRealm程序中一些參數(shù)最好在以后的版本當(dāng)中修改為從配置文件中讀取。
//LDAP服務(wù)器的IP或者HOSTNAME
String LDAPSERVER = "192.168.1.30";
//搜索用戶的開始節(jié)點
String USER_DN_BASE = "cn=users,dc=xxx,dc=com";
String USER_DN_BASE = "dc=xxx,dc=com";
//搜索組的開始節(jié)點
String GROUP_DN_BASE = "cn=groups,dc=xxx,dc=com";
String GROUP_DN_BASE = "dc=xxx,dc=com";
// com.ibm.jndi.LDAPCtxFactory = IBM SecureWay
//C:\Program Files\IBM\LDAP\java\ibmjndi.jar
// com.sun.jndi.ldap.LdapCtxFactory = Any Version 3 compliant LDAP
String FACTORY_INITIAL = "com.ibm.jndi.LDAPCtxFactory";
//登陸目錄服務(wù)器的有管理員權(quán)限的用戶名稱
String SECURITY_PRINCIPAL = "cn=root";
//登陸目錄服務(wù)器的有管理員權(quán)限的用戶口令
String CREDENTIALS = "root";
4.4 將servlet中使用WebLogic Security API的代碼改為WebSphere Security API。
注意:WebLogic API weblogic.servlet.security.ServletAuthentication.weak是讓瀏覽器用戶登陸到WebLogic服務(wù)器上, 而com.ibm.websphere.security.auth.callback.WSCallbackHandlerImpl/LoginContext.login是讓用戶在java程序(包含servlet)中登陸到服務(wù)器上。在下面的代碼中,WebSphere API只是起到了驗證增加的用戶是否能夠登陸到WebSphere應(yīng)用服務(wù)器上。
4.5 WebSphere 5 User Registry
WebLogic/WebSphere都支持將LDAP服務(wù)器中的用戶和J2EE角色之間的綁定,都支持定制用戶注冊表,比如兩者在產(chǎn)品中都提供了基于文件的用戶注冊表。
WebLogic提供了RDBMS Realm的example實現(xiàn),該實現(xiàn)采用數(shù)據(jù)庫的兩個表來保存用戶和組的信息,表的定義如下:
CREATE TABLE aclentries (A_NAME varchar(255), A_PRINCIPAL varchar(255), A_PERMISSION varchar(255));
CREATE TABLE groupmembers(GM_GROUP varchar(255), GM_MEMBER varchar(255));
WebSphere 5 Security redbook中提供了基于RDBMS User Registry的實現(xiàn),表的定義如下:
create table users(username varchar2(250), password varchar2(250), description varchar2(250), userid varchar(250));
create table groups(name varchar2(250), description varchar2(250), gid varchar2(250));
create table uidgid(gid varchar2(250), userid varchar2(250));
附件中的WebSphere RDBMS User Registry在兩方面進行了修改:
支持JDBC數(shù)據(jù)庫連接池。WebSphere Security/User
Registry是在WebSphere其他服務(wù)啟動之前啟動的,因此在WebSphere數(shù)據(jù)庫連接池/名字服務(wù)器啟動之前,User Registry不能使用數(shù)據(jù)庫連接池,但之后就可以使用數(shù)據(jù)庫連接池了。
將SQL語句作為static常量定義,便于修改數(shù)據(jù)庫的表設(shè)計。
這個RDBMS User Registry在部署的時候需要配置四個參數(shù):
DBDRIVER:比如COM.ibm.db2.jdbc.app.DB2Driver
DBURL:比如java:db2:sample
DBUSERNAME:比如db2admin
DBPASSWORD:比如db2admin
JDBCJNDINAME:比如jdbc/sample