#
http://callan.javaeye.com/blog/123374
關鍵字: java, 定時器, timertask
在web中,定時器的啟動一般隨web server的啟動而啟動,一般有兩種方法.
方法一:在web.xml里配置一個Servlet,并設置其隨web server的啟動而啟動。然后在該Servlet的init()方法里啟動定時器,在destory()方法里銷毀定時器。
方法二:在web.xml里配置一個Listener,然后在該Listener的初始化方法里啟動定時器,在其銷毀的方法朝左銷毀定時器。
在servlet中啟動定時器
java 代碼
- import java.io.IOException;
- import java.util.Timer;
- import javax.servlet.RequestDispatcher;
- import javax.servlet.ServletContext;
- import javax.servlet.ServletException;
- import javax.servlet.http.HttpServlet;
- import javax.servlet.http.HttpServletRequest;
- import javax.servlet.http.HttpServletResponse;
-
- import org.apache.commons.lang.StringUtils;
-
- public class ConvergeDataServlet extends HttpServlet {
-
- private static final long serialVersionUID = 1L;
-
- private Timer timer1 = null;
-
- private Task task1;
-
- /**
- * Constructor of the object.
- */
- public ConvergeDataServlet() {
- super();
- }
-
- /**
- * Destruction of the servlet.
- */
- public void destroy() {
- super.destroy();
- if(timer1!=null){
- timer1.cancel();
- }
- }
-
-
- public void doGet(HttpServletRequest request, HttpServletResponse response)
- throws ServletException, IOException {
-
- }
-
-
- public void doPost(HttpServletRequest request, HttpServletResponse response)
- throws ServletException, IOException {
- doGet(request, response);
- }
-
- // init方法啟動定時器
- public void init() throws ServletException {
-
- ServletContext context = getServletContext();
-
- // (true為用定時間刷新緩存)
- String startTask = getInitParameter("startTask");
-
- // 定時刷新時間(分鐘)
- Long delay = Long.parseLong(getInitParameter("delay"));
-
- // 啟動定時器
- if(startTask.equals("true")){
- timer1 = new Timer(true);
- task1 = new Task(context);
- timer1.schedule(task1, delay * 60 * 1000, delay * 60 * 1000);
- }
- }
- }
定時執行操作
java 代碼
-
- /**
- *
- * @author callan
- */
- import java.util.TimerTask;
-
- import javax.servlet.ServletContext;
-
- public class Task extends TimerTask{
-
- private ServletContext context;
-
- private static boolean isRunning = true;
-
- public Task(ServletContext context){
- this.context = context;
- }
-
-
- @Override
- public void run() {
- if(isRunning){
-
- }
- }
-
- }
在web.xml配置這個servlet為服務啟動時就調用servlet
<servlet></servlet>
< servlet >
< servlet-name >taskservlet< / servlet-name >
< servlet-class >com.task< /servlet-class >
< init-param >
< param-name >startTask< /param-name >
< param-value >true< /param-value >
< /init-param >
< init-param >
< param-name >intervalTime< /param-name >
< param-value >1< /param-value >
< /init-param >
< load-on-startup >300< /load-on-startup >
< /servlet >
<servlet></servlet>
最近,公司用servlet做一個跟蹤圖片點擊技術的模塊,我個人略有心得想和大家分享,
不知大家愿意審視。
這個模塊挺大,我僅說說用servlet顯示圖片部分。我先說說用servlet顯示圖片的一個流程:
1. 設置response的輸出類型:
對應的語句--response.setContentType("image/gif;charset=GB2312") ,response
便能輸出gif圖片,"image/gif;charset=GB2312"便是輸出類型,當然你可以輸出
"image/jpg;charset=GB2312"類型文件。
2. 得到文件流:
servlet是以流的形式件圖片文件從服務器讀出,通過response將流發到瀏覽器的。
3. 得到輸出流:
對應的語句--OutputStream output = response.getOutputStream();
當然,處理圖片文件需要以二進制形式的流。
4. 文件流的編碼(但也不一定必須編碼的,如果不是文件流,則必須編碼)
所以我給大家一個用編碼的代碼和不用編碼的代碼.
順便說一句,sun公司僅提供了jpg圖片文件的編碼api。
我想基本流程都講完了,下面我把代碼拿給大家看一下,大家自然一目了然了:
package xjw.personal.servet;
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import com.sun.image.codec.jpeg.*;//sun公司僅提供了jpg圖片文件的編碼api
import javax.imageio.stream.*;
import java.awt.*;
import java.awt.image.BufferedImage;
public class ShowPicture extends HttpServlet
{
private static final String GIF="image/gif;charset=GB2312";//設定輸出的類型
private static final String JPG="image/jpeg;charset=GB2312";
public void init() throws ServletException
{
}
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException
{
doPost(request, response);
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException
{
String spec=request.getParameter("spec");//輸出圖片的類型的標志
int int_spec=Integer.parseInt(spec);
if(spec==1)
{
String imagePath="/jfgg/b1.jpg";//圖片相對web應用的位置
}
else
{
String imagePath="/jfgg/b2.gif";//圖片相對web應用的位置
}
OutputStream output = response.getOutputStream();//得到輸出流
if(imagePath.toLowerCase().endsWith(".jpg"))//使用編碼處理文件流的情況:
{
response.setContentType(JPG);//設定輸出的類型
//得到圖片的真實路徑
imagePath = getServletContext().getRealPath(imagePath);
//得到圖片的文件流
InputStream imageIn = new FileInputStream(new File(imagePath));
//得到輸入的編碼器,將文件流進行jpg格式編碼
JPEGImageDecoder decoder = JPEGCodec.createJPEGDecoder(imageIn);
//得到編碼后的圖片對象
BufferedImage image = decoder.decodeAsBufferedImage();
//得到輸出的編碼器
JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(output);
encoder.encode(image);//對圖片進行輸出編碼
imageIn.close();//關閉文件流
}
if(imagePath.toLowerCase().endsWith(".gif"))//不使用編碼處理文件流的情況:
{
response.setContentType(GIF);
ServletContext context = getServletContext();//得到背景對象
InputStream imageIn=context.getResourceAsStream(imagePath);//文件流
BufferedInputStream bis=new BufferedInputStream(imageIn);//輸入緩沖流
BufferedOutputStream bos=new BufferedOutputStream(output);//輸出緩沖流
byte data[]=new byte[4096];//緩沖字節數
int size=0;
size=bis.read(data);
while (size!=-1)
{
bos.write(data,0,size);
size=bis.read(data);
}
bis.close();
bos.flush();//清空輸出緩沖流
bos.close();
}
output.close();
}
}
最后是如何調用,你可以簡單的映射一下servelt,我就將servet的名映射為
ShowPic, 于是下代碼調用
<html>
<body>
<img src="ShowPic?spec=2"></a>
</body>
</html>
這樣圖片便顯示在htm上了,本人研究java的時間不長,如有問題,盡情指正。
一、完整的自我介紹1
Good morning !
It is really my honor to have this
opportunity for an interview,I hope I can make a good performance today.
I’m confident that I can succeed.Now I will introduce myself briefly.
I
am 26 years old,born in shandong province .I was graduated from qingdao
university. My major is electronic and i got my bachelor degree after
my graduation in the year of 2003.I spend most of my time on study,I
have passed CET4/6 . and I have acquired basic knowledge of my major
during my school time.In July 2003, I began work for a small private
company as a technical support engineer in QingDao city.Because I’m
capable of more responsibilities, so I decided to change my job.And in
August 2004,I left QingDao to BeiJing and worked for a foreign
enterprise as a automation software test engineer.Because I want to
change my working environment, I’d like to find a job which is more
challenging. Morover Motorola is a global company, so I feel I can gain
the most from working in this kind of company ennvironment. That is the
reason why I come here to compete for this position.
I think I’m a good team player and I’m a person of great honesty to others. Also I am able to work under great pressure.
That’s all. Thank you for giving me the chance.
完整的自我介紹2
good
morning, my name is jack, it is really a great honor to have this
opportunity for a interview, i would like to answer whatever you may
raise, and i hope i can make a good performance today, eventually enroll
in this prestigious university in september. now i will introduce
myself briefly.
i am 21 years old,born in heilongjiang province
,northeast of china,and i am curruently a senior student at beijing XX
uni.my major is packaging engineering.and i will receive my bachelor
degree after my graduation in june.in the past 4 years,i spend most of
my time on study,i have passed CET4/6 with an ease. and i have acquired
basic knowledge of packaging and publishing both in theory and in
practice. besides, i have attend several packaging exhibition hold in
Beijing, this is our advantage study here, i have taken a tour to some
big factory and company. through these i have a deeply understanding of
domestic packaging industry. compared to developed countries such as us,
unfortunately, although we have made extraordinary progress since
1978,our packaging industry are still underdeveloped, mess, unstable,
the situation of employees in this field are awkard.
but i have
full confidence in a bright future if only our economy can keep the
growth pace still. i guess you maybe interested in the reason itch to
law, and what is my plan during graduate study life, i would like to
tell you that pursue law is one of my lifelong goal,i like my major
packaging and i won’t give up,if i can pursue my master degree here i
will combine law with my former education. i will work hard in
thesefields ,patent ,trademark, copyright, on the base of my years study
in department of p&p.
my character? i cannot describe it
well, but i know i am optimistic and confident. sometimes i prefer to
stay alone, reading, listening to music, but i am not lonely, i like to
chat with my classmates, almost talk everything ,my favorite pastime is
valleyball,playing cards or surf online. through college life,i learn
how to balance between study and entertainment. by the way, i was a
actor of our amazing drama club. i had a few glorious memory on stage.
that is my pride.
二、模板1:自我介紹(self-introduction)
Good morning. I am glad to be here for this interview.
First
let me introduce myself. My name is ***, 24. I come from ******,the
capital of *******Province. I graduated from the ******* department of
*****University in July ,2001.In the past two years I have been
preparing for the postgraduate examination while I have been teaching
*****in NO.****middle School and I was a head-teacher of a class in
junior grade two.Now all my hard work has got a result since I have a
chance to be interview by you . I am open-minded ,quick in thought and
very fond of history.In my spare time,I have broad interests like many
other youngsters.I like reading books, especially those about
*******.Frequently I exchange with other people by making comments in
the forum on line.In addition ,during my college years,I was once a
Net-bar technician.So, I have a comparatively good command of network
application.I am able to operate the computer well.I am skillful in
searching for information in Internet.I am a football fan for
years.Italian team is my favorite.
Anyway,I feel great pity for
our country’s team. I always believe that one will easily lag behind
unless he keeps on learning .Of course, if I am given a chance to study
****** in this famous University,I will stare no effort to master a good
command of advance ******.
模板2:自我介紹(self-introduction)
Good afternoon .I am of great hornor to stand here and introduce myself to you .
First
of all ,my english name is …and my chinese name is ..If you are going
to have a job interview ,you must say much things which can show your
willness to this job ,such as ,it is my long cherished dream to be …and I
am eager to get an opportunity to do…and then give some examples which
can give evidence to .then you can say something about your hobbies .and
it is best that the hobbies have something to do with the job.
What
is more important is do not forget to communicate with the
interviewee,keeping a smile and keeping your talks interesting and funny
can contribute to the success.
I hope you will give them a wonderful speech .Good luck to you !
三、面試現場提問環節:
1. What is your greatest strength?(你最突出的優點是什么?)
這是很多面試
考官喜歡問的一個問題,這是你展示自己的最佳機會,不要吹噓自己或過于自負,但要讓雇主知道你相信自己,你知道自己的優點。如可答:“我認為我最大的優點
是能夠執著地盡力把事情辦好。當做完一件工作而其成果又正合我的預想時,我會有一種真正的成就感。我給自己定了一些高目標。比如說,我要成為出色的畢業
生。盡管在大學一年級時我啟動慢了些,但最終我以優等論文完成了學業。”
I feel that my strongest asset is
my ability to stick to things to get them done. I feel a real sense of
accomplishment when I finish a job and it turns out just as I’d planned.
I’ve set some high goals for myself. For example, I want to graduate
with highest distinction. And even though I had a slow start in my
freshman year, I made up for it by doing an honor’s thesis.
2. What is your greatest weakness?(你最大的弱點是什么?)
你不應該說你沒有任何弱點,以此來回避這個問題。每個人都有弱點,最佳策略是承認你的弱點,但同時表明你在予以改進,并有克服弱點的計劃。可能的話,你可說出一項可能會給公司帶來好處的弱點,如可說:“我是一個完美主義者。工作做得不漂亮,我是不會撒手的。”
I’m such a perfectionist that I will not stop until a job is well done.
I'm married and I have two beautiful children. I'm interested in cricket, history and bird watching.
I graduated from university with a Business Management degree with high marks. I studied
courses such as accounting and English. I've got two years' work experience, first as a clerk and
then as a manager in a factory in the capital, so I can easily do the duties of this job. I'm also very
skilled with computers.
我
結婚了,有兩個漂亮的孩子。我喜歡打板球,喜歡歷史,喜歡觀察野鳥。我的大學專業是商務管理,并且成績優秀。在校時我研讀的課程有會計,英語等。我有兩年
的工作經驗。第一份工作是做一名普通的職員,第二次是在工廠里做管理人員。所以我可以勝任這份工作。另外,我的電腦技術也很不錯。
I'm efficient; when I have a project to do, I do it thoroughly and on
time. For example, I always double-check my reports. I like to be
organized; in my last managerial position I had many duties so I
organized a schedule and prioritized my jobs. I'm a fast learner: I
didn't know anything about computers before university, it was difficult
at first but I studied hard, got high marks and can use it effectively
now…. I believe that problems are really challenges and that I can solve
them.
我做事有效率。當我做一個項目時,我考慮很周到并且按時完工。比如,我總是對報告進行二度確認。我做事有條理。在我
上一份工作中,我是管理人員,我有許多事情要做,所以我自己列了一個做事提綱并且按事情的輕重緩急來安排時間。我學習能力很強。在大學前我不懂電腦,對我
來說,起初很困難,但我用功學習電腦并且得到了高分,所以現在對電腦駕輕就熟。我認為,工作中的問題是有挑戰性的,但我可以解決。
老師點評:
This is an excellent answer. It gives a lot of additional information which is not on the CV
It gives specific details, examples and evidence of key skills and qualities
It reveals the personality and values of the candidate (for example, 'problems are really challenges')
It shows a problem-solving approach and self-management skills.
摘要: 為了便于管理,先引入個基礎類:
package algorithms;
/**
* @author yovn
*
*/
public abstract class Sorter<E extends Comparable<E>> {... 閱讀全文
經常在論壇上面看到覆寫hashCode函數的問題,很多情況下是一些開發者不了解hash code,或者和equals一起用的時候不太清楚為啥一定要復寫hashCode。
對于hash code的理論我不想多說,這個話題太大。我只想說用hash code的原因只有一個:效率。理論的說法它的復雜度只有O(1)。試想我們把元素放在線性表里面,每次要找一個元素必須從頭一個一個的找它的復雜度有O(n)。如果放在平衡二叉樹,復雜度也有O(log n)。
為啥很多地方說“覆寫equals的時候一定要覆寫hashCode”。說到這里我知道很多人知道有個原則:如果a.equals(b)那么要確保a.hashCode()==b.hashCode()。為什么?hashCode和我寫的程序的業務邏輯毫無關系,為啥我要override? 要我說如果你的class永遠不可能放在hash code為基礎的容器內,不必勞神,您真的不必override hashCode() :)
說得準確一點放在HashMap和Hashtable里面如果是作為value而不是作為key的話也是不必override
hashCode了。至于HashSet,實際上它只是忽略value的HashMap,每次HashSet.add(o)其實就是
HashMap.put(o, dummyObject)。
那為什么放到Hash容器里面要overide hashCode呢?因為每次get的時候HashMap既要看equals是不是true也要看hash code是不是一致,put的時候也是要看equals和hash code。
如果說到這里您還是不太明白,咱就舉個例子:
譬如把一個自己定義的class
Foo{...}放到HashMap。實際上HashMap也是把數據存在一個數組里面,所以在put函數里面,HashMap會調
Foo.hashCode()算出作為這個元素在數組里面的下標,然后把key和value封裝成一個對象放到數組。等一下,萬一2個對象算出來的
hash
code一樣怎么辦?會不會沖掉?先回答第2個問題,會不會沖掉就要看Foo.equals()了,如果equals()也是true那就要沖掉了。萬一
是false,就是所謂的collision了。當2個元素hashCode一樣但是equals為false的時候,那個HashMap里面的數組的這
個元素就變成了鏈表。也就是hash code一樣的元素在一個鏈表里面,鏈表的頭在那個數組里面。
回過來說get的時候,HashMap也先調key.hashCode()算出數組下標,然后看equals是不是true,所以就涉及了equals。
反觀假設如果a.equals(b)但是a.hashCode()!=b.hashCode()的話,在put元素a之后,我們又用一個
a.equals(b)但是b.hashCode()!=a.hashCode()的b元素作為key來get的時候就找不到a了。如果
a.hashCode()==b.hashCode()但是!a.equals(b)倒是不要緊,這2個元素會collision然后被放到鏈表,只是效
率變差。
這里有個非常簡化版的HashMap實現幫助大家理解。
- /*
- * Just to demonstrate hash map mechanism,
- * Please do not use it in your commercial product.
- *
- * @author Shengyuan Lu 盧聲遠 <michaellufhl@yahoo.com.cn>
- */
- public class SimpleHashMap {
- ArrayList<LinkedList<Entry>> entries = new ArrayList<LinkedList<Entry>>();
-
- /**
- * Each key-value is encapsulated by Entry.
- */
- static class Entry {
- Object key;
- Object value;
- public Entry(Object key, Object value) {
- this.key = key;
- this.value = value;
- }
- }
- void put(Object key, Object value) {
- LinkedList<Entry> e = entries.get(key.hashCode());
- if (e != null) {
- for (Entry entry : e) {
- if (entry.key.equals(key)) {
- entry.value = value;// Match in lined list
- return;
- }
- }
- e.addFirst(new Entry(key, value));// Add the entry to the list
- } else {
- // Put the new entry in array
- LinkedList<Entry> newEntry = new LinkedList<Entry>();
- newEntry.add(new Entry(key, value));
- entries.add(key.hashCode(), newEntry);
- }
- }
- Object get(Object key) {
- LinkedList<Entry> e = entries.get(key.hashCode());
- if (e != null) {
- for (Entry entry : e) {
- if (entry.key.equals(key)) {
- return entry.value;
- }
- }
- }
- return null;
- }
-
- /**
- * Do we need to override equals() and hashCode() for SimpleHashMap itself?
- * I don't know either:)
- */
- }
這個問題的權威闡釋可以參考Bloch的<Effective Java>的 Item 9: Always override hashCode when you override equals
責怪糟糕的代碼(或不良代碼對象)并不能幫助您發現瓶頸,提高 Java™ 應用程序速度,猜測也不能幫您解決。Ted Neward 引導您關注 Java 性能監控工具,從5 個技巧開始,使用Java 5 的內置分析器JConsole 收集和分析性能數據。
當應用程序性能受到損害時,大多數開發人員都驚慌失措,這在情理之中。跟蹤 Java 應用程序瓶頸來源一直以來都是很麻煩的,因為 Java 虛擬機有黑盒效應,而且 Java 平臺分析工具一貫就有缺陷。
然而,隨著 Java 5 中 JConsole 的引入,一切都發生了改變。JConsole 是一個內置 Java
性能分析器,可以從命令行或在 GUI shell
中運行。它不是完美的,但是當尖頭老板來問你關于性能的問題時,用它來應對還是綽綽有余的——這比查詢 Papa Google 要好得多。
在本期 5 件事 系列中,我將向您展示 5 個方法,使您可以輕松地使用 JConsole(或者,它更高端的 “近親” VisualVM )來監控 Java 應用程序性能和跟蹤 Java 中的代碼。
1. JDK 附帶分析器
許多開發人員沒有意識到從 Java 5 開始 JDK 中包含了一個分析器。JConsole(或者 Java
平臺最新版本,VisualVM)是一個內置分析器,它同 Java 編譯器一樣容易啟動。如果是從命令行啟動,使 JDK 在 PATH 上,運行
jconsole 即可。如果從 GUI shell 啟動,找到 JDK 安裝路徑,打開 bin 文件夾,雙擊 jconsole。
當分析工具彈出時(取決于正在運行的 Java 版本以及正在運行的 Java 程序數量),可能會出現一個對話框,要求輸入一個進程的 URL 來連接,也可能列出許多不同的本地 Java 進程(有時包含 JConsole 進程本身)來連接。
使用 JConsole 進行工作
在 Java 5 中,Java 進程并不是被設置為默認分析的,而是通過一個命令行參數 —
-Dcom.sun.management.jmxremote — 在啟動時告訴 Java 5 VM 打開連接,以便分析器可以找到它們;當進程被
JConsole 撿起時,您只能雙擊它開始分析。
分析器有自己的開銷,因此最好的辦法就是花點時間來弄清是什么開銷。發現 JConsole
開銷最簡單的辦法是,首先獨自運行一個應用程序,然后在分析器下運行,并測量差異。(應用程序不能太大或者太小;我最喜歡使用 JDK 附帶的
SwingSet2 樣本。)因此,我使用 -verbose:gc 嘗試運行 SwingSet2 來查看垃圾收集清理,然后運行同一個應用程序并將
JConsole 分析器連接到它。當 JConsole 連接好了之后,一個穩定的 GC 清理流出現,否則不會出現。這就是分析器的性能開銷。
2. 遠程連接進程
因為 Web 應用程序分析工具假設通過一個套接字進行連通性分析,您只需要進行少許配置來設置 JConsole(或者是基于 JVMTI 的分析器,就這點而言),監控/分析遠程運行的應用程序。
如果 Tomcat 運行在一個名為 “webserve” 的機器上,且 JVM 已經啟動了 JMX 并監聽端口 9004,從
JConsole(或者任何 JMX 客戶端)連接它需要一個 JMX URL
“service:jmx:rmi:///jndi/rmi://webserver:9004/jmxrmi”。
基本上,要分析一個運行在遠程數據中心的應用程序服務器,您所需要的僅僅是一個 JMX URL。更多關于使用 JMX 和 JConsole 遠程監控和管理的信息,參見 參考資料。)
3. 跟蹤統計
JConsole 有許多對收集統計數據有用的選項卡,包括:
Memory:在 JVM 垃圾收集器中針對各個堆跟蹤活動。
Threads:在目標 JVM 中檢查當前線程活動。
Classes:觀察 VM 已加載類的總數。
這些選項卡(和相關的圖表)都是由每個 Java 5 及更高版本 VM 在 JMX 服務器上注冊的 JMX 對象提供的,是內置到 JVM
的。一個給定 JVM 中可用 bean 的完整清單在 MBeans
選項卡上列出,包括一些元數據和一個有限的用戶界面來查看數據或執行操作。(然而,注冊通知是在 JConsole 用戶界面之外。)
使用統計數據
假設一個 Tomcat 進程死于 OutOfMemoryError。如果您想要弄清楚發生了什么,打開 JConsole,單擊
Classes 選項卡,過一段時間查看一次類計數。如果數量穩定上升,您可以假設應用程序服務器或者您的代碼某個地方有一個 ClassLoader
漏洞,不久之后將耗盡 PermGen 空間。如果需要更進一步的確認問題,請看 Memory 選項卡。
4. 為離線分析創建一個堆轉儲
生產環境中一切都在快速地進行著,您可能沒有時間花費在您的應用程序分析器上,相反地,您可以為 Java 環境中的每個事件照一個快照保存下來過后再看。在 JConsole 中您也可以這樣做,在 VisualVM 中甚至會做得更好。
先找到 MBeans 選項卡,在其中打開 com.sun.management 節點,接著是 HotSpotDiagnostic
節點。現在,選擇 Operations,注意右邊面板中的 “dumpHeap” 按鈕。如果您在第一個(“字符串”)輸入框中向 dumpHeap
傳遞一個文件名來轉儲,它將為整個 JVM 堆照一個快照,并將其轉儲到那個文件。
稍后,您可以使用各種不同的商業分析器來分析文件,或者使用 VisualVM 分析快照。(記住,VisualVM 是在 Java 6 中可用的,且是單獨下載的。)
5. JConsole 并不是高深莫測的
作為一個分析器實用工具,JConsole 是極好的,但是還有更好的工具。一些分析插件附帶分析器或者靈巧的用戶界面,默認情況下比 JConsole 跟蹤更多的數據。
JConsole 真正吸引人的是整個程序是用 “普通舊式 Java ” 編寫的,這意味著任何 Java
開發人員都可以編寫這樣一個實用工具。事實上,JDK 其中甚至包括如何通過創建一個插件來定制 JConsole 的示例(參見 參考資料)。建立在
NetBeans 頂部的 VisualVM 進一步延伸了插件概念。
如果 JConsole(或者
VisualVM,或者其他任何工具)不符合您的需求,或者不能跟蹤您想要跟蹤的,或者不能按照您的方式跟蹤,您可以編寫屬于自己的工具。如果您覺得
Java 代碼很麻煩,Groovy 或 JRuby 或很多其他 JVM 語言都可以幫助您更快完成。
您真正需要的是一個快速而粗糙(quick-and-dirty)的由 JVM 連接的命令行工具,可以以您想要的方式確切地跟蹤您感興趣的數據。
結束語
Java 性能監控不止于 JConsole 或 VisualVM — 在 JDK 中隱藏著一整套工具,只是大多數開發人員并不知道。 本系列
中的下一篇文章將深入探究一些實驗性的命令行工具,可以幫助您挖掘更多的您所需要的性能數據。因為這些工具通常只關注特殊數據,比一個完整的分析器更小更
輕巧,所以它們的性能開銷要小一些。
Velocity是什么?
Velocity是一個基于java的模板引擎(template engine)。它允許任何人僅僅簡單的使用模板語言(template language)來引用由java代碼定義的對象。
當Velocity應用于web開發時,界面設計人員可以和java程序開發人員同步開發一個遵循MVC架構的web站點,也就是說,頁面設計人
員可以只關注頁面的顯示效果,而由java程序開發人員關注業務邏輯編碼。Velocity將java代碼從web頁面中分離出來,這樣為web站點的長
期維護提供了便利,同時也為我們在JSP和PHP之外又提供了一種可選的方案。
Velocity的能力遠不止web站點開發這個領域,例如,它可以從模板(template)產生SQL和PostScript、XML,它也
可以被當作一個獨立工具來產生源代碼和報告,或者作為其他系統的集成組件使用。Velocity也可以為Turbine
web開發架構提供模板服務(template
service)。Velocity+Turbine提供一個模板服務的方式允許一個web應用以一個真正的MVC模型進行開發。
Velocity能為我們作什么?
The Mud Store Example
假設你是一家專門出售Mud的在線商店的頁面設計人員,讓我們暫且稱它為“在線MUD商店”。你們的業務很旺,客戶下了各種類型和數量的mud訂
單。他們都是通過輸入用戶名和密碼后才登陸到你的網站,登陸后就允許他們查看訂單并購買更多的mud。現在,一種非常流行的mud正在打折銷售。另外有一
些客戶規律性的購買另外一種也在打折但是不是很流行的Bright Red
Mud,由于購買的人并不多所以它被安置在頁面的邊緣。所有用戶的信息都是被跟蹤并存放于數據庫中的,所以某天有一個問題可能會冒出來:為什么不使用
velocity來使用戶更好的瀏覽他們感興趣的商品呢?
Velocity使得web頁面的客戶化工作非常容易。作為一個web site的設計人員,你希望每個用戶登陸時都擁有自己的頁面。
你會見了一些公司內的軟件工程師,你發現他們每個人都同意客戶應該擁有具有個性化的信息。那讓我們把軟件工程師應該作的事情發在一邊,看一看你應該作些什么吧。
你可能在頁面內嵌套如下的VTL聲明:
<html>
<body>
Hello $customer.Name!
<table>
#foreach( $mud in $nudsOnSpecial )
#if ( $customer.hasPurchased( $mud ) )
<tr><td>$flogger.getPromo( $mud )</td></tr>
#end
#end
</table>
Velocity Template Language(VTL):AN introduction
VTL意味著提供最簡單、最容易并且最整潔的方式合并頁面動態內容。
VTL使用references來在web
site內嵌套動態內容,一個變量就是一種類型的reference。變量是某種類型的refreence,它可以指向java代碼中的定義,或者從當前
頁面內定義的VTL statement得到值。下面是一個VTL statement的例子,它可以被嵌套到HTML代碼中:
#set ( $a = “Velocity” )
和所有的VTL
statement一樣,這個statement以#字符開始并且包含一個directive:set。當一個在線用戶請求你的頁面時,Velocity
Templating Engine將查詢整個頁面以便發現所有#字符,然后確定哪些是VTL statement,哪些不需要VTL作任何事情。
#字符后緊跟一個directive:set時,這個set directive使用一個表達式(使用括號封閉)――一個方程式分配一個值給變量。變量被列在左邊,而它的值被列在右邊,最后他們之間使用=號分割。
在上面的例子中,變量是$a,而它的值是Velocity。和其他的references一樣以$字符開始,而值總是以雙引號封閉。Velocity中僅有String可以被賦值給變量。
記住以下的規則:
使用$字符開始的references用于得到什么;使用#字符開始的directives用于作些什么。
Hello Velocity World!
一旦某個變量被分配了一個值,那么你就可以在HTML文件的任何地方引用它。在下面的例子中,一個值被分配給$foo變量,并在其后被引用。
<html>
<body>
#set ( $foo = “Velocity” )
Hello $foo World!
</body>
</html>
上面的實現結果是在頁面上打印“Hello Velocity World!”
為了使包含VTL directives的statement更具有可讀性,我們鼓勵你在新行開始每個VTL statement,盡管你不是必須這么作。Set directive將在后面詳細描述。
注釋
單行注釋:
## This is a single line comment.
多行注釋:
#*
Thus begins a multi-line comment. Online visitors won’t
see this text because the Velocity Templating Engine will
ignore it.
*#
文檔格式:
#**
This is a VTL comment block and
may be used to store such information
as the document author and versioning
information:
@version 5
@author
*#
References
在VTL中有三種類型的references:變量(variables)、屬性(properties)、方法(methods)。作為一個使
用VTL的頁面設計者,你和你的工程師必須就references的名稱達成共識,以便你可以在你的template中使用它們。
Everything coming to and from a reference被作為一個String對象處理。如果有一個對象$foo是一個Integer對象,那么Velocity將調用它的toString()方法將這個對象轉型為String類型。
變量
格式要求同java。
屬性
例子:
$customer.Address
$purchase.Total
$customer.Address有兩種含義。它可以表示:查找hashtable對象customer中以Address為關鍵字的值;也可
以表示調用customer對象的getAddress()方法。當你的頁面被請求時,Velocity將確定以上兩種方式選用那種,然后返回適當的值。
方法
一個方法就是被定義在java中的一段代碼,并且它有完成某些有用工作的能力,例如一個執行計算和判斷條件是否成立、滿足等。方法是一個由$開始并跟隨VTL標識符組成的References,一般還包括一個VTL方法體。例如:
$customer.getAddress()
$purchase.getTotal()
$page.setTitle( “My Home Page” )
$person.setAttributes( [“Strange”, “Weird”, “Excited”] )
前兩個例子$customer.getAddress()和$purchase.getTotal()看起來挺想上面的屬性$customer.Address 和 $purchase.Total。如果你覺得他們之間有某種聯系的話,那你是正確的。
VTL屬性可以作為VTL方法的縮寫。$customer.Address屬性和使用$customer.getAddress()方法具有相同的效果。如果可能的話使用屬性的方式是比較合理的。屬性和方法的不同點在于你能夠給一個方法指定一個參數列表。
正式reference標記
reference的正是格式如下:
${mudSlinger} 變量
${customer.Address} 屬性
${purchase.getTotal()} 方法
非正是格式更見常用,但是有時還是使用正是格式比較適合。例如:你希望通過一個變量$vice來動態的組織一個字符串。
Jack is a $vicemaniac.
本來變量是$vice現在卻變成了$vicemaniac,這樣Veloctiy就不知道您到底要什么了。所以,應該使用正是格式書寫
Jack is a ${vice}maniac
現在Velocity知道變量是$vice而不是$vicemaniac。
Quiet reference notation
例如:
<input type=”text” name=”email” value=”$email” />
當頁面的form被初始加載時,變量$email還沒有值,這時你肯定是希望它能夠顯示一個blank text來代替輸出”$email”這樣的字段。那么使用quiet reference notation就比較合適。
<input type=”text” name=”email” value=”$!email”/>
這樣文本框的初始值就不會是email而是空值了。
正式和quiet格式的reference notation也可一同使用,像下面這樣:
<input type=”text” name=”email” value=”$!{email}”/>
Getting literal
Velocity使用特殊字符$和#來幫助它工作,所以如果要在template里使用這些特殊字符要格外小心。本節將討論$字符。
貨幣字符
在VTL中使用$2.5這樣的貨幣標識是沒有問題得的,VTL不會將它錯認為是一個reference,因為VTL中的reference總是以一個大寫或者小寫的字母開始。
Escaping valid VTL reference
VTL中使用“"”作為逃逸符。
例如:
#set( $email = “foo” )
$email
"$email
""$email
"""$email
將render為:
foo
$email
"foo
""$email
如果email變量沒有被定義則
$email
"$email
""$email
"""$email
將被render為:
$email
"$email
""$email
"""$email
注意:VTL中未被定義的變量將被認為是一個字符串,所以以下例子:
#set( $foo = “gibbous” )
$moon = $foo
的輸出結果是:
$moon = gibbous
Case substitution
現在你已經對reference比較熟悉了,你可以將他們高效的應用于你的template了。Velocity利用了很多java規范以方便了設計人員的使用。例如:
$foo
$foo.getBar()
## is the same as
$foo.Bar
$data.getUser(“jon”)
## is the same as
$data.User(“jon”)
$data.getRequest().getServerName()
# is the same as
$data.Request.ServerName
## is the same as
${data.Request.ServerName}
但是,注意VTL中不會將reference解釋為對象的實例變量。例如:$foo.Name將被解釋為Foo對象的getName()方法,而不是Foo對象的Name實例變量。
Directives
Reference允許設計者使用動態的內容,而directive使得你可以應用java代碼來控制你的顯示邏輯,從而達到你所期望的顯示效果。
#set
#set directive被用于設置一個reference的值。例如:
#set ( $primate = “monkey” )
#set ( $customer.Behavior = $primate )
賦值左側的(LHS)必須是一個變量或者屬性reference。右側(RHS)可以是以下類型中一種:
l 變量reference
l String literal
l 屬性reference
l 方法reference
l number literal
l ArrayList
下面是應用各種類型的RHS的例子:
#set ( $monkey = $bill ) ##變量reference
#set ( $monkey.Friend = “monica” ) ##String literal
#set ( $monkey.Blame = $whitehouse.Leak )##屬性reference
#set ( $monkey.Plan = $spindoctor.weave($web) )##方法reference
#set ( $monkey.Number = 123 )##Number literal
#set ( $monkey.Say = [“Not”, $my, “fault”] )##ArrayList
注意:最后一個例子的取值方法為:$monkey.Say.get(0)
RHS也可以是一個簡單的算術表達式:
#set ( $value = $foo + 1 )
#set ( $value = $bar -1 )
#set ( $value = $foo * $bar )
#set ( $value = $foo / $bar )
如果你的RHS是一個null,VTL的處理將比較特殊:它將指向一個已經存在的reference,這對初學者來講可能是比較費解的。例如:
#set ( $resut = $query.criteria(“name”) )
The result of the first query is $result
#set ( $resut = $query.criteria(“address”) )
The result of the second query is $result
如果$query.criteria(“name”)返回一個“bill”,而$query.criteria(“address”)返回的是null,則顯示的結果如下:
The result of the first query is bill
The result of the first query is bill
看看下面的例子:
#set( $criteria = ["name", "address"] )
#foreach( $criterion in $criteria )
#set( $result = $query.criteria($criterion) )
#if( $result )
Query was successful
#end
#end
在上面的例子中,程序將不能智能的根據$result的值決定查詢是否成功。在$result被#set后(added to the
context),它不能被設置回null(removed from the
context)。打印的結果將顯示兩次查詢結果都成功了,但是實際上有一個查詢是失敗的。
為了解決以上問題我們可以通過預先定義的方式:
#set( $criteria = [“name”, “address”] )
#foreach( $criterion in $criteria )
#set( $result = false )
#set( $result = $query.criteria( $criterion ) )
#if( $result )
Query was successful
#end
#end
String Literals
當你使用#set directive,String literal封閉在一對雙引號內。
#set ( $directoryRoot = “www” )
#set ( $templateName = “index.vm” )
#set ( $template = “$directoryRoot/$tempateName” )
$template
上面這段代碼的輸出結果為:www/index.vm
但是,當string literal被封裝在單引號內時,它將不被解析:
#set ( $foo = “bar” )
$foo
#set ( $blargh = ‘$foo’ )
結果:
bar
$foo
上面這個特性可以通過修改velocity.properties文件的stringliterals.interpolate = false的值來改變上面的特性是否有效。
條件語句
if/elseif/else
當一個web頁面被生成時使用Velocity的#if directrive,如果條件成立的話可以在頁面內嵌入文字。例如:
#if ( $foo )
<strong>Velocity!</strong>
#end
上例中的條件語句將在以下兩種條件下成立:
l $foo是一個boolean型的變量,且它的值為true
l $foo變量的值不為null
這里需要注意一點:Velocity context僅僅能夠包含對象,所以當我們說“boolean”時實際上代表的時一個Boolean對象。即便某個方法返回的是一個boolean值,Velocity也會利用內省機制將它轉換為一個Boolean的相同值。
如果條件成立,那么#if和#end之間的內容將被顯示。
#elseif和#else元素可以同#if一同使用。例如:
#if( $foo < 10 )
<strong> Go North </strong>
#elseif( $foo == 10 )
<strong> Go East </strong>
#elseif( $foo == 6 )
<strong> Go South </strong>
#else
<strong> Go West </strong>
#end
注意這里的Velocity的數字是作為Integer來比較的――其他類型的對象將使得條件為false,但是與java不同它使用“==”來比較兩個值,而且velocity要求等號兩邊的值類型相同。
關系、邏輯運算符
Velocity中使用等號操作符判斷兩個變量的關系。例如:
#set ( $foo = “deoxyribonucleic acid” )
#set ( $bar = “ribonucleic acid” )
#if ( $foo == $foo )
In this case it’s clear they aren’t equivalent.So…
#else
They are not equivalent and this will be the output.
#end
Velocity有AND、OR和NOT邏輯運算符。下面是一些例子:
## logical AND
#if( $foo && $bar )
<strong> This AND that </strong>
#end
## logical OR
#if ( $foo || $bar )
<strong>This OR That </strong>
#end
##logical NOT
#if ( !$foo )
<strong> NOT that </strong>
#end
循環
Foreach循環
例子:
<ul>
#foreach ( $product in $allProducts )
<li> $product </li>
#end
</ul>
每次循環$allProducts中的一個值都會賦給$product變量。
$allProducts可以是一個Vector、Hashtable或者Array。分配給$product的值是一個java對象,并且可以
通過變量被引用。例如:如果$product是一個java的Product類,并且這個產品的名字可以通過調用他的getName()方法得到。
現在我們假設$allProducts是一個Hashtable,如果你希望得到它的key應該像下面這樣:
<ul>
#foreach ( $key in $allProducts.keySet() )
<li>Key: $key -> Value: $allProducts.get($key) </li>
#end
</ul>
Velocity還特別提供了得到循環次數的方法,以便你可以像下面這樣作:
<table>
#foreach ( $customer in $customerList )
<tr><td>$velocityCount</td><td>$customer.Name</td></tr>
#end
</table>
$velocityCount變量的名字是Velocity默認的名字,你也可以通過修改velocity.properties文件來改變它。
默認情況下,計數從“1”開始,但是你可以在velocity.properties設置它是從“1”還是從“0”開始。下面就是文件中的配置:
# Default name of loop counter
# variable reference
directive.foreach.counter.name = velocityCount
# Default starting value of the loop
# counter variable reference
directive.foreach.counter.initial.value = 1
include
#include script element允許模板設計者引入本地文件。被引入文件的內容將不會通過模板引擎被render。為了安全的原因,被引入的本地文件只能在TEMPLATE_ROOT目錄下。
#inclued ( “one.txt” )
如果您需要引入多個文件,可以用逗號分隔就行:
#include ( “one.gif”, “two.txt”, “three.htm” )
在括號內可以是文件名,但是更多的時候是使用變量的:
#inclue ( “greetings.txt”, $seasonalstock )
parse
#parse script element允許模板設計者一個包含VTL的本地文件。Velocity將解析其中的VTL并render模板。
#parse( “me.vm” )
就像#include,#parse接受一個變量而不是一個模板。任何由#parse指向的模板都必須包含在TEMPLATE_ROOT目錄下。與#include不同的是,#parse只能指定單個對象。
你可以通過修改velocity.properties文件的parse_direcive.maxdepth的值來控制一個template可以包含的最多#parse的個數――默認值是10。#parse是可以遞歸調用的,例如:如果dofoo.vm包含如下行:
Count down.
#set ( $count = 8 )
#parse ( “parsefoo.vm” )
All done with dofoo.vm!
那么在parsefoo.vm模板中,你可以包含如下VTL:
$count
#set ( $count = $count – 1 )
#if ( $count > 0 )
#parse( “parsefoo.vm” )
#else
All done with parsefoo.vm!
#end
的顯示結果為:
Count down.
8
7
6
5
4
3
2
1
0
All done with parsefoo.vm!
All done with dofoo.vm!
Stop
#stop script element允許模板設計者停止執行模板引擎并返回。把它應用于debug是很有幫助的。
#stop
Velocimacros
#macro script element允許模板設計者定義一段可重用的VTL template。例如:
#macro ( d )
<tr><td></td></tr>
#end
在上面的例子中Velocimacro被定義為d,然后你就可以在任何VTL directive中以如下方式調用它:
#d()
當你的template被調用時,Velocity將用<tr><td></td></tr>替換為#d()。
每個Velocimacro可以擁有任意數量的參數――甚至0個參數,雖然定義時可以隨意設置參數數量,但是調用這個Velocimacro時必須指定正確的參數。下面是一個擁有兩個參數的Velocimacro,一個參數是color另一個參數是array:
#macro ( tablerows $color $somelist )
#foreach ( $something in $somelist )
<tr><td bgcolor=$color>$something</td</tr>
#end
#end
調用#tablerows Velocimacro:
#set ( $greatlakes = [ “Superior”, “Michigan”, “Huron”, “Erie”, “Ontario” ] )
#set ( $color = “blue” )
<table>
#tablerows( $color $greatlakes )
</table>
經過以上的調用將產生如下的顯示結果:
<table>
<tr><td bgcolor=” blue”> Superior </td></tr>
<tr><td bgcolor=” blue”> Michigan </td></tr>
<tr><td bgcolor=” blue”> Huron </td></tr>
<tr><td bgcolor=” blue”> Erie </td></tr>
<tr><td bgcolor=” blue”> Ontario </td></tr>
</table>
Velocimacros可以在Velocity模板內實現行內定義(inline),也就意味著同一個web
site內的其他Velocity模板不可以獲得Velocimacros的定義。定義一個可以被所有模板共享的Velocimacro顯然是有很多好處
的:它減少了在一大堆模板中重復定義的數量、節省了工作時間、減少了出錯的幾率、保證了單點修改。
上面定義的#tablerows( $color $list
)Velocimacro被定義在一個Velocimacros模板庫(在velocity.properties中定義)里,所以這個macro可以在
任何規范的模板中被調用。它可以被多次應用并且可以應用于不同的目的。例如下面的調用:
#set ( $parts = [ “volva”, “stipe”, “annulus”, “gills”, “pileus” ] )
#set ( $cellbgcol = “#CC00FF” )
<table>
#tablerows( $cellbgcol $parts )
</table>
上面VTL將產生如下的輸出:
<table>
<tr><td bgcolor=”#CC00FF”> volva </td</tr>
<tr><td bgcolor=”#CC00FF”> stipe </td</tr>
<tr><td bgcolor=”#CC00FF”> annulus </td</tr>
<tr><td bgcolor=”#CC00FF”> gills </td</tr>
<tr><td bgcolor=”#CC00FF”> pileus </td</tr>
</table>
Velocimacro arguments
Velocimacro可以使用以下任何元素作為參數:
l Reference:任何以$開頭的reference
l String literal:
l Number literal:
l IntegerRange:[1….3]或者[$foo….$bar]
l 對象數組:[“a”,”b”,”c”]
l boolean值:true、false
當將一個reference作為參數傳遞給Velocimacro時,請注意reference作為參數時是以名字的形式傳遞的。這就意味著參數
的值在每次Velocimacro內執行時才會被產生。這個特性使得你可以將一個方法調用作為參數傳遞給Velocimacro,而每次
Velocimacro執行時都是通過這個方法調用產生不同的值來執行的。例如:
#macro ( callme $a )
$a $a $a
#end
#callme( $foo.bar() )
執行的結果是:reference $foo的bar()方法被執行了三次。
如果你不需要這樣的特性可以通過以下方法:
#set ( $myval = $foo.bar() )
#callme ( $myval )
Velocimacro properties
Velocity.properties文件中的某幾行能夠使Velocimacros的實現更加靈活。注意更多的內容可以看Developer Guide。
Velocity.properties文件中的velocimacro.libraary:一個以逗號分隔的模板庫列表。默認情況下,velocity查找唯一的一個庫:VM_global_library.vm。你可以通過配置這個屬性來指定自己的模板庫。
Velocity.properties文件中的velocimacro.permissions.allow.inline屬性:有兩個可選的
值true或者false,通過它可以確定Velocimacros是否可以被定義在regular
template內。默認值是ture――允許設計者在他們自己的模板中定義Velocimacros。
Velocity.properties文件中的
velocimacro.permissions.allow.inline.replace.global屬性有兩個可選值true和
false,這個屬性允許使用者確定inline的Velocimacro定義是否可以替代全局Velocimacro定義(比如在
velocimacro.library屬性中指定的文件內定義的Velocimacro)。默認情況下,此值為false。這樣就阻止本地
Velocimacro定義覆蓋全局定義。
Velocity.properties文件中的
velocimacro.permissions.allow.inline.local.scale屬性也是有true和false兩個可選
值,默認是false。它的作用是用于確定你inline定義的Velocimacros是否僅僅在被定義的template內可見。換句話說,如果這個
屬性設置為true,一個inline定義的Velocimacros只能在定義它的template內使用。你可以使用此設置實現一個奇妙的VM敲
門:a template can define a private implementation of the second VM that
will be called by the first VM when invoked by that template. All other
templates are unaffected。
Velocity.properties文件中的velocimacro.context.localscope屬性有true和false兩個
可選值,默認值為false。當設置為true時,任何在Velocimacro內通過#set()對context的修改被認為是針對此
velocimacro的本地設置,而不會永久的影響內容。
Velocity.properties文件中的velocimacro.library.autoreload屬性控制Velocimacro
庫的自動加載。默認是false。當設置為ture時,對于一個Velocimacro的調用將自動檢查原始庫是否發生了變化,如果變化將重新加載它。這
個屬性使得你可以不用重新啟動servlet容器而達到重新加載的效果,就像你使用regular模板一樣。這個屬性可以使用的前提就是resource
loader緩存是off狀態(file.resource.loader.cache = false)。注意這個屬性實際上是針對開發而非產品的。
Velocimacro Trivia
Velocimacro必須被定義在他們被使用之前。也就是說,你的#macro()聲明應該出現在使用Velocimacros之前。
特別要注意的是,如果你試圖#parse()一個包含#macro()的模板。因為#parse()發生在運行期,但是解析器在
parsetiem決定一個看似VM元素的元素是否是一個VM元素,這樣#parse()-ing一組VM聲明將不按照預期的樣子工作。為了得到預期的結
果,只需要你簡單的使用velocimacro.library使得Velocity在啟動時加載你的VMs。
Escaping VTL directives
VTL directives can be escaped with “"”號,使用方式跟VTL的reference使用逃逸符的格式差不多。
## #include( “a.txt” ) renders as <ontents of a.txt>(注釋行)
#include( “a.txt” )
## "#include( “a.txt” ) renders as "#include( “a.txt” )
"#include( “a.txt” )
## ""#include ( “a.txt” ) renders as "<contents of a.txt>
""#include( “a.txt” )
在對在一個directive內包含多個script元素的VTL directives使用逃逸符時要特別小心(比如在一個if-else-end statement內)。下面是VTL的if-statement的典型應用:
#if ( $jazz )
Vyacheslav Ganelin
#end
如果$jazz是ture,輸出將是:
Vyacheslav Ganelin
如果$jazz是false,將沒有輸出。使用逃逸符將改變輸出。考慮一下下面的情況:
"#if ( $jazz )
Vyacheslav Ganelin
"#end
現在無論$jazz是true還是false,輸出結果都是:
#if ( $jazz )
Vyacheslav Ganelin
#end
事實上,由于你使用了逃逸符,$jazz根本就沒有被解析為boolean型值。在逃逸符前使用逃逸符是合法的,例如:
""#if ( $jazz )
Vyacheslav Ganelin
""#end
以上程序的顯示結果為:
" Vyacheslav Ganelin
但是如果$jazz為false,那么將沒有輸出。(書上說會沒有輸出,但是我覺得應該還有有“"”字符被輸出。)
VTL:Formatting issues
盡管在此用戶手冊中VTL通常都開始一個新行,如下所示:
#set ( $imperial = [ “Munetaka”, “Koreyasu”, “Hisakira”, “Morikune” ] )
#foreach ( $shogun in $imperial )
$shogun
#end
但是像下面這種寫法也是可以的:
Send me #set($foo = [“$10 and”,”a cake”])#foreach($a in $foo)$a #end please.
上面的代碼可以被改寫為:
Send me
#set ( $foo = [“$10 and “,”a cake”] )
#foreach ( $a in $foo )
$a
#end
please.
或者
Send me
#set($foo = [“$10 and “,”a cake”])
#foreach ($a in $foo )$a
#end please.
這兩種的輸出結構將一樣。
其他特性和雜項
math 在模板中可以使用Velocity內建的算術函數,如:加、減、乘、除
#set ( $foo = $bar + 3 )
#set ( $foo = $bar - 4 )
#set ( $foo = $bar * 6 )
#set ( $foo = $bar / 2 )
當執行除法時將返回一個Integer類型的結果。而余數你可以使用%來得到:
#set ( $foo = $bar % 5 )
在Velocity內使用數學計算公式時,只能使用像-n,-2,-1,0,1,2,n這樣的整數,而不能使用其它類型數據。當一個非整型的對象被使用時它將被logged并且將以null作為輸出結果。
Range Operator
Range operator可以被用于與#set和#foreach statement聯合使用。對于處理一個整型數組它是很有用的,Range operator具有以下構造形式:
[n..m]
m和n都必須是整型,而m是否大于n則無關緊要。例子:
First example:
#foreach ( $foo in [1..5] )
$foo
#end
Second example:
#foreach ( $bar in [2..-2] )
$bar
#end
Third example:
#set ( $arr = [0..1] )
#foreach ( $i in $arr )
$i
#end
Fourth example:
[1..3]
上面四個例子的輸出結果為:
First example:
1 2 3 4 5
Second example:
2 1 0 -1 -2
Third example:
0 1
Fourth example:
[1..3]
注意:range operator只在#set和#foreach中有效。
Advanced Issue:Escaping and!
當一個reference被“!”分隔時,并且在它之前有逃逸符時,reference將以特殊的方式處理。注意這種方式與標準的逃逸方式時不同的。對照如下:
#set ( $foo = “bar” )
特殊形式 標準格式
Render前 Render后 Render前 Render后
$"!foo $!foo "$foo "$foo
$"!{foo} $!{foo} "$!foo "$!foo
$""!foo $"!foo "$!{foo} "$!{foo}
$"""!foo $""!foo ""$!{foo} "bar
Velocimacro雜記
Can I user a directive or another VM as an argument to a VM?
例如:#center ( #bold( “hello” ) )
不可以。一個directive的參數使用另外一個directive是不合法的。
但是,還是有些事情你可以作的。最簡單的方式就是使用雙引號:
#set ( $stuff = “#bold( ‘hello’ )” )
#center ( $stuff )
上面的格式也可以縮寫為一行:
#center ( “#bold( ‘hello’ ) )
請注意在下面的例子中參數被evaluated在Velocimacro內部,而不是在calling level。例子:
#macro ( inner $foo )
inner : $foo
#end
#macro ( outer $foo )
#set ( $bar = “outerlala” )
outer : $foo
#end
#set ( $bar = ‘calltimelala’ )
#outer( “#inner($bar)” )
輸出結果為:
outer : inner : outerlala
記住Veloctiy的特性:參數的傳遞是By Name的。例如:
#macro ( foo $color )
<tr bgcolor = $color ><td>Hi</td></tr>
<tr bgcolor = $color ><td>There</td></tr>
#end
#foo ( $bar.rowColor() )
以上代碼將導致rowColor()方法兩次調用,而不是一次。為了避免這種現象的出現,我們可以按照下面的方式執行:
#set ( $color = $bar.rowColor() )
#foo ( $color )
can I register velocimacros via #parse()?
目前,Velocimacros必須在第一次被模板調用前被定義。這就意味著你的#macro()聲明應該出現在使用Velocimacros之前。
如果你試圖#parse()一個包含#macro()
directive的模板,這一點是需要牢記的。因為#parse()發生在運行期,但是解析器在parsetiem決定一個看似VM元素的元素是否是一
個VM元素,這樣#parse()-ing一組VM聲明將不按照預期的樣子工作。為了得到預期的結果,只需要你簡單的使用
velocimacro.library使得Velocity在啟動時加載你的VMs。
What is velocimacro autoreloading?
velocimacro.library.autoreload是專門為開發而非產品使用的一個屬性。此屬性的默認值是false。
String concatenation
開發人員最常問的問題是我如何作字符拼接?在java中是使用“+”號來完成的。
在VTL里要想實現同樣的功能你只需要將需要聯合的reference放到一起就行了。例如:
#set ( $size = “Big” )
#set ( $name = “Ben” )
The clock is $size$name.
輸出結果將是:The clock is BigBen.。更有趣的情況是:
#set ( $size = “Big” )
#set ( $name = “Ben” )
#set ( $clokc = “$size$name” )
The clock is $clock.
上例也會得到同樣的結果。最后一個例子,當你希望混合固定字段到你的reference時,你需要使用標準格式:
#set ( $size = “Big” )
#set ( $name = “Ben” )
#set ( $clock = “${size}Tall$name” )
The clock is $clock.
輸出結果是:The clock is BigTallBen.。使用這種格式主要是為了使得$size不被解釋為$sizeTall。
開始學習在 Java™ 平臺上使用諸如 Spring、Hibernate 或 MySQL
之類的開放源碼工具時可能非常困難。再加上 Ant 或 Maven,以及與 DWR 一起的小 Ajax,還有 Web 框架 —— 即
JSF,我們必須睜大眼睛盯著如何配置應用程序。AppFuse
減少了集成開放源碼項目的痛苦。它可以把測試變成一等公民,讓我們可以從數據庫表生成整個 UI,并使用 XFire 來支持 Web
服務。另外,AppFuse 的社區也非常健全,這是不同 Web 框架用戶可以一起融洽相處的地方之一。
AppFuse
是一個開放源碼的項目和應用程序,它使用了在 Java 平臺上構建的開放源碼工具來幫助我們快速而高效地開發 Web
應用程序。我最初開發它是為了減少在為客戶構建新 Web 應用程序時所花費的那些不必要的時間。從核心上來說,AppFuse
是一個項目骨架,類似于通過向導創建新 Web 項目時 IDE 所創建的東西。當我們使用 AppFuse
創建一個項目時,它會提示我們將使用開放源碼框架,然后才創建項目。它使用 Ant
來驅動測試、代碼生成、編譯和部署。它提供了目錄和包結構,以及開發基于 Java 語言的 Web 應用程序所需要的庫。
與大部分 “new project” 向導不同,AppFuse
創建的項目從最開始就包含很多類和文件。這些文件用來實現特性,不過它們同時也會在您開發應用程序時被用作示例。通過使用 AppFuse
啟動新項目,我們通常可以減少一到兩周的開發時間。我們不用擔心如何將開放源碼框架配置在一起,因為這都已經完成了。我們的項目都已提前配置來與數據庫進
行交互,它會部署到應用服務器上,并對用戶進行認證。我們不必實現安全特性,因為這都早已集成了。
當我最初開發 AppFuse 時,它只支持 Struts 和 Hibernate。經過幾年的努力,我發現了比 Struts 更好的 Web
框架,因此我還添加了為這些 Web 框架使用的選項。現在,AppFuse 可以支持 Hibernate 或 iBATIS 作為持久性框架。對于
Web 框架來說,我們可以使用 JavaServer Faces(JSF)、Spring MVC、Struts、Tapestry 或
WebWork。
AppFuse 提供了很多應用程序需要的一些特性,包括:
- 認證和授權
- 用戶管理
- Remember Me(這會保存您的登錄信息,這樣就不用每次都再進行登錄了)
- 密碼提醒
- 登記和注冊
- SSL 轉換
- E-mail
- URL 重寫
- 皮膚
- 頁面修飾
- 模板化布局
- 文件上載
這種 “開箱即用” 的功能是 AppFuse 與其他 CRUD 代 框架的區別之一(CRUD 取自創建、檢索、更新 和刪除 幾個操作的英文首字母),包括 Ruby on Rails、Trails 和 Grails。上面提到的這些框架,以及 AppFuse,都讓我們可以從數據庫表或現有的模型對象中生成主頁/細節頁。
圖 1 闡述了一個典型 AppFuse 應用程序的概念設計:
圖 1. 典型的 AppFuse 應用程序
清單 1 給出了我們在創建 devworks 項目時所使用的命令行交互操作,同時還給出了所生成的結果。這個項目使用了 WebWork 作為自己的 Web 框架(請參考下面 參考資料 一節給出的鏈接)。
清單 1. 使用 AppFuse 創建新項目
alotta:~/dev/appfuse mraible$ ant new
Buildfile: build.xml
clean:
[echo] Cleaning build and distribution directories
init:
new:
[echo]
[echo] +-------------------------------------------------------------+
[echo] | -- Welcome to the AppFuse New Application Wizard! -- |
[echo] | |
[echo] | To create a new application, please answer the following |
[echo] | questions. |
[echo] +-------------------------------------------------------------+
[input] What would you like to name your application [myapp]?
devworks
[input] What would you like to name your database [mydb]?
devworks
[input] What package name would you like to use [org.appfuse]?
com.ibm
[input] What web framework would you like to use [webwork,tapestry,spring,js
f,struts]?
webwork
[echo] Creating new application named 'devworks'...
[copy] Copying 359 files to /Users/mraible/Work/devworks
[copy] Copying 181 files to /Users/mraible/Work/devworks/extras
[copy] Copying 1 file to /Users/mraible/Work/devworks
[copy] Copying 1 file to /Users/mraible/Work/devworks
install:
[echo] Copying WebWork JARs to ../../lib
[copy] Copying 6 files to /Users/mraible/Work/devworks/lib
[echo] Adding WebWork entries to ../../lib.properties
[echo] Adding WebWork classpath entries
[echo] Removing Struts-specific JARs
[delete] Deleting directory /Users/mraible/Work/devworks/lib/struts-1.2.9
[delete] Deleting directory /Users/mraible/Work/devworks/lib/strutstest-2.1.3
[echo] Deleting struts_form.xdt for XDoclet
[delete] Deleting directory /Users/mraible/Work/devworks/metadata/templates
[echo] Deleting Struts merge-files in metadata/web
[delete] Deleting 7 files from /Users/mraible/Work/devworks/metadata/web
[echo] Deleting unused Tag Libraries and Utilities
[delete] Deleting 2 files from /Users/mraible/Work/devworks/src/web/org/appfu
se/webapp
[echo] Modifying appgen for WebWork
[copy] Copying 12 files to /Users/mraible/Work/devworks/extras/appgen
[echo] Replacing source and test files
[delete] Deleting directory /Users/mraible/Work/devworks/src/web/org/appfuse/
webapp/form
[delete] Deleting directory /Users/mraible/Work/devworks/src/web/org/appfuse/
webapp/action
[copy] Copying 13 files to /Users/mraible/Work/devworks/src
[delete] Deleting directory /Users/mraible/Work/devworks/test/web/org/appfuse
/webapp/form
[delete] Deleting directory /Users/mraible/Work/devworks/test/web/org/appfuse
/webapp/action
[copy] Copying 5 files to /Users/mraible/Work/devworks/test
[echo] Replacing web files (images, scripts, JSPs, etc.)
[delete] Deleting 1 files from /Users/mraible/Work/devworks/web/scripts
[copy] Copying 34 files to /Users/mraible/Work/devworks/web
[delete] Deleting: /Users/mraible/Work/devworks/web/WEB-INF/validator-rules-c
ustom.xml
[echo] Modifying Eclipse .classpath file
[echo] Refactoring build.xml
[echo] ----------------------------------------------
[echo] NOTE: It's recommended you delete extras/webwork as you shouldn't ne
ed it anymore.
[echo] ----------------------------------------------
[echo] Repackaging info written to rename.log
[echo]
[echo] +-------------------------------------------------------------+
[echo] | -- Application created successfully! -- |
[echo] | |
[echo] | Now you should be able to cd to your application and run: |
[echo] | > ant setup test-all |
[echo] +-------------------------------------------------------------+
BUILD SUCCESSFUL
Total time: 15 seconds
|
 |
為什么使用 WebWork?
Struts 社區最近在熱情地擁抱 WebWork,這種結合導致為 Java 平臺提供了一個非常優秀的新 Web 框架:Struts
2。當然,Spring MVC 是一個非常優秀的基于請求的框架,但是它不能像 Struts 2 一樣支持 JSF。基于內容的框架(例如 JSF 和
Tapestry)也都很好,但是我發現 WebWork 更為直觀,更容易使用(更多有關 Structs 2 和 JSF 的內容請參看 參考資料)。
|
|
在創建一個新項目之后,我們就得到了一個類似于圖 2 所示的目錄結構。Eclipse 和 Intellij IDEA 項目文件都是作為這個過程的一部分創建的。
圖 2. 項目的目錄結構
這個目錄結構與 Sun 為 Java 2 Platform Enterprise Edition(J2EE)Web
應用程序推薦的目錄結構非常類似。在 2.0 版本的 AppFuse 中,這個結構會變化成適合 Apache Maven
項目的標準目錄結構(有關這兩個目錄介紹的內容,請參看 參考資料
中的鏈接)。AppFuse 還會從 Ant 遷移到 Maven 2 上,從而獲得相關下載的能力和對生成 IDE 項目文件的支持。目前基于
Ant 的系統要求提交者維護項目文件,而 Maven 2 可以通過簡單地使用項目的 pom.xml 文件生成 IDEA、Eclipse 和
NetBeans 項目文件。(這個文件位于您項目的根目錄中,是使用 Maven 構建應用程序所需要的主要組件)。它與利用 Ant 所使用的
build.xml 文件非常類似。)
現在我們對 AppFuse 是什么已經有一點概念了,在本文剩下的部分中,我們將介紹使用 AppFuse 的 7 點理由。即使您選擇不使用
AppFuse 來開始自己的項目,也會看到 AppFuse 可以為您提供很多樣板代碼,這些代碼可以在基于 Java 語言的 Web
應用程序中使用。由于它是基于 Apache 許可證的,因此非常歡迎您在自己的應用程序中重用這些代碼。
理由 1:測試
測試是在軟件開發項目中很少被給予足夠信任的一個環節。注意我并不是說在軟件開發的一些刊物中沒有得到足夠的信任!很多文章和案例研究都給出了測試
優先的開發方式和足夠的測試覆蓋面以提高軟件的質量。然而,測試通常都被看作是一件只會延長項目開發時間的事情。實際上,如果我們使用測試優先的方法在編
寫代碼之前就開始撰寫測試用例,我相信我們可以發現這實際上會加速 開發速度。另外,測試優先也可以使維護和重用更加 容易。如果我們不編寫代碼來測試自己的代碼,那么就需要手工對應用程序進行測試 —— 這通常效率都不高。自動化才是關鍵。
當我們首次開始使用 AppFuse 時,我們可能需要閱讀這個項目 Web 站點上提供的快速入門指南和教程(請參看 參考資料
中的鏈接)。這些教程的編寫就是為了您可以首先編寫測試用例;它們直到編寫接口和/或實現之后才能編譯。如果您有些方面與我一樣,就會在開始編寫代碼之前
就已經編寫好測試用例了;這是真正可以加速編寫代碼的惟一方式。如果您首先編寫了代碼的實現,通過某種方式驗證它可以工作,那么您可能會對自己說,“哦,
看起來不錯 —— 誰需要測試呢?我還有更多的代碼需要編寫!”這種情況不幸的一面是您通常都會做一些事情 來測試自己的代碼;您簡單地跳過了可以自動化進行測試的地方。
AppFuse 的文檔展示了如何測試應用程序的所有 層次。它從數據庫層開始入手,使用了 DbUnit(請參看 參考資料)在運行測試之前提前使用數據來填充自己的數據庫。在數據訪問(DAO)層,它使用了 Spring 的 AbstractTransactionalDataSourceSpringContextTests 類(是的,這的確是一個類的名字!)來允許簡單地加載 Spring 上下文文件。另外,這個類對每個 testXXX() 方法封裝了一個事務,并當測試方法存在時進行回滾。這種特性使得測試 DAO 邏輯變得非常簡單,并且不會對數據庫中的數據造成影響。
在服務層,jMock (請參看 參考資料)用來編寫那些可以消除 DAO 依賴的真正 單元測試。這允許進行驗證業務邏輯正確的快速測試;我們不用擔心底層的持久性邏輯。
 |
HtmlUnit 支持
HtmlUnit 團隊在 1.8 發行版中已經完成了相當多的工作來確保包可以與流行的 Ajax 框架(Prototype 和 Scriptaculous)很好地工作。
|
|
在 Web 層,測試會驗證操作(Struts/WebWork)、控件(Spring MVC)、頁面(Tapestry)和管理
bean(JSF)如我們所期望的一樣進行工作。Spring 的 spring-mock.jar
可以非常有用地用來測試所有這些框架,因為它包含了一個 Servlet API 的仿真實現。如果沒有這個有用的庫,那么測試 AppFuse 的
Web 框架就會變得非常困難。
UI 通常是開發 Web 應用程序過程中最為困難的一部分。它也是顧客最經常抱怨的地方 ——
這既是由于它并不是非常完美,也是由于它的工作方式與我們期望的并不一樣。另外,沒有什么會比在客戶面前作演示的過程中看到看到異常堆棧更糟糕的了!您的
應用程序可能會非常可怕,但是客戶可能會要求您做到十分完美。永遠不要讓這種事情發生。Canoo WebTest 可以對 UI 進行測試。它使用了
HtmlUnit 來遍歷測試 UI,驗證所有的元素都存在,并可以填充表單的域,甚至可以驗證一個假想的啟用 Ajax 的 UI
與我們預期的工作方式一樣。(有關 WebTest 和 HTMLUnit 的鏈接請參看 參考資料。)
為了進一步簡化 Web 的測試,Cargo(請參看 參考資料)對 Tomcat 的啟動和停止(分別在運行 WebTest 測試之前和之后)進行了自動化。
理由 2:集成
正如我在本文簡介中提到的一樣,很多開放源碼庫都已經預先集成到 AppFuse 中了。它們可以分為以下幾類:
- 編譯、報告和代碼生成:Ant、Ant Contrib Tasks、Checkstyle、EMMA、Java2Html、PMD 和 Rename Packages
- 測試框架:DbUnit、Dumbster、jMock、JUnit 和 Canoo WebTest
- 數據庫驅動程序:MySQL 和 PostgreSQL
- 持久性框架:Hibernate 和 iBATIS
- IoC 框架:Spring
- Web 框架:JSF、Spring MVC、Struts、Tapestry 和 WebWork
- Web 服務:XFire
- Web 工具:Clickstream、Display Tag、DWR、JSTL、SiteMesh、Struts Menu 和 URL Rewrite Filter
- Security:Acegi Security
- JavaScript 和 CSS:Scriptaculous、Prototype 和 Mike Stenhouse 的 CSS Framework
除了這些庫之外,AppFuse 還使用 Log4j 來記錄日志,使用 Velocity 來構建 e-mail 和菜單模板。Tomcat
可以支持最新的開發,我們可以使用 1.4 或 5 版本的 Java 平臺來編譯或構建程序。我們應該可以將 AppFuse 部署到任何 J2EE
1.3 兼容的應用服務器上;這已經經過了測試,我們知道它在所有主要版本的 J2EE 服務器和所有主要的 servlet 容器上都可以很好地工作。
圖 3 給出了上面創建的 devworks 項目的 lib 目錄。這個目錄中的 lib.properties 文件控制了每個依賴性的版本號,這意味著我們可以簡單地通過把這些包的新版本放到這個目錄中并執行諸如 ant test-all -Dspring.version=2.0 之類的命令來測試這些包的新版本。
圖 3. 項目依賴性
預先集成這些開放源碼庫可以在項目之初極大地提高生產效率。盡管我們可以找到很多文檔介紹如何集成這些庫,但是定制工作示例并簡單地使用它來開發應用程序要更加簡單。
除了可以簡化 Web 應用程序的開發之外,AppFuse 讓我們還可以將 Web 服務簡單地集成到自己的項目中。盡管 XFire 也在 AppFuse 下載中一起提供了,不過如果我們希望,也可以自己集成 Apache Axis(請參看 參考資料 中有關 Axis 集成的教程)。另外,Spring 框架和 XFire 可以一起將服務層作為 Web 服務非常簡單地呈現出來,這就為我們提供了開發面向服務架構的能力。
另外,AppFuse 并不會將我們限定到任何特定的 API
上。它只是簡單地對可用的最佳開放源碼解決方案重新進行打包和預先集成。AppFuse 中的代碼可以處理這種集成,并實現了 AppFuse
的基本安全性和可用性特性。只要可能,就會減少代碼,以便向 AppFuse 的依賴框架添加一個特性。例如,AppFuse 自帶的 Remember
Me 和 SSL 切換特性最近就因為類似的特性而從 Acegi Security 中刪除了。
理由 3:自動化
Ant 使得簡化了從編譯到構建再到部署的自動化過程。Ant 是 AppFuse 中的一等公民,這主要是因為我發現在命令行中執行操作比從 IDE 中更加簡單。我們可以使用 Ant 實現編譯、測試、部署和執行任何代碼生成的任務。
盡管這種能力對于有些人來說非常重要,但是它并不適用于所有的人。很多 AppFuse 用戶目前都使用 Eclipse 或 Intellij
IDEA 來構建和測試自己的項目。在這些 IDE 中運行 Ant 的確可以工作,但是這樣做的效率通常都不如使用 IDE 內置的 JUnit
支持來運行測試效率高。
幸運的是,AppFuse 支持在 IDE 中運行測試,不過管理這種特性對于 AppFuse 開發人員來說就變得非常困難了。最大的痛苦在于
XDoclet 用來生成 Hibernate 映射文件和 Web 框架所使用的一些工件(例如 ActionForms 和 Struts 使用的
struts-config.xml)。IDE 并不知道需要生成的代碼,除非我們配置使用 Ant 來編譯它們,或者安裝了一些可以認識
XDoclet 的插件。
這種對知識的缺乏是 AppFuse 2.0 切換到 JDK 5 和 Maven 2 上的主要原因。JDK 5、注釋和 Struts 2
將讓我們可以擺脫 XDoclet。Maven 2 將使用這些生成的文件和動態類路徑來生成 IDE
項目文件,這樣對項目的管理就可以進行簡化。目前基于 Ant 的編譯系統已經為不同的層次生成了一些工件(包括
dao.jar、service.jar 和 webapp.war),因此切換到 Maven 的模型上應該是一個非常自然的調整。
除了 Ant 之外(它對于編譯、測試、部署和報告具有豐富的支持),對于 CruiseControl 的支持也構建到了 AppFuse
中。CruiseControl 是一個 Continuous Integration
應用程序,讓我們可以在源代碼倉庫中代碼發生變化時自動運行所有的測試。extras/cruisecontrol 目錄包含了我們為基于
AppFuse 的項目快速、簡單地設置 Continuous Integration 時所需要的文件。
設置 Continuous Integration 是軟件開發周期中我們首先要做的事情之一。它不但激發程序員去編寫測試用例,而且還通過 “You broke the build!” 游戲促進了團隊之間的合作和融合。
理由 4:安全特性和可擴展性
AppFuse 最初是作為我為 Apress 編寫的書籍 Pro JSP
中示例應用程序的一部分開發的。這個示例應用程序展示了很多安全特性和用于簡化 Struts 開發的特性。這個應用程序中的很多安全特性在 J2EE
的安全框圖中都不存在。使用容器管理認證(CMA)的認證方法非常簡單,但是 Remember Me、密碼提示、SSL
切換、登記和用戶管理等功能卻都不存在。另外,基于角色的保護方法功能在非 EJB 環境中也是不可能的。
最初,AppFuse 使用自己的代碼和用于 CMA 的解決方案完全實現了這些特性。我在 2004 年年初開始學習 Spring
時就聽說過有關 Acegi Security 的知識。我對 Acegi 所需要的 XML 的行數(175)與 web.xml 中所需要的 CMA
的行數(20)進行了比較,很快就決定丟棄 Acegi 了,因為它太過復雜了。
一年半之后 —— 在為另外一本書 Spring Live 中編寫了一章有關使用 Acegi Security 的內容之后 —— 我就改變了自己的想法。Acegi 的確(目
前仍然)需要很多 XML,但是一旦我們理解了這一點,它實際上是相當簡單的。當我們最終作出改變,使用 Acegi Security
的特性來全部取代 AppFuse 的特性之后,我們最終刪除了大量的代碼。類之上的類都已經沒有了,“Acegi handles that now”
中消失的部分現在全部進入了 CVS 的 Attic 中了。
Acegi Security 是 J2EE 安全模型中曾經出現過的最好模型。它讓我們可以實現很多有用的特性,這些特性在 Servlet
API 的安全模型中都不存在:認證、授權、角色保護方法、Remember Me、密碼加密、SSL
切換、用戶切換和注銷。它讓我們還可以將用戶證書存儲到 XML 文件、數據庫、LDAP 或單點登錄系統(例如 Yale 的 Central
Authentication Service (CAS) 或者 SiteMinder)中。
AppFuse 對很多與安全性相關的特性的實現從一開始都是非常優秀的。現在 AppFuse 使用了 Acegi Security,這些特性
—— 以及更多特性 —— 都非常容易實現。Acegi 有很多地方都可以進行擴充:這是它使用巨大的 XML
配置文件的原因。正如我們已經通過去年的課程對 Acegi 進行集成一樣,我們已經發現對很多 bean 的定義進行定制可以更加緊密地與
AppFuse 建立聯系。
Spring IoC 容器和 Acegi Security 所提供的簡單開發、容易測試的代碼和松耦合特性的組合是 AppFuse
是這么好的一種開發平臺的主要原因。這些框架都是不可插入的,允許生成干凈的可測試代碼。AppFuse
集成了很多開放源碼項目,依賴注入允許對應用程序層進行簡單的集成。
理由 5:使用 AppGen 生成代碼
有些人會將代碼生成稱為代碼氣味的散播(code smell)。在他們的觀點中,如果我們需要生成代碼,那么很可能就會做一些錯事。我傾向于這種確定自己代碼使用的模式和自動化生成代碼的能力應該稱為代碼香味的彌漫(code perfume)。如果我們正在編寫類似的 DAO、管理器、操作或控件,并且不想為它們生成代碼,那么這就需要根據代碼的氣味來生成代碼。當然,當語言可以為我們提供可以簡化任務的特性時,一切都是那么美好;不過代碼生成通常都是一個必需 —— 通常其生產率也非常高 —— 的任務。
AppFuse 中提供了一個基于 Ant 和 XDoclet 的代碼生成工具,名叫 AppGen。默認情況下,常見的 DAO 和管理器都可以允許我們對任何普通老式 Java 對象(POJO)進行 CRUD 操作,但是在 Web 層上這樣做有些困難。AppGen 有幾個特性可以用來執行以下任務:
- (使用 Middlegen 和 Hibernate 工具)從數據庫表中生成 POJO
- 從 POJO 生成 UI
- 為 DAO、管理器、操作/控制器和 UI 生成測試
在運行 AppGen 時,您會看到提示說 AppGen 要從數據庫表或 POJO 中生成代碼。如果在命令行中執行 ant install-detailed ,AppGen 就會安裝 POJO 特定的 DAO、管理器以及它們的測試。運行 ant install 會導致 Web 層的類重用通用的 DAO 和默認存在的管理器。
為了闡述 AppGen 是如何工作的,我們在 devworks MySQL 數據庫中創建了如清單 2 所示的表:
清單 2. 創建一個名為 cat 的數據庫表
create table cat (
cat_id int(8) auto_increment,
color varchar(20) not null,
name varchar(20) not null,
created_date datetime not null,
primary key (cat_id)
) type=InnoDB;
|
在 extras/appgen 目錄中,運行 ant install-detailed 。這個命令的輸出結果對于本文來說實在太長了,不過我們在清單 3 中給出了第一部分的內容:
清單 3. 運行 AppGen 的 install-detailed 目標
$ ant install-detailed
Buildfile: build.xml
init:
[mkdir] Created dir: /Users/mraible/Work/devworks/extras/appgen/build
[echo]
[echo] +-------------------------------------------------------+
[echo] | -- Welcome to the AppGen! -- |
[echo] | |
[echo] | Use the "install" target to use the generic DAO and |
[echo] | Manager, or use "install-detailed" to general a DAO |
[echo] | and Manager specifically for your model object. |
[echo] +-------------------------------------------------------+
[input] Would you like to generate code from a table or POJO? (table,pojo)
table
[input] What is the name of your table (i.e. person)?
cat
[input] What is the name, if any, of the module for your table (i.e. organization)?
[echo] Running Middlegen to generate POJO...
|
要對 cat 表使用這個新生成的代碼,我們需要修改
src/dao/com/ibm/dao/hibernate/applicationContext-hibernate.xml,來為
Hibernate 添加 Cat.hbm.xml 映射文件。清單 3 給出了我們修改后的 sessionFactory bean 的樣子:
清單 4. 將 Cat.hbm.xml 添加到 sessionFactory bean 中
<bean id="sessionFactory" class="...">
<property name="dataSource" ref="dataSource"/>
<property name="mappingResources">
<list>
<value>com/ibm/model/Role.hbm.xml</value>
<value>com/ibm/model/User.hbm.xml</value>
<value>com/ibm/model/Cat.hbm.xml</value>
</list>
</property>
...
</bean>
|
在運行 ant setup deploy 之后,我們就應該可以在部署的應用程序中對 cat 表執行 CRUD 操作了:
圖 4. Cat 列表
圖 5. Cat 表單
我們在上面的屏幕快照中看到的記錄都是作為代碼生成的一部分創建的,因此現在就有測試數據了。
理由 6:文檔
我們可以找到 AppFuse 各個風味的教程,并且它們都以 6 種不同的語言給出了:中文、德語、英語、韓語、葡萄牙語和西班牙語。使用風味(flavor)
一詞,我的意思是不同框架的組合,例如 Spring MVC 加上 iBATIS、Spring MVC 加上 Hibernate 或 JSF
加上 Hibernate。使用這 5 種 Web 框架和兩種持久框架,可以有好幾種組合。有關它們的翻譯,AppFuse 為自己的默認特性提供了 8
種翻譯。可用語言包括中文、荷蘭語、德語、英語、法語、意大利語、葡萄牙語和西班牙語。
除了核心教程之外,還添加了很多教程(請參看 參考資料) 來介紹與各種數據庫、應用服務器和其他開放源碼技術(包括 JasperReports、Lucene、Eclipse、Drools、Axis 和 DWR)的集成。
理由 7:社區
Apache 軟件基金會對于開放源碼有一個有趣的看法。它對圍繞開放源碼項目開發一個開放源碼社區最感興趣。它的成員相信如果社區非常強大,那么產生高質量的代碼就是一個自然的過程。下面的內容引自 Apache 主頁:
“我們認為自己不僅僅是一組共享服務器的項目,而且是一個開發人員和用戶的社區。”
AppFuse 社區從 2003 年作為 SourceForge 上的一個項目(是 struts.sf.net
的一部分)啟動以來,已經獲得了極大的增長。通過在 2004 年 3 月轉換到 java.net 上之后,它已經成為這里一個非常流行的項目,從
2005 年 1 月到 3 月成為訪問量最多的一個項目。目前它仍然是一個非常流行的項目(有關 java.net 項目統計信息的鏈接,請參看 參考資料),不過在這個站點上它正在讓位于 Sun 贊助的很多項目。
在 2004 年年末,Nathan Anderson 成為繼我之后第一個提交者。此后有很多人都加入了進來,包括 Ben
Gill、David Carter、Mika G?ckel、Sanjiv Jivan 和 Thomas
Gaudin。很多現有的提交者都已經通過各種方式作出了自己的貢獻,他們都幫助 AppFuse 社區成為一個迅速變化并且非常有趣的地方。
郵件列表非常友好,我們試圖維護這樣一條承諾 “沒有問題是沒有人理會的問題”。我們的郵件列表歸檔文件中惟一一條 “RTFM”
是從用戶那里發出的,而不是從開發者那里發出的。我們絕對信奉 Apache 開放源碼社區的哲學。引用我最好的朋友 Bruce Snyder
的一句話,“我們為代碼而來,為人們而留下”。目前,大部分開發者都是用戶,我們通常都喜歡有一段美妙的時間。另外,大部分文檔都是由社區編寫的;因此,
這個社區的知識是非常淵博的。
結束語
我們應該嘗試使用 AppFuse 進行開發,這是因為它允許我們簡單地進行測試、集成、自動化,并可以安全地生成 Web 應用程序。其文檔非常豐富,社區也非常友好。隨著其支撐框架越來越好,AppFuse 也將不斷改進。
從 AppFuse 2.0 開始,我們計劃遷移到 JDK 5(仍然支持部署到 1.4)和 Maven 2 上去。這些工具可以簡化使用
AppFuse 的開發、安裝和升級。我們計劃充分利用 Maven 2 的功能來處理相關依賴性。我們將碰到諸如
appfuse-hibernate-2.0.jar 和 appfuse-jsf-2.0.jar 之類的工件。這些工件都可以在 pom.xml
文件中進行引用,它們負責提取其他相關依賴性。除了在自己的項目中使用 AppFuse 基類之外,我們還可以像普通的框架一樣在 JAR
中對這些類簡單地進行擴展,這應該會大大簡化它的升級過程,并鼓勵更多用戶將自己希望的改進提交到這個項目中。
如果沒有其他問題,使用 AppFuse 可以讓您始終處于 Java Web 開發的技術前沿上 —— 就像我們一樣!
參考資料
學習
獲得產品和技術
討論
摘要: http://smc.sourceforge.net/SmcManSec1g.htm
教程第一部分:.sm文件的布局
1.1任務類(對象)
SMC生成有限狀態機的對象-而不是進程或應用程序,而是一個單獨的對象,如果你有對象的接收或者異步回調,以及基于對象的狀態如何應對這些回調,SMC提供了一個強大的解決方案.
(注:這個例子是基于Java和簡單容易翻譯成其他語言。此外,這個例子假設你知道面... 閱讀全文
引言
數據庫的設計范式是數據庫設計所需要滿足的規范,滿足這些規范的數據庫是簡潔的、結構明晰的,同時,不會發生插入(insert)、刪除
(delete)和更新(update)操作異常。反之則是亂七八糟,不僅給數據庫的編程人員制造麻煩,而且面目可憎,可能存儲了大量不需要的冗余信息。
設計范式是不是很難懂呢?非也,大學教材上給我們一堆數學公式我們當然看不懂,也記不住。所以我們很多人就根本不按照范式來設計數據庫。
實質上,設計范式用很形象、很簡潔的話語就能說清楚,道明白。本文將對范式進行通俗地說明,并以筆者曾經設計的一個簡單論壇的數據庫為例來講解怎樣將這
些范式應用于實際工程。
范式說明
第一范式(1NF):數據庫表中的字段都是單一屬性的,不可再分。這個單一屬性
由基本類型構成,包括整型、實數、字符型、邏輯型、日期型等。
例如,如下的數據庫表是符合第一范式的:
而這樣的數據庫表是不符合第一范式的:
字段1 |
字段2 |
字段3 |
字段4 |
|
|
字段3.1 |
字段3.2 |
|
很顯然,在當前的任何關系數據庫管理系
統(DBMS)中,傻瓜也不可能做出不符合第一范式的數據庫,因為這些DBMS不允許你把數據庫表的一列再分成二列或多列。因此,你想在現有的DBMS中
設計出不符合第一范式的數據庫都是不可能的。
第二范式(2NF):數據庫表中不存在非關鍵字段對任一候選關鍵字段的部分函數依賴(部
分函數依賴指的是存在組合關鍵字中的某些字段決定非關鍵字段的情況),也即所有非關鍵字段都完全依賴于任意一組候選關鍵字。
假定選課關系表為SelectCourse(學號, 姓名, 年齡, 課程名稱, 成績, 學分),關鍵字為組合關鍵字(學號,
課程名稱),因為存在如下決定關系:
(學號, 課程名稱) → (姓名, 年齡, 成績, 學分)
這個數據庫表
不滿足第二范式,因為存在如下決定關系:
(課程名稱) → (學分)
(學號) → (姓名, 年齡)
即存在組合關鍵字中的字段決定非關鍵字的情況。
由于不符合2NF,這個選課關系表會存在如下問題:
(1)
數據冗余:
同一門課程由n個學生選修,"學分"就重復n-1次;同一個學生選修了m門課程,姓名和年齡就重復了m-1次。
(2) 更新異常:
若調整了某門課程的學分,數據表中所有行的"學分"值都要更新,否則會出現同一門課程學分不同的情況。
(3) 插入異常:
假設要開設一門新的課程,暫時還沒有人選修。這樣,由于還沒有"學號"關鍵字,課程名稱和學分也無法記錄入數據
庫。
(4) 刪除異常:
假設一批學生已經完成課程的選修,這些選修記錄就應該從數據庫表中刪除。但是,與此同
時,課程名稱和學分信息也被刪除了。很顯然,這也會導致插入異常。
把選課關系表SelectCourse改為如下三個表:
學生:Student(學號, 姓名, 年齡);
課程:Course(課程名稱, 學分);
選課關
系:SelectCourse(學號, 課程名稱, 成績)。
這樣的數據庫表是符合第二范式的,消除了數據冗余、更新異常、插入異常
和刪除異常。
另外,所有單關鍵字的數據庫表都符合第二范式,因為不可能存在組合關鍵字。
第三范式(3NF):在
第二范式的基礎上,數據表中如果不存在非關鍵字段對任一候選關鍵字段的傳遞函數依賴則符合第三范式。所謂傳遞函數依賴,指的是如果存在"A → B →
C"的決定關系,則C傳遞函數依賴于A。因此,滿足第三范式的數據庫表應該不存在如下依賴關系:
關鍵字段 → 非關鍵字段x →
非關鍵字段y
假定學生關系表為Student(學號, 姓名, 年齡, 所在學院, 學院地點, 學院電話),關鍵字為單一關鍵字"學號",因為存在如下決定關系:
(學號) →
(姓名, 年齡, 所在學院, 學院地點, 學院電話)
這個數據庫是
符合2NF的,但是不符合3NF,因為存在如下決定關系:
(學號) → (所在學院) → (學院地點, 學院電話)
即存在非關鍵
字段"學院地點"、"學院電話"對關鍵字段"學號"的傳遞函數依
賴。
它也會存在數據冗余、更新異常、插入異常和刪除異常的情況,讀者可自行分析得知。
把學生關系表分為如下兩個
表:
學生:(學號, 姓名, 年齡, 所在學院);
學院:(學院, 地點, 電話)。
這樣的數據庫表是符合第三范式的,消除了數據冗余、更
新異常、插入異常和刪除異常。
鮑依斯-科得范式(BCNF):在第三范式的基礎上,數據庫表中如果不存在任何字段對任一候選關鍵字段
的傳遞函數依賴則符合第三范式。
假設倉庫管理關系表為
StorehouseManage(倉庫ID, 存儲物品ID, 管理員ID,
數量),且有一個管理員只在一個倉庫工作;一個倉庫可以存儲多種物品。這個數據庫表中存在如下決定關系:
(倉庫ID,
存儲物品ID) →(管理員ID, 數量)
(管理員ID, 存儲物品ID) → (倉庫ID, 數量)
所以,
(倉庫ID, 存儲物品ID)和(管理員ID,
存儲物品ID)都是StorehouseManage的候選關鍵字,表中的唯一非關鍵字段為數量,它是符合第三范式的。但是,由于存在如下決定關系:
(倉庫ID) → (管理員ID)
(管理員ID) → (倉庫ID)
即存在關鍵字段決定關鍵字段的情況,所以
其不符合BCNF范式。它會出現如下異常情況:
(1) 刪除異常:
當倉庫被清空后,所有"存儲物品ID"和"數
量"信息被刪除的同時,"倉庫ID"和"管理員ID"信息也被刪除了。
(2) 插入異常:
當倉庫沒有存儲任何物
品時,無法給倉庫分配管理員。
(3) 更新異常:
如果倉庫換了管理員,則表中所有行的管理員ID都要修改。
把倉庫管理關系表分解為二個關系表:
倉庫管理:StorehouseManage(倉庫ID, 管理員ID);
倉庫:Storehouse(倉庫ID, 存儲物品ID, 數量)。
這樣的數據庫表是符合BCNF范式的,消除了刪除異常、插入異
常和更新異常。
范式應用
我們來逐步搞定一個論
壇的數據庫,有如下信息:
(1) 用戶:用戶名,email,主頁,電話,聯系地址
(2)
帖子:發帖標題,發帖內容,回復標題,回復內容
第一次我們將數據庫設計為僅僅存在表:
用戶名 |
email |
主頁 |
電話 |
聯系地址 |
發帖標題 |
發帖內容 |
回復標題 |
回復內容 |
這個數據庫表符合第一范式,但是沒有任何一組候選關鍵字能決定數據庫表的整行,唯一的關鍵字段用戶名也不能完全決定整個元組。我們需要增加"
發帖ID"、"回復ID"字段,即將表修改為:
用戶名 |
email |
主頁 |
電話 |
聯系地址 |
發帖ID |
發帖標題 |
發帖內容 |
回復ID |
回復標題 |
回復內容 |
這樣數據表中的關鍵字(用戶名,發帖ID,回復ID)能決定整行:
(用戶名,發帖ID,回復ID) →
(email,主頁,電話,聯系地址,發帖標題,發帖內容,回復標題,回復內容)
但是,這樣的設計不符合第二范式,因為存在如下決定
關系:
(用戶名) → (email,主頁,電話,聯系地址)
(發帖ID) → (發帖標題,發帖內容)
(回復ID) → (回復標題,回復內容)
即非關鍵字段部分函數依賴于候選關鍵字段,很明顯,這個設計會導致大量的數據冗余和操作
異常。
我們將數據庫表分解為(帶下劃線的為關鍵字):
(1) 用戶信息:用戶名,email,主頁,電話,聯系地址
(2) 帖子信息:發帖ID,標題,內容
(3) 回復信息:回復ID,標題,內容
(4)
發貼:用戶名,發帖ID
(5) 回復:發帖ID,回復ID
這樣的設計是滿足第1、2、3范式和BCNF范式要求
的,但是這樣的設計是不是最好的呢?
不一定。
觀察可知,第4項"發帖"中的"用戶名"和"發帖ID"之間是
1:N的關系,因此我們可以把"發帖"合并到第2項的"帖子信息"中;第5項"回復"中的"發帖ID"和"回復ID"之間也是1:N的關系,因此我們可以
把"回復"合并到第3項的"回復信息"中。這樣可以一定量地減少數據冗余,新的設計為:
(1)
用戶信息:用戶名,email,主頁,電話,聯系地址
(2) 帖子信息:用戶名,發帖ID,標題,內容
(3)
回復信息:發帖ID,回復ID,標題,內容
數據庫表1顯然滿足所有范式的要求;
數據庫表2中存在非關鍵字段"標
題"、"內容"對關鍵字段"發帖ID"的部分函數依賴,即不滿足第二范式的要求,但是這一設計并不會導致數據冗余和操作異常;
數據庫
表3中也存在非關鍵字段"標題"、"內容"對關鍵字段"回復ID"的部分函數依賴,也不滿足第二范式的要求,但是與數據庫表2相似,這一設計也不會導致數
據冗余和操作異常。
由此可以看出,并不一定要強行滿足范式的要求,對于1:N關系,當1的一邊合并到N的那邊后,N的那邊就不再滿足
第二范式了,但是這種設計反而比較好!
對于M:N的關系,不能將M一邊或N一邊合并到另一邊去,這樣會導致不符合范式要求,同時導致
操作異常和數據冗余。
對于1:1的關系,我們可以將左邊的1或者右邊的1合并到另一邊去,設計導致不符合范式要求,但是并不會導致操作異常和數
據冗余。
結論
滿足范式要求的數據庫設計是結構清晰的,同時可避免數據冗余和操作異常。這并意味著不符合范式要求
的設計一定是錯誤的,在數據庫表中存在1:1或1:N關系這種較特殊的情況下,合并導致的不符合范式要求反而是合理的。
在我們設計數
據庫的時候,一定要時刻考慮范式的要求。
|