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

2014年10月21日 星期二

JAVA - 基本properties的運用


說起來真是慚愧,用java這麼久,一直都沒有真正使用過properties file,今天就來練習一下:(其實是因為專案需要多國語系的關係啦,不得不用...)


假設我們設計一個網站 or app 需要支援多國語系,但不可能有人願意把每個跟語系有關的文字harcode在程式裡,畢竟那太愚蠢了,變成一個專案多個版本,卻只是為了那些不同的顯示文字,使用properties可以很好地解決這個問題:

今天我需要依照不同的語系,輸出不同的招呼語(中文=你好,英文=hello,日文=こんにちは),我該怎麼做呢:

首先是專案架構:
很簡單src只有一個檔案,而resource下的3個properties就是今天的主角囉,properties.sh只是方便執行測試的sh檔案,先看唯一的code Main.java:

package com.example.shihanne.properties;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.Locale;
import java.util.ResourceBundle;

public class Main {

 public static void main(String[] args) throws IOException {
  Locale locale = Locale.getDefault();
  String key    = "hello";
  if(args.length == 1)
   key = args[0];
  if(args.length == 2){
   key = args[0];
   locale = Locale.forLanguageTag(args[1]);
  }
  System.out.println(new Main().getMessageByBundle(key,locale));
 }
    
    public String getMessageByBundle(String key,Locale local) throws UnsupportedEncodingException{
     ResourceBundle rs = ResourceBundle.getBundle("config",local); 
     return new String(rs.getString(key).getBytes("ISO-8859-1"),"UTF-8");
    }
}


以下是我的properties file:

config_en.properties:
hello=hello

config_zh_TW.properties:
hello=你好

config_ja.properties:
hello=こんにちは

很簡單,都各只有一行,=左邊的hello代表這個propertie的參數名稱(key),=右邊的也就是我們要輸出的各國語系value啦~

必須要注意的是properties的命名規則:[filename]_[locale].properties
在這裡我們使用的檔案名稱為config,繁體中文的locale=zh_TW,日文locale=ja,英文=en

程式很簡單,Main先偵測是否有指定參數:
第一個參數為欲讀取的properties key(此例為hello),第二個參數為語系(若沒有,則使用本機的預設語系(當然是zh_TW囉) )
之後我們再使用ResourceBundle.getBundle(basename,locale) 讀取properties file,最後再使用ResourceBundle.getString(key)讀出我們需要的value

properties.sh如下:

java -cp ../bin:../resource com.example.shihanne.properties.Main $1 $2

結果畫面:

2013年10月5日 星期六

javamail - 使用SMTP(非SSL)寄出信件時一直收到javax.net.ssl.SSLHandshakeException...


參考:Java Mail: SSLHandshakeException when sending email on port 25 without SSL


需求:
  最近一個小工具需使用到JAVAMAIL,奇怪的是我明明使用SMTP(非SSL)寄信,卻一直報SSL的錯誤:

javax.mail.MessagingException: Can't send command to SMTP host;
  nested exception is:
    javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
    at com.sun.mail.smtp.SMTPTransport.sendCommand(SMTPTransport.java:1420)
    at com.sun.mail.smtp.SMTPTransport.sendCommand(SMTPTransport.java:1408)
    at com.sun.mail.smtp.SMTPTransport.ehlo(SMTPTransport.java:847)
    at com.sun.mail.smtp.SMTPTransport.protocolConnect(SMTPTransport.java:384)
    at javax.mail.Service.connect(Service.java:275)
    at javax.mail.Service.connect(Service.java:156)


查了半天才發現,只要關閉下列的Properties設定參數即可:
props.put("mail.smtp.starttls.enable", "true");
(把上一行取消就可)

2013年9月11日 星期三

JAVA Reflection:如何取得Class的Super Class、Interfaces..


參考:http://tutorials.jenkov.com/java-reflection/classes.html#superclass

需求:
最近開始研究Spring對於他使用XML可以做DI(Dependency injection)的方式產生了興趣,其中產生bean之後的constructor parameter居然也可以使用XML直接設定而不需要在application中指定id名稱的特性...想來想去也只有Reflection可以辦到了,因此有了以下的小實驗:
我們需要達到兩項目標:
  • 取得指定Class的Super Class
  • 取得指定Class的implement Interface(s)
直接看Code:

我們定義了兩個Interface:ISuperA、ISuperB
 
public interface ISuperA {
}
 
public interface ISuperB {
}

接下來定義implement:
 
public class ImplementClass implements ISuperA, ISuperB {
}

呼叫看結果:
 
 public static void main(String[] args) {
  Class superclass = ImplementClass.class.getSuperclass();
  Class[] interfaces = ImplementClass.class.getInterfaces();
  
  System.out.println("superclass:"+superclass.getName());
  
  System.out.println("interfaces:");
  for(Class interface1 : interfaces)
   System.out.print("\t"+interface1.getName());

 }

結果:
 
superclass:java.lang.Object
interfaces:
 ISuperA ISuperB


補充說明,推測Spring的用法:
下面的Code來自Spring in Action 3rd - Listing 1.4、1.6、1.7:
package com.springinaction.knights;

public classBraveKnightimplementsKnight{
  privateQuestquest;
  public BraveKnight(Questquest){
    this.quest=quest;
  }
  public voidembarkOnQuest()throwsQuestException{
    quest.embark();
  }
}

XML設定:
  
  
    
  

  
  

Spring中呼叫方式如下:
package com.springinaction.knights;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public classKnightMain{
   public staticvoidmain(String[]args){
      ApplicationContextcontext = new ClassPathXmlApplicationContext("knights.xml");
      Knightknight=(Knight)context.getBean("knight");
      knight.embarkOnQuest();
   }
}

依照上述呼叫的Code我們可以看到,指定了XML中的"knight" create...ok這沒甚麼問題,"knight"直接對應到XML的bean id="knight",然而有趣的事情在於,需create的BraveKnight並非是使用default constructor直接建立,是有帶參數的!然而在上述呼叫中卻沒有看到指定bean id="quest"的部分。僅僅是在bean中設定了<constructor-arg ref="quest"/>就對應的到....而quest代表的並非Type型別。
因此如果按照上述的實驗,推測是先取得id="quest"的"com.springinaction.knights.SlayDragonQuest",接者取得"com.springinaction.knights.BraveKnight"中的Constructor,由Reflection機制可以取得各種Constructor的參數型別,只要能找到"一個參數",這裡我們找到的為public BraveKnight(Questquest),因此只要SlayDragonQues符合以下條件之一,那麼XML的指定條件就可成立:

  1. SlayDragonQues的Type為Questquest
  2. Questquest為SlayDragonQuest的super class
  3. Questquest為Interface且被SlayDragonQuest implement
最後答案是3(未列出Questquest Code),因此XML設定正確,可以被順利creation :)

Eclipse - 出現Access restriction: The type XXXX is not accessible due to restriction on required library

參考:http://www.digizol.com/2008/09/eclipse-access-restriction-on-library.html
需求:最近承接一個舊系統翻新的案子,由於舊系統使用了 sun.net.ftp.FtpClient 然而被報錯誤:Access restriction: The type FtpClient is not accessible due to restriction on required library C:\Program Files\Java\jre7\lib\rt.jar



  1. Eclipse->window->Preferences
  2. Java->Compiler->Errors/Warings
  3. Deprecated and restricted API->Forbidden reference將原本下拉Error改為Warring

其實這種作法還真是鴕鳥...視而不見...= =

2013年8月25日 星期日

Java Reflection - 存取Field 資訊

參考:Java 學習筆記 (10) - Reflection

因為摸了MyBatis(請見MyBatis - 基本安裝 + 使用Select)後對於他可以利用Xml可以做各種設定以及回傳對應Data的方式相當感興趣,直覺是使用Reflection機制實做,因此有了這篇小實驗 :)

需求:假設我們有一個User Class如下,我們需要能
  • 需求1 - 取得指定所有Public的Field資訊
  • 需求2 - 取得指定指定的Declare的Field資訊(包含private、protected)
 
public class User {
 private String userid;
 private String name;
 private String password;
 private String email;
  //...other
}

  1. 取得指定Class中所有Public的Field資訊(由於以上的User Class沒有任何public field,因此只列出使用方式)
        Class aClass = User.class;
        Field[] publics = aClass.getFields();
    
  2. 取得指定指定的Declare的Field資訊(包含private、protected)
    假設我們現在想要取得其中的userid與email,並對他其中一個已存在的實體Instance做修改,怎麼做?:
    • 取得User Class的useir、email Field:
        Class aClass = User.class;
        Field fuserid = aClass.getDeclaredField("userid"); 
        Field femail = aClass.getDeclaredField("email"); 
        fuserid.setAccessible(true);
        femail.setAccessible(true);
      
      由於我們希望能修改其中的內容,因此記得要呼叫setAccessible(true),讓該field可以被修改
    • 對已建立的實體Instance進行修改:
         Constructor cons = aClass.getConstructor(null);
         User u = (User) cons.newInstance(null);
         fuserid.set(user, "ifAfter");
         femail.set(user, "mail@After.com");

以下為完整範例:

  • User.java
    package cmpnts;
    
    public class User {
     private String userid;
     private String name;
     private String password;
     private String email;
    
     public String getUserId() {
      return userid;
     }
    
     public void setUserId(String userid) {
      this.userid = userid;
     }
    
     public String getName() {
      return name;
     }
    
     public void setName(String username) {
      this.name = username;
     }
    
     public String getPassword() {
      return password;
     }
    
     public void setPassword(String pwd) {
      this.password = pwd;
     }
     
     public String getEmaill(){
      return email;
     }
     
     public void setEmaill(String mail){
      email = mail;
     }
     
     @Override
     public String toString(){
      StringBuffer buf = new StringBuffer();
      buf.append("UserId:").append(this.getUserId());
      buf.append("\r\n\tname:").append(this.getName());
      buf.append("\r\n\tpassword:").append(this.getPassword());
      buf.append("\r\n\temail:").append(this.getEmaill());
      return buf.toString();
     }
    }
    
  • FieldReflectionTest.java:
    package reflection;
    
    import java.lang.reflect.Constructor;
    import java.lang.reflect.Field;
    
    import cmpnts.User;
    
    public class FieldReflectionTest {
    
     public static void main(String[] args) {
      User user = new User();
      user.setUserId("idBefore");
      user.setName("name");
      user.setPassword("password");
      user.setEmaill("mail@before.com");
      
      try {
       alterFieldUserIdEmail(user);
      } catch (Exception e) {
       e.printStackTrace();
      }
     }
     
     public Field[] getPublicFields(){
      Class aClass = User.class;
      Field[] publics = aClass.getFields();
      return publics;
     }
     
     public static void alterFieldUserIdEmail(User user) throws Exception{
      System.out.println(user);
      
      Class aClass = User.class;
      Field fuserid = aClass.getDeclaredField("userid"); 
      Field femail = aClass.getDeclaredField("email"); 
      fuserid.setAccessible(true);
      femail.setAccessible(true);
      
      Constructor cons = aClass.getConstructor(null);
      User u = (User) cons.newInstance(null);
      fuserid.set(user, "idAfter");
      femail.set(user, "mail@After.com");
      System.out.println(user);
     }
    
    }
    


  • 執行結果:
    UserId:idBefore
    name:name
    password:password
    email:mail@before.com

    UserId:idAfter
    name:name
    password:password
    email:mail@After.com

    修改成功!!

    2013年7月20日 星期六

    Java IO: Exception Handling - 不安全的IO Exception處理(2) - 使用try-with-resources

    參考:Try-with-resources in Java 7  、 try-with-resources

    請注意這個機制只有java 7後才適用

    上篇,我們這次來看看java7的新機制(同樣是參考文章後的心得形式,若需詳細請直接連入上方參考文章連結)。


    先假設下面的code:
     
    
    public class TryWithResource implements AutoCloseable {
    
     private FileInputStream _input;
    
     public void doIt() throws WrapperException, FileNotFoundException {
      _input = new FileInputStream("datasheet/file.txt");
      throw new WrapperException("doIt has an Exception!!!");
     }
    
     @Override
     public void close() throws IOException, WrapperException {
      _input.close();
      throw new WrapperException("close has an Exception!!!");
     }
     }
    

    以下列方式呼叫使用:
     
     public static void withoutTryWithResources() throws WrapperException {
      TryWithResource tt = new TryWithResource();
      try {
       tt.doIt();
      } catch (Exception ex) {
       throw new WrapperException(ex);
      } finally {
       try {
        tt.close();
       } catch (IOException e) {
        throw new WrapperException(e);
       }
      }
     }
     public static void main(String[] args) {
      try {
       withoutTryWithResources();
      } catch (WrapperException wex) {
       wex.printStackTrace();
      }
     }
    

    會有以下結果:
    Exception in thread "main" java.lang.IndexOutOfBoundsException
    exception.WrapperException: close has an Exception!!!
     at exception.failsave.TryWithResource.close(TryWithResource.java:12)
     at exception.failsave.TryWithResource.withoutTryWithResources(TryWithResource.java:32)
     at exception.failsave.TryWithResource.main(TryWithResource.java:49)
    

    (上述的exception 行號每人都會不一樣)
    執行流程如下:

    1. doIt()
    2. throw new WrapperException("doIt...")
    3. first catch(Exception ex) block
    4. finally block
    5. close()
    6. throw new WrapperException("close...")

    最始祖的exception為應該為doIt()中出現的,但如同前一篇所說,始祖exception會被吃掉,因此,到了步驟6原先的始祖exception(步驟2)就消失了,這造成我們難以在第一時間發現最根本的問題點,因此我們可以使用java7的新機制,修改如下:

     
     public static void withResources() throws WrapperException {
      try (TryWithResource tt = new TryWithResource()) {
       tt.doIt();
      } catch (Exception e) {
       throw new WrapperException(e);
      }
     }
    
     public static void main(String[] args) {
      try {
    //   withoutTryWithResources();
       withResources();
      } catch (WrapperException wex) {
       wex.printStackTrace();
      }
     }
    

    而這次的exception 顯示為:
    exception.WrapperException: exception.WrapperException: doIt has an Exception!!!
     at exception.failsave.TryWithResource.withResources(TryWithResource.java:43)
     at exception.failsave.TryWithResource.main(TryWithResource.java:50)
    Caused by: exception.WrapperException: doIt has an Exception!!!
     at exception.failsave.TryWithResource.doIt(TryWithResource.java:15)
     at exception.failsave.TryWithResource.withResources(TryWithResource.java:41)
     ... 1 more
     Suppressed: exception.WrapperException: close has an Exception!!!
      at exception.failsave.TryWithResource.close(TryWithResource.java:21)
      at exception.failsave.TryWithResource.withResources(TryWithResource.java:42)
      ... 1 more
    

    (再一次提醒,exception中的行號每人不同)
    上述流程為:
    1. doIt()
    2. throw new WrapperException("doIt...")
    3. immediately call close() 
    4. close()
    5. Suppressed:throw new WrapperException("close...")  
    6. throw WrapperException("doIt...") (step 2)

    不僅少了很多恐怖的try catch跟finally還很清楚地馬上就知道始祖exception問題處。
    注意其中的Suppressed: exception.WrapperException: close has an Exception!!!(步驟5)
    由於使用了try-with-resources機制,因此後面發生在close()中的exception就會被suppress(壓制)。

    而try-with-resources機制也可以同時使用多個:
     
    private static void printFileJava7() throws IOException {
    
        try(  FileInputStream     input         = new FileInputStream("file.txt");
              BufferedInputStream bufferedInput = new BufferedInputStream(input)
        ) {
    
            int data = bufferedInput.read();
            while(data != -1){
                System.out.print((char) data);
        data = bufferedInput.read();
            }
        }
    }
    

    如果發生了問題,則destructor (就是呼叫close())的順序為反向的:

    1. bufferedInput.close()
    2. input.close()

    另外還有一個限制,使用在try-with-resources都必須在try(...)中宣告,而不能使用下列方式:
     
    private static void printFileJava7() throws IOException {
       FileInputStream     input = null;
       BufferedInputStream bufferedInput = null;
        try(  input         = new FileInputStream("file.txt");
              bufferedInput = new BufferedInputStream(input)
        ) {
    
            int data = bufferedInput.read();
            while(data != -1){
                System.out.print((char) data);
        data = bufferedInput.read();
            }
        }
    }
    


    因此只要implement AutoCloseable 這個interface,除了原生java的class以外,自己實作的class也同樣可以享受try-with-resources 的好處呢!

    以後解決這種惱人的io問題總算有解了!!exception handling真是個很重要的課題阿!!該去買文章作者的kindle電子書了Java Exception Handling
    話說好便宜阿作者真是佛心來者...

    Java IO: Exception Handling - 不安全的IO Exception處理

    參考:Fail Safe Exception Handling

    其實這篇等於是把參考文章直接以自己說法再寫一次當作心得筆記,想看詳細的話看上述參考文章比較妥當


    處理IO時常常發生個惱人的問題,假設下列code:
     
    public class WrapperException extends Exception {
    
     public WrapperException(IOException e) {
      super(e);
     }
    }
    

     
    InputStream input = null;
    
      try{
    
        input = new FileInputStream("myFile.txt");
    
        //do something with the stream
    
      } catch(IOException e){
        throw new WrapperException(e);
      } finally {
        try{
         input.close();
        } catch(IOException e){
           throw new WrapperException(e);
        }
      }
    

    假設"myFile.txt"這個檔案並不存在,那麼上述Code執行如下:

    1. throw java.io.FileNotFoundException() (at FileInputStream() constructor)
    2. catch(IOException e) block,rethrow WrapperException
    3. finally block
    4. 由於input未初始化成功,因此再次被finally中的try catch捕捉,然而這次拋出的為NullPointerException
    因此上述的exception顯示如下:

    Exception in thread "main" java.lang.NullPointerException
     at exception.FailSaveException.failSave(FailSaveException.java:30)
     at exception.FailSaveException.main(FailSaveException.java:15)
    

    原先造成exception的始祖,最該被拋出的FileNotFoundException()就這樣默默的被吃掉了....
    解決方式是:在finally的input.clode中加入null check:
    if(input != null) 
       input.close();
    

    然而讓我們假設另一個情況:
      假設檔案存在,input順利被建立,但在try catch中處理內容時因為某些原因再度造成io exception,此時順序會變為:

    1. throw some IOException in first try catch block
    2. first try catch catch it ,rethrow WrapperException
    3. finally block
    4. rethrow WrapperException again.....if input.close() also error
    然而要是執行到步驟3時,input.close()又再次因為某些不知明原因,再度throw IOException,則就會多了步驟4,再一次的,最關鍵的始祖IOException又被吃掉(步驟1)!!
    (上述假設無example code,推測是因為很難馬上弄出個簡單的input.close()也出問題的範例)
    而為了解決上述問題,讓我們這些苦命developer可以在第一時間就發現始祖exception,Java 7帶來了一個叫 try-with resource的機制。

    詳情請見下篇介紹XD

    2013年5月16日 星期四

    dbunit初體驗 - QueryDataSet.addTable((tableName, query) 出現 org.dbunit.dataset.DataSetException: java.sql.SQLSyntaxErrorException: user lacks privilege or object not found:


    照者Juni in Action 中的第17章 p338的做法輸入:
    QueryDataSet data = new QueryDataSet(_dbunitConnection);
    data.addTable("TableName","select * from TableName where id="+value );
    

    卻一直出現

    org.dbunit.dataset.DataSetException: java.sql.SQLSyntaxErrorException: user lacks privilege or object not found: value

    的錯誤,後來才發現因為我要比較的對象id為String,因此前後應加上單引號,上述應更正如下:
    data.addTable("TableName","select * from TableName where id='"+value+"'");
    

    OK 更改完畢,順利通過^^
    這個錯誤也有可能是在使用之前Table未建立而產生

    2013年3月31日 星期日

    JMock - 參考文章與網站





    JUnit4 与 JMock 之双剑合璧:

    http://www.ibm.com/developerworks/cn/java/j-lo-junit4jmock/


    JMock官方網站Cookbook:
    http://jmock.org/cookbook.html


    良葛格:
    http://caterpillar.onlyfun.net/Gossip/JUnit/JMock.html



    In-process Web Integration Tests with Jetty and JWebUnit

    http://johannesbrodwall.com/2006/12/10/in-process-web-integration-tests-with-jetty-and-jwebunit/

    2013年3月30日 星期六

    JMock初體驗


    以下範例為測試 PreMathM.all() 的行為:

    1.  呼叫MathM.returnOne()
    2. 檢驗return Value == 1
    3. 呼叫_mockMathM.echo(3)
    4. 檢驗return Value == 3

     
    import org.jmock.Expectations;
    import org.jmock.Mockery;
    import org.jmock.Sequence;
    import org.jmock.integration.junit4.JMock;
    import org.jmock.integration.junit4.JUnit4Mockery;
    import org.jmock.lib.legacy.ClassImposteriser;
    import org.junit.After;
    import org.junit.Before;
    import org.junit.Test;
    import org.junit.runner.RunWith;
    
    
    @SuppressWarnings("deprecation")
    @RunWith(JMock.class)
    public class JMockTest {
    
    
     Mockery _context = new JUnit4Mockery(){{
            setImposteriser(ClassImposteriser.INSTANCE);
        }};
     MathM _mockMathM  = _context.mock(MathM.class);
    
     public class PreMathM{
      MathM _mathm;
      public PreMathM(MathM mathm){
       _mathm = mathm;
      }
      public void all(int echov){
       _mathm.returnOne();
       _mathm.echo(echov);
      }  
     }
     public class MathM {
      public int returnOne() {
       return 1;
      }
    
      public int echo(int echov) {
       return echov;
      }
     }
    
     @Before
     public void setUp() throws Exception {
      
     }
    
     @After
     public void tearDown() throws Exception {
     }
    
     @Test
     public void testSequenceOneEcho() {
      final Sequence sequenceOneEcho = _context.sequence("sequenceOneEcho");
      _context.checking(new Expectations() {
       {
        atLeast(1).of(_mockMathM).returnOne();
        inSequence(sequenceOneEcho);
        will(returnValue(1));
    //    oneOf(_mockMathM).echo(3);
        atLeast(1).of(_mockMathM).echo(3);
        inSequence(sequenceOneEcho);
        will(returnValue(3));
       }
      });
      
      PreMathM mall = new PreMathM(_mockMathM);
      mall.all(3);
     }
    
    }
    
    
    上述範例中,每次呼叫inSequence(sequenceOneEcho),都是為了確保每一個行為是【按順序】執行呼叫

    JMock - java.lang.IllegalArgumentException: ... is not an interface


    解法:


    出現:java.lang.IllegalArgumentException: unittest.MathM is not an interface
    at java.lang.reflect.Proxy.getProxyClass(Unknown Source)

    ....


    (unittest.MathM為自訂預測試Class)


    Mockery _context = new JUnit4Mockery();

    改為


     
     Mockery _context = new JUnit4Mockery(){{
            setImposteriser(ClassImposteriser.INSTANCE);
        }};
    



    探討:

    看到exception上其實說得很明白 unittest.MathM is not an interface,就大概可以猜到若要將我們欲使用的Mock物件(這裡為MathM)變成可使用的,就得為interface;

    以下是Mockery.setImposteriser()的doc:


    Changes the imposteriser used to adapt mock objects to the mocked type. The default imposteriser allows a test to mock interfaces but not classes, so you'll have to plug a different imposteriser into the Mockery if you want to mock classes.



    因此對於已經為concreate class的 object來說,就得另外使用  setImposteriser(ClassImposteriser.INSTANCE); 改變其中的Imposteriser才能做到。

    原本對於細節實作還挺有興趣的,但無奈功力不夠,追了一些source就追不下去了0rz...只能大約知道有使用reflection機制去實作。
    看來真的要開始培養自己鑽研open source的能力了...................