2015年7月16日 星期四

Spring MVC中Controller與View的傳值

Spring MVC中很有趣的地方就是Controller可以為一個POJO或是一個普通的Java Bean,不用繼承如HttpServlet等類別,而要將請求導向View(例如JSP)時,也只要回傳String或ModeAndView等,不用使用像response.sendRedirect()或request.getRequestDispatcher("XXX").forward(request, response)等語法,使得單元測試變得更為簡單與容易。

那麼View和Controller之間要怎麼溝通呢?在這裡以一個簡單的例子實作來展示一些在Spring中View和Controller之間溝通的方式。

例子要實做的內容為,使用者一開始會進入一個表單輸入頁面,可以輸入名字及選擇稱謂(先生或小姐),按下送出按鈕後,會引導到招呼畫面,其中招呼內容包含輸入的名字及稱謂,而如果名字為雨果及稱謂為先生的話,招呼畫面的內容會稍有不同。

首先先來看專案的配置:



其中包括:

  1. dispatcher-servlet.xml:Spring的dispatcherServlet設定檔。
  2. FormView.html:一開始的表單輸入頁面。
  3. FormCheckResultView.jsp:招呼畫面。
  4. FormController.java:接收及轉送參數的Controller。
在web.xml中,我們讓所有的url請求都先讓Spring處理:

2015年7月15日 星期三

Java Swing的DnD (Drag and Drop)

Drag and Drop (DnD) 就是中文的拖放,例如我們平常在Windows下常常會用滑鼠直接將檔案從一個資料夾移到另一個資料夾、或是將影片檔直接移到播放器的介面上以播放影片,這些動作就是所謂的DnD,而在Java Swing裡也提供了DnD的實作功能。

在這篇文章中,我們要演示一個簡單的DnD實作,在主要Swing畫面上放置一個JTextArea,當我們在Windows下把一個或多個檔案用滑鼠圈選移到JTextArea後,JTextArea裡會顯示所移上去的檔案路徑列表。

程式非常簡單,首先我們可以建立一個Java Swing的專案(例如使用NetBeans),命名為 "DragAndDropTest"。並在上面放一個JTextArea,我們把它命名為 "dragAndDropJTextArea"。
接著在DragAndDropTest的建構子中加上如下程式碼即可:

public DragAndDropTest() {
        //產生各元件
        initComponents();
        //為dragAndDropJTextArea設置DropTarget,其用來接收DropTargetDropEvent,
        //可從DropTargetDropEvent取得Transferable物件,即被包裝的拖曳物件。
        dragAndDropJTextArea.setDropTarget(new DropTarget() {
            public synchronized void drop(DropTargetDropEvent evt) {
                try {
                    //設置可接受的拖曳類型
                    //如果拖曳物件可支援檔案列表格式(javaFileListFlavor)
                    if (evt.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) {
                        //接受拖曳
                        evt.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
                        //得到Transferable物件,並將其轉成檔案列表格式(javaFileListFlavor),
                        //轉型成List<File>
                        List<File> droppedFiles = (List<File>) evt.getTransferable().getTransferData(DataFlavor.javaFileListFlavor);
                        //清空dragAndDropJTestArea的內容
                        dragAndDropJTextArea.setText("");
                        //將拖曳的檔案以檔案列表的方式顯示在dragAndDropJTestArea中 
                        //字顏色為黑色
                        dragAndDropJTextArea.setForeground(Color.BLACK);
                        for (File file : droppedFiles) {
                            //顯示檔案列表
                            dragAndDropJTextArea.append(file.getPath() + "\n");
                        }
                    } else {
                        //若拖曳的物件不能支持檔案列表格式,
                        //拒絕Drop
                        evt.rejectDrop();
                        //顯示錯誤訊息,字顏色為紅色
                        dragAndDropJTextArea.setText("錯誤:拖曳類型不支援檔案列表格式!");
                        dragAndDropJTextArea.setForeground(Color.red);
                    }
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
        });
    }

最後試試看結果:

  1. 如果拖曳的是一般檔案,即可在JTextArea中顯示檔案列表,如下所示:


  2.  如果拖曳的是不可轉成檔案列表格式的物件,則在JTextArea中會顯示錯誤訊息:


最後附上源始碼下載:
DnDTest.7z

參考資料:

  1. How to drag and drop with Java 2
  2. Java中的Drag and Drop詳解與代碼示例
  3. java swing中實現拖拽功能示例_java

2015年7月12日 星期日

JSP/Servlet中web.xml裡的<welcome-file>與Spring MVC的<mvc:resources>

在使用Spring框架時,如果在web.xml裡將所有的請求都交給Spring來處理的話,例如下面這樣:
<servlet>
        <servlet-name>dispatcher</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <load-on-startup>2</load-on-startup>
    </servlet>
<servlet-mapping>
        <servlet-name>dispatcher</servlet-name>
        <url-pattern>/</url-pattern>
</servlet-mapping>

此時一些靜態資源可能就會因為沒有相應的Url Mapping或Controller來處理而無法回應靜態資源(如html, jpg, css, js等)的請求,這時我們可以有兩種方法來解決:
  1. 在Spring設定檔中,例如dispatcher-servlet.xml,加入如下內容,作用為再交由Spring的url mapping等處理之前,如果是靜態資源就交還給Web應用服務器管理:

  2. <mvc:default-servlet-handler />
    

  3.  在Spring設定檔中,加入以下內容,作用為對於符合"mapping"的靜態資源邏輯路徑(**表示任意子路徑),告知Web應用服務器去"location"的地方查找。例如對於以下設定,邏輯路徑為/resource/img/picture.jpg就會去找/img/picture.jpg:

  4. <mvc:resources location="/" mapping="/resource/**"/>
    
這裡要注意的地方是,如果我們有在web.xml裡設定歡迎頁面,例如像下面這樣:

<welcome-file-list>
        <welcome-file>index.html</welcome-file>
</welcome-file-list>

然後請求Web應用程式的根目錄時,例如 http://XXX.XXX.XXX.XXX/projectName ,根目錄的url請求,即 "/" ,會被Spring所接收處理,假設Spring設定檔中設定如下:

<mvc:resources location="/" mapping="/**"/>

根目錄 "/" 將會與mapping內容符合,並以location的值 "/" 返回給Web應用服務器,Web應用服務器發現為根目錄("/")的請求時,就會根據web.xml裡的歡迎頁面設定查找index.html。

也就是說,如果我們現在把index.html放在 "/html/index.html" 的路徑中,並且想要以http://XXX.XXX.XXX.XXX/projectName 的請求找到index.html的話,以下的設定方法是錯的

2015年7月9日 星期四

整合Spring、Hibernate和C3P0的配置

當我們只有使用Hibernate的時候,其中要獲取SessionFactory的話必須要使用Hibernate的API手動獲取,例如像這樣:

SessionFactory sessionFactory = AnnotationConfiguration().configure().buildSessionFactory();

如果配合Spring的話,就可以使用注入的概念將SessionFactory注入到要使用SessionFactory的類別中。

整合Spring與Hibernate有兩種方法:

  1. 在Spring配置中直接將原Hibernate的配置檔抓進來。
  2. 在Spring自己配置Hibernate的配置、連接池(例如C3P0)等,可以捨棄原來的Hibernate配置檔,也可把它抓進來,然後覆蓋部份配置。

讓我們再一次看一次原來的Hibernate配置檔,hibernate.cfg.xml:

2015年7月8日 星期三

Hibernate中C3P0連接池的設置

在一開始使用Hibernate做ORM(Object/Relational Mapping)時,如果我們沒有配置連接池(Connection Pool),可能會發生一些底層連接Database會碰到的問題,例如sessionFactory所產生的連接太久沒有動作而被Database端以Timeout為由強行關閉,而之後在使用連接時就會出錯(在Hibernate端似乎無法用SessionFactory的isClosed()來判斷),此時就可能要自己在做CRUD(Create,Read,Update,Delete)時手動測試連接是否還在等等。

如果配置了連接池,就可以簡單地受益於連接池帶來的許多好處,例如幫我們管理連線,最小連接數量、最大連接數量、定時檢測連線等等。

在Hibernate裡已經包含了一個很好用且有名的開源數據庫連接池,C3P0,如果你在NetBeans裡使用了Hibernate框架,那麼你就可以在Library中看到C3P0的jar,如下圖紅框內所示:

對於C3P0,以下的連結提供了不錯的介紹、範例與相闗資料:

  1. C3P0百度百科
  2. How to configure the C3P0 connection pool in Hibernate
  3. 關於MySQL的wait_timeout連接超時問題報錯解決方案
  4. c3p0 - JDBC3 Connection and Statement Pooling(官方網站)
在這邊,我把我使用的例子在這邊做了一個紀錄,在Hibernate的配置文件hibernate.cfg.xml中,加入如以下配置,這樣Hibernate就會自動偵測並使用C3P0了:
        <!--==============C3P0的配置=============-->
        <!-- 最小連接數 -->
        <property name="hibernate.c3p0.min_size">5</property>
        <!-- 最大連接數 -->
        <property name="hibernate.c3p0.max_size">20</property>
        <!-- 多久會把無用的連接視為timeout並移除到min_size的連接數量,單位毫秒 -->
        <property name="hibernate.c3p0.timeout">300</property>
        <!-- 最大的PreparedStatement的數量 -->
        <property name="hibernate.c3p0.max_statements">50</property>
        <!-- 多久進行空閒連接的檢查,確定連接還存在,例如有無被Database端關掉,單位是秒-->
        <property name="hibernate.c3p0.idle_test_period">3000</property>
        <!--======================================-->

注意:此處例出來的配置只是C3P0配置的一部份,其功能不只這些,其他的配置可以在上述的資料連接中查到。

2015年7月7日 星期二

Java - 在HttpGet及HttpPost中塞入資料參數的方法

當我們想用HttpClient的方式進行HttpGet及HttpPost連線時,常會需要在其中放入一些資料參數,在這邊紀錄整理了一些個人覺得不錯的、如何向連線放入參數的方法:

Note:
這裡使用了以下 Maven dependency,其中官方說明
commons-httpclient 官方已停止更新,
改由 org.apache.httpcomponents 繼續提供更新
(官方原文 :
The Commons HttpClient project is now end of life, and is no longer being developed. It has been replaced by the Apache HttpComponents project in its HttpClient and HttpCore modules, which offer better performance and more flexibility.
)

        <!-- https://mvnrepository.com/artifact/commons-httpclient/commons-httpclient -->
<dependency>
    <groupId>commons-httpclient</groupId>
    <artifactId>commons-httpclient</artifactId>
    <version>3.0.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.httpcomponents/httpclient -->
<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
    <version>4.5.13</version>
</dependency>

HttpGet:
HttpGet的參數都是放在Url後面的,例如"http://XXX.XXX.XXX.XXX?param1=1&param2=2",所以我們的目標就是創造出帶有參數的Url即可。
參考資料:How to add parameters to a HTTP GET request in Android?

  1. 最簡單的方式就是手動在HttpGet的Url後面加上要的參數資料(限文字),例如直接自行組合字串 (param 的值可能需要做 url encode):
  2. String url = "http://XXX.XXX.XXX.XXX?" + "param1=" + param1 + "&param2=" + param2;
    
    
  3. 使用URIBuilder來幫助我們產生Uri:
  4. URI uri = new URIBuilder("http://XXX.XXX.XXX.XXX:8080/path1/path2/xxx.jsp")
        .addParameter("param1", param1)
        .addParameter("param2", param2)
        .addParameters(listOfParameters)  //如果參數以NamerValuePair放在
        //List<NameValuePair>中的話可以
        //使用addParameters()
        .build();
    
    以上也可也拆成多個方法變成如下的樣子:
    URI uri = new URIBuilder()
        .setScheme("http")
        .setHost("XXX.XXX.XXX.XXX")
        .setPort(8080)
        .setPath("/path1/path2/xxx.jsp")
        .addParameter("param1", param1)
        .addParameter("param2", param2)
        .addParameters(listOfParameters)
        .build();
    
    因為 addParameter() 已經會對輸入值做 url encode ,所以不用再對資料做一次 url encode
HttpPost:
HttpPost的參數都是放在Entity中的,所以我們只要用List<NameValuePair>的型式把資料放到HttpPost的Entity中就行了。
參考資料:How to add parameters to a HTTP GET request in Android?

String url = "http://XXX.XXX.XXX.XXX:8080/path1/path2/xxx.jsp";
//建立HttpClient
//DefaultHttpClient httpClient = new DefaultHttpClient(); //舊方法,現已 Deprecated
CloseableHttpClient httpClient = HttpClients.createDefault();
//建立HttpPost
HttpPost httpPost = new HttpPost( url ) ;
//建立要送的資料參數
ArrayList<BasicNameValuePair> params = new ArrayList<BasicNameValuePair>();
params.add(new BasicNameValuePair("param1" , param1)) ;
params.add(new BasicNameValuePair("param2" , param2)) ;
//將資料參數放到HttpPost的Entity裡面,並指定編碼
httpPost.setEntity(new UrlEncodedFormEntity(params, StandardCharsets.UTF_8));
//Execute連線發送訊息並取得回應
HttpResponse response = httpClient.execute(httpPost);
System.out.println(EntityUtils.toString(response.getEntity(), "UTF-8"));
或是使用PostMethod
final HttpClient client = new HttpClient();
final PostMethod postMethod = new PostMethod("http://XXX.XXX.XXX.XXX:8080/path1/path2/xxx.jsp");
postMethod.getParams().setParameter(HttpMethodParams.HTTP_CONTENT_CHARSET,"utf-8");  
postMethod.addParameter("param1", param1);
postMethod.addParameter("param2", param2);
//如果想將文字資料直接放到HttpPost的Entity裡面,可以這樣
//postMethod.setRequestEntity(new StringRequestEntity("", ContentType.APPLICATION_JSON.toString(), StandardCharsets.UTF_8.name()));

int statusCode = client.executeMethod(postMethod);
System.out.println(postMethod.getStatusLine());
System.out.println(postMethod.getResponseBodyAsString());

JUnit與Mockito

在程式進行測試時,常常會使用JUnit進行測試,其在NetBeans中已有內建。

而Mockito是一個需要下載的工具(下載jar後引入library中使用),它可以很方便地讓我們模擬還未撰寫完成的模組單元(類別還未實作完的類別),用模擬的單元來進行測試。

在單元測試中,我們有時會碰到一個單元要使用另一個單元的情況,例如單元A要使用單元B,而我們想在假設單元B一定正確的前提下測試單元A、或者是單元B根本就還沒實作完成(不過方法介面等要先出來),這時我們就會需要在單元A的測試中模擬出一個虛擬的單元B幫助測試。

在這裡"JUnit + Mockito 單元測試"有不錯的示例與說明可以參考。

我們在這裡演示一個簡單的例子:

有一個有兩個類別,Singer與Song,其中在Singer的建構子中我們注入一個Song給它,並在Singer的sing()方法中呼叫Song的getLyrics(),sing()方去會回傳從getLyrics()得到的lyrics,而lyrics的值就是Song建構子要傳入的參數,兩個類別的內容如下所示。

Gradle Excpetion - Could not find property XXX on root project 'XXX' 的解決辨法

今天用Gradle 2.4寫了一個最簡單的Hello World的Task,竟然無法執行成功,錯誤如下:


而build.gradle的內容只是很簡單的Hello World:

task hello << {
    println 'Hello world!'
}

後來Google了一下,在這篇文 "Gradle Hello World Error" 得到了靈感,可能是編碼造成的,才想到當初程式碼是用記事本寫的,並用UTF-8格式存的。馬上把編碼換成ANSI再存一次,果然這次就成功了,如下圖:

2015年7月2日 星期四

用Eclipse開發Android實機測試時,遇到The connection to adb is down, and a severe error has occured. 的錯誤時之解決辦法

關掉tfadb的進程,為風行網等軟件搞的鬼。

Java - HttpClient 的基本身份認證

在HttpClient中,如果要使用有基本身份認證的連線時,必須在程式碼中加入身認證的Code,如下面所示:

//建立認證提供者
CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
//建立其本帳號密碼對的身份認證
UsernamePasswordCredentials urseNamePasswordCredentials = new UsernamePasswordCredentials(Admin, Pass);
//將身份認證交給認證提供者
credentialsProvider.setCredentials(AuthScope.ANY, urseNamePasswordCredentials );
//建立HttpClient連線
DefaultHttpClient defaultHttpclient = new DefaultHttpClient();
//設定HttpClient連線的認證提供者
defaultHttpclient.setCredentialsProvider(credentialsProvider );