這個不錯用.
不用買MAC 就可以用到它的選單了
2008年5月15日 星期四
使用beanutil
有一次寫一個小小的系統.
就有遇到要一直從DB撈資料塞到java bean 裡.
就要寫一大堆setter ,
分享一個小小程式,可以自動對應欄位和bean properties,
不過就在table 的欄位名稱和bean 的attribute 要取一樣的名字
例如:
tableA.ID
talbleA.CHINESE_NAME
talbleA.EMAIL
bean.id
bean.chineseName
bean.email
protected Object setBeanProperty(Object bean, ResultSet rs) {
String[] columns = getColumnName(rs);
try {
for (int i = 0; i < columns.length; i++) {
String str = determineClassType(rs.getObject(i + 1));
BeanUtils.setProperty(bean, columns[i], str);
}
} catch (Exception e) {
throw new DAOException(e, "Bean property conversion failed!");
}
return bean;
}
/**
* get all column name.
*
* @param ResultSet
* @return String []
* @author kenny
*/
protected String[] getColumnName(ResultSet rs) {
int columnCount = 0;
ResultSetMetaData metaData = null;
String columns[] = null;
try {
metaData = rs.getMetaData();
columnCount = metaData.getColumnCount();
columns = new String[columnCount];
for (int i = 0; i < columnCount; i++) {
StringBuffer sb = new StringBuffer(metaData
.getColumnName(i + 1).toLowerCase());
int index = index = sb.indexOf("_");
;
while (index != -1) {
String tmp = sb.substring(index + 1, index + 2)
.toUpperCase();
sb.replace(index + 1, index + 2, tmp);
sb.deleteCharAt(index);
index = sb.indexOf("_");
}
columns[i] = sb.toString();
}
} catch (Exception e) {
logger.error("error at getColumnName:" + e.getMessage(), e);
}
return columns;
}
/**
*
* @param Object
*
* @author kenny
*/
private String determineClassType(Object o) {
String rtn = null;
if (o instanceof String) {
rtn = Util.dataBlank(o);
} else if (o instanceof java.sql.Timestamp ) {
long l = ((java.sql.Timestamp) o).getTime();
Date d = new Date(l);
rtn = Util.commonDateFormat(d, "yyyy/MM/dd HH:mm");
} else if (o instanceof java.util.Date) {
rtn = Util.commonDateFormat((java.util.Date) o, "yyyy/MM/dd");
}else if (o instanceof java.lang.Number) {
rtn = o.toString();
}else if(o == null){
rtn="";
} else {
rtn = o.toString();
}
return rtn;
}
2008年5月13日 星期二
使用java.util.TimeZone 取得各時區的時間
所謂的UTC時間就是指表示自從標準基準時間,即1970年1月1日00:00:00 GMT)以來的指定毫秒數,所以這個時間所有計算都是一樣的。
利用 java.util.TimeZone 可以幫助我們取得各時區的時間,
一個sample code:
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm");
TimeZone timeZoneParis = TimeZone.getTimeZone("Asia/Taipei");
Locale localeEN = Locale.US;
Date curDate = new Date();
dateFormat.setTimeZone(timeZoneParis);
System.out.println(dateFormat.format(curDate));
要取得各時區的iD 可以用以下的method
timezone.getAvailableIDs();
Sybase SQL 語法--left outer join
想像結合兩個表格.
一個左一個右.以左邊對右邊的資料,不管左邊對到右邊的資料有對到或者沒對到.都不能排除掉左邊的資料
SELECT Table1.col1, Table2.col1, Table3.col1
FROM {oj {oj Table1 LEFT OUTER JOIN Table2 ON Table1.col1 = Table2.col1}
LEFT OUTER JOIN Table3 ON Table2.col1 = Table3.col1}
2008年5月7日 星期三
Tomcat 限制特定主機存取
有時,你可能只想讓來自特定的主機名稱或 IP 位址的使用者,能存取 Tomcat 的 web 應用程式。這樣一來,就只有位於指定站點的客戶端能夠取得服務的內容了。Tomcat 內有兩個 Valve 可做此用途的設定:RemoteHostValve 和 RemoteAddrValve。
這些 Valve 可以讓你用主機名稱或 IP 位址來過濾請求,並允許或拒絕比對成功的主機。其功用類似於 Apache httpd 中,以目錄為基準的 Allow/Deny 指令。如果執行 Admin 應用程式,你可能想只允許從 localhost 來存取它,如下所示:
<Context path="/path/to/secret_files" ...>
<Valve className="org.apache.catalina.valves.RemoteAddrValve"
allow="127.0.0.1" deny=""/>
</Context>
如果未給定 allow 樣式,則會拒絕符合 deny 屬性的樣式,但允許所有其它的樣式。類似地,如果未給定 deny 樣式,則會允許符合 allow 屬性的樣式,但拒絕所有其它的樣式。
2008年5月6日 星期二
Unit Test Hibernate With Spring
摘要:探討 hibernate unit test 的四種可能作法,分析其利弊,結論是建議使用第三種作法 -- 實際的 db 搭配 transaction rollback。後半則分享 Hibernate 常見的三種 case: save、query及 lazyInitializationException 的測試方法和 建立 fixture 的技巧。
今天討論的是硬題目,Hibernate 的 Unit Test。UI 的 Unit Test 難度是最高的相信這個大家都同意。但 persistent 這個環結的測試也算是數一數二難的了,原因在於準備 fixture (註一)非常的困難,而測試結束後還要刪除所有 fixture,更是會讓人抓狂。所以有一些hibernate 測試的方法都是為了解決 fixture 的問題而想出來的。目前看到的四種作法是:
- 不接觸 db,直接測 hibernate 的語法和設定
- 測試時使用 in-memory db,快速且不用特別去刪用完的 fixture
- 使用實際的 db。在測試其間開啟單一 transaction,測試完成後藉 transaction rollback 清除 fixture。
- 使用實際的 db。fixture 的建立與刪除全部靠手寫的 SQL 來處理。
上面四種方法由上而下,integration 的程度越高,測試相對來講也越完整,但也代表測試越來越難寫。第一種不接觸 db 的做法是直接針對 hbm.xml 這個 xml 做 Unit Test,或是針對 HQL 的語法寫測試。這種做法不需準備 fixture,所以很快也很好寫,但缺點是沒什麼用!。hbm.xml、HQL 設定和語法寫對了,跟實際下法 query/insert... 得到結果根本是兩回事,尤其是 hibernate 有些地方是環環相扣的 (cascade/inverse=true... 之類),單一個 hbm.xml 寫對了不代表任何意義。
第二種是依賴 HSQLDB 之類的 in-memory db。即然是 in-memory,測試一跑完資料就可丟了,所以難的地方只剩另一半 -- 建立 fixture。聽起來是個不錯的作法,但有一個致命的缺點 -- 它不是針對 production db 作測試的;這種作法最常遇到的問題是 unit test 都跑的好好的,上線後出現一些難以捉摸的問題,追到後來才知道原來某某 db 在某某狀況下會怎麼怎麼樣。原本 db 間的相容性就不高了,在加上一層 abstract 的 jdbc driver 更是讓問題雪上加霜。很多不可預期的錯誤都是某版的 db + 某版的 jdbc driver 上才會出現 (對,我就是在說 oracle)。我們花了大把的時間建立 fixture、寫 persistent 的 unit test,最後還是沒事先幫我們捉到問題,這花的功夫可真是浪費了。
第三、四種都是使用實際的 db,所以可克服第二種作法的缺點。第三種作法藉 transaction rollback 的功能自動去除 fixture。換言之,跟第二種方法一樣,只需煩惱 fixture 的建立即可,唯一的缺點是無法測試跨 transaction 的功能。不過這是小問題,因為絕大多數的 DAO 實作都不控管 transaction,而是將它交由上一層的 service facade 來處理, DAO 本身通常在同一 transaction 裡操作。如果真有那種需求,可搭配第四種方法來測試。第四種是全手動的,所以可以支援所有狀況的測試,但也最費時費力,非到必要時不用。
好了,終於要回到正題了,我們來看看第三種的 hibernate unit test 該怎麼寫。首先你必需準備一個測試用的 db,而且最好能與實際上線的 db 版本一致,這個測試的 db 可以整組人共用,也可以每個人自己架一個自用。很多書上都建議最好每個人自備 test db,但我們 team 一直都是共用同一個,開發起來到也沒什麼問題,而且共用 db等於是一直處在整合的狀態,可以及早發現問題。
以下探討的架構是常見 Dao 配 hibernateTemplate 這樣的做法,spring 的細部設定就略過不提了,書上都有寫。測試的 TestCase 採用 spring-mock.jar 裡的 AbstractTransactionalSpringContextTests,下面是一個 UserDao 的 test case:
# public class UserDaoTest extends
# AbstractTransactionalSpringContextTests {
#
# //這個測試使用的 spring 設定檔,如果沒有特別的需求,直接拿 production 的來用即可。
# @Override
# protected String[] getConfigLocations() {
# return new String[] { "classpath:/applicationContext*.xml" };
# }
#
# // inject hibernateTemplate,方便做一些細部控制
# private HibernateTemplate hibernateTemplate;
#
# // class under test
# private UserDao dao ;
#
# // this will be auto-injected.
# public void setHibernateTemplate(HibernateTemplate hibernateTemplate) {
# this.hibernateTemplate = hibernateTemplate;
# }
#
# // this will be auto-injected.
# public void setUserDao(UserDao userDao) {
# this.dao = userDao;
# }
#
# //取代原來的 setUp(),這個 method 在 transaction 內。
# @Override
# protected void onSetUpInTransaction() throws Exception {
# }
#
# //取代原來的 tearDown(),這個 method 在 transaction 內。
# @Override
# protected void onTearDownInTransaction() throws Exception {
# }
#
# //真正的測試 method
# public void testSaveUser() throws Exception {
# //test here...
# }
# }
AbstractTransactionalSpringContextTests 繼承自 AbstractDependencyInjectionSpringContextTests,所以只要 test case 裡寫 setXXX(),它會自動從 spring container 裡 inject 同 type 的 bean。上面的例子我們有兩個 setter,一是 setHibernateTemplate(...),二是 setUserDao(...),它們都會自動 inject 供測試使用。getConfigLocations() 則是 config 檔的位置,起動 spring container 時會用到。
這個 test case 特別的地方在於,每次執行 testXXX() 時,spring 會自動開啟一個 transaction,直到 method 結束,到了 tearDown() 才會 rollback()。如果你需要寫 setUp()/tearDown(),可以用 onSetUpInTransaction() 和 onTearDownInTransaction() 替代,它們執行的時候也會在同一個 transaction 內。好,開始寫 testSaveUser():
public class UserDaoTest extends AbstractTransactionalSpringContextTests {
public void testSaveUser() {
User user = new User("aName");
dao.save(user);
assertNotNull(user.getId());
User found = hibernateTemplate.get(User.class, user.getId());
assertEquals(user.getId(), found.getId());
assertEquals(user.getName(), found.getName());
}
}
public class UserDao {
public void save(User user) {
hibernateTemplate.saveOrUpdate(user);
}
}
一開始先用 dao save 一個 user,然後 assert 它有取得的 database id,接下來利用 hibernateTemplate.get() 確認資料庫是否真的已儲存了剛才的那一筆。如果你的 hibernate 設定和資料庫都正確的話,你應該會拿到一個綠燈。測試完後,儲存的 user 資料會被 rollback,所以可以一直反覆執行這個測試。
初步的測試大概就是這樣寫... 還蠻好寫的,就寫些 java 程式就好了,也不用去管啥殺資料的索事了。但是很不幸地,上面的測試是無效的。首先我們來看看 dao.save(user) 做了什麼? 如果是一般使用 sequence 產生 id 的資料庫,你會看到像是這樣的 query:
select next value for seq_user from dual_seq_user
然後就沒了,沒有 insert 的 statement 啊!那接下的 hibernateTemplate.get(User.class, user.getId()) 呢?哇咧,仔細一看居然也沒有下任何的 sql。搞了半天上面的測試只執行了一行 sequence 相關的 sql,其他什麼都沒執行到!難怪會說這個測試是無效的。Why? Why? Why?
原因是 hibernate 的 first level cache 和 delay flush 兩個功能所引起的。Hibernate 執行 save()/saveOrUpdate() 時,當下只會去資料庫要個 id,還不會真的 insert (註二)。但經過這一個操作後, hibernate 便視 user 為 persistent,把該 instance 放在 1st level cache 裡。直到 flush() 才會真正的去執行 insert。一般來說 transaciton commit 時,就是 flush() 的時間點。可惜的是 AbstractTransactionalSpringContextTests 永遠不會做 commit 啊,它只會 rollback。因此遇到這個情況,我們得手動 flush():
public class UserDaoTest extends AbstractTransactionalSpringContextTests {
public void testSaveUser() {
User user = new User("aName");
dao.save(user);
//手動 flush,就會有 insert 了
hibernateTemplate.flush();
assertNotNull(user.getId());
//.. 其他略...
}
}
好,解決一半的問題,但還是沒看到 hibernateTemplate.get(User.class, user.getId()) 所執行的 sql 啊?原因是 hibernate 的 get() 會先去 1st level cache 找找看,如果找到同個 class,同個 id 的,它就直接回傳 cache 裡的物件給你,而不會下額外的 query。解決的辦法是手動清除 1st level cache:
public class UserDaoTest extends AbstractTransactionalSpringContextTests {
public void testSaveUser() {
User user = new User("aName");
dao.save(user);
//手動 flush,就會有 insert 了
hibernateTemplate.flush();
assertNotNull(user.getId());
//清除 first level cache:
hibernateTemplate.clear();
//這一行就真的會下 query 到 db,實際去撈撈看有沒有該筆資料。
User found = hibernateTemplate.get(User.class, user.getId());
assertEquals(user.getId(), found.getId());
assertEquals(user.getName(), found.getName());
}
}
呼~ 總算讓這個測試真的做到事了。雖然多了兩個討厭的步驟,但怎麼樣也比手動 tearDown 殺資料好多了。這點犧牲是小意思。由於 flush() 常常會配 clear(),那乾脆包成個 flushThenClearCache(),可以方便一點。
在寫 hibernate unit test 時,何時 flush, 何時 clear 是很重要的觀念和技巧,請小心使用。
上面第一個範例是小 case,接下來我們看一個 query 的例子:
public class UserDaoTest extends AbstractTransactionalSpringContextTests {
//共用的 fixture
List<User> users ;
@Override
protected void onSetUpInTransaction() throws Exception {
//手動 inject 到 UserOM, 如果嫌麻煩可以弄個 AbstractDaoTest 統一設定
UserOM.hibernateTemplate = hibernateTemplate;
//使用特別的 helper class 產生 fixture
users = UserOM.savedUsers("foo", "userX", "foo", "bar" );
}
public void testFindUsersByName_simple() {
//不要忘記清除 cache
flushThenClearCache();
//一般來講 query 的測試要從 0 筆開始測試。
List<User> notFound = dao.findUsersByName("not exist name");
assertEquals(0, notFound.size()):
//測試一個符合
List<User> barUsers = dao.findUsersByName("bar");
assertEquals(1, barUsers.size()):
//確認 id 就足夠了
assertEquals(users.get(0).getId(), barUsers.get(0).getId());
}
public void testFindUsersByName_complex() {
//不要忘記清除 cache
flushThenClearCache();
//測試要涵蓋到多個符合。
List<User> fooUsers = dao.findUsersByName("foo");
assertEquals(2, fooUsers.size()):
assertEquals(users.get(0).getId(), fooUsers.get(0).getId());
assertEquals(users.get(2).getId(), fooUsers.get(1).getId());
}
protected void flushThenClearCache() {
hibernateTemplate.flush();
hibernateTemplate.clear();
}
}
//專門產生假資料的 helper class (OM 意指 ObjectMother)
public class UserOM {
//manually inject in TestCase:
public static HibernateTemplate hibernateTemplate;
public static List<User> savedUsers(String...names) {
List<User> users = new ArrayList<User>();
for(String name : names) {
User user = new User(name);
hibernateTemplate.saveOrUpdate(user);
users.add(user);
}
return users;
}
//other methods
public static User savedUserWithGroup() {//略......}
}
//真正的 production 程式:
public class UserDao {
public List<User> findUsersByName(String name) {
String hql = " from User where name = ? " ;
return (List<User>) hibernateTemplate.find(hql, name);
}
}
這一段程式是查相同名字的使用者。真正的主程式才兩行,測試的程式卻快三十行,真是嘔死人。可是 db 就是這麼難測啊... 上面的 test case 我們寫了 simple 和 complex 兩個,simple 涵蓋 0~1 個的測試,complex 則是多個的。你想要再包含更多的 case 那也由你,不過個人通常就寫到這樣就停了,因為這樣的測試已經可以讓我對 produciton 程式有足夠的自信。而 UserOM 是很久之前提過的 ObjectMother pattern,我們會在一開始測試時給它 hibernateTemplate,讓它可以在測試程式中自由的 insert 假資料。這裡的 UserOM 仍然使用 hibernate 來做假資料,而不是用純 sql 或是 dbunit xml 的做法。個人是比較傾向這樣寫,因為用 hibernate 就只要操作 POJO 的屬性就好了,若有重覆的地方也可以做 refactoring (就 java 嘛) 久而久之 UserOM 裡 method 越來越多,重用性也越來越高,建立 fixture 就不會像一開始這麼累了。
另一個地方我們可以使力的地方是:
assertEquals(2, fooUsers.size()):
assertEquals(users.get(0).getId(), fooUsers.get(0).getId());
assertEquals(users.get(2).getId(), fooUsers.get(1).getId());
看上面的 test case,這樣的程式碼一直重覆,我們該 extract 出來,變成一個 util:
public class UserDaoTest extends AbstractTransactionalSpringContextTests {
public void testFindUsersByName_complex() {
flushThenClearCache();
List<User> fooUsers = dao.findUsersByName("foo");
TestUtils.assertIdEquals(
new User[]{users.get(0), users.get(2)}, fooUsers );
}
}
public class TestUtils {
public static assertIdEquals(Object[] expects, Collection<?> actuals) {
// assert size, and all id equals
}
}
extract 成 TestUtils 之後清爽多了。至於 assertIdEquals() 該怎麼寫就留給各位了。
好,我們要進入第三個課題 - Lazy Initialization Exception,這個是讓全世界 Hibernate developer 恨得牙癢癢的Exception,當然要測!下面是一個 findUserById 的 程式片段:
public class UserDaoTest extends
AbstractTransactionalSpringContextTests {
//一開始只測一點點
public void testFindUserById() {
User user = UserOM.savedUser();
flushThenClearCache();
User found = dao.findUser(user.getId());
assertEquals(user.getId(), found.getId());
}
}
//production 程式:
public class UserDao {
public User findUser(Long id ) {
String hql = " from User where id = ? " ;
return (User) hibernateTemplate.find(hql, id).get(0);
}
}
現在假設 user 底下有多個 groups,是一對多、並且設 lazy=true。如果我們的網頁端的程式呼叫這個 findUser(id) 抽出的 User,而且還讓網頁顯示 user 所屬的 groups 時,就出現 LazyInitializationException 了,因為目前的 findUser(id) 並沒有把 groups 一起 query 出來。解決的方法之一是 HQL 加上 join fetch group。當然,本著 Test First 的精神,再修改 production 程式之前,我們要先把原來的測試改成紅燈:
public class UserDaoTest extends AbstractTransactionalSpringContextTests {
public void testFindUserById() {
User user = UserOM.savedUserWith3Groups();
flushThenClearCache();
User found = dao.findUser(user.getId());
assertEquals(user.getId(), found.getId());
//這樣應該會 throw LazyInitializationException 讓測試變紅燈。
List<Group> groups = found.getGroups() ;
groups.get(0).getGroupName(); // 沒有LazyInitializationException!
}
}
重跑一次測試,啊咧... 沒有紅燈啊?!還是綠燈耶... 又是個無效的測試了... 不是該丟 exception 嗎?仔細看一下產生的 sql,多了一行抽 group 的 sql :
- select group0_.id ...中略... from Group group0_ where group0_.user_id= ? select group0_.id ...中略... from Group group0_ where group0_.user_id= ?
啊!原來是 session 尚未關閉,所以 groups.get(0).getGroupName() 這一行讓 hibernate session 再下了一個 sql。但實際網頁上的情況是 session 早就關了啊!要模擬 session 關閉的狀況,我們得用 endTransaction():
public class UserDaoTest extends AbstractTransactionalSpringContextTests {
public void testFindUserById() {
User user = UserOM.savedUserWith3Groups();
flushThenClearCache();
User found = dao.findUser(user.getId());
//加了這一行 transaction 就直接 rollback,session 也跟著一起關了
endTransaction();
List<Group> groups = found.getGroups() ;
groups.get(0).getGroupName(); // 這次真的會 LazyInitializationException 了!
}
}
endTransaction() 是 AbstractTransactionalSpringContextTests 提供的 method,可以讓我們提前終止目前的 transaction,也代表關閉 hibernate session。這一次上面的測試就是紅燈了,耶!現在開始改 production code,加上 join fetch,再跑一次測試就是綠燈了:
//Test
public class UserDaoTest extends
AbstractTransactionalSpringContextTests {
User user ;
@Override
protected void onSetUpInTransaction() throws Exception {
UserOM.hibernateTemplate = hibernateTemplate;
user = UserOM.savedUserWith3Groups();
}
public void testFindUserById() {
flushThenClearCache();
User found = dao.findUser(user.getId());
TestUtils.assertIdEquals(user, found);
}
public void testFindUserById_WithGroups() {
flushThenClearCache();
User testGroupLazy = dao.findUser(user.getId());
endTransaction();
assertNotNull("group should be fetched",
testGroupLazy.getGroups().get(0).getGroupName());
}
}
//production 程式:
public class UserDao {
public User findUser(Long id ) {
//加上 left join fetch user.groups
String hql = " from User user left join fetch user.groups where id = ? " ;
return (User) hibernateTemplate.find(hql, id).get(0);
}
}
總結:有了 Spring 提供的 AbstractTransactionalSpringContextTests ,讓我們寫第三種測試方法更為容易。由於 hibernate session 的特性使然,很容易寫出無效的空炮彈測試。藉由 flush/clear/endTransaction 等等的處理技巧,可一一有效解決。
註一:fixture 即測試用的假資料。
註二:依據你的資料庫的不同,可能會有不同的結果,如果資料庫產生的 id 不是用 sequence,而是用 increment column (如 MySQL),那 session.save() 當下就會 insert 一筆資料到 db,以取得 id。
本文中常提及的 "production" 字眼意指正式上線使用的程式碼。
2008年5月5日 星期一
監視效能--所有的系統狀態
網路資源真是很多.這篇介紹UNIX的效能狀況的說明.寫得很詳細.但來源我忘了.供大家參考.
我們可以使用uptime來得到整個系統的效能狀態。Uptime 指令得到這個系統已
經開機多久了,有多少使用者登錄,系統在最近 1 分鐘、5 分鐘和 15 分鐘的平
均負載。系統的平均負載顯示有多少工作在執行序列等待,行程準備執行但還未
執行的有多少。下面狀態顯示最近 1 分鐘內系統的平均負載是 0.03,在最近 5
分鐘內系統的平均負載是 0.05,在最近的 15 分鐘內系統的平均負載是 0。我們
也可以使用 cron 定時工作,每隔十五分鐘就記載其平均負載,這樣累計一天,
我們就可以得到最進一天的平均負載。
我們在 8 分鐘後使用 uptime 來觀看系統狀態,其平最近 1 分鐘和最近 5 分鐘的
平均負載變成0。因為 uptime是快照這15分鐘以內的情況,所以只能看到最近
系統的效能。
系統活動報告(system activity reporter)我們簡稱為sar,我們可以使用 sar指令。
這個指令指示sar來計數count 次的範本其間隔為 secs秒。
語法﹕
sar secs count
sar -u 1 10這個指令指示sar每隔1秒總共10次來顯示CPU的使用率。
-u的選項告訴sar 來報告CPU的使用率。%user指示CPU花費執行使用者模式
的百分比(使用在應用程式),%nice 花費在非零 nice 優先權的使用者模式,
%system指示花費在核心(系統呼叫),%idle顯示CPU暫停的百分比。一般規則,
假如%system很高,應用程式可能使用系統呼叫。假如%idle 很高而 uptime的
電腦名詞介紹:Grayware(灰色軟體)
Grayware之所以稱為「灰」色軟體,是因為grayware是介於「好軟體」與「壞病毒」之間的灰 色地帶,包括合法的共享軟體、惡意的間諜軟體,以及所有其它的惡意程式或擾人軟體。大部份的grayware都會在未經使用者許可的情況下自行安裝、追 蹤,或是記錄使用者的相關訊息並對外傳送(當然一樣不會告訴使用者)。也有部份的grayware 是偽裝成有用的軟體,欺騙使用者動手安裝的木馬程式。
Grayware的感染來源很多,不論是使用者下載的共享軟體或免費軟體、其他形式的交換檔案、開啟中毒的電子郵件、不小心點選彈出式廣告、進入偽造網站,或是被植入木馬程式等等,都有可能受到感染。
一般來說,grayware 包含以下種類的軟體:
u 廣告軟體 (Adware)
Adware 通常都是夾帶在使用者下載的免費軟體裡,在這個免費軟體執行的時間當中,會顯示彈出式廣告。
u 惡意撥號程式(Dialers)
用來控制使用者的數據機,會被駭客利用來撥打長途電話或是付費電話謀利。
u 惡意遊戲(Gaming)
一些玩笑或是令人煩厭的小遊戲。玩笑軟體(Joke) 通常會更動一些不影響系統的應用程式設定。
u 點對點檔案傳輸軟體(Peer-to-peer, P2P)
進行檔案交換的軟體。雖然 P2P 本身是合法商業用途的通訊協定,但灰色軟體應用程式卻將它做為非法音樂、電影或其他檔案的交換管道。
u 間諜軟體(Spyware)
通常夾帶於免費軟體中。間諜軟體會追蹤並分析使用者的活動,像是瀏覽習慣等。這些訊息都會送回到駭客指定的網站進行記錄與分析。
u 鍵盤側錄軟體(Key Logger)
被安裝在使用者電腦中,會記錄使用者鍵盤上所有的按鍵動作,藉此得知使用者的密碼、信用卡號碼、電子郵件、線上交談、即時訊息及其他相關訊息等。
u 綁架軟體(Hijacker)
會操縱瀏覽器或其他系統設定的程式,它能更改使用者「我的最愛」或書籤所記錄的網站位址、首頁設定,或是功能列選單上的選項等。部份綁架軟體甚至可以更改DNS 設定,不知不覺地將使用者打算瀏覽的網址「繞」到指定的惡意網站去。
u 遠端遙控管理軟體(Remote Administration Tools) 允許外部的使用者存取、更動,或是監控網路上的電腦。
u 工具列(Toolbar)
安裝之後會更改現有的工具列功能,這些程式多半用來監控使用者的瀏覽習慣、將資訊回送至駭客處,或是更改主機的功能。
u 下載(Download)
會在使用者未發覺的情形下,自動允許其他程式進行下載或安裝。通常在電腦的開機階段就會自行啟動,用以安裝廣告、撥號程式或是其他的惡意程式碼。
各種Grayware的感染癥狀
被 grayware 感染後,電腦通常會出現下列癥狀:
•電腦愈來愈慢:grayware 會吃掉許多CPU 與記憶體資源。
•在使用者並未執行相關的網路程序時,從視窗右下角系統狀態列裡的網路連線圖示不斷閃爍。
•彈出式廣告過多:就算是未連結上網際網路,亦未啟動瀏覽器的時候,也會顯示彈出式訊息或廣告。
•瀏覽器首頁被竄改:使用者原設的首頁莫名奇妙地被改掉,就算去重新設定也不一定有用。
•Internet Explorer 的預設搜尋引擎被改:使用者無法使用原來預設的搜尋引擎,並且是由非期待中的搜尋網站提供搜尋結果。
•電話費用爆增:無緣無故地增加了使用者未撥打的付費電話費用。
•防毒程式或是防間諜軟體程式停止運作。老奸巨滑的grayware 會在安裝自己之前,先行關掉資訊安全相關程式。
Grayware的預防之道
既然有這麼多種的可能性,grayware 看來真是令人防不勝防,,在此提供各位一些防範的注意事項,讓大家都能做好預防的功夫。
•養成良好上網習慣,不從網路上下載及安裝軟體。
•養成良好上網習慣,對於網頁中彈跳出的視窗,不予理會,不要點選。
•養成良好上網習慣,登入網站請注意網址,常用網站設成我的最愛,勿相信搜尋網頁的連結。
•安裝防毒軟體,注意病毒碼是否正常更新。
超沒誠意的PCHOME現金積點
PChome 現金積點生效通知,詳細資訊如下,歡迎您即刻使用享受折扣!
訂單編號:20080415008930
現金積點:1 點 ( 1 點相當於 1 元折扣 )
生效日期:2008/05/06
有效期間:2008/05/06~2008/06/06
買了半天的東西.才送你一塊錢積點..還要限你一個月內用完..
限制還真多.
電腦中毒後無法上網的解決方法.
電腦中毒後,雖然把病毒清掉了,但還是無法上網,
狀況描述:
用ping的指令來ping,結果有通,但ping出來的結果有點奇怪,出現了?,如下面的情形:
ping tw.yahoo.com
ping ? with 32 bytes of data:
Reply from 202.43.195.52: bytes=32 time=7ms TTL=55
Reply from 202.43.195.52: bytes=32 time=7ms TTL=55
Reply from 202.43.195.52: bytes=32 time=7ms TTL=55
原因:
因為有些惡意程式或間諜軟體安裝一些附加元件至系統的Winsock2機碼中,以利於監視系統的網路訊息。
當你(防毒軟體或反間碟軟體)不小心移除它時,會造成網路中斷或網路不穩定。
解決方法:
在開始功能表,選擇「執行」,打「cmd」,
輸入:netsh winsock reset
如果出現:
順利重設Winsock Catalog。
您必須重新啟動電腦來設成重設。
重新開機就可以了。
ps.以上方法僅適用於windows xp sp2
windows xp sp2以外解決方法
下載 <http://www.pchell.com/downloads/WinsockXPFix.exe> 後直接執行Fix按鈕即可
oracle sql trace
on line trace:
set autotrace on;
select * from app;
set autotrace off;
sql trace:
[sqlplus]
alter session set sql_trace = true;
select * from app;
alter session set sql_trace = false;
[command]
cd c:\oracle\admin\stanley\udump\
tkprof stanley_ora_3444.trc stanley_ora_3444.out aggregate=yes sys=no sort=prscpu
notepad stanley_ora_3444.out
timing:
set timing on;
set timing off;
登入了 vsFTP 但無法上傳檔案
之前也碰過此問題.那時候搞了好久..
後來在網路上看到有人也同樣遇到..在無意中看到一篇說怎麼解決此問題..所以就順便貼下來囉..
基本上只要跟著鳥哥的說明去設定應該就沒問題..
但如果遇到登入後不能上傳試試以下方法:
vi /etc/sysconfig/selinux,將"SELINUX=enforcing"修改成"SELINUX=disabled"。改好後就能正常了
如何自動登入 Windows 2000,XP,2003
若要自動登入 Windows 2000/XP/2003,而無需每次都需要動手輸入帳號與密碼的話,可以修改機碼,其步驟如下:
- 由 [開始]-> [執行],輸入 regedit,按下 [確定]。
- 找到下列機碼:
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\Current Version\Winlogon - 在右邊窗格空白處,按滑鼠右鍵,選擇 [新增] -> [字串值]
名稱為:AutoAdminLogon
數值資料為:1 - 在右邊窗格空白處,按滑鼠右鍵,選擇 [新增] -> [字串值]
名稱為:DefaultUserName
數值資料為:請填入您要自動登入的使用者名稱 - 在右邊窗格空白處,按滑鼠右鍵,選擇 [新增] -> [字串值]
名稱為:DefaultPassword
數值資料為:請填入使用者的密碼 - 重新開機,讓設定生效
注意:
- 不建議在公司正式上線的電腦上使用自動登入的功能!
- 自動登入的使用者,並不一定是 Administrator 。
- 若要使用其他使用者登入的話,請按住 Shift 鍵不放,直到出現登入畫面之後才放掉。
2008年5月2日 星期五
使用ArrayList,LinkedList 及使用0 長度的Array
在SUN的網站中有一篇介紹如何使用使用ArrayList/LinkedList 及使用0 長度的Array的文章,
覺得這兩個物件平時佷常用,但是比較少去注意細節的部份.以下把自己看完的心得寫下來.
原文出處: <http://java.sun.com/javase/reference/techart.jsp>
ArrayList and LinkedList 是兩個collection物件.
其實ArrayList背後是用Object array,也因為這原因,它在隨機存取的效能上遠比LinkedList來得好.
然而,同樣使用get()來取 ArrayList 及 LinkedList的物件,在 LinkedList 方面必須由開始到結束的
節點走訪一遍,而使用 ArrayList則可隨意的指定某一個節點並取得其內容.
在存取方面, ArrayList 的確優於LinkedList,但在新增及刪除方面則相反..
以下有個binarySearch 的範例:
import java.util.*;
public class ListDemo1 {
static final int N = 10000;
static List values;
// make List of increasing Integer values
static {
Integer vals[] = new Integer[N];
Random rn = new Random();
for (int i = 0, currval = 0; i < N; i++) {
vals[i] = new Integer(currval);
currval += rn.nextInt(100) + 1;
}
values = Arrays.asList(vals);
}
// iterate across a list and look up every
// value in the list using binary search
static long timeList(List lst) {
long start = System.currentTimeMillis();
for (int i = 0; i < N; i++) {
// look up a value in the list
// using binary search
int indx = Collections.binarySearch(
lst, values.get(i));
// sanity check for result
// of binary search
if (indx != i) {
System.out.println(
"*** error ***\n");
}
}
return System.currentTimeMillis() - start;
}
public static void main(String args[]) {
// do lookups in an ArrayList
System.out.println("time for ArrayList = " +
timeList(new ArrayList(values)));
// do lookups in a LinkedList
System.out.println(
"time for LinkedList = " +
timeList(new LinkedList(values)));
}
}
執行結果:
time for ArrayList = 31
time for LinkedList = 4640
兩者間的效率差了相當多.
常用FTP 指令
ls 顯示 ascii 模式的檔案 ls
dir 顯示所有目錄清單 dir
cd 切換遠端伺服器目錄 cd download
lcd 切換本機目錄 lcd c:\
get 下載單一檔案 get abc.exe
mget 下載複數檔案 mget *.exe
put 上傳單一檔案 put abc.exe
mput 上傳複數檔案 mput *.exe
prompt 開╱關詢問模式 prompt
pwd 顯示目前所在目錄位置 pwd
bye 離開 FTP Server bye
? 顯示指令清單 ?
binary 切換到二進制下載模式 binary
ascii 切換到文字下載模式 ascii
open 連線到 FTP Server open cle.linux.org.tw
help 顯示\指令說明 help ls
2008年5月1日 星期四
Java Metadata類別
ResultSetMetadata儲存於ResultSet物件中
•查詢資料庫的資訊須使用ResultSetMetadata, ResultSet兩個
類別,配合getMetaData()方法以取得ResultSetMetadata的物
件索引
•可查詢的資料庫資訊包括
•多少欄位
•欄位名稱是否有大小寫的區分
•資料的型別
•那一記錄是屬於那一資料表
概述 BeanUtils
轉貼自城市胡同 一、概述 第一次看到BeanUtils包,是在Struts项目中,作为Struts一个工具来使用的, 估计功能越弄越强,就移到Common项目中了吧。 BeanUtils一共有四个package: org.apache.commons.beanutils org.apache.commons.beanutils.converters org.apache.commons.beanutils.locale org.apache.commons.beanutils.locale.converters 后三个包主要是用于数据的转换,围绕着一个Converter接口,该接口只有一个方法: java.lang.Object convert(java.lang.Class type, java.lang.Object value) , 用于将一个value转换成另一个类型为type的Object。在一些自动化的应用中应该会有用。 这里不作评论,以后有兴趣了,或者觉得有用了,再行研究。 这里只讲第一个包。 二、测试用的Bean 在开始所有的测试之前,我写了一个简单的Bean,以便于测试,代码如下: package test.jakarta.commons.beanutils; /** * @author SonyMusic * */ public class Month { private int value; private String name; private int[] days={11,22,33,44,55}; public Month(int v, String n){ value=v; name=n; } /** * Returns the name. * @return String */ public String getName() { return name; } /** * Returns the value. * @return int */ public int getValue() { return value; } /** * Sets the name. * @param name The name to set */ public void setName(String name) { this.name = name; } /** * Sets the value. * @param value The value to set */ public void setValue(int value) { this.value = value; } /** * @see java.lang.Object#toString() */ public String toString() { return value+"("+name+")"; } public int[] getDays() { return days; } public void setDays(int[] is) { days = is; } } 三、BeanUtils 这是一个主要应用于Bean的Util(呵呵,这个解释很绝吧),以下是其中几个方法的例子 //static java.util.Map describe(java.lang.Object bean) //这个方法返回一个Object中所有的可读属性,并将属性名/属性值放入一个Map中,另外还有 //一个名为class的属性,属性值是Object的类名,事实上class是java.lang.Object的一个属性 Month month=new Month(1, "Jan"); try { Map map=BeanUtils.describe(month); Set keySet=map.keySet(); for (Iterator iter = keySet.iterator(); iter.hasNext();) { Object element = (Object) iter.next(); System.out.println("KeyClass:"+element.getClass().getName()); System.out.println("ValueClass:"+map.get(element).getClass().getName()); System.out.print(element+"\t"); System.out.print(map.get(element)); System.out.println(); } } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } catch (NoSuchMethodException e) { e.printStackTrace(); } 输出为: KeyClass:java.lang.String ValueClass:java.lang.String value 1 KeyClass:java.lang.String ValueClass:java.lang.String class class test.jakarta.commons.beanutils.Month KeyClass:java.lang.String ValueClass:java.lang.String name Jan 注意到所有Map中的key/value都是String,而不管object中实际的值是多少。 与此对应的还有static void populate(java.lang.Object bean, java.util.Map properties) 用于将刚才describe出的Map再装配成一个对象。 再看这样一段代码 曹晓钢也许还记得,为了取一个不确定对象的property,着实花了不少时间, 难度不大,但要做到100%的正确,仍然需要付出很大的精力。 //static java.lang.String getProperty(java.lang.Object bean, java.lang.String name) Month month=new Month(1, "Jan"); try { System.out.println(BeanUtils.getProperty(month,"value")); } catch (Exception e) { e.printStackTrace(); } //输出是:1 与getProperty类似的还有getIndexedProperty, getMappedProperty, 以getIndexedProperty为例: Month month=new Month(1, "Jan"); try { System.out.println(BeanUtils.getIndexedProperty(month,"days",1)); System.out.println(BeanUtils.getIndexedProperty(month,"days[1]")); } catch (Exception e) { e.printStackTrace(); } 这两个调用是相同的。 BeanUtils中还有一个方法: static void copyProperties(java.lang.Object dest, java.lang.Object orig) 它真是太有用,还记得struts中满天飞的都是copyProperties,我甚至怀疑整个BeanUtils最初 是不是因为这个方法的需求才写出来的。 它将对象orig中的属性复制到dest中去。 四、PropertyUtils 这个类和BeanUtils类很多的方法在参数上都是相同的,但返回值不同。 BeanUtils着重于"Bean",返回值通常是String,而PropertyUtils着重于属性, 它的返回值通常是Object。 五、ConstructorUtils 这个类中的方法主要分成两种,一种是得到构造方法,一种是创建对象。 事实上多数时候得到构造方法的目的就是创建对象,这里只介绍一下创建对象。 //static java.lang.Object ConstructorUtils.invokeConstructor //(java.lang.Class klass, java.lang.Object[] args) //根据一个java.lang.Class以及相应的构造方法的参数,创建一个对象。 Object obj=ConstructorUtils.invokeConstructor(Month.class, {new Integer(1), "Jan"}); Month month=(Month)obj; try { System.out.println(BeanUtils.getProperty(month,"value")); } catch (Exception e) { e.printStackTrace(); } 输出证明,构造方法的调用是成功的。 如果需要强制指定构造方法的参数类型,可以这样调用: Object[] args={new Integer(1), "Jan"}; Class[] argsType={int.class, String.class}; Object obj; obj = ConstructorUtils.invokeExactConstructor(Month.class, args, argsType); Month month=(Month)obj; System.out.println(BeanUtils.getProperty(month,"value")); argsType指定了参数的类型。
JavaScript--禁止使用者按Backspace鍵的小技巧
如何在IE/Firefox中禁止使用者按下"Backspace"鍵回上一頁的問題,順便將結果分享給有需要的人。
作法:一般來說,在處理這樣的問題時,我們都會從"事件"(Event)這方面去著手。在瀏覽器(或一般視窗程式)中,移動/點擊滑鼠、按下鍵盤、網頁讀取完成等等動作都會造成事件被觸發,而要避免使用者進行這些行為,就要在事件被觸發的同時將其攔下,就會造成功能失效。
下面這段JavaScript可以攔截使用者按"Backspace"鍵:
<script language="JavaScript">
document.onkeydown = check;
function check(e) {
var code;
if (!e) var e = window.event;
if (e.keyCode) code = e.keyCode;
else if (e.which) code = e.which;
alert(code);
if (code == 8)
{
code = 0;
window.alert('test');
return false;
{color:#0000ff}}
return true;
{color:#0000ff}}
</script>
上面這段程式在IE中可以正常運作,但是搬到了Firefox後卻會發現頁面已經換了,警告視窗才跳出來。
這是因為在Firefox的事件處理程序與IE不同所導致的情況,換頁事件會比keydown還早發生,所以在keydown攔下事件之前,換頁事件已經先發生了,也就是說...keydown欄太慢,無效!!
解決的方法就是將keydown事件改用keypress事件,這樣在Firefox就可以正常運作了........
不過很不幸的,在IE裡面這套又行不通了,所以程式碼要加上判斷的功能,依照不同的瀏覽器做不同的事件設定:
if(window.navigator.userAgent.indexOf("MSIE")>=1)
{
document.onkeydown = check;
{color:#0000ff}}else if (window.navigator.userAgent.indexOf("Firefox")>=1){
document.onkeypress = check;
{color:#0000ff}}
function check(e){
var code;
if (!e) var e = window.event;
if (e.keyCode) code = e.keyCode;
else if (e.which) code = e.which;
if (code == 8)
{
code = 0;
window.alert('請勿返回程式');
return false;
{color:#0000ff}}
return true;
{color:#0000ff}}
剛才網友詢問如何在IE/Firefox中禁止使用者按下"Backspace"鍵回上一頁的問題,順便將結果分享給有需要的人。
作法:一般來說,在處理這樣的問題時,我們都會從"事件"(Event)這方面去著手。在瀏覽器(或一般視窗程式)中,移動/點擊滑鼠、按下鍵盤、網頁讀取完成等等動作都會造成事件被觸發,而要避免使用者進行這些行為,就要在事件被觸發的同時將其攔下,就會造成功能失效。
下面這段JavaScript可以攔截使用者按"Backspace"鍵:
(IE版)
程序代碼
[color=#0000ff]<script language="JavaScript">
document.onkeydown = check;
function check(e) {
var code;
if (!e) var e = window.event;
if (e.keyCode) code = e.keyCode;
else if (e.which) code = e.which;
alert(code);
if (code == 8)
{
code = 0;
window.alert('test');
return false;
{color:#0000ff}}
return true;
{color:#0000ff}}
</script>[/color]
上面這段程式在IE中可以正常運作,但是搬到了Firefox後卻會發現頁面已經換了,警告視窗才跳出來。
這是因為在Firefox的事件處理程序與IE不同所導致的情況,換頁事件會比keydown還早發生,所以在keydown攔下事件之前,換頁事件已經先發生了,也就是說...keydown欄太慢,無效!!
解決的方法就是將keydown事件改用keypress事件,這樣在Firefox就可以正常運作了........
不過很不幸的,在IE裡面這套又行不通了,所以程式碼要加上判斷的功能,依照不同的瀏覽器做不同的事件設定:
程序代碼
if [color=#0000ff](window.navigator.userAgent.indexOf("MSIE")>=1)
{
document.onkeydown = check;
{color:#0000ff}}else if (window.navigator.userAgent.indexOf("Firefox")>=1){
document.onkeypress = check;
{color:#0000ff}}
function check(e){
var code;
if (!e) var e = window.event;
if (e.keyCode) code = e.keyCode;
else if (e.which) code = e.which;
if (code == 8)
{
code = 0;
window.alert('請勿返回程式');
return false;
{color:#0000ff}}
return true;
{color:#0000ff}}
這樣,就打造出一個能夠在IE/Firefox上使用的攔截"Backspace"的小程式了
2008年4月29日 星期二
TOMCAT + JSP 的中文問題
1、JSP頁面顯示的中文問題
Page的第一行改成:<%@ page contentType="text/html; charset=UTF-8" %>
Head里加:<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
2、頁面Form 內容提交的中文問題
1) 只要把%TOMCAT安裝目錄%/ webapps\jsp-examples\WEB-INF\classes\filters\SetCharacterEncodingFilter.class 文件拷到你的webapp目錄下的filters下,如果沒有filters目錄,就創建一個。
2)在你的web.xml的</web-app>之前加入:
<filter>
<filter-name>Set Character Encoding</filter-name>
<filter-class>filters.SetCharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>Set Character Encoding</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
或者你不習慣用這個,可以在每個jsp頁面�使用request.setCharacterEncoding("UTF-8");我個人感覺是一樣的
3、get方式的解決辦法
1) 打開tomcat的server.xml文件,找到區塊,加入如下一行:URIEncoding="UTF-8"
完整的應如下:
<Connector port="8090" maxThreads="150" minSpareThreads="25"
maxSpareThreads="75"
enableLookups="false" redirectPort="8443" acceptCount="100"
debug="0" connectionTimeout="20000"
disableUploadTimeout="true"
URIEncoding="UTF-8"/>
2)重啓tomcat,一切OK。
當然還有最土的解決方案,雖然不太會用到,不過還是列出來,以備最無奈的時候使用:
String encodeStr=new String(fieldValue.getBytes("8859_1"), "UTF-8");
執行如下test.jsp頁面,測試是否成功
<%@ page contentType="text/html;charset=UTF-8"%>
<%@ page import="java.util.*"%>
<META http-equiv=Content-Type content="text/html;charset=UTF-8">
<%
String q=request.getParameter("q");
q = q == null?"沒有值":q;
%>
<HTML>
<HEAD>
<TITLE>新聞列表顯示</TITLE>
<body>你提交了:<%=q%><br>
<form action="test.jsp" method="post">
輸入中文:<input type="text" name="q"><input type="submit" value="確定"> <br>
<a href="test.jsp?q=中國">通過get方式提交</a>
</form>
</BODY>
</HTML>
4、mysql的中文問題
首先要修改mysql配置文件的encoding爲UTF-8,然後把jdbc connection改成如下:
jdbc:mysql://localhost:3306/bookuseUnicode=true;characterEncoding=UTF-8
電腦編碼的基本概念(轉貼)
編碼 (Encoding)
(作者: 謝東翰)
--------------------
A. 電腦編碼系統簡介:
電腦系統中資料儲存的最基本單位是「位元 (bit)」,而每個「位元」所
能代表的數值只有 0 與 1 而已。因此當我們要用電腦做資料的儲存與處理時,
我們通常會將連續幾個位元和起來看成一個單位,稱之為「位元組 (byte)」,
其所能代表的數值就不會只限於 0 與 1,而可以達到 2^n-1 (其中 n 為該位
元組中所包含的位元個數),如此我們就可以將我們想記錄在電腦系統的符號
一一編號,以位元組為單位儲存在電腦中,這就是編碼 (Encoding) 的基本觀念。
由於早期的電腦系統是發源於美國,因此最早的編碼系統也是發源於此。由於
他們的資料只需數字、26 個英文字母 (包括大小寫)、標點與其他特殊符號、
外加一些電腦系統的控制碼即可,因此當時一個位元組的大小只需 7 個位元
即可包含所有所需的資訊,總共可容納 128 個符號,這也就是大家所熟知的
ASCII 編碼。
然而對於歐洲語系而言,7 個位元的編碼空間不符合所需,因為除了 26 個英
文字母外,歐洲許多國家還需要拉丁字母、特殊字母的上下標與其他符號等,
才能完整表示他們的語言。因此原來的編碼系統必須再擴充,由 7 個位元變
為 8 個位元,以容納那些多出的符號,這就是 ISO8859 的編碼標準。而依地
區語系的不同, ISO8859 的編碼標準又分成幾個部分,例如 ISO8859-1 所包
含的符號可使用於英語、丹麥語、芬蘭語、德語 .... 等,而 ISO8859-2 則
可用於英語、德語、羅馬尼亞語、波蘭語 .... 等。每一個部分的 0-127 碼
的編碼方式與 ASCII 都是一樣的,只有在 128-255 碼才依地區不同而不同。
如此不同地區的人們可以選用不同部分的 ISO8859 編碼來處理他們的資料。
然而到了亞洲地區,上述的 8 位元編碼又不夠用了。因為亞洲地區不再是拼
音文字,因此多出了成千上萬個符號,我們已無法仿照當初 ISO8859 的做法,
弄出屬於中文的部分,或屬於日文的部分。唯一的作法只有合併幾個位元來
共同表示一個符號,如此才有足夠的空間來容納一個地區所有的文字。例如
我們台灣地區最常見的 BIG5 編碼,就是將兩個位元組合起來看成一個字。
但當一份文件中同時要有中英文時,意味著我們要有一個規則可以區分這個
位元組應該視為 ASCII 碼,或者它與它的下一個位元組應合起來視為一個
BIG5 碼。為了定出這樣的規則,就表示說我們無法全部使用整個雙位元組
的所有空間 (共 65536 個值)。就如我們的 BIG5 碼,其所包含的字數就只
有 13000 多個。
以上就地區語文使用似乎已經足夠了,然而在某些場合下,我們仍需要在同
一份文件中記錄多國文字。以 BIG5 編碼為例,它可以確保中文字與 ASCII
碼可以同時存在,不至於混餚 (事實上對於其他語系而言,他們的編碼方式多
半都是如此: 可以讓該地區的文字與 ASCII 碼同時存在),但如果我們要同時
表示中文、日文、韓文的文字呢?這裡就真正牽涉到多國語文的編碼範籌了。
而編碼系統的擴充就有兩種形式:
1. 文法式 (Modal) 編碼系統:
這裡最有名的就是 ISO2022 編碼。它的特徵是讓一個字串有「狀態」的區
別,而不同「狀態」的改變則以幾個特殊位元組值來代表。例如當一個字串
中讀到了位元組值 a 與位元組值 b 時,就表示字串進入狀態一,若讀到位
元組值 a 與 c 時就表示字串進入狀態二 .... 等。然後我們可以定義狀態
一以後的字串應解釋為日文 JIS 編碼,狀態二以後應解釋為簡體中文的 GB
編碼 .... 等等。如此便可以有系統地將各國的編碼方式整合進來。
很明顯的,這樣的編碼方式有一個限制,就是可以整合進來的編碼系統不能
與狀態改變標記位元 (如上例的 ab, ac .... 等) 衝突,否則的話就無法
整合進來。我們的 BIG5 編碼就是因為衝突了,所以無法整合到 ISO2022 中。
而且,這樣的編碼方式很複雜,程式實作困難,因此近年來較少有人使用了。
2. 定長式編碼系統:
此編碼系統是仿照 ASCII、ISO8859 的概念,給定一個固定長度的位元數,
試圖將世界上所有的符號都包含進來,並依序給它們編號。此方式最著名的
就是 Unicode (或稱 UCS2) 與 UCS4。以 Unicode 為例,其每個「位元組」
的長度固定為 16 個位元,即拿兩個傳統 8 位元的位元組合併成一個新的位
元組,然後編號從 0 到 65535 全部拿來使用,其中 0 到 127 是與 ASCII
碼相容的。如此方式,大概可以包含世界上主要的語言與符號如歐美地區、
中日韓越、中亞地區 .... 等等。至於 UCS4 則是 Unicode 的再擴充,其每
個「位元組」有 32 個位元,其中 0 到 65535 是與 Unicode 相同的,其餘
則是用來編入世界其他少數語言與罕用文字。
此編碼方式很明顯的好處就是沒有了文法式編碼系統的限制,同時程式容易
實作,只要提供一個適當的轉碼表,我們就可以很容易地將地區常用編碼系
統轉換為 Unicode 或 UCS4。此轉碼表可能相當大,特別是在繁體中文的
情況,但對於目前電腦系統的能力 (記憶體容量與運算速度 .... 等) 而言
已不是問題。因此,此編碼方式也逐漸成為世界的主流。儘管在某些地區仍
然對此編碼方式有意見,但在可預見的未來它將會越來越普遍。
然而,定長式編碼系統並非完全沒有限制的。最明顯的一點就是目前的電腦
硬體與程式語言都是將一個「位元組」定義為 8 個位元長,而非 16 或 32
個位元長,而且考慮到相容性的問題,在可預見的未來恐怕不會有全面性的
改變。同時由於不同 CPU 對於「位元序」的解釋不同,使得同樣是 16 或 32
位元的一個數值,在不同的 CPU 下會呈現不同的值 (例如 32 位元下在 x86
CPU 所呈現的數值 1,若將同樣的位元資料移到其他 RISC CPU 下可能呈現
出 16777216)。因此定長式編碼多半只能用於正執行中的程式內部,以便
於資料處理。若程式需要做資料的輸出入時,最理想的方式是將它轉為適當
的「多位元組編碼」 (見後述) 然後才進行。
B. GNU/Linux 下的編碼與字集:
在很多情況下,我們經常將「編碼 (Encoding)」與「字集 (Character Set)」
混為一談,但實際上它們各自有明確的定義的。所謂的「字集」是一組符號
(或文字) 的集合,而「編碼」則是將這一組符號 (或文字) 以適當的方式編
入位元組中,以便電腦能夠表示與儲存。在許多情況下,由於一個編碼方式所
能容納的字數是有限的,也因此就限定了它所能使用的「字集」,故有時我們
會將「編碼」與「字集」視為同一個東西。
在使用 glibc-2.2.X 系列的 GNU/Linux 系統中,由於已完整支援了 I18N 架
構,使得世界各地的語系資料庫都已完整地建立在系統底層的函式庫 (libc)
中,這其中也包含了各語系所用的字集與其相對應的編碼系統。以我們中文為
例,兩岸三地可能會常用到的編碼系統包括:
1. BIG5: 台灣地區最常見的編碼方式。目前 glibc 中的 BIG5 是以微軟公司
所定義的 CP950 為基礎,外加文鼎公司所採用的倚天外字集與 Unicode
轉換表而成。
2. EUC-TW: 此編碼即等價於教育部所編定的 ISO10646 國家標準編碼,目前
已內含了所有與 UCS4 可以互轉的中文字,其他尚未定義與 UCS4 可互
轉的中文字則要等定義完成後才會納入。
3. BIG5HKSCS: 此為香港地區所使用的 Big5 與香港政府增補字集。它與 BIG5
編碼是相容的,並加以擴充以容納其他罕用字。
4. GB2312: 此為大陸地區最通行的編碼方式,可容納常用的簡體字集。
5. GBK: 此為 GB2312 的擴充,除了容納原本 GB2312 中的簡體字以外,同時
還包含了繁體字與其他罕用字。
6. GB18030: 此為最近新定義出的字集編碼,與 GB2312 相容。
7. Unicode 與 UCS4: 其中 UCS4 在 GNU/Linux 系統中被用作轉碼系統的「基
底字集」(見後述)。
8. UTF-8: 此為 Unicode 的多位元編碼形式,可用於 Unicode 編碼的輸出。
以上的編碼系統在 GNU/Linux 中都有完整的支援,至於教育部新定的 Big5+
目前則不在支援之列。
C. GNU/Linux 的轉碼系統:
在具備完整的 I18N 環境支援的 UNIX 系統中 (包括 GNU/Linux 系統),其編
碼系統的轉換有兩種方式:
1. 一般轉換介面: 此即為 libc 中的 iconv() 編碼轉換介面,它可以做到各
種編碼的轉換。然而,在一般的 UNIX 系統中,並不見得系統同時支援編碼
系統 A 與編碼系統 B, 就一定能做到 A 與 B 之間的互轉,即使 A 與 B
內含相同的 (子) 字集。但在 GNU/Linux 底下,只要 A 與 B 內含相同的
(子) 字集,則可以直接透過 iconv() 做轉換。例如將 BIG5 轉成 BIG5HKSCS
或反過來,或者 GB2312 與 GBK 與 GB18030 之間的互轉。當然,各字集與
UCS4 之間的互轉都不是問題,原因就是 glibc 是以 UCS4 做為轉碼系統的
「基底字集」。
所謂的「基底字集」意指各地區的語文符號在系統內部的表式方式,而且它
還是各地區所有語文符號的集合。根據此定義,表示我們可以將世界各地所
有的編碼方式轉換成「基底字集」來統一處理 (理論上)。而 glibc 所採用
的「基底字集」的編碼方式就是 UCS4, 它是定長編碼系統,採用 32 位元
的位元組來編碼,它是目前已定義最大的一個字集的編碼方式,而且它仍持
續擴編世界各地的文字與符號。
在 GNU/Linux 下如果要透過 iconv() 進行轉碼工作時,它實際上是先將該
編碼轉成基底字集,然後再轉成目的編碼,如此便二確保只要編碼中有共同
的 (子) 字集時,轉換可以順利進行。
便對於沒有共同子字集的情況呢?例如 BIG5 與 GB2312 之間的互轉,在
GNU/Linux 下也提供額外的 iconv() 模組來進行,而這些都統一在 iconv()
介面之下,故在實際使用上不會感到任何差別。然而這類的額外模組目前
並未包含所有可能的轉換情況,但仍持續在建構中。例如 BIG5 與 GB2312
的互轉模組已於日前完成,目前尚在實驗版的 glibc-2.3 測試中,預計不
久的未來等 glibc-2.3 正式發表,並且大部分的 GNU/Linux 系統都換裝
glibc-2.3 之後,中文繁簡轉換的部分將不是問題。
2. 區域資料庫編碼轉換介面: 此轉換介面是與系統區域化資料庫 (locale) 相
關的,也是絕大部分的 I18N 程式會使用的介面。由於不同的區域化資料庫
所採用的編碼系統是固定的,例如我們台灣地區的區域化資料庫名稱為
zh_TW (或 zh_TW.Big5), 其採用的編碼系統即為 BIG5。而區域化資料庫主
要的任務只有在讓同一份程式碼可以處理各地區的語言,因而可以適用於各
地區,而不需要額外修改程式本身,故它的任何不在於處理多國語文的問題。
因此,這個轉碼介面只負責該資料庫所採用的編碼,與系統基底字集所採用的
編碼系統互轉而已。它不包含如上述 iconv() 轉碼介面那樣,可以做到任意
編碼系統間的互轉。
此轉碼介面最大的用途在於方便程式做文字處理。因為地區原本的編碼系統
多半是採用「多位元組」編碼方式,例如 BIG5 就是將兩個 8 位元的位元組
組合起來使用,故稱「多位元組編碼」。但當我們要在字串中區分中文字與
英文字母時就會遇到麻煩,因為前者是以兩個位元組來代表,後者確是以單
一一個位元組來代表,故我們要在程式中額外寫入 BIG5 碼的編碼規則才行,
而這樣的程式就不能適用與其他的編碼方式了。因此,當我們用此轉換介面
將它轉成定長編碼方式時,由於每個字 (不論中英文) 都是故定的長度 (即
位元組數),我們稱之為「寬字元 (wide character)」,這使得我們可以很
方便做字串處理。同時,我們的程式中也不必加入 BIG5 的編碼規則,因為
這些都在此轉碼介面中處理掉了,因此,這樣寫出來的程式就是一個支援 I18N
的程式,可以在不需要修改程式碼本身的情況下適用於各語系與編碼系統。
然而,我們在前面也提過,定長編碼方式 (即寬字元) 只能用於運作中的程式內部,
如果在做資料輸出入時可能因 CPU 的位元序不同與各硬體系統的限制而出錯。因此,
當我們要做資料交換時,最好的方式是將它轉回多位元組編碼,才能確保不會有問
題。這在第 2 點的轉換介面是不成問題的,因為基底字集與其多位元組編碼之間
的對應是定義在區域化資料庫中的;但在第 1 點的轉換介面中,特別是 Unicode
或 UCS4,而且包含了如中日韓等多國語文資料的情況又如何呢?對於 Unicode
而言,其所對應的多位元組編碼為 UTF-8, 它可以將所有 Unicode 碼轉換成不定
長度的位元組,視其 Unicode 碼值而定,而且轉出來的多位元組與 ASCII 編碼相
容,故可以適用於所有的 UNIX 應用程式。至於 UCS4 所對應的多位原組編碼則為
UTF-16, 它是將兩個 UTF-8 字元組合而成的。
參考資料:
1. CJKV: Information Processing, by Ken Lunde,
Publish: O'Reilly, ISBN: 1-56592-224-7
2. Documents of glibc-2.2: man pages and info pages.
利用JavaMail.收發Gmail郵件(SSL)
運用 Java Bean 連結資料庫 - 以 MySQL 為例
因市面上 JSP 的教學書籍在講到 JavaBeans 時大都僅只於其特性介紹,版工日前在某本書上看到實際運用的範例 [1],挑選其中較實用的一則做了適當修改後,提供給網友作為參考。其功能為將資料庫連結的動作寫於一共用元件中,以達到保密性(避免資料庫連結帳號、密碼直接寫在頁面中)、程式碼方便修改的彈性,以及程式碼共用,避免同樣的程式碼重複出現在各個頁面中。在微軟 ASP.NET 中也早在 1.0 版即有相同功能,稱為程式碼後置(Code-Behind),可將資料庫的連結和控制項的處理寫在裡面,其由 IIS(嚴格說起來是.NET Framework)在執行時作處理。而其元件的副檔名為 .vb(VB.net) 或 .cs(C#)。
將資料庫連結寫在 Java Bean 中還有一項好處,即為可達成一位使用者連線時,只會建立一組資料庫的連結。比較能夠避免因不當的程式撰寫觀念,而導致在各種情況下(分頁功能、使用者重新整理頁面)都不斷重新建立資料庫連結,而嚴重影響網站運作效能、浪費伺服器資源與頻寬。下方分別為 Java Bean 與其被引用 JSP 的程式碼。
DBconn.java
package tw.idv.stephen;
import javax.servlet.http.*;
import java.sql.*;
// HttpSessionBindingListener 用來聆聽 Bean 物件何時會被加入或被移出一個 Session (該介面的實作,以本例而言並非必要)
public class DBconn implements HttpSessionBindingListener {
private Connection conn = null;
String stConn = "jdbc:mysql://localhost:3306/資料庫名稱?user=資料庫帳號&password=資料庫密碼&" +
"useUnicode=true&characterEncoding=MS950";
public DBconn() {
//buildConn(); // 亦可在建構子中完成資料庫連結
}
private void buildConn() {
try {
Class.forName("com.mysql.jdbc.Driver");
conn = DriverManager.getConnection(stConn);
} catch(Exception e) {
System.out.println(e.getMessage()); // 錯誤訊息顯示在 Web Container 的控制台中
}
}
public Connection getConn() { // 傳回型別為Connection
if (conn == null)
buildConn();
return this.conn;
}
public void closeConn() {
try {
conn.close(); // 關閉連結
conn = null; // 註記以等待資源回收
} catch(SQLException ex) {
System.out.println(ex.getMessage()); // 亦可用 ex.toString() 顯示更完整訊息
}
}
// 當 Java Bean 物件加入 Session 物件時,將自動執行此內建函數
public void valueBound(HttpSessionBindingEvent event) {}
// 當 Java Bean 物件從 Session 中移除時,或 Session 物件消滅時,將自動執行此內建函數
public void valueUnbound(HttpSessionBindingEvent event) {
if (conn != null)
closeConn();
}
}
上方 Java Bean 的程式中實作了 HttpSessionBindingListener 介面,該介面定義了 Session 範圍內的物件其生命週期。該聆聽器的主要用途,包括當你的屬性物件本身想知道它被加入一個 Session 範圍的時間點,以便它能同時去讀出後端資料庫的最新資料;還有當 Session 將它移除時,它就可趁機去更改資料庫中的資料 [2]。但在本例中引用該介面,僅用於提示其可應用時機,並未如上述般使用。
程式員亦應養成好習慣,之後若 Session 內的物件不再使用時,應呼叫 Session 的 removeAttribute() 移除該屬性;而當 Session 本身不再使用時,亦可由程式員主動呼叫 Session 的 invalidate() 終結掉 Container 中的 Session。如此可節省伺服器的記憶體使用,並有助於運作效能。
DBconn.jsp
<%@ page contentType="text/html;charset=MS950" import="java.sql.*" %>
<HTML>
<BODY>
<CENTER>
利用 Java Bean 建立 MySql 資料庫連結。<br>
每一位使用者連線,只會建立一個資料庫連結。<hr>
<!-- JSP 2.0 以後,Java Bean 至少要放在 Web Container 根目錄的再下面一層目錄內 -->
<jsp:useBean id="ConDbBean" scope="session" class="tw.idv.stephen.DBconn"/>
<%
Connection conn = ConDbBean.getConn(); // 呼叫 Bean 物件的 getConn() 方法,取得已建立完成的資料庫連結
Statement stmt = conn.createStatement(); // 可依需求改為 PreparedStatement
ResultSet rs = stmt.executeQuery("SELECT Title, Price, Page FROM StoreDB");
%>
<TABLE bgcolor=#FFCC66>
<TR bgcolor=#FF9933>
<TD>書 名</TD><TD>價 格</TD><TD>頁 數</TD>
</TR>
<%
while (rs.next()) {
out.print("<TR bgcolor=LightYellow>");
out.print("<TD>" + rs.getString(1) + "</TD>"); // 可用欄位索引值(1,2,3,...)撈取資料,
out.print("<TD align=right>" + rs.getString("Price") + "</TD>"); // 亦可用欄位名稱撈取。
out.print("<TD align=right>" + rs.getInt("Page") + "</TD>"); // 此欄位在資料庫中是以整數型別儲存
out.print("</TR>");
}
rs.close();
stmt.close();
ConDbBean.closeConn(); // 呼叫 Java Bean 的 closeConn() 方法,關閉資料庫連結
%>
</TABLE>
</CENTER>
</BODY>
</HTML>
在 Bean 之中通常不會一併完成 Statement 物件的建立,是因為 Statement 物件會對應到一組 SQL 敘述。因此,若用 Bean 物件提供 Statement,則一個連線僅會有一個 Statement 物件。有可能當某個連線正在開啟某 SQL 敘述所產生的 ResultSet 物件時,又有網頁執行另一個 SQL 敘述,如此將導致原來開啟的 ResultSet 物件關閉,因而造成不可預期的錯誤。
參考文件 :
[1] 位元文化,2003,「JSP動態網頁入門實務」,文魁出版社,第 12 章。
[2] Bryan Basham、Kathy Sierra、Bert Bates,2005,「Head First Servlets & JSP」,O'Reilly 出版社,第 254 頁。



