blog.Ring.idv.tw

Java

Apache Tomcat 8 + SQLite

很長一段時間沒有更新該Blog了,由於最近為了做一些side project,所以打算在Mac筆電上架個簡單的開發環境,初步就想說直接利用SQLite來當做資料庫,反正只要中間層的DAO設計好,未來要改個後端也不是什麼太大的負擔,所以更不需要動到HBase之類的儲存方案。所以本文就純粹當作Memo...

必要軟體

.Apache Tomcat 8.5.43

為什麼選擇Tomcat 8的理由是,由於筆電的Java版本為Java 7,加上沒有急著升級的必要,所以就維持著...

至於SQLite的話,直接下載SQLite JDBC Driver即可,目前最新版本為sqlite-jdbc-3.27.2.1.jar

接下來就一步步地做些必要的操作:

修改 /etc/hosts

127.0.0.1    helloworld

這步驟主要是用來方便在筆電端快速地來做測試而已,上述的【helloworld】你可以改成你任意喜歡的,之後要測試時就直接在網頁上輸入http://helloworld:8080即可。

建立必要的目錄及檔案

$TOMCAT_HOME/helloworld/ROOT

$TOMCAT_HOME/helloworld/ROOT/index.jsp

上述index.jsp檔案裡任意寫個Hello JSP之類的文字即可。

修改 $TOMCAT_HOME/conf/server.xml

<Host name="helloworld"  appBase="helloworld"  unpackWARs="true" autoDeploy="true">
	     <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
               prefix="helloworld_access_log." suffix=".txt"
               pattern="%h %l %u %t "%r" %s %b" />
</Host>

啟動Tomcat

在啟動之前,如果你也是在Unix-like系統下安裝的話,那你可以考慮刪除掉$TOMCAT_HOME/bin/目錄底下的所有.bat檔案,這樣在透過Tab鍵補字操作下會方便些。

$TOMCAT_HOME/bin/startup.sh

接著在網頁上輸入http://helloworld:8080,應該就可以work了,接下來處理SQLite資料庫的部份。

建立必要的目錄及檔案

$TOMCAT_HOME/helloworld/ROOT/META-INF/context.xml

$TOMCAT_HOME/helloworld/ROOT/WEB-INF/classes

$TOMCAT_HOME/helloworld/ROOT/WEB-INF/lib

P.S. 請將sqlite-jdbc-3.27.2.1.jar直接copy到$TOMCAT_HOME/helloworld/ROOT/WEB-INF/lib/目錄下,至於其它目錄及檔案,待會再說明。

修改 META-INF/context.xml

<?xml version="1.0" encoding="UTF-8"?>
<Context>
  <Resource name="jdbc/helloworld" auth="Container" type="javax.sql.DataSource" driverClassName="org.sqlite.JDBC"
            url="jdbc:sqlite:/Users/yourname/apache-tomcat-8.5.43/helloworld/ROOT/helloworld.db"
            factory="org.apache.tomcat.dbcp.dbcp2.BasicDataSourceFactory">
  </Resource>
</Context>

上述的【url】用來設定SQLite的檔案位置,接著會透過下面的Servlet程式來操作該資料庫。

建立一個 Servlet 程式

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.sql.DataSource;
import java.io.*;
import java.sql.*;

@WebServlet("/HelloServlet")
public class HelloServlet extends HttpServlet
{
	private static final long serialVersionUID = 1L;
	
	@Override
	protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
	{
		req.setCharacterEncoding("UTF-8");
		resp.setContentType("text/html; charset=utf-8");
		
		PrintWriter out = resp.getWriter();
		Connection con = null;
		ResultSet rs = null;
		PreparedStatement ps = null;

		try
		{
			Context ctx = new InitialContext();
			DataSource ds = (DataSource)ctx.lookup("java:comp/env/jdbc/helloworld");
			con =  ds.getConnection();

			Statement stat = con.createStatement();
			stat.executeUpdate("drop table if exists member;");
			stat.executeUpdate("create table member (id, name);");
			ps = con.prepareStatement("insert into member values (?, ?);");

			ps.setInt(1, 1);
			ps.setString(2, "Tony Lee");
			ps.addBatch();
			ps.setInt(1, 2);
			ps.setString(2, "Jack Wang");
			ps.addBatch();
			ps.executeBatch();


			rs = stat.executeQuery("select * from member;");
			while (rs.next()) {
				out.println("ID=" + rs.getInt("id")+"<br/>");
				out.println("Name=" + rs.getString("name")+"<br/>");
			}

		}catch (SQLException e)
		{
			e.printStackTrace();
		} catch (NamingException e) {
			e.printStackTrace();
		} finally {
			out.flush();
			out.close();

			try { rs.close(); } catch (SQLException e) { e.printStackTrace(); }
			try { ps.close(); } catch (SQLException e) { e.printStackTrace(); }
			try { con.close(); } catch (SQLException e) { e.printStackTrace(); }
		}
	}
}

最後將上述程式編譯後,將HelloServlet.class檔案copy至$TOMCAT_HOME/helloworld/ROOT/WEB-INF/classes/目錄下。

重新啟動Tomcat,輸入網址http://helloworld:8080/HelloServlet就可以看到下述結果:

ID=1
Name=Tony Lee
ID=2
Name=Jack Wang

2019-07-24 19:49:43 | Add Comment

BTrace - A Dynamic Tracing Tool for Java

BTrace 是一套可以讓你動態追踨(trace)運作中程式的工具,它是由當時Sun公司的兩位高級工程師(Staff Engineer)Sundararajan AthijegannathanKannan Balasubramanian,在2007年開始進行的一項計劃,並於2008年在JavaOne的會議上正式對外公開:BTrace: Java™ Platform Observability by Bytecode Instrumentation (pdf),不過隨著Oracle在2010年併購Sun之後,目前只有A. Sundararajan仍然在Oracle公司服務,而B. Kannan則是在VMWare服務。

那究竟BTrace有何過人之處呢?舉個實例來說,以往要評估一個method會耗多少執行時間,可能的寫法如下:

long start = System.nanoTime();
test();
long end = System.nanoTime();
System.out.println(end - start);

但這樣的作法在小程式還可行,倘若要用來追踨一些大型Project原始碼的話就太麻煩了...Orz

雖然還有另一種方法,就是用JDK 5所提供的java.lang.instrument API來達成,透過AOP的方式在class載入之前就注入(inject)一些bytecode,如此便能無須更動原始碼即可完成所需,但這若是面對一個正在「運作中的程式(running program)」仍有些麻煩,除非透過JVM Tool Interface (JVM TI)的技術去達成,不過採用JVM TI還要寫一些native code,這實在還是不夠方便... 而就在2006年底時JDK 6(Mustang)正式推出之後,它為Java帶來了一些新的API,如:Java Attach APIJava Compiler API等。

簡單來說,透過Java Attach API不像JVM TI技術需要寫些native code,它就能藉由VirtualMachine class用純Java的方式去attach to a JVM,如:

VirtualMachine vm = VirtualMachine.attach(processid);
String agent = ...
vm.loadAgent(agent);

也正因為如此,所以BTrace是一個結合Java Attach APIJava Compiler APIJava Instrumentation APIASM (A Java bytecode engineering library)等技術來實現的,而這些關鍵API對應BTrace的原始碼如下:

Java Attach API: net.java.btrace.client.Client.java

Java Compiler API: net.java.btrace.compiler.Compiler.java

Java Instrumentation API: net.java.btrace.agen.Main.java

ASM: net.java.btrace.instr.Instrumentor.java

P.S. The ASM name does not mean anything, it's just a reference to the __asm__ keyword in C. (ASM 4.0 A Java bytecode engineering library)

這裡我們來看一個實際的例子,如下述程式:

Hello.java

import java.util.*;

public class Hello
{
    public Hello(){}
    public void sayHello()throws Exception
    {
        Random random = new Random();
        int r = random.nextInt(1000);
        Thread.sleep(r);
        System.out.println("Hello: "+r);
    }
    public static void main(String args[])
    {
        Hello h = new Hello();
        try
        {
            while(true)
            {
                Thread.sleep(1000l);
                h.sayHello();
            }
        }catch(Exception e){
            e.printStackTrace();
        }
    }
}

接下來~ 假設我們想知道上述程式sayHello method耗多少執行時間或stack trace都可藉由BTrace來完成,如下:

HelloTest.java

import static com.sun.btrace.BTraceUtils.*;
import com.sun.btrace.annotations.*;
 
@BTrace public class HelloTest
{
    @OnMethod(clazz="Hello",method="sayHello")
    public static void trace()
    {
        jstack();
    }
    @OnMethod(clazz="Hello",method="sayHello",location=@Location(value=Kind.RETURN))
    public static void func(@Duration long duration)
    {
        println(duration);
    }
}

上述annotations的詳細用法及BTrace的使用限制可參考BTrace User's Guide

執行BTrace

$BTRACE_HOME/bin/btrace [pid] HelloTest.java

P.S. [pid]可透過「jps」指令來取得。

結果

Hello.sayHello(Hello.java)
Hello.main(Hello.java:21)
803517667
Hello.sayHello(Hello.java)
Hello.main(Hello.java:21)
681229152

相關資源

BTrace Developer's Guide

BTrace – a Simple Way to Instrument Running Java Applications

btrace一些你不知道的事

btrace记忆

Java 動態程式碼變更 (一)

2013-01-08 01:56:18 | Add Comment

我把程式寫美了,但卻變慢了...

昨天晚上我和同事在討論一個小程式,該程式只是需要一點小邏輯而已,並不會很複雜~ 而且可以有多種寫法,整個故事如下:

該程式需要進行一些轉換的工作,條件是將「1到12的整數,能轉換成1->12, 2->1, 3->2.... 12->11」這樣的處理,很顯然的~ 這只需要一個「if-else」判斷式即可搞定,程式如下:

if (i == 1)
	return 12;
else
	return i-1;

看起來相當直覺~ 而且可讀性也還不錯~ 只是龜毛的我硬是想要用公式來解決它,解法如下:

return (i+10)%12+1;

好了,問題來了~ 以程式碼的可讀性來說前者俱備這樣的優勢,唯一可以挑剔的就是和後者相比程式碼多了點...(其實也還好 XD)

那這兩種解法哪一種執行的效率高呢?「if-else」判斷式?還是公式?

筆者寫了個Java程式來測試~ 不過得不到答案,可能是JVM的關係導致一下子解法一快~ 一下子又解法二比較快... Orz

public class Test
{
    public static int test1(int i)
    {
        return (i + 10) % 12 + 1;
    }

    public static int test2(int i)
    {
        return (i == 1) ? 12 : i - 1;
    }

    public static void main(String[] args)
    {
        long s1 = System.nanoTime();
        for (int i = 0; i < 1000000000; i++)
            test1(i);
        long e1 = System.nanoTime();
        System.out.println(e1 - s1);

        long s2 = System.nanoTime();
        for (int i = 0; i < 1000000000; i++)
            test2(i);
        long e2 = System.nanoTime();
        System.out.println(e2 - s2);
    }
}

不過可以確定的是,如果只從被編譯後的class檔所包含的opcode來看~ 用公式解的opcode數量少了一個,但是執行測試的結果不代表用公式解的效率就比較好:

public static int test1(int);
  Code:
   Stack=2, Locals=1, Args_size=1
   0:    iload_0
   1:    bipush    10
   3:    iadd
   4:    bipush    12
   6:    irem
   7:    iconst_1
   8:    iadd
   9:    ireturn

public static int test2(int);
  Code:
   Stack=2, Locals=1, Args_size=1
    0:    iload_0
    1:    iconst_1
    2:    if_icmpne    10
    5:    bipush    12
    7:    goto    13
   10:    iload_0
   11:    iconst_1
   12:    isub
   13:    ireturn

既然如此,筆者就改用C來測試看看:

#include <stdio.h>
#include <time.h>
int test1(int i)
{
        return (i+10)%12+1;
}
int test2(int i)
{
        return (i==1)?12:i-1;
}
int main(void)
{
        clock_t start_tick, end_tick;
        double elapsed;
        start_tick = clock();
        for(int i = 0 ; i < 1000000000 ; i++)
                test1(i);

        end_tick = clock();
        elapsed = (double) (end_tick - start_tick) / CLOCKS_PER_SEC;
        printf("test1 taken:=%f\n",elapsed);

        start_tick = clock();
        for(int i = 0 ; i < 1000000000 ; i++)
                test2(i);

        end_tick = clock();
        elapsed = (double) (end_tick - start_tick) / CLOCKS_PER_SEC;
        printf("test2 taken:=%f\n",elapsed);
        return 0;
}
gcc -std=c99 -o test test.c

測試結果出爐:

test1 taken:=6.090000
test2 taken:=4.050000

「if-else」判斷式獲勝!! Orz... 那我幹嘛還花時間去想公式~ 唯一的好處就只剩下程式比較「美」罷了~ (自我感覺良好...)

最後謝謝我那位同事(匿名)陪我一起討論 ^^

2010-08-24 14:29:30 | Comments (4)

Java class File Format

昨天試著在整理電腦的東西~ 發現一個塵封已久的Word檔~ 裡頭包含的是以前研究Java class File Format記錄~

還記得當初剛看到「0xCAFEBABE」真是會心一笑啊~ 咖啡寶寶?XDDD 是的,沒錯! Java就是拿這四個Bytes當做File Signature,真是有創意極了!!

依稀記得這個class format曾經大幅更動過一次~ 那時候是從「Java 1.4.x」直接跳到「Java 5.0」,而當時最流行的就是「二隻老虎」~ 一隻是Java 5.0的代號「Tiger」,另一隻則是「Mac OS X 10.4」代號也稱為「Tiger」~ 直到去年才被「自然界」所取代... 因為開始出現很多的「Cloud」和「Air」.. 不是「雲」就是「大氣」~ 不勝枚舉 XDD(Cloud Computing、Tag Cloud、Adobe AIR、MacBook AIR...)

上述格式的原始檔如下:

Hello.java

public class Hello
{
	public static void main(String arg[])
	{
		String s = "Hello";
	}
}

有興趣的人可以對照著「VM Spec The class File Format」來剖析~

而Java 5.0 更動的class format請至「JSR 202: JavaTM Class File Specification Update」下載。

筆者的記錄檔:Hello.class 格式剖析 (整張圖放上來會漏漏長...)

2009-01-06 12:54:34 | Add Comment

PDFBox - 擷取PDF檔案中的純文字

PDFBox.是一個Open Source的Java PDF Library,可以利用它來協助處理PDF檔案的一些應用(用iText也是可行的,不過它好像不支援擷取純文字「iText in Action, pp. 576」),例如:擷取PDF檔案中的純文字、轉換PDF檔案到Image檔等等.. 諸如此類的應用。

而且「Lucene」就是用它來轉換PDF到純文字再進行索引的~

下述是擷取PDF檔案中的純文字:(FontBox-0.1.0-dev.jar、PDFBox-0.7.3.jar required!)

import java.io.IOException;
import org.pdfbox.pdmodel.PDDocument;
import org.pdfbox.util.PDFTextStripper;

public class PDFTextExtractor
{
    private PDDocument document;
    
    public String extractText(String file) throws IOException
    {
        document = PDDocument.load(file);       
        PDFTextStripper stripper = new PDFTextStripper();
        stripper.setStartPage(1);
        stripper.setEndPage(document.getNumberOfPages());
        return stripper.getText(document);
    }

    public static void main(String[] args)
    {
        PDFTextExtractor extractor = new PDFTextExtractor();
        try
        {
            String text = extractor.extractText("C:\\test.pdf");
            System.out.println(text);
        }catch (IOException e)
        {
            e.printStackTrace();
        }
    }
}

相關資源

PDF Reference and Adobe Extensions to the PDF Specification - PDF規格書

Glyph & Cog: Text Extraction - 解釋為何擷取PDF中的純文字不是那麼容易

2009-01-03 18:17:22 | Comments (3)

Previous Posts
Copyright (C) Ching-Shen Chen. All rights reserved.

::: 搜尋 :::

::: 分類 :::

::: 最新文章 :::

::: 最新回應 :::

::: 訂閱 :::

Atom feed
Atom Comment