顯示具有 java 標籤的文章。 顯示所有文章
顯示具有 java 標籤的文章。 顯示所有文章

2012/04/16

ResouseBundle.getBundle(String)在static情境下的問題


如果在一個static block或是static變數中呼叫ResourseBundle.getBundle(String)
在某些特定的情況下可能會造成MissingResourceException

其原因出在官方文件中敘述此method等同於呼叫
getBundle(baseName, Locale.getDefault(), this.getClass().getClassLoader())

問題在於this.getClass().getClassLoader()的部分,this在static內是取不到參照的
而實際上底層的實作是透過下列這段code取得ClassLoader
/*
 * Automatic determination of the ClassLoader to be used to load
 * resources on behalf of the client.  N.B. The client is getLoader's
 * caller's caller.
 */
private static ClassLoader getLoader() {
    Class[] stack = getClassContext();
    /* Magic number 2 identifies our caller's caller */
    Class c = stack[2];
    ClassLoader cl = (c == null) ? null : c.getClassLoader();
    if (cl == null) {
        // When the caller's loader is the boot class loader, cl is null
        // here. In that case, ClassLoader.getSystemClassLoader() may
        // return the same class loader that the application is
        // using. We therefore use a wrapper ClassLoader to create a
        // separate scope for bundles loaded on behalf of the Java
        // runtime so that these bundles cannot be returned from the
        // cache to the application (5048280).
        cl = RBClassLoader.INSTANCE;
    }
    return cl;
}

這樣會導致你取得的ClassLoader跟預期的結果不同
進而取不到目標resource

若要在static中使用ResourceBundle.getBundle(String)最好的方式還是自行呼叫
ResourseBundle.getBundle(baseName, Locale.getDefault(), [TargetClass].class.getClassLoader())

2011/10/04

Java中的Singleton實作

在Java 1.5之前
以往我們可能用提供 private constructor 和 static final field 的方式,來確保只會得到唯一的 Class 實例
/* 
 * 獨一無二的貓王(Elvis),這世上就只有一個貓王 
 * Singleton with public final field 
 */
public class Elvis {
   public static final Elvis INSTANCE = new Elvis();
   private Elvis() { //忽略細節 }
   public void leaveTheBuilding() { //忽略細節 }
}
當需要取得貓王實例時,簡單的存取 Elvis.INSTANCE 即可。不過 private constructor 依然有一個問題,用 reflection 機制的 AccessibleObject.setAccessible 可以破解 private 的限制,如果要防範此一漏洞,還是得在 constructor 內做些手腳。


或是用 static factory 的方式,來提供唯一的 Class 實例
/* 
 * 獨一無二的貓王(Elvis),這世上就只有一個貓王 
 * Singleton with static factory
 */
public class Elvis {
   private static final Elvis INSTANCE = new Elvis();
   private Elvis() { //忽略細節 }
   public static Elvis getInstance() { return INSTANCE; }
   public void leaveTheBuilding() { //忽略細節 }
}
雖然有同上的問題(private constructor 部分),但是此作法相較起來更有彈性,如果你改變了想法,不想用 singleton 的方式實作此 Class,可以在不變動 API 的情況下修改 getInstance() 達成。


但是無論用上面哪種方式,當你需要一個 Serializable 的 Singleton 時,都會遇到一個同樣的問題:每次 deserialize 時都會建立一個新的實例。

解法就是將所有 field 宣告為 transient,並加上 readResolve 方法
// readResolve method to preserve singleton property
private Object readResolve() {
   // Return the one true Elvis and let the garbage collector
   // take care of the Elvis impersonator.
   return INSTANCE;
}
如果有定義此方法,則它會自動在 deserialize 時被呼叫,並回傳我們指定的物件。

Java 1.5之後的最好的實作方式
現在有了第三種簡明又實用的新解法 - 實作單一元素的 Enum。
// Enum singleton - the preferred approach
public enum Elvis {
   INSTANCE;
   public void leaveTheBuilding() { ... }
}
這種做法接近 public final field 而且
  • 程式碼更簡潔
  • 沒有 private constructor 漏洞
    (因為 Enum 型別像抽象類別一樣不能被實例化)
  • 本身還提供了 Serialization 機制
  • Enum 本身的特性確保只會有一個實體

所以單一元素的 Enum 是目前最好的 Singleton 實作方式。

2011/09/28

Eclipse Plugin開發遇到No Schema Found的問題

開發 Plugin 時遇到一個問題,在設定 plugin.xml 內的 Extensions 時,遇到 no schema found for the 'org.eclipse.ui.views' extension point 這樣的訊息,而且右鍵點該 Extension 只能 New → Generic,無法建立對應的元件...

google 了一下之後發現原來跟 Eclipse plugin 的 library 有關,上官網查了一下後發現原來我用的 J2EE 版並沒有含 PDE/RCP 套件的 source code,所以也就沒有含 schema 和其他文件

既然知道原因,那就比較好解了,Classic 版的 PDE/RCP 套件是有含 source code 的,於是重新抓相同版號(這裡用 3.6.2)的 Eclipse 下來,將對應的 source code jar 檔複製到 Eclipse 安裝目錄/plugins 下就好了,個人是整個把 plugins 目錄覆蓋掉,懶人作法 ._.

怕有衝突的話最好先做個備份,或是用 import library 的方式把 Classic 版的 plugin library 加到 project 也行。

2011/09/26

java中的Class.this


首先先複習一下基本:
在 Java 的 Class 內宣告的 Class,稱為 Nested Class(巢狀類別)

Nested Class 又分為 staticnon-static 兩種,其中加上 static 宣告的就叫做 Static Nested Class;而 non-static 的 Nested Class 又稱為 Inner Class(內部類別)

Inner Class 還可以再細分為三種又是另外一個故事了,這邊就不提
那 Class.this 究竟是代表甚麼呢...
public class ClassThisTest {
	public static void main(String[] args) {
		OuterClass outerInst1 = 
				new OuterClass("Outer class no.1");
		OuterClass.InnerClass innerInst1 = 
				outerInst1.new InnerClass();
		
		System.out.println(innerInst1.toString()); 
											//Inner class
		System.out.println(innerInst1.outerToString()); 
											//Outer class no.1
												   
		OuterClass outerInst2 = 
				new OuterClass("Outer class no.2");
		OuterClass.InnerClass innerInst2 = 
				outerInst2.new InnerClass();
		
		System.out.println(innerInst2.outerToString()); 
											//Outer class no.2
	}	
}

class OuterClass {
	private String outerMsg;
	
	public OuterClass(String msg) {
		this.outerMsg = msg;
	}
	
	public String toString() {
		return outerMsg;
	}
	
	public class InnerClass {
		private final String innerMsg = "Inner class";
		
		public String toString() {
			return innerMsg;
		}
		
		public String outerToString() {
			//Class.this
			return OuterClass.this.toString();
		}
	}
}
簡單說就是 Inner Class 用來參照相依的 Outer Class 實體用的。

因為在 Inner Class 裡面用 this 關鍵字的話,將會指向 Inner Class 實體,所以若是需要參照 Outer Class 實體時,用 Class.this 的方式就可以了。


JadEclipse安裝步驟

這邊用的是 Eclipse Helios(3.6.x)

Plug-in 安裝
首先從上方選單列
Help → Install New Software...

開啟 Install 視窗後,按下 Add... 按鈕
Name 可隨意輸入,這邊用 JadClipse
Location 輸入 http://jadclipse.sf.net/update

然後 Install 視窗中間出現 JDT Decompiler Features 可勾選
只有 3.4 版的可選擇,與 3.6 似乎沒有相容性問題,選了之後就按 Next 開始安裝吧

Plug-in 設定
由於 plug-in 似乎只是連結 Jad 與 Eclipse 之間的橋梁
所以裝 plug-in 之前還得先裝 Jad (只是一個執行檔而已)
可以到這個網址去下載 JAD Java Decompiler Download Mirror
根據 OS 選擇對應版本後下載,解壓縮後放在隨意位置即可

一樣是上方選單列
Window → Preferences

如果安裝成功的話,在 Preferences 視窗依下面的順序應該可以打開 Jad 的設定頁面
Java → Decopilers → Jad

在 Path to decompiler 的欄位輸入 Jad.exe 的絕對路徑
然後重啟 Eclipse 即可

2011/08/29

OpenEJB for Tomcat

其實本來專案中的 EJB 是要部署在其他 container 上的,只是懶的重新建立 server,於是找了一下有沒有可以讓 Tomcat 可以支援 EJB 的東西,沒想到還真的有呢!於是簡單的紀錄一下測試的步驟。

環境設定
基本需求:
快速安裝步驟:
  1. 首先你要有一個能正常運行的 Tomcat server
  2. 把從 OpenEJB 抓下來的 openejb.war 放到 ${tomcat.home}/webapps/ 下
    (注意:這個檔案必須名為openejb.war,所以不要去改名稱...)
  3. 啟動 Tomcat,打開網址 http://localhost:8080/openejb/installer 並點下 Install 按鈕

EJB 程式碼部分
由於是舊系統,所以這邊用的是 EJB 2.x 的規格來寫,範例寫的是一顆 stateless 的 session bean,EJB 的介紹在這邊就不詳述了。

Remote介面
import java.rmi.RemoteException;
import javax.ejb.EJBObject;

public interface FruitShop extends EJBObject {
    public String getBanana() throws RemoteException;
}
Home介面
import java.rmi.RemoteException;
import javax.ejb.CreateException;
import javax.ejb.EJBHome;

public interface FruitShopHome extends EJBHome {
    public FruitShop create() throws CreateException, RemoteException;
}
Session Bean
import java.rmi.RemoteException;
import javax.ejb.*;

public class FruitShopBean implements SessionBean {
    public void ejbActivate() throws EJBException, RemoteException {}
    public void ejbPassivate() throws EJBException, RemoteException {}
    public void ejbRemove() throws EJBException, RemoteException {}
    public void setSessionContext(SessionContext context)
        throws EJBException, RemoteException {}
		
    public String getBanana() {
        return "You get a banana.";
    }
}
別忘了在 classpath 上加上 javaEE 的 jar 檔,以免編譯錯誤。如果照上面的步驟安裝的話,在 ${tomcat.home}/webapps/openejb/lib/ 下可以找到 openEJB 提供的 javaee-api-5.0-3.jar

然後建立一個 ejb-jar.xml 如下
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE ejb-jar PUBLIC "-//Sun Microsystems, Inc.//DTD Enterprise 
JavaBeans 2.0//EN" "http://java.sun.com/dtd/ejb-jar_2_0.dtd">

<ejb-jar>
    <enterprise-beans>
    <session>
		<ejb-name>FruitShop</ejb-name>
		<remote>FruitShop</remote>
        <home>FruitShopHome</home>                
        <ejb-class>FruitShopBean</ejb-class>
        <session-type>Stateless</session-type>
        <transaction-type>Bean</transaction-type>
        <security-identity>
            <description></description>
            <use-caller-identity></use-caller-identity>
        </security-identity>
    </session>
    </enterprise-beans>
</ejb-jar>

部署 EJB
編譯成 jar 檔後,放在 ${tomcat.home}/conf/openejb.xml 中設定的路徑下
<!--
#
#  The <Deployments> element can be used to configure file
#  paths where OpenEJB should look for ejb jars or ear files.
#
#  See http://openejb.apache.org/3.0/deployments.html
#
#  The below entry is simply a default and can be changed or deleted
-->
<!-- 可以用相對路徑的方式, 相對於openejb.base設定的路徑 -->
<Deployments dir="ejb/" /> 
<!-- 或是指定一個目錄, 讓openEJB自動搜尋相關jar檔 -->
<Deployments dir="D:/Tomcat/ejb/" /> 
<!-- 最後還可以直接指定目標jar檔 -->
<Deployments jar="D:/Tomcat/ejb/FruitShop.jar" /> 

測試 client 端呼叫 EJB
根據官方的說明文件,用 Remote Client with HTTP (in tomcat) 的方式來呼叫 EJB
import java.util.Properties;
import javax.naming.InitialContext;
import javax.rmi.PortableRemoteObject;

public class EJBTest {
	public static void main(String args[]) throws Exception {
		Properties p = new Properties();
		p.put("java.naming.factory.initial",
		    "org.apache.openejb.client.RemoteInitialContextFactory");
		p.put("java.naming.provider.url",
		    "http://localhost:8080/openejb/ejb");
		InitialContext ctx = new InitialContext(p);
		Object ref = ctx.lookup("FruitShopRemoteHome");
		FruitShopHome home = (FruitShopHome)
		    PortableRemoteObject.narrow(ref, FruitShopHome.class);
		FruitShop remote = home.create();
		System.out.println(remote.getBanana()); //You get a banana.
	}
}

沒意外的話應該可以從 console 看到輸出。
另外... 關於 eclipse helios 怎樣將 EJB project 輸出 client 用的 jar 檔還有待研究。