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

2016-12-04

Java Synchronization

在多執行緒的環境(例如 Web)如果有同時寫入的需求,就得用 Java synchronization,同時讀取則不用(唯讀)。

synchronized method

最簡單的作法就是在 method 上加 synchronzed。
public static void main(String[] args) {
  Sync s1 = new Sync("S1");
  Thread s1aThread = new Thread() {

    @Override
    public void run() {
      super.run();
      s1.normalMethod();
    }
  };
  Thread s1bThread = new Thread() {

    @Override
    public void run() {
      super.run();
      s1.normalMethod();
    }
  };
  s1aThread.start();
  s1bThread.start();
}

public void normalMethod() {
  System.out.println(">>> " + this.id);
  try {
    Thread.sleep(1000);
  }
  catch (InterruptedException e) {
    e.printStackTrace();
  }
  System.out.println("<<< " + this.id);
}
在尚未使用 synchronized 前,兩個 Thread 會交錯執行。

2016-11-27

Java Thread.join() 讓 Thread 可以按順序執行

一般的情況下在多執行緒執行時,無法保證哪個執行緒會先完成。
System.out.println(Thread.currentThread().getName() + " is Started");
Thread aThread = new Thread() {

  public void run() {
    try {
      System.out.println("aThread is Started");
      Thread.sleep(2000);
      System.out.println("aThread is Completed");
    }
    catch (InterruptedException e) {
      e.printStackTrace();
    }
  }
};
aThread.start();
System.out.println(Thread.currentThread().getName() + " is Completed");
例如上面的情況,由於 aThread 需要兩秒,所以 main 先完成。

2016-11-23

Java String 有個 Pool!

從一個自以為是常識的問題開始:
String a = "java";
String b = "java";
System.out.println(a.equals(b));
這當然是 true 啦!

那這樣呢?
System.out.println(a == b);
false 囉,還用說!

錯,是 true!

2015-06-02

以 SimpleCaptcha 實做驗證碼

驗證碼?

就是防止機器人送出表單的驗證圖片,這是目前辨識真假人最常見的作法。

驗證流程

  1. 使用 <img src="..."/> 產生驗證圖片,注意 src 是指向 Java Servlet,用以動態產生驗驗證圖片,而不是一張靜態的圖片。
  2. 在 web.xml 設定產生驗證圖片的 <servlet/> 與 <servlet-mapping/>
  3. 撰寫 Java Servlet,每次呼叫 Java Servlet 時,除產生驗證圖片塞到 response 以外,還要將答案寫到 session 裡,供事後比對。
  4. Form submit 到後台時,左手拿使用者輸入的驗證碼,右手拿 session 裡的答案,兩相做比較即可。

2014-07-05

java.lang.Integer 的 cache?

前幾天遇到鬼打牆的問題,先用簡單的方式來還原現場。
for (int i = -130; i < 130; i++) {
  System.out.println(i + ">" + (Integer.valueOf(i) == Integer.valueOf(i)));
}
以前不知道所以也不會去用 Integer.valueOf(int) 來產生 Integer 物件,只知道用 new Integer(int),那這兩個 method 有什麼差別嗎?先看看上面程式的執行結果。
-130>false
-129>false
-128>true
-127>true
...
126>true
127>true
128>false
129>false
奇怪了,在 -128 到 127 之間的 Integer 相等(不正常),在這範圍外的 Integer 不相等(這才正常),見鬼了!

2013-03-25

透過 HttpServletResponse 下載中文檔名出現亂碼

ㄚ兜仔的作法:
@RequestMapping("/download")
public void download(HttpServletRequest request, HttpServletResponse response) throws IOException {
  UploadVO uv = null; // TODO
  response.setContentType(uv.getFileType());
  response.setHeader("Content-disposition", "attachment;filename=" + uv.getFileName());
  ServletOutputStream out = response.getOutputStream();
  IOUtils.copy(new FileInputStream(uv.getFilePath()), out);
  out.flush();
  out.close();
}
檔名是 ISO8859-1 認識的字元沒問題,一旦遇到其他字元集,例如中文,還是可以下載,只是中文字都會變成空白或亂碼。

2012-10-26

Javascript 與 Java Applet 合作

以前只知道可以用 Javascript 呼叫 Java Applet,但都沒有動機去嘗試,今天有了。

Javascript 不能讀寫本機的檔案,這是可以確定的,另外能不能執行本機的程式(例如 ipconfig),大概也不行吧,而這些 Java 都可以!

合作的原理很簡單,在 Applet 所在的 HTML 裡,Javascript 可以呼叫 Applet 帶來的 Java class,至於為什麼可以呼叫,這個我還不知道,就先擱下吧。

2012-10-25

在 Java 呼叫外部程式(Command-line or Application)

這是很基本的工具程式,但是不會想去記起來,卻每隔一陣子就會用到。
public class ExecCmd {

  public static void main(String args[]) {
    try {
      Process pr = Runtime.getRuntime().exec("ipconfig");
      BufferedReader input = new BufferedReader(new InputStreamReader(pr.getInputStream(), "MS950"));
      String line = null;
      while ((line = input.readLine()) != null) {
        System.out.println(line);
      }
      int exitVal = pr.waitFor();
      System.out.println("Exit code " + exitVal);
    }
    catch (Exception e) {
      e.printStackTrace();
    }
  }
}
---
---
---

2012-10-16

借助 Maven 執行 JSP Precompile

本來以為 JSP Precompile 只有提昇執行效能一項好處,而且這好處只有在第一次執行有效果,所以一直不感興趣。

今天才知道另一項好處,保護 Source code,也就是說不只 Java 檔,連 JSP 都可以只交付 class 檔,不用交付原始檔,雖然這需求不常見。

JSP Precompile 不是只有將 JSP 轉成 Java,再編譯成 class 檔就好,還得在 web.xml 加上 JSP 所產生的 Servlet 檔宣告,並加上對應的 url-pattern。

2012-08-24

Hello JNative 1.4 RC3

聽說 Java 可以呼叫 DLL 檔,這就來玩玩看。

官方用法是 Java Native Interface(JNI),但是會牽扯到非 Java 的程式,還是先跳過好了。

找到一些 3rd-party library 可以用,包括 Jawin、Jacob 與 JNative,試過之後,發現 JNative 比較簡單用。

先去下載 JNative_1.4RC3_bin,解開後裡面只有一個 JNative.jar 檔,不像其他網路文章說得有二到三個檔案,包括一個 jar 檔、一個 Windows 用的 dll 檔和一個 Linux 用的 so 檔,這類 3rd-party library 運作邏輯大概是這樣呼叫流程:
Your Java Code -> JNative.jar -> JNative.dll -> Your DLL

2012-08-06

使用 Launch4j 將 jar 包成 exe 檔

先前的 可執行的 Jar(Executable Jar) 還不夠,這邊來產生道道地地的 exe 檔。

試過幾個軟體,Launch4j 比較成熟,功能也比較完整。

Launch4j 會先產生一個 xml 設定檔,這也方便稍後修改設定重新產生 exe 檔,不用重新做設定。

這邊使用「可執行的 jar 檔」做練習。

在 Basic 頁,輸入 Output file 與 Jar 路徑。

Change dir 可以設為「.」,表示 exe 所在目錄,用在需要讀取或輸出檔案時。

也可以輸入 Icon,讓產生的 exe 檔有個漂亮的小圖示。

下面的 Java download URL 用來當執行該 exe 環境沒有 JRE 時,會自動連結到 Java 網站,方便使用者下載並安裝 JRE。

2012-07-26

使用自訂的 JDK 啟動 Eclipse

用了好久的 64 位元 Windows 7,今天在下載新版的 Eclipse(Juno)時,突然想試試 64 位元的 Eclipse 有沒有比較厲害。

碰!連啟動都不能啟動,出現錯誤訊息「Failed to load the JNI shared library "C:\Program Files(x86)\Java\jdk1.7.0_05\bin\..\jre\bin\client\jvm.dll」,嗯,JDK 也要是 64 位元才行,但是目前的工作環境是 32 位元,所以也不敢貿然將 JAVA_HOME 就改成 64 位元。

那可以指定 Eclipse 使用別個 JDK 嗎?當然可以!

可執行的 Jar(Executable Jar)

Web 程式寫太多,都不知道桌面程式要怎寫。

先直接跳到終點前,寫完程式了,怎麼交付給使用者,總不能給一堆 class 檔吧,不能作成 exe 檔(使用 Launch4j 將 jar 包成 exe 檔),至少要作成可執行的 jar 檔。

產生「可執行的 jar 檔」

產生 jar 檔沒問題,尤其是用 Maven,下個 mvn package 就好了,但是不會自動執行啊,問題在於要從哪個 class 執行,也就是說哪個 class 有 main。

只要在 jar 檔裡建個 META-INF/MANIFST.MF 檔案,並填入以下內容就可以了。

2012-06-05

從哪進入這一頁?

PHP 用 $_SERVER['HTTP_REFERER'],Javascript 用 document.referrer。
要小心若是沒有 $_SERVER['HTTP_REFERER'],像是首頁,或是來自我的最愛,要先用 isset() 判斷,否則會出錯。
// Javascript
alert(document.referrer);
// PHP
alert('<?php echo $_SERVER['HTTP_REFERER'] ?>');
// java
alert('<%= request.getHeader("referer") %>');

2011-04-19

使用 Google App Engine 開發 Java 網路應用程式

開發 GAE Java web app 有兩種方式:

非 Eclipse 族群

Google App Engine 簡介

用 Java 寫一個網路應用程式( web app)需要哪些東西?
  1. 首先,當然要會寫 Java,還有要懂得 JavaEE 在 web 的兩大主角,Servlet 與 JSP,如果不想用這麼原始的方式,還要學會 Spring MVC 或者 Struts 之類的 web framework。
  2. 現在應該不會有任何一個 web app 沒用到資料庫,所以還需要 JDBC,或者較好用但複雜的 Hibernate,以及一個真正的 database server,最常用的當然是 mysql 了。
  3. 程式寫完之後,我們需要一個可以執行程式的地方,那就是 web container(或 web server),一般都是用 Tomcat。
  4. 再來就是一台真正的機器,用來執行 Tomcat。
  5. 最後就是對外網路與網域名稱。

2011-03-03

Generic method

為了寫共用的 dao,有了 generic method 的需求。

以前的作法:
@Override
public BaseModel getEntity(Class<? extends BaseModel> clazz, Long id) {
    EntityManager em = EMF.get().createEntityManager();
    BaseModel obj = em.find(clazz, id);
    em.close();
    return obj;
}
缺點是呼叫完 getEnity(...) 之後還要 cast 成想要的型別。
突然想到,研究了一下 ArrayList<E>,得到想要的作法,這樣就不用再 cast 了。
@Override
public <T> T getEntity(Class<T> clazz, Long id) {
    EntityManager em = EMF.get().createEntityManager();
    T obj = em.find(clazz, id);
    em.close();
    return obj;
}

2011-02-14

為什麼要用 interface 與 implementation?

主要目的就是減少實做關聯(reduce coupling)。

舉例來說,存取使用者資料的 dao 名為 UserDaoImpl,如果整個系統都是直接使用 UserDaoImpl 的話,問題有二:

第一點,不利測試,當為了測試系統邏輯而非資料庫操作時,仍必須連線資料庫,為了測試必須準備測試資料且效能也不好。
如果整個系統是使用 UserDao 這樣的 interface 的話,可以在測試時,提供一個如 UserDaoTestImpl 這樣的模擬實做,就不用真的去連資料庫。


2010-03-15

Java中文字串長度

需求:用txt上傳資料,一行一筆資料,每一筆資料使用固定寬度,不足部份補空白。

問題:讀入檔案後得先檢查每一行總長度是否相符,在Editplus行末游標顯示Col398,表示該行應為397字元,但string.length()卻顯示393字元。

原因:中文字關係!Java將每個中文字當成一個字,但是Editplus將中文字當成兩個字。

解法:要比對txt檔與Java字串的長度,得用string.getBytes("想要的編碼").length,若不指定編碼,會用預設的UTF-8,就是每個中文字為3 Bytes,若指定為MS950,則是2 Bytes!

2007-03-22

[Debug] Error from Java Object Reference, Hibernate and Spring MVC

環境介紹
使用Hibernate與Spring MVC,主要物件有三:Document、Status與StatusLog。

每次Document的status變更時,必須同時寫一筆log檔到StatusLog,而StatusLog也會紀錄status作為歷史紀錄使用。

程式如下:
// Domain objects
public class Document {
private Status status;
private Collection logs = new ArrayList();
// ...
}
public class Status {
// ...
}
public class StatusLog {
private Status status;
public StatusLog() {
}
public StatusLog(Status status) {
this.status = status;
}
// ...
}
// Spring Controller
public class DocumentFormCtrl extends CommonFormCtrl {
// ...
@Override
protected Object formBackingObject(HttpServletRequest request)
throws ServletRequestBindingException {
Long id = ServletRequestUtils.getRequiredLongParameter(request, "id");
Document cmd = this.documentService.getDocument(id);
// ...
return cmd;
}
@Override
protected void onBind(HttpServletRequest request, Object command,
BindException errors) throws ServletRequestBindingException {
Document cmd = (Document) command;
// ...
cmd.getStatus().setId(statusId);    // 呆會看這邊
// 同時寫一筆log檔到StatusLog
cmd.getLogs.add(new StatusLog(...));
// ...
}
}
狀況描述
背景:假設StatusLog裡已經有三筆歷史資料,分別為Status A、Status B與Status C,所以Document裡的status也為Status C。

執行:加入Status D到Document裡。

預期:StatusLog有四筆歷史資料,分別為Status A、Status B、Status C與Status D,Document裡的status也為Status D。

結果:StatusLog有四筆歷史資料,分別為Status A、Status B、Status D與Status D,Document裡的status也為Status D。

問題所在
為什麼既有資料會變?我又沒改它,它怎會變?經過測試後發現,兇手應該是Java Object Reference、Hibernate與Spring MVC三人合作無間,加上本人我參一腳造成的。

先從Hibernate看,Hibernate在載入Document時,也同時載入Status C,以及三筆StatusLog,因為第三筆StatusLog的status也為Status C,所以第三筆StatusLog的status是從Hibernate session的cache中取出來的,不同於Document的status是從資料庫取出來的,所以Document的status與第三筆StatusLog的status是reference到Java heap裡的同一筆物件,所以改了其中一筆自然會影響另一筆,反之亦然。

接下來看Spring MVC,controller在binding parameter時,是將statusId直接塞到Document的status裡,並非塞一筆全新的Status物件,所以在此同時第三筆StatusLog的status的id也被改掉了。

解決方法
所以controller在binding parameter時,得塞一筆全新的Status物件,不可以拿既有物件來改!
@Override
protected void onBind(HttpServletRequest request, Object command,
BindException errors) throws ServletRequestBindingException {
Document cmd = (Document) command;
// ...
// 詭異絕倫的bug,這裡一定要設為新的物件,不然在歷史紀錄裡共用原物件的都會受到影響
cmd.setStatus(new Status());
cmd.getStatus().setId(statusId);
// ...
}