2006年11月10日
#
1. Remote
Method Invocation (RMI)
2. Hessian
3. Burlap
4. HTTP invoker
5. EJB
6. JAX-RPC
7. JMX
zz from http://marakana.com/forums/tomcat/general/106.html

Valve and Filter:
"Valve" is Tomcat
specific notion, and they get applied at a higher level than anything in a specific webapp. Also, they work only in Tomcat.
"Filter" is a Servlet Specification notion and should work in any compliant servlet container. They get applied at a lower level than all of Tomcat's
Valves.
However, consider also the division between your application and the application
server. Think whether the feature you're planning is part of your application, or is it rather a generic feature of the application server, which could have uses in other applications as well. This would be the correct criteria to decide between Valve and Filter.
Order for filter: The order in which they are
defined matters. The container will execute the filters in the order
in which they are defined.
Use one single table "blank_fields" for both A and B. "blank_fields" has fields: 'ref_id', 'blank_field', 'type'. 'type' is used to identify which entity the record belongs to. Use 'type' + 'ref_id' to specify the collection of elements for one entity.
@Entity
@Table(name = "table_a")
public class A {
private Set<BlankField> blankFields = new HashSet<BlankField>();
@CollectionOfElements
@Fetch(FetchMode.SUBSELECT)
@Enumerated(EnumType.ORDINAL)
@JoinTable(name = "blank_fields", joinColumns = { @JoinColumn(name = "ref_id") })
@Cascade(value = org.hibernate.annotations.CascadeType.DELETE_ORPHAN)
@Column(name = "blank_field", nullable = false)
@SQLInsert(sql = "INSERT INTO blank_fields(ref_id, blank_field, type) VALUES(?,?,0)")
@Where(clause = "type=0")
public Set<BlankField> getBlankFields() { // BlankField is an enum
return blankFields;
}
@SuppressWarnings("unused")
private void setBlankFields(Set<BlankField> blankFields) {
this.blankFields = blankFields;
}
} // End B
@Entity
@Table(name = "table_b")
public class B {
private Set<BlankField> blankFields = new HashSet<BlankField>();
@CollectionOfElements
@Fetch(FetchMode.SUBSELECT)
@Enumerated(EnumType.ORDINAL)
@JoinTable(name = "blank_fields", joinColumns = { @JoinColumn(name = "ref_id") })
@Cascade(value = org.hibernate.annotations.CascadeType.DELETE_ORPHAN)
@Column(name = "blank_field", nullable = false)
@SQLInsert(sql = "INSERT INTO blank_fields(ref_id, blank_field, type) VALUES(?,?,1)") // used for insert
@Where(clause = "type=1") // used for query, if not @CollectionOfElements, such as @OneToMany, use @WhereJoinTable instead
public Set<BlankField> getBlankFields() {
return blankFields;
}
@SuppressWarnings("unused")
private void setBlankFields(Set<BlankField> blankFields) {
this.blankFields = blankFields;
}
}
當然還有其他的方式來實現上面的需求,上面采用的單表來記錄不同實體的associations(這兒是CollectionOfElements,并且返回的是Set<Enum>,不是Set<Embeddable>),然后用'type'來區分不同的實體,這樣做的好處是:數據庫冗余少,易于擴展,對于新的實體,只需加一個type值,而不需更改數據庫表結構。另外一種采用單表的方式是為每個實體增加新的字段,如"blank_fields": 'a_id', 'b_id', 'blank_field', a_id reference table_a (id), b_id reference table_b (id). 這樣在映射的時候更簡單,
對于A,映射為
@JoinTable(name = "blank_fields", joinColumns = { @JoinColumn(name = "a_id") })
對于B,映射為
@JoinTable(name = "blank_fields", joinColumns = { @JoinColumn(name = "b_id") })
這樣作的缺點是:帶來了數據庫冗余,對于blank_fields來講,任一條記錄,a_id和b_id中只有一個不為null。當多個實體共用這個表時,用上面的方法更合理,如果共用實體不多時,這種方法更方便。
The case to use One Hibernate Session Multiple Transactions:
each transaction would NOT affect others.
i.e., open multiple transactions on the same session, even though one transaction rolls back, other transactions can be committed. If one action fails, others should fail too, then we should use one transaction for all actions.
Note:
A rollback with a single Session will lead to that Session being cleared (through "Session.clear()").
So do lazy collections still work if the session is cleared? =>Not of any objects that you loaded up until the rollback. Only for new objects loaded afterwards.
We should load necessary objects to session for each transactional action to avoid LazyInitializationException, even if those objects are loaded before other forward transactional actions, since forward action may be rolled back and clear the session.
BTW, Hibernate Session.merge() is different with Session.update() by:
Item item2 = session.merge(item);
item2 == item; // false, item - DETACHED, item2 - PERSIST
session.update(item); // no return value, make item PERSIST
發生這種異常的case:
@Transactional
public void foo() {
try{
bar();
} catch (RuntimeException re) {
// caught but not throw further

}

}
@Transactional
public void bar() {

}
如果foo在調用bar的時候,bar拋出RuntimeException,Spring在bar return時將Transactional標記為Rollback only, 而foo捕獲了bar的RuntimeException,所以Spring將會commit foo的事務,但是foo和bar使用的是同一事務,因此在commit foo事務時,將會拋出UnexpectedRollbackException。注意:如果foo和bar在同一class中,不會出現這種情況,因為:
Since this mechanism is based on proxies, only 'external' method calls coming in through the proxy will be intercepted. This means that 'self-invocation', i.e. a method within the target object calling some other method of the target object, won't lead to an actual transaction at runtime even if the invoked method is marked with @Transactional!
可以通過配置log4j來debug Spring事務獲取情況:
To delve more into it I would turn up your log4j logging to debug and also look at what ExerciseModuleController is doing at line 91, e.g.: add a logger for org.springframework.transaction
這周被Quartz折騰了一番。
我們知道,Quartz采用JobDataMap實現向Job實例傳送配置屬性,正如Quartz官方文檔說的那樣:
How can I provide properties/configuration for a Job instance? The key is the JobDataMap, which is part of the JobDetail object.
The JobDataMap can be used to hold any number of (serializable) objects
which you wish to have made available to the job instance when it
executes.
JobDataMap map = context.getJobDetail().getJobDataMap();
我們通過map向Job實例傳送多個objects,其中有一個是個bean,一個是基本類型。對于scheduled triggers,我們要求bean對于所有的序列都不變,包括其屬性,而基本類型可以在Job運行過程中改變,并影響下一個序列。實際情況是,對于下個序列,bean的屬性被上次的修改了,而基本類型卻維持第一次put到Map里面的值。正好和我們要求的相反。
受bean的影響,以為map里面包含的都是更新的對象,即每個序列里面的JobDetail是同一個對象,但是基本類型的結果否認了這一點?;仡^重新翻閱了下Quartz的文檔:
Now, some additional notes about a job's state data (aka JobDataMap): A
Job instance can be defined as "stateful" or "non-stateful".
Non-stateful jobs only have their JobDataMap stored at the time they
are added to the scheduler. This means that any changes made to the
contents of the job data map during execution of the job will be lost,
and will not seen by the job the next time it executes.
Job有兩個子接口:StatefulJob and InterruptableJob,我們繼承的是InterruptableJob,或許Quartz應該有個InterruptableStatefulJob。另外StatefulJob不支持并發執行,和我們的需求不匹配,我們有自己的同步控制,Job必須可以并發運行。
然后查看了Quartz的相關源碼:
// RAMJobStore.storeJob
public void storeJob(SchedulingContext ctxt, JobDetail newJob,
boolean replaceExisting) throws ObjectAlreadyExistsException {
JobWrapper jw = new JobWrapper((JobDetail)newJob.clone()); // clone a new one

.
jobsByFQN.put(jw.key, jw);


}
也就是說,store里面放的是初始JobDetail的克隆,在序列運行完時,只有StatefulJob才會更新store里面的JobDetail:
// RAMJobStore.triggeredJobComplete
public void triggeredJobComplete(SchedulingContext ctxt, Trigger trigger,
JobDetail jobDetail, int triggerInstCode) {
JobWrapper jw = (JobWrapper) jobsByFQN.get(jobKey);


if (jw != null) {
JobDetail jd = jw.jobDetail;
if (jd.isStateful()) {
JobDataMap newData = jobDetail.getJobDataMap();
if (newData != null) {
newData = (JobDataMap)newData.clone();
newData.clearDirtyFlag();
}
jd.setJobDataMap(newData); // set to new one




}


}
然后,每次序列運行時所用的JobDetail,是存放在Store里面的克隆。
// RAMJobStore.retrieveJob
public JobDetail retrieveJob(SchedulingContext ctxt, String jobName,
String groupName) {
JobWrapper jw = (JobWrapper) jobsByFQN.get(JobWrapper.getJobNameKey(
jobName, groupName));
return (jw != null) ? (JobDetail)jw.jobDetail.clone() : null; // clone a new
}
問題很清楚了,存放在Store里面的JobDetail是初始對象的克隆,然后每個序列所用的JobDetail, 是Store里面的克隆,只有Stateful job,Store里面的JobDetail才更新。
最有Quartz里面使用的clone():
// Shallow copy the jobDataMap. Note that this means that if a user
// modifies a value object in this map from the cloned Trigger
// they will also be modifying this Trigger.
if (jobDataMap != null) {
copy.jobDataMap = (JobDataMap)jobDataMap.clone();
}
所以對于前面所講的,修改bean的屬性,會影響所有clone的對象,因此,我們可以將基本類型封裝到一個bean里面,map里面存放的是bean,然后通過修改bean的屬性,來達到影響下一個序列的目的。
From:
Web application design: the REST of the story
Key points:
- HTTP is a very general, scalable protocol. While most people only
think of HTTP as including the GET and POST methods used by typical
interactive browsers, HTTP actually defines several other methods that
can be used to manipulate resources in a properly designed application
(PUT and DELETE, for instance). The HTTP methods provide the verbs in a web interaction.
- Servers are completely stateless. Everything necessary to service a request is included by the client in the request.
- All application resources are described by unique URIs. Performing
a GET on a given URI returns a representation of that resource's state
(typically an HTML page, but possibly something else like XML). The
state of a resource is changed by performing a POST or PUT to the
resource URI. Thus, URIs name the nouns in a web interaction.
剛剛看CCTV實話實說,很有感觸,義烏技術職業學院給人眼前一亮,尤其是他們副院長的一番言論。
技術職業學院非得要升本科,本科非要成清華,義烏職業技術學院副院長評價當前高校的現狀,定位嚴重有問題,技術職業學院應該培養應用型人才,而清華就應該培養研究性人才,兩種學校的定位不能一樣,培養方式,評判標準都應該不同,而現在大多數高校的定位都一樣,這是不對的。個人非常贊同這個觀點,其實,這個觀點也可以應用到我們這些剛開始工作的年輕人身上,消除浮躁,找準定位,然后沿著定位踏實做事,并且應該采取相應的評判標準,這個很重要。
1. RFC documents
2. SCEP operations
-
PKIOperation:
-
Certificate Enrollment - request: PKCSReq, response: PENDING, FAILURE, SUCCESS
-
Poll for Requester Initial Certificate - request: GetCertInitial, response: same as for PKCSReq
-
Certificate Access - request: GetCert, response: SUCCESS, FAILURE
-
CRL Access - request: GetCRL, response: raw DER encoded CRL
- Non-PKIOperation: clear HTTP Get
-
Get Certificate Authority Certificate - GetCACert, GetNextCACert, GetCACaps
-
Get Certificate Authority Certificate Chain - GetCACertChain
3. Request message formats for PKIOperation
- Common fields in all PKIOperation messages:
-
senderNonce
-
transactionID
- the SCEP message being transported(SCEP messages) -> encrypted using the public key of the recipient(Enveloped-data)
-> signed by one of certificates(Signed-data): the requester can generate a self-signed certificate, or the requester can use
a previously issued certificate, if the RA/CA supports the RENEWAL option.
- SCEP messages:
-
PKCSReq: PKCS#10
- GetCertInitial: messages for old versions of scep clients such as Sscep, AutoSscep, and Openscep, are different with draft-18
issuerAndSubject ::= SEQUENCE {
issuer Name,
subject Name
}
-
GetCert: an ASN.1 IssuerAndSerialNumber type, as specified in PKCS#7 Section 6.7
-
GetCRL: an ASN.1 IssuerAndSerialNumber type, as defined in PKCS#7 Section 6.7
--zz: http://forums13.itrc.hp.com/service/forums/questionanswer.do?admit=109447627+1230261484567+28353475&threadId=1213960
Question:
We are planning to calculate the percentage of physical memory utilised as below:
System Page Size: 4Kbytes
Memory: 5343128K (1562428K) real, 13632356K (3504760K) virtual, 66088K free Page# 1/604
Now the formula goes as below:
(free memory / actual active real memory) * 100
(66088/1562428) * 100 = 4.22 %
Please let us know if its the correct formula .
Mainly we are interested in RAM percentage utilised
Reply 1:
Red Hat/Centos v 5 take spare ram and use it for a buffer cache.
100%
memory allocation is pretty meaningless because allocation is almost
always near 100%. The 2.6.x kernel permits rapid re-allocation of
buffer to other purposes eliminating a performance penalty that you see
on an OS like HP-UX
I'm not thrilled with your formula because
it includes swap(virtual memory). If you start digging too deep into
virtual memory, your system start paging processes from memory to disk
and back again and slows down badly.
The formula is however essentially correct.
Reply 2:
Here, a quick example from the machine under my desk:
Mem: 3849216k total, 3648280k used, 200936k free, 210960k buffers
Swap: 4194296k total, 64k used, 4194232k free, 2986460k cached
If
the value of 'Swap used' is up (i.e. hundreds of megabytes), then
you've got an issue, but as you can see, it's only 64k here.
Your formula for how much memory is used is something along the lines of this:
(Used - (Buffers + Cached) / Total) * 100 = Used-by-programs%
(Free + Buffers + Cached / Total) * 100 = Free%
.. Roughly ..
昨天遇到個非常奇怪的bug:更新了一下后臺的代碼,結果每次點擊頁面都會導致servlet方法調用兩次,從而頁面報錯(邏輯上不讓調兩次 ),我們的前臺采用gwt,servlet engine采用tomcat,debug的時候,斷點放在servlet所調用的method上,結果invoke兩次,由此斷定,前臺代碼的問題(有點武斷哦

),然后負責前臺的同事debugging前臺的代碼,噼里啪啦半天。。。,說是前臺好像沒有調兩次(之所以用好像,是debugging時部分代碼走兩次,部分走一次),而我當時的想法是,后臺怎么操作,也不至于讓servlet調用兩次吧,所以我個人就認定是前臺邏輯導致重復rpc調用(gwt),但是這個bug在這兩天才出現的,從svn的歷史記錄來看,前臺代碼在這兩天基本沒什么改變,同事只好從svn上一個version接一個version的check,最后確定出兩個相鄰的versions,前一個能用,后一個出bug,這時我隱約感覺到是后臺的問題,但是還是想不明白,后臺的邏輯怎么就能讓前臺重復調用,非常不解,沒辦法,在同事的建議下,在servlet的那個method上加上一條debug信息,做了兩次試驗,一次是完整的代碼,一次是把method中調用后臺的接口注釋掉,結果從日志上看出,前一次試驗debug信息打印了兩次,后一次試驗debug只打印了一次,此時,確定是后臺邏輯影響了前臺的調用(此時,覺得走彎路了,為什么不早點做這個試驗,其實確定是前臺還是后臺的問題,只需要做這樣一個簡單的試驗。。。)。接下來,我思考的就是到底是什么在作怪呢,對比svn上的兩個版本,只有兩處可能的改動,一處是將return改成throw exception, 一處是調用了Thread.currentThread.interrupt(),我一個感覺是后者,注掉這句后,一切OK,呵呵,慶幸沒有先嘗試前者,要不改動很大,。。。
剛剛看了gwt的源碼,還沒找到問題的根源,我的觀點是,thread接收到interrupt信號時,會重復發送rpc調用,(呵呵,還沒確定)。。。
最近心情不是很好,上周三,父親的一次意外,給家里本來平靜的生活帶來了很大的波瀾,我也第一次感受到來自于家庭的壓力,由此帶來的一系列問題,一直縈繞著我,責任,responsibility,是這幾天我告誡自己最多的一個詞,是啊,該到了承受家庭責任的時候了。
父親的這次意外,揪住了全家人的心,我也更多的為兩位老人思考了,這兩天,老想起一句話:人只有經歷的多了,才能成熟。我很喜歡類比,其實就跟我們做數學題一樣,看的多了,做的多了,考試的時候才能迎刃而解,什么東西,或許只有自己親身經歷,才能體會其中的更多細節,才能激發更多的收獲。
祝福父親的身體早日康復,bless。。。
最近,時不時地回想自己這一路的教育經歷,使得我越來越相信——命運!
總體來說,我自認為我這一路上走的太順利,缺少更多的經歷,缺少一些該有的挫折!
但是,順利歸順利,在兩次作抉擇的時候,隨機的選擇決定現在的方向!一次當然是過獨木橋——高考,另一次是碩士入學時選擇導師!兩次選擇都很隨意,甚至于無意,尤其是第二次。第一次的隨意更多的是無知,而第二次的無意,卻源于自己的不適應。隨意帶來了大學時代的混亂,無意卻給自己帶來了意外的收獲,人生無常,命運有數。
高考時,分數超出了自己的預料,志愿填的有些草率,一方面,是因為自己的年輕和無知,另一方面是由于周圍缺少必要的指點,填的很倉促,很隨意,非常的“高效”。正值00年高校擴招猖獗之時,我所填報的學校就是由三個學校合并而成,并且是在高考的前兩個月宣布合并的,其中有兩個合并之前不是一本,但是合并之后,肯定都是一本了。我當時選報了自動化這個專業,當時填的時候就因為高中班主任說了一聲:“現在自動化是一個很好的方向。”然而,此時命運開始現數,其中有兩個學校都有自動化這個專業,一個之前就是一本(合并后,稱之為‘校本部’,不知道這個是什么意思,或許我要去查查字典,好好揣測一下本部的含義。),另一個是三個學校中最差的一個,報道那天才知道有兩個自動化,但是由于剛合校,還沒來得及合并專業,當時就想,我該在哪個校區的自動化呢?最后隨著師長的指引,我被校車拉到了分校區,也就是那個最差的了,一路上,還在思索兩個自動化的分配算法,還是直到開學一個月以后,一次偶然的機會,才得知:兩個自動化是根據當時各省分數的交替順序分配,安徽省生源的第一名在本部,第二名在分校區,第三名本部,第四名分校區。。。。只能怪自己被動的排在了一個偶數的序位上,如果用一個函數來表示這個序位的話,其自變量的個數還是蠻多的,當年安徽省報考該校該專業的人生,你在這些人中的名次,另外還有,我還不太確定的因素,但是我能確定因素的存在。。。
后來,進一步得知,分校區的自動化之前沒有,我們是第一屆,當時在合校之前就已經確定要新增這個專業,合的時候,各個學校的招生計劃都沒變,只是將三個計劃簡單的數學累加,現在看來,合校是多么的可笑,一個學校從任意層次可以一下成為中國最好的學校,只要清華愿意合并它,而合并后再很長一段時間,那個學校除了學生的層次提高之外,沒有任何的改變,教師還是那些教師,設施還是那些設施,思想還是那些思想,我不知道這可不可以稱之為赤裸裸的搶劫,它無視了那些默默地而踏踏實實前進的高校,助長了一些不公正的風氣,或許正應了中國當時浮躁的社會氛圍。
就這樣在這度過了自己的三年大學時光,就在最后一個大學暑假之前,學校經過三年的發展和磨合,決定將我們這個專業撤銷,統一合并到本部去,我們被迫搬回了第一天報道的地方,其實兩個自動化的方向是不一樣的,或許我們要慶幸,我們學習了兩個專業,在大學的四年中,但是或許,更多的人可能會埋怨兩個方向影響了自己的學習,其實,我想,大多數的人根本不在于什么方向,什么專業了,一個大框架的混亂,注定了最終的結果,就像當前的中國足球。。。
我要說的是,其實我在大學中過得很愉快,我認識了一批很好的同學,我經歷了到目前為止最好的一段時光,雖然期間有很多遺憾,比如沒談一次戀愛。。。我想這段時光勢必會在我的記憶集合中占據非常重要的一塊。這里,我只不過是要論述命運有數,這樣的一個過程多少還是影響了我的人生軌跡。
下面要談論我的第二次抉擇,碩士時選擇導師。大學畢業時,我選擇了繼續就讀,一切都很順利,到了04年9月,我來到了新的學校,在合肥,這兒離家很近,因為我是安徽人,經歷了大學時回家的艱辛,再加上我又是個比較戀家的人。剛入校,就遇到了一個。。。。
明天繼續。。。。
1.
多行注釋:
ctrl+v 進入列模式,向下或向上移動光標,把需要注釋的行的開頭標記起來,然后按大寫的I,再插入注釋符,比如#,再按esc,就會全部注釋了.
zz: java.sys-con.com
Java servlet technology provides developers with functionality,
scalability, and portability that can't be found in other server-side
languages. One feature of the Java servlet specification that's
commonly used, and sometimes misused, is the HttpSession interface.
This simple interface allows you to maintain a session or state for Web
site visitors.
In my previous article ("Introduction to Session Management," [JDJ,
Vol. 7, issue 9]), I introduced you to session management and the
HttpSession interface. In that article, we walked through using the
HttpSession API to create, use, and destroy session objects for Web
site visitors. The next step is to better understand how to manage the
sessions and those objects in a session. This article will help you
achieve this by helping you understand the following concepts:
- Code-based session management through listeners
- Proper design of the session and the objects it contains
- Controlling what is in the session and why it's there
- Session persistence
- Memory management
The Java APIs discussed in this article are from Sun's Java Servlet 2.3 specification.
Listeners
A listener is an object that's
called when a specified event occurs. There are four listener
interfaces that allow you to monitor changes to sessions and the
objects that are in those sessions:
- HttpSessionListener
- HttpSessionBindingListener
- HttpSessionAttributeListener
- HttpSessionActivationListener
Figure 1 provides a method summary for each of the listener
interfaces. The implementing class that you write will override these
methods to provide the functionality you need.
HttpSessionListener
The HttpSessionListener
interface is used to monitor when sessions are created and destroyed on
the application server. Its best practical use would be to track
session use statistics for a server.
The use of HttpSessionListener requires a configuration entry
in the deployment descriptor, or web.xml file, of the application
server. This entry points the server to a class that will be called
when a session is created or destroyed. The entry required is simple.
All you need is a listener and listener-class element in the following
format. The listener-class element must be a fully qualified class
name.
<listener>
<listener-class>package.Class</listener-class>
</listener>
As you can see in Figure 1, the class that implements this
listener can override two methods: sessionCreated() and
sessionDestroyed(). These methods will be notified when the server
creates or destroys a session.
These methods take an HttpSessionEvent object as a parameter.
HttpSessionEvent is simply a class that represents notifications of
changes to the Web application's sessions. HttpSessionEvent has one
method, getSession(), that returns the HttpSession object that's been
modified.
HttpSessionBindingListener
The
HttpSessionBindingListener interface is implemented when an object
needs to be notified if it's being bound to a session or unbound from a
session.
This interface has two methods, valueBound() and
valueUnbound(), that are notified when the status of the object has
changed (see Figure 1).
These methods have an HttpSessionBindingEvent parameter that
can be used to retrieve the session that the object was bound to and
the name it was given in the session. In Figure 2, you can see the
methods of this object that are used to get the name that's assigned to
the object, the session it's bound to, and the actual object.
HttpSessionAttributeListener
The
HttpSessionAttributeListener interface is used to monitor changes to
attributes in any session on the server. This can be useful when you
know the name assigned to a specific object that gets put into the
session and you want to track how often it's being used.
As with HttpSessionListener, HttpSessionAttributeListener also
requires an entry in the deployment descriptor for the server. This
entry tells the server which class to call when an attribute in a
session has changed.
The HttpSessionAttributeListener interface has three methods -
attributeAdded(), attributeRemoved(), and attributeReplaced(). These
methods, shown in Figure 1, are called by the server when attributes of
a session are changed.
HttpSessionActivationListener
The final listener,
HttpSessionActivationListener, is implemented when an object needs to
know if the session that it's bound to is being activated or passivated
(moved). You would come across this scenario if your session is being
shared across JVMs or your server is persisting the session in a
database or file system.
This interface, displayed in Figure 1, has two methods that
are overridden by the implementing class: sessionDidActivate() and
sessionWillPassivate(). These methods are called when the status of the
session in a JVM is changed.
Session Persistence
Today's J2EE-compliant
servers allow for fault-tolerance and failover to provide support in
the event that a server suddenly becomes unavailable because of
hardware, software, or network failure. This support is usually
provided by allowing two or more application servers, often called a
cluster, to run together and provide backup support for each other. If
one server fails, the others pick up the requests and continue on as if
nothing happened. This allows your Web site visitors to keep going
without interruption.
A proxy server is usually used in front of the application
servers. This server is responsible for directing each HTTP request to
the appropriate server. The proxy server can be set up to ensure that
the server receiving the first request from a user will continue to
receive all subsequent requests from that user. This means that a
session created for the user on the application server will continue to
be available for that user. If the server suddenly fails, there has to
be a system in place to allow the session to continue on without it.
Session persistence allows the session contents to be saved
outside the application server so that other servers can access it.
Figure 3 shows the relationship between the persisted session data and
the application servers that access it. In this figure, you see a
client accessing a Web site's HTTP server. The HTTP server is
forwarding requests for application resources to one of the application
servers through the use of a proxy server. The application servers are
persisting the session data in an external form.
There are four types of session persistence:
- Memory persistence (one server or a cluster of two or more)
- File system persistence
- Database persistence
- Cookie persistence
Every application server will handle session persistence
differently and all servers may not support all types of persistence.
Objects that are placed in the session must be serializable for
persistence to work.
Memory Persistence
In most cases, a single
standalone server will store sessions in memory. This allows for fast
retrieval and update of the information. It also means that the session
information will be lost when the server is shut down. This is usually
the default configuration on most application servers. Memory
persistence can be used when two or more servers need to share the
session information. The application servers can be configured to share
any changes made to the session so that the information is available on
multiple servers. This redundancy of the session information helps the
cluster preserve the session during a failure.
File System Persistence
File system persistence
can be used to serialize any objects that are in the session. The
object contents are placed in a file on the server. The location of the
files created is configurable; however, the files must be accessible by
all the servers in the cluster. The speed at which the file system is
accessed can be a factor in the performance of your Web site. A slow
disk drive, for example, would result in a delay as data is read from
or written to the file.
Database Persistence
Database persistence can be
used to provide a central data store for the session contents. Each
application server in the cluster must be able to access the database.
When sessions are modified, the changes are immediately persisted in
the database. A data source is usually set up for JDBC persistence and
the connections are pooled. This provides a quicker response. There's
also the issue of database failover, which would be addressed at the
database level of the system.
Cookie Persistence
The fourth type of session
persistence, cookie persistence, is so ineffective and insecure that it
doesn't deserve consideration when designing a fail-safe system. Cookie
persistence, as the name implies, persists session data by storing the
session information in browser cookie(s). There's a limitation on data
handling because cookies store only text, not objects, and the amount
of data that can be transmitted in a cookie is limited. There's also
the fact that cookies transmit data back and forth between the client
and the server. This prevents you (at least it should) from saving
sensitive information, like a social security number. This type of
persistence should be used in only the smallest of Web sites, and only
if there's a good reason not to store the session in memory.
The most common type of persistence is database persistence.
It provides an efficient way of saving session data and it's usually
fairly easy to set up on the application server. Memory persistence in
a cluster is also easy to use, if your application server supports it.
The only drawback is that sessions can sometimes hold large amounts of
data. Storing the session in memory reduces the amount of memory
available to the other processes on the server. File system persistence
can be slow at times and the file system may not always be accessible
to multiple servers.
Watching the Session Size
As you and your
fellow employees work on a Web application, you may notice that more
and more objects are being thrown into the session, often "for
convenience" or "just temporarily." The session becomes a quick
catch-all for any information you need to get from your servlets to
your JSPs. The HttpSession interface makes sessions easy to use, which
can lead to the session being overused. This is a concern because the
session takes up space. In most cases that would be memory space. In
other cases, it could be database or file system space. In all cases,
it means more work for the server and more work for the programmers to
manage what is there.
Although the session is convenient because it's accessible
from every servlet or JSP, it's not always the best place to put
information. Most of the data that's retrieved for display in a Web
application will only be used on one page. Instead of putting the
information into the session scope, use the request scope and then
forward the request from the servlet to the JSP. This causes the
objects to be destroyed after the request has ended, which is after the
data is displayed by the JSP. If you put the objects into the session,
you would either have to remove them in your code or leave them there.
Leaving objects in the session is not a good idea because you're using
up valuable resources for no reason. This becomes even more of an issue
when your Web site has hundreds or thousands of visitors, all of whom
have a session that's loaded with objects.
Some objects should be stored in the session. Objects that may
be needed over and over again as a user moves through a Web site are
those that should be put into the session. Anything that needs to exist
longer than one request can be stored in the session, as long as these
objects are removed as soon as they're no longer needed.
Considerations for Managing Sessions
When working with sessions, there are a few things to consider before designing or redesigning a Web application:
- Are sessions needed in the application?
- How long should the session be inactive before timing out?
- Are all the objects in the session serializable?
- Are the objects being bound to the session too large?
- Do the objects that are in the session really need to be there?
A Need for Sessions
If you have unique
users on a Web site and need to know who they are or need to get
specific information to them, such as search results, then you should
be using sessions. If you follow the guidelines set here, there's no
reason not to use the HttpSession interface that Java provides. It's
easy to use, flexible, secure, and it helps you to build a better Web
site.
There's another architecture that deals with maintaining state for
a client. Instead of relying on the HttpSession interface, state for
clients can be maintained within Enterprise JavaBeans (EJBs). The EJB
architecture takes the business logic for an application and places it
in components or beans. A session bean is a type of EJB that exists for
a given client/server session and provides database access or other
business logic, such as calculations. Session beans can be stateless or
they can maintain the state for a client, very much like an HttpSession
object.
There is still some debate over where the state for a Web site
visitor should be maintained. The best design for the application at
this time is to continue using the HttpSession object for maintaining
the state of the presentation layer of the Web application and to use
stateful EJBs to maintain the state of the business logic and data
layer. There are many other factors that should be considered with
EJBs, one being the better performance of stateless beans over those
that maintain state. These issues, which are outside the scope of this
article, should be considered carefully when architecting an
application.
Session Timeout
By default, on most servers
the session is set to expire after 30 minutes of inactivity. The amount
of time can be configured in the deployment descriptor of the Web
application. The HttpSession API also provides a
setMaxInactiveInterval() method that you can use to specify the timeout
period for a session. The getMaxInactiveInterval() method will return
this timeout value. The value given is in seconds.
The length of time will vary depending on what your visitors
are doing on your site. If they're logging in to check their account
balance, a shorter session timeout period can be used because it
doesn't take long for a person to read a couple of numbers. If, on the
other hand, the user is logging in to read large amounts of data, you
need to be sure that you provide enough time for the user to do what he
or she wants without being logged out. If the user is constantly
navigating through your site, the session will last indefinitely.
Implement Serializable
It's important to
make sure that all objects placed in the session can be serialized.
This may not be an issue if you know that your Web application will not
run in a cluster, but it should still be done anyway. What happens if
your Web site grows too big for one server and you suddenly have to
move to two? If you implement Serializable in your code now, you won't
have to go back and do it later.
Keep It Simple
You should design objects
that are going to be placed into a session so that they're not too big
and don't contain unnecessary information. A JavaBean that contains a
customer's name, address, phone number, e-mail address, credit card
numbers, and order history should not be placed into the session if
you're only going to use the object to get the customer's name.
Session Contents
When you're working on a
Web site, it's important to know which objects are in the session and
why they're needed. The size of the session should be kept as small as
possible. If you're building a new Web site, work out ahead of time
what goes in the session, why it's there, and where it gets removed. If
you're redesigning an existing site, this may be a little tougher,
especially when you have hundreds of servlets and JSPs to deal with. In
this case, try implementing an HttpSessionAttributeListener to get an
idea of what is going into the session. With this information, you may
be able to better manage your sessions.
Conclusion
Hopefully this article helped
you to better understand the design issues involved in using the
HttpSession interface. Java provides a more robust session
implementation than other languages. It's because of this power and
flexibility that you must take the time to properly lay out the use of
the session. A well-designed session will help make a Web application
better for the programmers and the users.
References
Hall, M. (2002). More Servlets and JavaServer Pages. Prentice Hall PTR.
Java Servlet Technology:
http://java.sun.com/products/servlet
Enterprise JavaBeans Technology:
http://java.sun.com/products/ejb
Java BluePrints (J2EE):
http://java.sun.com/blueprints/guidelines/
designing_enterprise_applications
另外,還有一些收集的材料
關于HttpSession的誤解實在是太多了,本來是一個很簡單的問題,怎會搞的如此的復雜呢?下面說說我的理解吧:
1、HTTP協議本身是“連接-請求-應答-關閉連接”模式的,是一種無狀態協議(HTTP只是一個傳輸協議);
2、Cookie規范是為了給HTTP增加狀態跟蹤用的(如果要精確把握,建議仔細閱讀一下相關的RFC),但不是唯一的手段;
3、所謂Session,指的是客戶端和服務端之間的一段交互過程的狀態信息(數據);這個狀態如何界定,生命期有多長,這是應用本身的事情;
4、由于B/S計算模型中計算是在服務器端完成的,客戶端只有簡單的顯示邏輯,所以,Session數據對客戶端應該是透明的不可理解的并且應該受控于服務端;Session數據要么保存到服務端(HttpSession),要么在客戶端和服務端之間傳遞(Cookie或url rewritting或Hidden input);
5、由于HTTP本身的無狀態性,服務端無法知道客戶端相繼發來的請求是來自一個客戶的,所以,當使用服務端HttpSession存儲會話數據的時候客戶端的每個請求都應該包含一個session的標識(sid, jsessionid 等等)來告訴服務端;
6、會話數據保存在服務端(如HttpSession)的好處是減少了HTTP請求的長度,提高了網絡傳輸效率;客戶端session信息存儲則相反;
7、客戶端Session存儲只有一個辦法:cookie(url rewritting和hidden input因為無法做到持久化,不算,只能作為交換session id的方式,即a method of session tracking),而服務端做法大致也是一個道理:容器有個session管理器(如tomcat的 org.apache.catalina.session包里面的類),提供session的生命周期和持久化管理并提供訪問session數據的 api;
8、使用服務端還是客戶端session存儲要看應用的實際情況的。一般來說不要求用戶注冊登錄的公共服務系統(如google)采用 cookie做客戶端session存儲(如google的用戶偏好設置),而有用戶管理的系統則使用服務端存儲。原因很顯然:無需用戶登錄的系統唯一能夠標識用戶的就是用戶的電腦,換一臺機器就不知道誰是誰了,服務端session存儲根本不管用;而有用戶管理的系統則可以通過用戶id來管理用戶個人數據,從而提供任意復雜的個性化服務;
9、客戶端和服務端的session存儲在性能、安全性、跨站能力、編程方便性等方面都有一定的區別,而且優劣并非絕對(譬如TheServerSide號稱不使用HttpSession,所以性能好,這很顯然:一個具有上億的訪問用戶的系統,要在服務端數據庫中檢索出用戶的偏好信息顯然是低效的,Session管理器不管用什么數據結構和算法都要耗費大量內存和CPU時間;而用cookie,則根本不用檢索和維護session數據,服務器可以做成無狀態的,當然高效);
reply1:
不過我們也不能在session里面放入過多的東西
一般來說不能超過4K
太多了
對系統資源是一個很嚴重的浪費
reply2:
4K已是很大的一個數字了。
我一般喜歡寫一個類。封裝用戶登陸后的一些信息。
然后把這個類放在session中,取得直接用類的方法取相關信息,
最近接到兩個很小的tickets,兩個都是為了項目開發時的方便:一是將logs寫入到數據庫中,以方便日志的查詢;一是在build時,在war包加入svn revision info。
1) logging to database
經過調查,決定采用log4j的org.apache.log4j.jdbc.JDBCAppender,于是采用:
# logging to db
log4j.logger.com.example=DEBUG, DATABASE
log4j.additivity.com.example=false
log4j.appender.DATABASE=org.apache.log4j.jdbc.JDBCAppender
log4j.appender.DATABASE.url=jdbc:postgresql://localhost:5432/test
log4j.appender.DATABASE.driver=org.postgresql.Driver
log4j.appender.DATABASE.user=pguser
log4j.appender.DATABASE.password=post
log4j.appender.DATABASE.sql=INSERT INTO debug_log(created, logger, priority, message) VALUES (to_timestamp('%d{ISO8601}','YYYY-MM-DD HH:MI:SS.MS'),'%c.%M:%L','%p','%m')
log4j.appender.DB.layout=org.apache.log4j.PatternLayout
log4j.appender.DATABASE.layout.ConversionPattern=%d{ISO8601} %p %c.%M:%L %m
很直觀,用起來還很方便,但是不久就出現了問題,tomcat拋出了exception。只好把之前fixed ticket reopen,提交新的comments:Unfortunately, org.apache.log4j.jdbc.JDBCAppender that ships with the Log4j distribution is not able to process logging messages that have characters like ' (single quote) and , (comma) in it. When logging messages contains characters like single quote or comma, the program will throw an exception.
重新google了,找到了一個plusjdbc,Looking further, I found an alternative JDBCAppender package (org.apache.log4j.jdbcplus.JDBCAppender) from http://www.dankomannhaupt.de/projects/index.html. It can solve this problem. 長嘆了一下。
最后采用:
log4j.appender.DATABASE=org.apache.log4j.jdbcplus.JDBCAppender
log4j.appender.DATABASE.url=jdbc:postgresql://localhost:5432/test
log4j.appender.DATABASE.dbclass=org.postgresql.Driver
log4j.appender.DATABASE.username=pguser
log4j.appender.DATABASE.password=post
log4j.appender.DATABASE.sql=INSERT INTO debug_log(created, logger, priority, message) VALUES (to_timestamp('@LAYOUT:1@', 'YYYY-MM-DD HH:MI:SS.MS'),'@LAYOUT:3@','@LAYOUT:2@','@LAYOUT:4@')
log4j.appender.DATABASE.layout=org.apache.log4j.PatternLayout
log4j.appender.DATABASE.layout.ConversionPattern=%d{ISO8601}#%p#%c.%M:%L#%m
log4j.appender.DATABASE.layoutPartsDelimiter=#
log4j.appender.DATABASE.buffer=1
log4j.appender.DATABASE.commit=true
log4j.appender.DATABASE.quoteReplace=true
問題解決,但是中間有點小波折,在我的項目中,log4j.jar(>1.2.9)重復了,在$CATALINA_HOME/lib下有一份,在web工程下的WEB-INF/lib下也有一份,而plus-jdbc.jar放置在$CATALINA_HOME/lib下,結果啟動Tomcat,出現
log4j:ERROR A "org.apache.log4j.jdbcplus.JDBCAppender" object is not assignable to a "org.apache.log4j.Appender" variable.
log4j:ERROR The class "org.apache.log4j.Appender" was loaded by
log4j:ERROR [WebappClassLoader^M
delegate: false^M
repositories:^M
----------> Parent Classloader:^M
org.apache.catalina.loader.StandardClassLoader@1ccb029^M
] whereas object of type
log4j:ERROR "org.apache.log4j.jdbcplus.JDBCAppender" was loaded by [org.apache.catalina.loader.StandardClassLoader@1ccb029].
log4j:ERROR Could not instantiate appender named "DATABASE".
原來是兩個JDBCAppender實例不在同一個classlaoder里面,將WEB-INF/lib下的log4j.jar刪除掉,重啟就沒問題了,按理,將$CATALINA_HOME/lib下的plus-jdbc.jar移到WEB-INF/lib下,應該也沒問題,沒有測試。
2)Add build revision info in war file and read it on tomcat startup
這個經歷比較慘痛,兩個問題,如何獲取revision? And how to read it when tomcat startup? 第二個問題倒是沒什么,采用javax.servlet.ServletContextListener就可以實現,很簡單,走彎路的是第一個問題,google后發現有兩種常見的實現:
As I have learned, there are totally two solutions to get svn revision info.
First, retrieve the svn revision from local file($BASE_HOME/.svn/entries). Just parsing the xml file, get the revision property and write it to a properties file.(就是該死的xml,遠在烏克蘭的同事,該文件卻不是xml的,也只怪自己調研不充分,還得折騰了半天,后來發現,最新版的svn為了performance的考慮,采用meta data來實現entries)
Second, retrieve the svn revision from the remote repository. The solution always use a svn client to perform a demand with remote server to retrieve the revision info. Installing a snv client and using SvnAnt? are most commonly used at present. SvnAnt? is an ant task that provides an interface to Subversion revision control system and encapsulates the svn client. It uses javahl - a native (JNI) java interface for the subversion api if it can find the corresponding library. javahl is platform-dependent.
Because of needing interaction with the server(服務器在國外,更新很慢), now I employ the first solution. But I found a flaw of this method when i was going off duty. Generally, we may update our project with svn before committing. This may make a mismatch with svn revision between remote server and local file. Svn revision in local file is usually updated when we update our project. But when we take a commit after update, the svn revision in the remote server will change to a new one.
So, the case is that if we update, commit, and then build, we may get a mismatch with the newest svn revision, and build the error revision into our ROOT.war. If we update , then build ,without commit, we can get right revision info.
下面是第一版實現:
<!-- retrieve the svn revision from the remote repository
<path id="svnant.lib" >
<fileset dir="${lib.dir}">
<include name="svnant.jar"/>
<include name="svnClientAdapter.jar"/>
<include name="svnjavahl.jar"/>
</fileset>
</path>
<taskdef name="svn" classpathref="svnant.lib" classname="org.tigris.subversion.svnant.SvnTask" />
<target name="get-svn-revision">
<svn username="*******" password="******" javahl="true">
<status urlProperty="https://example.com" path="." revisionProperty="svn.revision" />
</svn>
<echo>svn revision: ${svn.revision}</echo>
</target>
-->
<!-- retrieve the svn revision from local file(.svn/entries). The file may contain several 'wc-entries.entry.revision' elements.
The property will get several values seperated by ',' when using xmlproperty task. Then the svn revison expected will be the
max one of these property values.
-->
<property name="svn.revision.file" value=".svn/entries" />
<!-- This property is used to run xmlproperty task successfully with a low version of svn client (under 1.3.1). Don't sure whether it really makes sense -->
<property name="build.id" value="foo" />
<target name="get-svn-revision">
<xmlproperty file="${svn.revision.file}" collapseAttributes="true"/>
<echo>svn revision: ${wc-entries.entry.revision}</echo>
</target>
<!--
If the file doesn't contain any 'wc-entries.entry.revision' element, the content of the property file will be: revision = ${wc-entries.entry.revision};
If contain a 'wc-entries.entry.revision' element, mark this value as $revision_value, then the content will be: revision = $revision_value;
If contain several 'wc-entries.entry.revision' elements, mark these values as $value1, $value2, ..., respectively, then the content will be: revision = $value1,$value2,..., seperated by a ',';
-->
<property name="svn.revision.propertyfile" value="${build.dir}/revision.properties" />
<target name="write-svn-revision-to-file" depends="get-svn-revision">
<delete file="${svn.revision.propertyfile}"/>
<propertyfile file="${svn.revision.propertyfile}" comment="record svn revision">
<entry key="revision" value="${wc-entries.entry.revision}"/>
</propertyfile>
</target>
結果write-svn-revision-to-file這個在我這倒是可以獲取本地的svn revision,但是遠方的同事可急了,build老失敗,只好把這部分build注釋了,還好,到周末了,可以在家好好研究一下,很快找了一個新的工具:
It's my fault. In my version of svn, the entries file is xml formatted. So i parse it using ant task - 'xmlproperty'. Now i have fix this problem by using 'svnkit' tools, a pure java svn toolkit. Now there are two ways to retrieve svn revision. One is from remote repository server. For this one, before building, you should set your own username and password for the remote repository server('remote.repository.username' and 'remote.repository.password' properties in build.xml,respectively). Another one is retrieving revision from local working copy. If using this one, you should set 'local.repository' property in build.xml to your own directory.
利用svnkit,從服務器上獲取revision大概是:
repository = SVNRepositoryFactory.create(SVNURL.parseURIDecoded(urlStr));
ISVNAuthenticationManager authManager = SVNWCUtil.createDefaultAuthenticationManager(username, password);
repository.setAuthenticationManager(authManager);
headRevision = repository.getLatestRevision();
從本地working copy獲取revision:
SVNClientManager clientManager = SVNClientManager.newInstance();
SVNWCClient wcClient = clientManager.getWCClient();
SVNInfo info = wcClient.doInfo(new File(fileUrl), SVNRevision.WORKING);
headRevision = info.getRevision().getNumber();
利用ant task將獲取的revision寫入到一個配置文件中(如revision.properties),在tomcat啟動的時候加載進來,就可以了。
zz: http://rosonsandy.blogdriver.com/rosonsandy/871539.html
1 - Tomcat的類載入器的結構
Tomcat Server在啟動的時候將構造一個ClassLoader樹,以保證模塊的類庫是私有的
Tomcat Server的ClassLoader結構如下:
+-----------------------------+
| Bootstrap |
| | |
| System |
| | |
| Common |
| / \ |
| Catalina Shared |
| / \ |
| WebApp1 WebApp2 |
+-----------------------------+
其中:
- Bootstrap - 載入JVM自帶的類和$JAVA_HOME/jre/lib/ext/*.jar
- System - 載入$CLASSPATH/*.class
- Common - 載入$CATALINA_HOME/common/...,它們對TOMCAT和所有的WEB APP都可見
- Catalina - 載入$CATALINA_HOME/server/...,它們僅對TOMCAT可見,對所有的WEB APP都不可見
- Shared - 載入$CATALINA_HOME/shared/...,它們僅對所有WEB APP可見,對TOMCAT不可見(也不必見)
- WebApp - 載入ContextBase?/WEB-INF/...,它們僅對該WEB APP可見
2 - ClassLoader的工作原理
每個運行中的線程都有一個成員contextClassLoader,用來在運行時動態地載入其它類
系統默認的contextClassLoader是systemClassLoader,所以一般而言java程序在執行時可以使用JVM自帶的類、$JAVA_HOME/jre/lib/ext/中的類和$CLASSPATH/中的類
可以使用Thread.currentThread().setContextClassLoader(...);更改當前線程的contextClassLoader,來改變其載入類的行為
ClassLoader被組織成樹形,一般的工作原理是:
1) 線程需要用到某個類,于是contextClassLoader被請求來載入該類
2) contextClassLoader請求它的父ClassLoader來完成該載入請求
3) 如果父ClassLoader無法載入類,則contextClassLoader試圖自己來載入
注意:WebApp?ClassLoader的工作原理和上述有少許不同:
它先試圖自己載入類(在ContextBase?/WEB-INF/...中載入類),如果無法載入,再請求父ClassLoader完成
由此可得:
- 對于WEB APP線程,它的contextClassLoader是WebApp?ClassLoader
- 對于Tomcat Server線程,它的contextClassLoader是CatalinaClassLoader
3 類的查找
ClassLoader類中loadClass方法為缺省實現,用下面的順序查找類:
1、調用findLoadedClass方法來檢查是否已經被加載。如果沒有則繼續下面的步驟。
2、如果當前類裝載器有一個指定的委托父裝載器,則用委托父裝載器的loadClass方法加載類,也就是委托給父裝載器加載相應的類。
3、如果這個類裝載器的委托層級體系沒有一個類裝載器加載該類,則使用類裝載器定位類的特定實現機制,調用findClass方法來查找類。
4 - 部分原代碼分析
4.1 - org/apache/catalina/startup/Bootstrap.java
Bootstrap中定義了三個classloader:commonLoader,catalinaLoader,sharedLoader.三者關系如下:
//注意三個自己定置的ClassLoader的層次關系:
// systemClassLoader (root)
// +--- commonLoader
// +--- catalinaLoader
// +--- sharedLoader
Tomcat Server線程的起點
構造ClassLoader樹,通過Thread.currentThread().setContextClassLoader(catalinaLoader)設置當前的classloader為catalinaLoader。
載入若干類,然后轉入org.apache.catalina.startup.Catalina類中
4.2 org.apache.catalina.loader.StandardClassLoader.java
通過看loadClass這個方法來看tomcat是如何加載類的,順序如下:
(0) Check our previously loaded class cache查找已經裝載的class
clazz = findLoadedClass(name);
(1) If a system class, use system class loader通過系統classloader來裝載class
ClassLoader loader = system;
clazz = loader.loadClass(name);
(2) Delegate to our parent if requested如果有代理則使用父類classloader
ClassLoader loader = parent;
if (loader == null)
loader = system;
clazz = loader.loadClass(name);
(3) Search local repositories 查找本地類池,比如$CATALINA_HOME/server
clazz = findClass(name);
(4) Delegate to parent unconditionally 默認使用代理裝載器
[查看代碼]
4.3 - org/apache/catalina/startup/ClassLoaderFactory.java
根據設置創建并返回StandardClassLoader的實例
[查看代碼]
4.4 - org/apache/catalina/loader/StandardClassLoader.java
類載入器
4.5 - org/apache/catalina/startup/SecurityClassLoad.java
該類僅包含一個靜態方法,用來為catalinaLoader載入一些類
[查看代碼]
Appendix - 參考
[1] http://jakarta.apache.org/tomcat/中的Tomcat 4.1.x文檔Class Loader HOW-TO
在一個JVM中可能存在多個ClassLoader,每個ClassLoader擁有自己的NameSpace。一個ClassLoader只能擁有一個class對象類型的實例,但是不同的ClassLoader可能擁有相同的class對象實例,這時可能產生致命的問題。如ClassLoaderA,裝載了類A的類型實例A1,而ClassLoaderB,也裝載了類A的對象實例A2。邏輯上講A1=A2,但是由于A1和A2來自于不同的ClassLoader,它們實際上是完全不同的,如果A中定義了一個靜態變量c,則c在不同的ClassLoader中的值是不同的。
[2] 深入Java2平臺安全
zz: http://mail-archives.apache.org/mod_mbox/tomcat-users/200212.mbox/raw/%3c20021204192034.P86616-100000@icarus.apache.org%3e
try {
Properties props = new Properties();
InputStream in = getClass().getResourceAsStream("/conf/db.properties");
props.load(in);
......
propertie1 = props.getProperty("propertie1");
The examples already given will find properties files for you just fine whether the file is in a directory structure or inside an archive. How do you think Java loads classes? It works out of archives, no? here are some various was to access a properties file ( or any resource, for that matter) in whether the app is deployed as a directory or as a .war file (even inside a .jar file in WEB-INF/lib)....
1. This will load a file in WEB-INF/classes/conf or any jar file in the classpath with a package of "conf"...
getClass().getResourceAsStream("/conf/db.properties");
2. This will load a file relative to the current class. For instance, if the class is "org.mypackage.MyClass", then the file would be loaded at "org.mypackage.conf.dbproperties". Note that this is because we didn't prepend "/" to the path. When that is done, the file is loaded from the root of the current classloader where this loads it relative to the current class...
getClass().getResourceAsStream("conf/db.properties");
3. This will find db.properties anywhere in the current classloader as long as it exists in a "conf" package...
getClass().getClassLoader().getResourceAsStream("conf/db.properties");
4. This will find the file in a "conf" directory inside the webapp (starting from the root). This starts looking in the same directory as contains WEB-INF. When I say "directory", I don't mean "filesystem". This could be in a .war file as well as in an actual directory on the filesystem...
getServletContext().getResourceAsStream("/conf/db.properties");
5. Of course you would probably not want just anyone seeing your db.properties file, so you'd probably want to put in inside WEB-INF of your webapp, so....
getServletContext().getResourceAsStream("/WEB-INF/conf/db.properties");
6. If your db.properties exists in another classloader which your app has access to, you can reach it by using:
Thread.currentThread().getContextClassLoader().getResourceAsStream("conf/db.properties");
that will act similar to getClass().getClassLoader(), but it can see across all available classloaders where the latter can only see within the classloader that loaded the current class.
1、sudo vim ~/.exrc
添加如下內容:
if &t_Co > 1
syntax enable
endif
2、如果不行,再sudo apt-get install vim
/usr/share/fonts/truetype/ 下面 的某文件夾,例如yahei
cd /usr/share/fonts/truetype/yahei
sudo mkfontscale && sudo mkfontdir && sudo fc-cache -fv
一、關于工作與生活
我有個有趣的觀察,外企公司多的是25-35歲的白領,40歲以上的員工很少,二三十歲的外企員工是意氣風發的,但外企公司40歲附近的經理人是很尷尬的。我見過的40歲附近的外企經理人大多在一直跳槽,最后大多跳到民企,比方說,唐駿。外企員工的成功很大程度上是公司的成功,并非個人的成功,西門子的確比國美大,但并不代表西門子中國經理比國美的老板強,甚至可以說差得很遠。而進外企的人往往并不能很早理解這一點,把自己的成功90%歸功于自己的能力,實際上,外企公司隨便換個中國區總經理并不會給業績帶來什么了不起的影響。好了問題來了,當這些經理人40多歲了,他們的薪資要求變得很高,而他們的才能其實又不是那么出眾,作為外企公司的老板,你會怎么選擇?有的是只要不高薪水的,要出位的精明強干精力沖沛的年輕人,有的是,為什么還要用你?
從上面這個例子,其實可以看到我們的工作軌跡,二三十歲的時候,生活的壓力還比較小,身體還比較好,上面的父母身體還好,下面又沒有孩子,不用還房貸,也沒有孩子要上大學,當個外企小白領還是很光鮮的,掙得不多也夠花了。但是人終歸要結婚生子,終歸會老,到了40歲,父母老了,要看病要吃藥,要有人看護,自己要還房貸,要過基本體面的生活,要養小孩……那個時候需要掙多少錢才夠花才重要。所以,看待工作,眼光要放遠一點,一時的誰高誰低并不能說明什么。
從這個角度上來說,我不太贊成過于關注第一份工作的薪水,更沒有必要攀比第一份工作的薪水,這在剛剛出校園的學生中間是很常見的。正常人大概要工作 35年,這好比是一場馬拉松比賽,和真正的馬拉松比賽不同的是,這次比賽沒有職業選手,每個人都只有一次機會。要知到,有很多人甚至堅持不到終點,大多數人最后是走到終點的,只有少數人是跑過終點的,因此在剛開始的時候,去搶領先的位置并沒有太大的意義。剛進社會的時候如果進500強公司,大概能拿到 3k-6k/月的工資,有些特別技術的人才可能可以到8k/月,可問題是,5年以后拿多少?估計5k-10k了不起了。起點雖然高,但增幅有限,而且,后面的年輕人追趕的壓力越來越大。
我前兩天問我的一個銷售,你會的這些東西一個新人2年就都學會了,但新人所要求的薪水卻只是你的一半,到時候,你怎么辦?
職業生涯就像一場體育比賽,有初賽、復賽、決賽。初賽的時候大家都剛剛進社會,大多數都是實力一般的人,這時候努力一點認真一點很快就能讓人脫穎而出,于是有的人二十多歲做了經理,有的人遲些也終于贏得了初賽,三十多歲成了經理。然后是復賽,能參加復賽的都是贏得初賽的,每個人都有些能耐,在聰明才智上都不成問題,這個時候再想要勝出就不那么容易了,單靠一點點努力和認真還不夠,要有很強的堅忍精神,要懂得靠團隊的力量,要懂得收服人心,要有長遠的眼光……
看上去贏得復賽并不容易,但,還不是那么難。因為這個世界的規律就是給人一點成功的同時讓人驕傲自滿,剛剛贏得初賽的人往往不知道自己贏得的僅僅是初賽,有了一點小小的成績大多數人都會驕傲自滿起來,認為自己已經懂得了全部,不需要再努力再學習了,他們會認為之所以不能再進一步已經不是自己的原因了。雖然他們仍然不好對付,但是他們沒有耐性,沒有容人的度量,更沒有清晰長遠的目光。就像一只憤怒的斗牛,雖然猛烈,最終是會敗的,而贏得復賽的人則象斗牛士一樣,不急不躁,跟隨著自己的節拍,慢慢耗盡對手的耐心和體力。贏得了復賽以后,大約已經是一位很了不起的職業經理人了,當上了中小公司的總經理,大公司的副總經理,主管著每年幾千萬乃至幾億的生意。
最終的決賽來了,說實話我自己都還沒有贏得決賽,因此對于決賽的決勝因素也只能憑自己的猜測而已,這個時候的輸贏或許就像武俠小說里寫得那樣,大家都是高手,只能等待對方犯錯了,要想輕易擊敗對手是不可能的,除了使上渾身解數,還需要一點運氣和時間。世界的規律依然發揮著作用,贏得復賽的人已經不只是驕傲自滿了,他們往往剛愎自用,聽不進去別人的話,有些人的脾氣變得暴躁,心情變得浮躁,身體變得糟糕,他們最大的敵人就是他們自己,在決賽中要做的只是不被自己擊敗,等著別人被自己擊敗。這和體育比賽是一樣的,最后高手之間的比賽,就看誰失誤少誰就贏得了決賽。
二、 根源
你工作快樂么?你的工作好么?
有沒有覺得干了一段時間以后工作很不開心?有沒有覺得自己入錯了行?有沒有覺得自己沒有得到應有的待遇?有沒有覺得工作像一團亂麻每天上班都是一種痛苦?有沒有很想換個工作?有沒有覺得其實現在的公司并沒有當初想象得那么好?有沒有覺得這份工作是當初因為生存壓力而找的,實在不適合自己?你從工作中得到你想要得到的了么?你每天開心么?
天涯上憤怒的人很多,你有沒有想過,你為什么不快樂?你為什么憤怒?
其實,你不快樂的根源,是因為你不知道要什么!你不知道要什么,所以你不知道去追求什么,你不知道追求什么,所以你什么也得不到。
我總覺得,職業生涯首先要關注的是自己,自己想要什么?大多數人大概沒想過這個問題,唯一的想法只是——我想要一份工作,我想要一份不錯的薪水,我知道所有人對于薪水的渴望,可是,你想每隔幾年重來一次找工作的過程么?你想每年都在這種對于工作和薪水的焦急不安中度過么?不想的話,就好好想清楚。飲鴆止渴,不能因為口渴就拼命喝毒藥。越是焦急,越是覺得自己需要一份工作,越饑不擇食,越想不清楚,越容易失敗,你的經歷越來越差,下一份工作的人看著你的簡歷就皺眉頭。于是你越喝越渴,越渴越喝,陷入惡性循環。最終只能哀嘆世事不公或者生不逢時,只能到天涯上來發泄一把,在失敗者的共鳴當中尋求一點心理平衡罷了。大多數人都有生存壓力,我也是,有生存壓力就會有很多焦慮,積極的人會從焦慮中得到動力,而消極的人則會因為焦慮而迷失方向。所有人都必須在壓力下做出選擇,這就是世道,你喜歡也罷不喜歡也罷。
一般我們處理的事情分為重要的事情和緊急的事情,如果不做重要的事情就會常常去做緊急的事情。比如鍛煉身體保持健康是重要的事情,而看病則是緊急的事情。如果不鍛煉身體保持健康,就會常常為了病痛煩惱。又比如防火是重要的事情,而救火是緊急的事情,如果不注意防火,就要常常救火。找工作也是如此,想好自己究竟要什么是重要的事情,找工作是緊急的事情,如果不想好,就會常常要找工作。往往緊急的事情給人的壓力比較大,迫使人們去趕緊做,相對來說重要的事情反而沒有那么大的壓力,大多數人做事情都是以壓力為導向的,壓力之下,總覺得非要先做緊急的事情,結果就是永遠到處救火,永遠沒有停歇的時候。(很多人的工作也像是救火隊一樣忙碌痛苦,也是因為工作中沒有做好重要的事情。)那些說自己活在水深火熱為了生存顧不上那么多的朋友,今天找工作困難是當初你們沒有做重要的事情,是結果不是原因。如果今天你們還是因為急于要找一份工作而不去思考,那么或許將來要繼續承受痛苦找工作的結果。
我始終覺得我要說的話題,沉重了點,需要很多思考,遠比唐笑打武警的話題來的枯燥乏味,但是,天下沒有輕松的成功,成功,要付代價。請先忘記一切的生存壓力,想想這輩子你最想要的是什么?所以,最要緊的事情,先想好自己想要什么。
三、什么是好工作
當初微軟有個唐駿,很多大學里的年輕人覺得這才是他們向往的職業生涯,我在清華bbs里發的帖子被這些學子們所不屑,那個時候學生們只想出國或者去外企,不過如今看來,我還是對的,唐駿去了盛大,陳天橋創立的盛大,一家民營公司。一個高學歷的海歸在500強的公司里拿高薪水,這大約是很多年輕人的夢想,問題是,每年畢業的大學生都在做這個夢,好的職位卻只有500個。
人都是要面子的,也是喜歡攀比的,即使在工作上也喜歡攀比,不管那是不是自己想要的。大家認為外企公司很好,可是好在哪里呢?好吧,他們在比較好的寫字樓,這是你想要的么?他們出差住比較好的酒店,這是你想要的么?別人會羨慕一份外企公司的工作,這是你想要的么?那一切都是給別人看的,你干嗎要活得那么辛苦給別人看?另一方面,他們薪水福利一般,并沒有特別了不起,他們的晉升機會比較少,很難做到很高階的主管,他們雖然厭惡常常加班,卻不敢不加班,因為“你不干有得是人干”,大部分情況下會找個臺灣人香港人新加坡人來管你,而這些人又往往有些莫名其妙的優越感。你想清楚了么?500強一定好么?找工作究竟是考慮你想要什么,還是考慮別人想看什么?
我的大學同學們大多數都到美國了,甚至畢業這么多年了,還有人最近到國外去了。出國真的有那么好么?我的大學同學們,大多數還是在博士、博士后、訪問學者地掙扎著,至今只有一個正經在一個美國大學里拿到個正式的教職。國內的教授很難當么?我有幾個表親也去了國外了,他們的父母獨自在國內,沒有人照顧,有好幾次人在家里昏倒都沒人知道,出國,真的這么光彩么?就像有人說的“很多事情就像看A片,看的人覺得很爽,做的人未必。”
人總想找到那個最好的,可是,什么是最好的?你覺得是最好的那個,是因為你的確了解,還是因為別人說他是最好的?即使他對于別人是最好的,對于你也一定是最好的么?
對于自己想要什么,自己要最清楚,別人的意見并不是那么重要。很多人總是常常被別人的意見所影響,親戚的意見,朋友的意見,同事的意見……問題是,你究竟是要過誰的一生?人的一生不是父母一生的續集,也不是兒女一生的前傳,更不是朋友一生的外篇,只有你自己對自己的一生負責,別人無法也負不起這個責任。自己做的決定,至少到最后,自己沒什么可后悔。對于大多數正常智力的人來說,所做的決定沒有大的對錯,無論怎么樣的選擇,都是可以嘗試的。比如你沒有考自己上的那個學校,沒有入現在這個行業,這輩子就過不下去了?就會很失?。坎灰姷?。
我想,好工作,應該是適合你的工作,具體點說,應該是能給你帶來你想要的東西的工作,你或許應該以此來衡量你的工作究竟好不好,而不是拿公司的大小,規模,外企還是國企,是不是有名,是不是上市公司來衡量。小公司,未必不是好公司,賺錢多的工作,也未必是好工作。你還是要先弄清楚你想要什么,如果你不清楚你想要什么,你就永遠也不會找到好工作,因為你永遠只看到你得不到的東西,你得到的,都是你不想要的。
可能,最好的,已經在你的身邊,只是,你還沒有學會珍惜。人們總是盯著得不到的東西,而忽視了那些已經得到的東西。
四、普通人
我發現中國人的勵志和國外的勵志存在非常大的不同,中國的勵志比較鼓勵人立下大志愿,臥薪嘗膽,有朝一日成富成貴。而國外的勵志比較鼓勵人勇敢面對現實生活,面對普通人的困境,雖然結果也是成富成貴,但起點不一樣,相對來說,我覺得后者在操作上更現實,而前者則需要用999個失敗者來堆砌一個成功者的故事。
我們都是普通人,普通人的意思就是,概率這件事是很準的。因此,我們不會買彩票中500萬,我們不會成為比爾蓋茨或者李嘉誠,我們不會坐飛機掉下來,我們當中很少的人會創業成功,我們之中有30%的人會離婚,我們之中大部分人會活過65歲……
所以請你在想自己要什么的時候,要得“現實”一點,你說我想要做李嘉誠,抱歉,我幫不上你。成為比爾蓋茨或者李嘉誠這種人,是靠命的,看我寫的這篇文章絕對不會讓你成為他們,即使你成為了他們,也絕對不是我這篇文章的功勞。“王侯將相寧有種乎”但真正當皇帝的只有一個人,王侯將相,人也不多。目標定得高些對于喜歡挑戰的人來說有好處,但對于大多數普通人來說,反而比較容易灰心沮喪,很容易就放棄了。
回過頭來說,李嘉誠比你有錢大致50萬倍,他比你更快樂么?或許。有沒有比你快樂50萬倍,一定沒有。他比你最多也就快樂一兩倍,甚至有可能還不如你快樂。尋找自己想要的東西不是和別人比賽,比誰要得更多更高,比誰的目標更遠大。雖然成為李嘉誠這個目標很宏大,但你并不見得會從這個目標以及追求目標的過程當中獲得快樂,而且基本上你也做不到。你必須聽聽你內心的聲音,尋找真正能夠使你獲得快樂的東西,那才是你想要的東西。
你想要的東西,或者我們把它稱之為目標,目標其實并沒有高低之分,你不需要因為自己的目標沒有別人遠大而不好意思,達到自己的目標其實就是成功,成功有大有小,快樂卻是一樣的。我們追逐成功,其實追逐的是成功帶來的快樂,而非成功本身。職業生涯的道路上,我們常常會被攀比的心態蒙住眼睛,忘記了追求的究竟是什么,忘記了是什么能使我們更快樂。
社會上一夜暴富的新聞很多,這些消息,總會在我們的心里面掀起很多漣漪,漣漪多了就變成驚濤駭浪,心里的驚濤駭浪除了打翻承載你目標的小船,并不會使得你也一夜暴富。“只見賊吃肉,不見賊挨揍。”我們這些普通人既沒有當賊的勇氣,又缺乏當賊的狠辣絕決,雖然羨慕吃肉,卻更害怕挨揍,偶爾看到幾個沒挨揍的賊就按奈不住,或者心思活動,或者大感不公,真要叫去做賊,卻也不敢。
我還是過普通人的日子,要普通人的快樂,至少,晚上睡得著覺。
五、跳槽與積累
首先要說明,工作是一件需要理智的事情,所以不要在工作上耍個性,天涯上或許會有人覺得你很有個性而叫好,煤氣公司電話公司不會因為覺得你很有個性而免了你的帳單。當你很帥地炒掉了你的老板,當你很酷地挖苦了一番招聘的HR,賬單還是要照付,只是你賺錢的時間更少了,除了你自己,沒人受損失。
我并不反對跳槽,但跳槽決不是解決問題的辦法,而且頻繁跳槽的后果是讓人覺得沒有忠誠度可言,而且不能安心工作。現在很多人從網上找工作,很多找工作的網站常常給人出些餿主意,要知道他們是盈利性企業,當然要從自身盈利的角度來考慮,大家越是頻繁跳槽頻繁找工作他們越是生意興隆,所以鼓動人們跳槽是他們的工作。所以他們會常常告訴你,你拿的薪水少了,你享受的福利待遇差了,又是“薪情快報”又是“贊嘆自由奔放的靈魂”。至于是否會因此讓你不能安心,你跳了槽是否解決問題,是否更加開心,那個,他們管不著。
要跳槽肯定是有問題,一般來說問題發生了,躲是躲不開的,很多人跳槽是因為這樣或者那樣的不開心,如果這種不開心,在現在這個公司不能解決,那么在下一個公司多半也解決不掉。你必須相信,90%的情況下,你所在的公司并沒有那么爛,你認為不錯的公司也沒有那么好。就像圍城里說的,“城里的人拼命想沖出來,而城外的人拼命想沖進去。”每個公司都有每個公司的問題,沒有問題的公司是不存在的。換個環境你都不知道會碰到什么問題,與其如此,不如就在當下把問題解決掉。很多問題當你真的想要去解決的時候,或許并沒有那么難。有的時候你覺得問題無法解決,事實上,那只是“你覺得”。
人生的曲線應該是曲折向上的,偶爾會遇到低谷但大趨勢總歸是曲折向上的,而不是象脈沖波一樣每每回到起點,我見過不少面試者,30多歲了,四五份工作經歷,每次多則3年,少則1年,30多歲的時候回到起點從一個初級職位開始干起,拿基本初級的薪水,和20多歲的年輕人一起競爭,不覺得有點辛苦么?這種日子好過么?
我非常不贊成在一個行業超過3年以后換行業,基本上,35歲以前我們的生存資本靠打拼,35歲以生存的資本靠的就是積累,這種積累包括人際關系,經驗,人脈,口碑……如果常常更換行業,代表幾年的積累付之東流,一切從頭開始,如果換了兩次行業,35歲的時候大概只有5年以下的積累,而一個沒有換過行業的人至少有了10年的積累,誰會占優勢?工作到2-3年的時候,很多人覺得工作不順利,好像到了一個瓶頸,心情煩悶,就想辭職,乃至換一個行業,覺得這樣所有一切煩惱都可以拋開,會好很多。其實這樣做只是讓你從頭開始,到了時候還是會發生和原來行業一樣的困難,熬過去就向上跨了一大步,要知道每個人都會經歷這個過程,每個人的職業生涯中都會碰到幾個瓶頸,你熬過去了而別人沒有熬過去你就領先了。跑長跑的人會知道,開始的時候很輕松,但是很快會有第一次的難受,但過了這一段又能跑很長一段,接下來會碰到第二次的難受,堅持過了以后又能跑一段,如此往復,難受一次比一次厲害,直到堅持不下去了。大多數人第一次就堅持不了了,一些人能堅持到第二次,第三次雖然大家都堅持不住了,可是跑到這里的人也沒幾個了,這點資本足夠你安穩活這一輩子了。
一份工作到兩三年的時候,大部分人都會變成熟手,這個時候往往會陷入不斷的重復,有很多人會覺得厭倦,有些人會覺得自己已經搞懂了一切,從而懶得去尋求進步了。很多時候的跳槽是因為覺得失去興趣了,覺得自己已經完成比賽了。其實這個時候比賽才剛剛開始,工作兩三年的人,無論是客戶關系,人脈,手下,和領導的關系,在業內的名氣……還都是遠遠不夠的,但稍有成績的人總是會自我感覺良好的,每個人都覺得自己跟客戶關系鐵得要命,覺得自己在業界的口碑好得很。其實可以肯定地說,一定不是,這個時候,還是要拿出前兩年的干勁來,穩扎穩打,積累才剛剛開始。
你足夠了解你的客戶嗎?你知道他最大的煩惱是什么嗎?你足夠了解你的老板么?你知道他最大的煩惱是什么嗎?你足夠了解你的手下么?你知道他最大的煩惱是什么嗎?如果你不知道,你憑什么覺得自己已經積累夠了?如果你都不了解,你怎么能讓他們幫你的忙,做你想讓他們做的事情?如果他們不做你想讓他們做的事情,你又何來的成功?
六、等待
這是個浮躁的人們最不喜歡的話題,本來不想說這個話題,因為會引起太多的爭論,而我又無意和人爭論這些,但是考慮到對于職業生涯的長久規劃,這是一個躲避不了的話題,還是決定寫一寫,不愛看的請離開吧。
并不是每次穿紅燈都會被汽車撞,并不是每個罪犯都會被抓到,并不是每個錯誤都會被懲罰,并不是每個貪官都會被槍斃,并不是你的每一份努力都會得到回報,并不是你的每一次堅持都會有人看到,并不是你每一點付出都能得到公正的回報,并不是你的每一個善意都能被理解……這個,就是世道。好吧,世道不夠好,可是,你有推翻世道的勇氣么?如果沒有,你有更好的解決辦法么?有很多時候,人需要一點耐心,一點信心。每個人總會輪到幾次不公平的事情,而通常,安心等待是最好的辦法。
有很多時候我們需要等待,需要耐得住寂寞,等待屬于你的那一刻。周潤發等待過,劉德華等待過,周星馳等待過,王菲等待過,張藝謀也等待過……看到了他們如今的功成名就的人,你可曾看到當初他們的等待和耐心?你可曾看到金馬獎影帝在街邊擺地攤?你可曾看到德云社一群人在劇場里給一位觀眾說相聲?你可曾看到周星馳的角色甚至連一句臺詞都沒有?每一個成功者都有一段低沉苦悶的日子,我幾乎能想象得出來他們借酒澆愁的樣子,我也能想象得出他們為了生存而掙扎的窘迫。在他們一生最中燦爛美好的日子里,他們渴望成功,但卻兩手空空,一如現在的你。沒有人保證他們將來一定會成功,而他們的選擇是耐住寂寞。如果當時的他們總念叨著“成功只是屬于特權階級的”,你覺得他們今天會怎樣?
曾經我也不明白有些人為什么并不比我有能力卻要坐在我的頭上,年紀比我大就一定要當我的領導么?為什么有些爛人不需要努力就能賺錢?為什么剛剛改革開放的時候的人能那么容易賺錢,而輪到我們的時候,什么事情都要正規化了?有一天我突然想,我還在上學的時候他們就在社會里掙扎奮斗了,他們在社會上奮斗積累了十幾二十年,我們新人來了,他們有的我都想要,我這不是在要公平,我這是在要搶劫。因為我要得太急,因為我忍不住寂寞。二十多歲的男人,沒有錢,沒有事業,卻有蓬勃的欲望。
人總是會遇到挫折的,人總是會有低潮的,人總是會有不被人理解的時候的,人總是有要低聲下氣的時候,這些時候恰恰是人生最關鍵的時候,因為大家都會碰到挫折,而大多數人過不了這個門檻,你能過,你就成功了。在這樣的時刻,我們需要耐心等待,滿懷信心地去等待,相信,生活不會放棄你,機會總會來的。至少,你還年輕,你沒有坐牢,沒有生治不了的病,沒有欠還不起的債。比你不幸的人遠遠多過比你幸運的人,你還怕什么?路要一步步走,雖然到達終點的那一步很激動人心,但大部分的腳步是平凡甚至枯燥的,但沒有這些腳步,或者耐不住這些平凡枯燥,你終歸是無法迎來最后的那些激動人心。
逆境,是上帝幫你淘汰競爭者的地方。要知道,你不好受,別人也不好受,你堅持不下去了,別人也一樣,千萬不要告訴別人你堅持不住了,那只能讓別人獲得堅持的信心,讓競爭者看著你微笑的面孔,失去信心,退出比賽。勝利屬于那些有耐心的人。
在最絕望的時候,我會去看電影《The Pursuit of Happyness》《Jerry Maguire》,讓自己重新鼓起勇氣,因為,無論什么時候,我們總還是有希望。當所有的人離開的時候,我不失去希望,我不放棄。每天下班坐在車里,我喜歡哼著《隱形的翅膀》看著窗外,我知道,我在靜靜等待,等待屬于我的那一刻。
原貼里伊吉網友的話我很喜歡,抄錄在這里:
每個人都希望,自己是獨一無二的特殊者
含著金匙出生、投胎到好家庭、工作安排到電力局拿1w月薪這樣的小概率事件,當然最好輪到自己
紅軍長征兩萬五、打成右派反革命、胼手胝足犧牲尊嚴去奮斗,最好留給祖輩父輩和別人
自然,不是每個吃過苦的人都會得到回報
但是,任何時代,每一個既得利益者身后,都有他的祖輩父輩奮斗掙扎乃至流血付出生命的身影
羨慕別人有個好爸爸,沒什么不可以
問題是,你的下一代,會有一個好爸爸嗎?
至于問到為什么不能有同樣的贏面概率?我只能問:為什么物種競爭中,人和猴子不能有同樣的贏面概率?
物競天擇。猴子的靈魂不一定比你卑微,但你身后有幾十萬年的類人猿進化積淀。
七、入對行跟對人
在中國,大概很少有人是一份職業做到底的,雖然如此,第一份工作還是有些需要注意的地方,有兩件事情格外重要,第一件是入行,第二件事情是跟人。第一份工作對人最大的影響就是入行,現代的職業分工已經很細,我們基本上只能在一個行業里成為專家,不可能在多個行業里成為專家。很多案例也證明即使一個人在一個行業非常成功,到另外一個行業,往往完全不是那么回事情,“你想改變世界,還是想賣一輩子汽水?”是喬布斯邀請百事可樂總裁約翰·斯考利加盟蘋果時所說的話,結果這位在百事非常成功的約翰,到了蘋果表現平平。其實沒有哪個行業特別好,也沒有哪個行業特別差,或許有報道說哪個行業的平均薪資比較高,但是他們沒說的是,那個行業的平均壓力也比較大??瓷先ズ苊赖男袠I一旦進入才發現很多地方其實并不那么完美,只是外人看不見。
說實話,我自己都沒有發大財,所以我的建議只是讓人快樂工作的建議,不是如何發大財的建議,我們只討論一般普通打工者的情況。我認為選擇什么行業并沒有太大關系,看問題不能只看眼前。比如,從前年開始,國家開始整頓醫療行業,很多醫藥公司開不下去,很多醫藥行業的銷售開始轉行。其實醫藥行業的不景氣是針對所有公司的,并非針對一家公司,大家的日子都不好過,這個時候跑掉是非常不劃算的,大多數正規的醫藥公司即使不做新生意撐個兩三年總是能撐的,大多數醫藥銷售靠工資撐個兩三年也是可以撐的,國家不可能永遠捏著醫藥行業不放的,兩三年以后光景總歸還會好起來的,那個時候別人都跑了而你沒跑,那時的日子應該會好過很多。有的時候覺得自己這個行業不行了,問題是,再不行的行業,做得人少了也變成了好行業,當大家都覺得不好的時候,往往卻是最好的時候。大家都覺得金融行業好,金融行業門檻高不說,有多少人削尖腦袋要鉆進去,競爭激勵,進去以后還要時時提防,一個疏忽,就被后來的人給擠掉了,壓力巨大,又如何談得上快樂?也就未必是“好”工作了。
太陽能這個東西至今還不能進入實際應用的階段,但是中國已經有7家和太陽能有關的公司在紐交所上市了,國美蘇寧永樂其實是貿易型企業,也能上市,魯泰紡織連續10年利潤增長超過50%,賣茶的一茶一座,賣衣服的海瀾之家都能上市……其實選什么行業真的不重要,關鍵是怎么做。事情都是人做出來的,關鍵是人。
有一點是需要記住的,這個世界上,有史以來直到我們能夠預見得到的未來,成功的人總是少數,有錢的人總是少數,大多數人是一般的,普通的,不太成功的。因此,大多數人的做法和看法,往往都不是距離成功最近的做法和看法。因此大多數人說好的東西不見得好,大多數人說不好的東西不見得不好。大多數人都去炒股的時候說明跌只是時間問題,大家越是熱情高漲的時候,跌的日子越近。大多數人買房子的時候,房價不會漲,而房價漲的差不多的時候,大多數人才開始買房子。不會有這樣一件事情讓大家都變成功,發了財,歷史上不曾有過,將來也不會發生。有些東西即使一時運氣好得到了,還是會在別的時候別的地方失去的。
年輕人在職業生涯的剛開始,尤其要注意的是,要做對的事情,不要讓自己今后幾十年的人生總是提心吊膽,更不值得為了一份工作賠上自己的青春年華。我的公司是個不行賄的公司,以前很多人不理解,甚至自己的員工也不理解,不過如今,我們是同行中最大的企業,客戶樂意和我們打交道,尤其是在國家打擊腐敗的時候,每個人都知道我們做生意不給錢的名聲,都敢于和我們做生意。而勇于給錢的公司,不是倒了,就是跑了,要不就是每天睡不好覺,人還是要看長遠一點。很多時候,看起來最近的路,其實是最遠的路,看起來最遠的路,其實是最近的路。
跟對人是說,入行后要跟個好領導好老師,剛進社會的人做事情往往沒有經驗,需要有人言傳身教。對于一個人的發展來說,一個好領導是非常重要的。所謂“好”的標準,不是他讓你少干活多拿錢,而是以下三個。
首先,好領導要有寬廣的心胸,如果一個領導每天都會發脾氣,那幾乎可以肯定他不是個心胸寬廣的人,能發脾氣的時候卻不發脾氣的領導,多半是非常厲害的領導。中國人當領導最大的毛病是容忍不了能力比自己強的人,所以常??梢钥吹降囊粋€現象是,領導很有能力,手下一群庸才或者手下一群閑人。如果看到這樣的環境,還是不要去的好。
其次,領導要愿意從下屬的角度來思考問題,這一點其實是從面試的時候就能發現的,如果這位領導總是從自己的角度來考慮問題,幾乎不聽你說什么,這就危險了。從下屬的角度來考慮問題并不代表同意下屬的說法,但他必須了解下屬的立場,下屬為什么要這么想,然后他才有辦法說服你,只關心自己怎么想的領導往往難以獲得下屬的信服。
第三,領導敢于承擔責任,如果出了問題就把責任往下推,有了功勞就往自己身上攬,這樣的領導不跟也罷。選擇領導,要選擇關鍵時刻能抗得住的領導,能夠為下屬的錯誤買單的領導,因為這是他作為領導的責任。
有可能,你碰不到好領導,因為,中國的領導往往是屁股決定腦袋的領導,因為他坐領導的位置,所以他的話就比較有道理,這是傳統觀念官本位的誤區,可能有大量的這種無知無能的領導,只是,這對于你其實是好事,如果將來有一天你要超過他,你希望他比較聰明還是比較笨?相對來說這樣的領導其實不難搞定,只是你要把自己的身段放下來而已。多認識一些人,多和比自己強的人打交道,同樣能找到好的老師,不要和一群同樣郁悶的人一起控訴社會,控訴老板,這幫不上你,只會讓你更消極。和那些比你強的人打交道,看他們是怎么想的,怎么做的,學習他們,然后跟更強的人打交道。
八、選擇
我們每天做的最多的事情,其實是選擇,因此在談職業生涯的時候不得不提到這個話題。
我始終認為,在很大的范圍內,我們究竟會成為一個什么樣的人,決定權在我們自己,每天我們都在做各種各樣的選擇,我可以不去寫這篇文章,去別人的帖子拍拍磚頭,也可以寫下這些文字,幫助別人的同時也整理自己的思路,我可以多注意下格式讓別人易于閱讀,也可以寫成一堆,我可以就這樣發上來,也可以在發以前再看幾遍,你可以選擇不刮胡子就去面試,也可以選擇出門前照照鏡子……每天,每一刻我們都在做這樣那樣的決定,我們可以漫不經心,也可以多花些心思,成千上萬的小選擇累計起來,就決定了最終我們是個什么樣的人。
從某種意義上來說我們的未來不是別人給的,是我們自己選擇的,很多人會說我命苦啊,沒得選擇阿,如果你認為“去微軟還是去IBM”“上清華還是上北大 ”“當銷售副總還是當廠長”這種才叫選擇的話,的確你沒有什么選擇,大多數人都沒有什么選擇。但每天你都可以選擇是否為客戶服務更周到一些,是否對同事更耐心一些,是否把工作做得更細致一些,是否把情況了解得更清楚一些,是否把不清楚的問題再弄清楚一些……你也可以選擇在是否在痛苦中繼續堅持,是否拋棄掉自己的那些負面的想法,是否原諒一個人的錯誤,是否相信我在這里寫下的這些話,是否不要再犯同樣的錯誤……生活每天都在給你選擇的機會,每天都在給你改變自己人生的機會,你可以選擇賴在地上撒潑打滾,也可以選擇咬牙站起來。你永遠都有選擇。有些選擇不是立桿見影的,需要累積,比如農民可以選擇自己常常去澆地,也可以選擇讓老天去澆地,誠然你今天澆水下去苗不見得今天馬上就長出來,但常常澆水,大部分苗終究會長出來的,如果你不澆,收成一定很糟糕。
每天生活都在給你機會,他不會給你一疊現金也不會拱手送你個好工作,但實際上,他還是在給你機會。我的家庭是一個普通的家庭,沒有任何了不起的社會關系,我的父親在大學畢業以后就被分配到了邊疆,那個小縣城只有一條馬路,他們那一代人其實比我們更有理由抱怨,他們什么也沒得到,年輕的時候文化大革命,書都沒得讀,支援邊疆插隊落戶,等到老了,卻要給年輕人機會了。他有足夠的理由象成千上萬那樣的青年一樣坐在那里抱怨生不逢時,怨氣沖天。然而在分配到邊疆的十年之后,國家恢復招研究生,他考回了原來的學校。研究生畢業,他被分配到了安徽一家小單位里,又是3年以后,國家第一屆招收博士生,他又考回了原來的學校,成為中國第一代博士,那時的他比現在的我年紀還大。生活并沒有放棄他,他也沒有放棄生活。10年的等待,他做了他自己的選擇,他沒有放棄,他沒有破罐子破摔,所以時機到來的時候,他改變了自己的人生。你最終會成為什么樣的人,就決定在你的每個小小的選擇之間。
你選擇相信什么?你選擇和誰交朋友?你選擇做什么?你選擇怎么做?……我們面臨太多的選擇,而這些選擇當中,意識形態層面的選擇又遠比客觀條件的選擇來得重要得多,比如選擇做什么產品其實并不那么重要,而選擇怎么做才重要。選擇用什么人并不重要,而選擇怎么帶這些人才重要。大多數時候選擇客觀條件并不要緊,大多數關于客觀條件的選擇并沒有對錯之分,要緊的是選擇怎么做。一個大學生畢業了,他要去微軟也好,他要賣豬肉也好,他要創業也好,他要做游戲代練也好,只要不犯法,不害人,都沒有什么關系,要緊的是,選擇了以后,怎么把事情做好。
除了這些,你還可以選擇時間和環境,比如,你可以選擇把這輩子最大的困難放在最有體力最有精力的時候,也可以走一步看一步,等到了40歲再說,只是到了40多歲,那正是一輩子最脆弱的時候,上有老下有小,如果在那個時候碰上了職業危機,實在是一件很苦惱的事情。與其如此不如在20多歲30多歲的時候吃點苦,好讓自己脆弱的時候活得從容一些。你可以選擇在溫室里成長,也可以選擇到野外磨礪,你可以選擇在辦公室吹冷氣的工作,也可以選擇40度的酷熱下,去見你的客戶,只是,這一切最終會累積起來,引導你到你應得的未來。
我不敢說所有的事情你都有得選擇,但是絕大部分事情你有選擇,只是往往你不把這當作一種選擇。認真對待每一次選擇,才會有比較好的未來。
九、選擇職業
職業的選擇,總的來說,無非就是銷售、市場、客服、物流、行政、人事、財務、技術、管理幾個大類,有個有趣的現象就是,500強的CEO當中最多的是銷售出身,第二多的人是財務出身,這兩者加起來大概超過95%。現代IT行業也有技術出身成為老板的,但實際上,后來他們還是從事了很多銷售和市場的工作,并且表現出色,公司才獲得了成功,完全靠技術能力成為公司老板的,幾乎沒有。這是有原因的,因為銷售就是一門跟人打交道的學問,而管理其實也是跟人打交道的學問,這兩者之中有很多相通的東西,他們的共同目標就是“讓別人去做某件特定的事情。”而財務則是從數字的層面了解生意的本質,從宏觀上看待生意的本質,對于一個生意是否掙錢,是否可以正常運作有著最深刻的認識。
公司小的時候是銷售主導公司,而公司大的時候是財務主導公司,銷售的局限性在于只看人情不看數字,財務的局限性在于只看數字不看人情。公司初期,運營成本低,有訂單就活得下去,跟客戶也沒有什么談判的條件,別人肯給生意做已經謝天謝地了,這個時候訂單壓倒一切,客戶的要求壓倒一切,所以當然要顧人情。公司大了以后,一切都要規范化,免得因為不規范引起一些不必要的風險,同時運營成本也變高,必須提高利潤率,把有限的資金放到最有產出的地方。對于上市公司來說,股東才不管你客戶是不是最近出國,最近是不是那個省又在搞嚴打,到了時候就要把業績拿出來,拿不出來就拋股票,這個時候就是數字壓倒一切。
前兩天聽到有人說一句話覺得很有道理,開始的時候我們想“能做什么?”,等到公司做大了有規模了,我們想“不能做什么。”很多人在工作中覺得為什么領導這么保守,這也不行那也不行,錯過很多機會。很多時候是因為,你還年輕,你想的是“能做什么”,而作為公司領導要考慮的方面很多,他比較關心“不能做什么”。
我并非鼓吹大家都去做銷售或者財務,究竟選擇什么樣的職業,和你究竟要選擇什么樣的人生有關系,有些人就喜歡下班按時回家,看看書聽聽音樂,那也挺好,但就不適合找個銷售的工作了,否則會是折磨自己。有些人就喜歡出風頭,喜歡成為一群人的中心,如果選擇做財務工作,大概也干不久,因為一般老板不喜歡財務太積極,也不喜歡財務話太多。先想好自己要過怎樣的人生,再決定要找什么樣的職業。有很多的不快樂,其實是源自不滿足,而不滿足,很多時候是源自于心不定,而心不定則是因為不清楚究竟自己要什么,不清楚要什么的結果就是什么都想要,結果什么都沒得到。
我想,我們還是因為生活而工作,不是因為工作而生活,生活是最要緊的,工作只是生活中的一部分。我總是覺得生活的各方方面都是相互影響的,如果生活本身一團亂麻,工作也不會順利。所以要有娛樂、要有社交、要鍛煉身體,要有和睦的家庭……最要緊的,要開心,我的兩個銷售找我聊天,一肚子苦水,我問他們,2年以前,你什么都沒有,工資不高,沒有客戶關系,沒有業績,處于被開的邊緣,現在的你比那時條件好了很多,為什么現在卻更加不開心了?如果你做得越好越不開心,那你為什么還要工作?首先的首先,人還是要讓自己高興起來,讓自己心態好起來,這種發自內心的改變會讓你更有耐心,更有信心,更有氣質,更能包容……否則,看看鏡子里的你,你滿意么?
有人會說,你說得容易,我每天加班,不加班老板就會把我炒掉,每天累得要死,哪有時間娛樂、社交、鍛煉?那是人們把目標設定太高的緣故,如果你還在動不動就會被老板炒掉的邊緣,那么你當然不能設立太高的目標,難道你還想每天去打高爾夫?你沒時間去健身房鍛煉身體,但是上下班的時候多走幾步可以吧,有樓梯的時候走走樓梯不走電梯可以吧?辦公的間隙扭扭脖子拉拉肩膀做做俯臥撐可以吧?誰規定鍛煉就一定要拿出每天2個小時去健身房?你沒時間社交,每月參加郊游一次可以吧,周末去參加個什么音樂班,繪畫班之類的可以吧,去嘗試認識一些同行,和他們找機會交流交流可以吧?開始的時候總是有些難的,但邁出這一步就會向良性循環的方向發展。而每天工作得很苦悶,剩下的時間用來咀嚼苦悶,只會陷入惡性循環,讓生活更加糟糕。
雖然離開惠普僅有十五天,但感覺上惠普已經離我很遠。我的心思更多放在規劃自己第二階段的人生,這并非代表我對惠普沒有任何眷戀,主要還是想以此驅動自己往前走。萬科王石登珠穆朗瑪峰的體驗給我很多啟發,雖然在出發時攜帶大量的物資,但是登頂的過程中,必須不斷減輕負荷,最終只有一個氧氣瓶和他登上峰頂。登山如此,漫長的人生又何嘗不是。我宣布退休后,接到同事朋友同學的祝賀。大部分人都認為我能夠在這樣的職位上及年齡選擇退休,是一種勇氣,也是一種福氣。還有一部分人懷疑我只是借此機會換個工作,當然還有一些人說我在HP做不下去了,趁此機會離開。
我多年來已經習慣別人對我的說三道四,但對于好友,我還是挺關心大家是否真正理解我的想法,這也是寫這篇文章的目的。
由于受我父親早逝的影響,我很早就下定決心,要在有生之年實現自己的愿望,我不要像我父親一樣,為家庭生活忙碌一輩子,臨終前感傷,懊惱自己有很多沒有實現的理想。
一本雜志的文章提到我們在生前就應該思考自己的墓志銘,因為那代表你自己對完美人生的定義,我們應該盡可能在有生之年去實現它。
我希望我的墓志銘上除了與家人及好友有關的內容外,是這樣寫著:
1.這個人曾經服務于一家全球最大的IT公司(HP)25年,和她一起經歷過數次重大的變革,看著她從以電子儀表為主要的業務變革成全球最大的IT公司。
2.這個人曾經在全球發展最快的國家(中國)工作16年,并擔任HP中國區總裁7年,見證及經歷過中國改革開放的關鍵最新突破階段,與中國一起成長。
3.這個人熱愛飛行,曾經是一個有執照的飛行員,累積飛行時數超過X小時,曾經在X個機場起降過。
4. 這個人曾經獲得管理碩士學位,在領導管理上特別關注中國企業的組織行為及績效,并且在這個領域上獲得中國企業界的認可。
我費時25年才總結1和2兩項成果,我不知還要費時多久才能達成3和4的愿望,特別是第4個愿望需要經歷學術的訓練,才能將我的經驗總結成知識。
否則我的經驗將無法有效影響及傳授他人。因此重新進入學校學習,拿一個管理學位是有必要的,更何況這是我一個非常重要的愿望。
另一方面,我25年的時間都花在運營(operation) 的領域,兢兢業業的做好職業人士的工作,它是一份好工作,特別是在HP,這份工作也幫助我建立財務的基礎,支持家庭的發展。
但是我不想終其一生,都陷入在運營的領域,我想象企業家一樣,有機會靠一些點子 (ideas)賺錢,雖然風險很高,但是值得一試,即使失敗,也不枉走一回,這也是第4個愿望其中的一部份。
Carly Fiorina 曾經對我說過“這個世界上有好想法的人很多,但有能力去實現的人很少”,2007 年5月21日在北大演講時,有人問起那些書對我影響較大,我想對我人生觀有影響的其中一本書叫“Trigger Point”,它的主要觀點是:人生最需要的不是規劃,而是在適當的時機掌握機會,采取行動。
我這些愿望在我心中已經醞釀一段很長的時間,開始的時候,也許一年想個一兩次,過了也就忘掉,但逐漸的,這個心中的聲音,愈來愈大,出現的頻率也愈來愈高,當它幾乎每一個星期都會來與我對話時,我知道時機已經成熟。
但和任何人一樣,要丟掉自己現在所擁有的,所熟悉的環境及穩定的收入,轉到一條自己未曾經歷過,存在未知風險的道路,需要絕大的勇氣,家人的支持和好友的鼓勵。有舍才有得,真是知易行難,我很高興自己終于跨出了第一步。
我要感謝HP的EER提前退休優惠政策,它是其中一個關鍵的Trigger Points,另一個關鍵因素是在去年五六月發生的事。
當時我家老大從大學畢業,老二從高中畢業,在他們繼續工作及求學前,這是一個黃金時段,讓我們全家可以相聚一段較長的時間,我為此很早就計劃休一個長假,帶著他們到各地游玩。
但這個計劃因為工作上一件重要的事情(Mark Hurd 訪華)不得不取消。這個事件刺激了我必須嚴肅的去對待那心中的聲音,我會不會繼續不斷的錯失很多關鍵的機會?
我已經年過50,我會不會走向和我父親一樣的道路?人事部老總Charles跟我說,很多人在所有對他有利的星星都排成一列時,還是錯失時機。
我知道原因,因為割舍及改變對人是多么的困難,我相信大部分的人都有自己人生的理想,但我也相信很多人最終只是把這些理想當成是
幻想,然后不斷的為自己尋找不能實現的藉口,南非前總統曼德拉曾經說過,“與改變世界相比,改變自己更困難”,真是一針見血。
什么是快樂及有意義的人生?我相信每一個人的定義都不一樣,對我來說,能實現我墓志銘上的內容就是我的定義。
在中國惠普總裁的位置上固然可以吸引很多的關注及眼球,但是我太太及較親近的好友,都知道那不是我追求的,那只是為扮演好這個角色必須盡力做好的地方。
做一個沒有名片的人士,雖然只有十多天的時間,但我發現我的腦袋里已經空出很多空間及能量,讓我可以靜心的為我Chapter II的新生活做細致的調研及規劃。
我預訂以兩年的時間來完成轉軌的準備工作,并且花多點時間與家人共處。這兩年的時間我希望拿到飛行執照,拿到管理有關的碩士學位,提升英文的水平,建立新的網絡,多認識不同行業的人,保持與大陸的聯系。希望兩年后,我可以順利回到大陸去實現我第四個愿望。
毫不意外,在生活上,我發現很多需要調整的地方。
二十多年來,我生活的步調及節奏,幾乎完全被公司及工作所左右,不斷涌出的deadline及任務驅動我每天的安排,一旦離開這樣的環境,第一個需要調整的就是要依靠自己的自律及意志力來驅動每天的活動,睡覺睡到自然醒的態度絕對不正確,放松自己,不給事情設定目標及時間表,或者對錯失時間目標無所謂,也不正確,沒有年度,季度,月及周計劃也不正確。
擔任高層經理多年,已經養成交待事情的習慣,自己的時間主要花在思考,決策及追蹤項目的進展情況,更多是依靠一個龐大的團隊來執行具體的事項及秘書來處理很多協調及繁瑣的事情。
到美國后,很多事情需要打800號電話聯系,但這些電話很忙,常讓你在waiting line上等待很長的時間,當我在等待時,我可以體會以前秘書工作辛苦的地方,但同時也提醒我自己,在這個階段要改變態度,培養更大的耐性及自己動手做的能力。
生活的內容也要做出很大的調整,多出時間鍛煉身體,多出時間關注家人,多出時間關注朋友,多出時間體驗不同的休閑活動及飛行,一步步的,希望生活逐步調整到我所期望的軌道上,期待這兩年的生活既充實又充滿樂趣及意義。
第一個快樂的體驗就是準備及參加大兒子的訂婚禮,那種全心投入,不需擔憂工作數字的感覺真好。同時我也租好了公寓,買好了家具及車子,陪家人在周末的時候到Reno 及Lake Tahoe玩了一趟,Lake Tahoe我去了多次,但這次的體驗有所不同,我從心里欣賞到它的美麗。
但同時我也在加緊調研的工作,為申請大學及飛行學校做準備,這段時間也和在硅谷的朋友及一些風險投資公司見面,了解不同的產業。
我的人生觀是“完美的演出來自充分的準備”,“勇于改變自己,適應不斷變化的環境,機會將不斷出現”,“快樂及有意義的人生來自于實現自己心中的愿望,而非外在的掌聲”。
我離開時,有兩位好朋友送給我兩個不同的祝語,Baron的是“多年功過化煙塵”,楊華的是“莫春者,風乎舞雩,詠而歸”,它們分別代表了我離開惠普及走向未來的心情。
我總結人生有三個階段,一個階段是為現實找一份工作,一個階段是為現實,但可以選擇一份自己愿意投入的工作,一個階段是為理想去做一些事情。
我珍惜我的福氣,感激HP及同事、好朋友給我的支持,鼓勵及協助,這篇文字化我心聲的文章與好友分享。
一、UDP Server
項目的需要,需要利用java實現一個udp server,主要的功能是偵聽來自客戶端的udp請求,客戶請求可能是大并發量的,對于每個請求Server端的處理很簡單,處理每個請求的時間大約在1ms左右,但是Server端需要維護一個對立于請求的全局變量Cache,項目本身已經采用Mina架構(http://mina.apache.org/),我要開發的Server作為整個項目的一個模塊,由于之前沒有開發UDP Server,受TCP Server的影響,很自然的想利用多線程來實現,對于每個客戶請求,新建一個線程來處理相應的邏輯,在實現的過程中,利用Mina的Thread Model,實現了一個多線程的UDP Server,但是由于要維護一個全局Cache,需要在各線程之間同步,加之處理請求的時間很短,很快就發現在此利用多線程,并不能提高性能,于是決定采用單線程來實現,在動手之前,還是發現有兩種方案來實現單線程UDP Server:
1) 采用JDK的DatagramSocket和DatagramPacket來實現
public class UDPServerUseJDK extends Thread{
/**
* Constructor
* @param port port used to listen incoming connection
* @throws SocketException error to consturct a DatagramSocket with this port
*/
public MediatorServerUseJDK(int port) throws SocketException{
this("MediatorServerThread", port);
}
/**
* Constructor
* @param name the thread name
* @param port port used to listen incoming connection
* @throws SocketException error to consturct a DatagramSocket with this port
*/
public MediatorServerUseJDK(String name, int port) throws SocketException{
super(name);
socket = new DatagramSocket(port);
System.out.println("Mediator server started on JDK model...");
System.out.println("Socket buffer size: " + socket.getReceiveBufferSize());
}
public void run(){
long startTime = 0;
while(true){
try {
buf = new byte[1024];
// receive request
packet = new DatagramPacket(buf, buf.length);
socket.receive(packet);
.........
}catch (IOException e) {
.........
}
}
}
}
2) 采用Mina的DatagramAcceptor來實現,在創建Exector的時候,只傳遞單個線程
public class MediatorServerUseMina {
private DatagramAcceptor acceptor;
public MediatorServerUseMina() {
}
public void startListener(InetSocketAddress address, IoHandler handler) {
// create an acceptor with a single thread
this.acceptor = new DatagramAcceptor(Executors.newSingleThreadExecutor());
// configure the thread models
DatagramAcceptorConfig acceptorConfig = acceptor.getDefaultConfig();
acceptorConfig.setThreadModel(ThreadModel.MANUAL);
// set the acceptor to reuse the address
acceptorConfig.getSessionConfig().setReuseAddress(true);
// add io filters
DefaultIoFilterChainBuilder filterChainBuilder = acceptor.getFilterChain();
// add CPU-bound job first,
filterChainBuilder.addLast("codec", new ProtocolCodecFilter(new StringCodecFactory()));
try {
// bind
acceptor.bind(address, handler);
System.out.println("Mediator Server started on mina model...");
System.out.println("Socket buffer size: " + acceptorConfig.getSessionConfig().getReceiveBufferSize());
} catch (IOException e) {
System.err.println("Error starting component listener on port(UDP) " + address.getPort() + ": "
+ e.getMessage());
}
}
}
二、Performance Test
為了測試兩個Server的性能,寫了個簡單的測試客戶端
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.net.UnknownHostException;
/**
* @author Herry Hong
*
*/
public class PerformanceTest {
/** Number of threads to be created */
static final int THREADS = 100;
/** Packets to be sent per thread */
static final int PACKETS = 500;
/** The interval of two packets been sent for each thread */
private static final int INTERVAL = 80;
private static final String DATA = "5a76d93cb435fc54eba0b97156fe38f432a4e1da3a87cce8a222644466ed1317";
private class Sender implements Runnable {
private InetAddress address = null;
private DatagramSocket socket = null;
private String msg = null;
private String name = null;
private int packet_sent = 0;
public Sender(String addr, String msg, String name) throws SocketException,
UnknownHostException {
this.address = InetAddress.getByName(addr);
this.socket = new DatagramSocket();
this.msg = msg;
this.name = name;
}
@Override
public void run() {
// send request
byte[] buf = msg.getBytes();
DatagramPacket packet = new DatagramPacket(buf, buf.length,
address, 8000);
try {
for (int i = 0; i < PerformanceTest.PACKETS; i++) {
socket.send(packet);
packet_sent++;
Thread.sleep(INTERVAL);
}
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
//System.out.println("Thread " + name + " sends " + packet_sent + " packets.");
//System.out.println("Thread " + name + " end!");
}
}
/**
* @param args
*/
public static void main(String[] args) {
if (args.length != 1) {
System.out.println("Usage: java PerformanceTest <hostname>");
return;
}
String msg;
for (int i = 0; i < THREADS; i++) {
if(i % 2 == 0){
msg = i + "_" + (i+1) + ""r"n" + (i+1) + "_" + i + ""r"n" + DATA;
}else{
msg = i + "_" + (i-1) + ""r"n" + (i-1) + "_" + i + ""r"n" + DATA;
}
try {
new Thread(new PerformanceTest().new Sender(args[0], msg, "" + i)).start();
} catch (SocketException e) {
e.printStackTrace();
} catch (UnknownHostException e) {
e.printStackTrace();
}
}
}
}
三、測試結果
測試環境:
Server:AMD Athlon(tm) 64 X2 Dual Core Processor 4000+,1G memory
Client:AMD Athlon(tm) 64 X2 Dual Core Processor 4000+,1G memory
在測試的過程中,當INTERVAL設置的太小時,服務器端會出現丟包現象,INTERVAL越小,丟包越嚴重,為了提高Server的性能,特將Socket的ReceiveBufferSize設置成默認大小的兩倍,
對于JDK實現:
public MediatorServerUseJDK(String name, int port) throws SocketException{
super(name);
socket = new DatagramSocket(port);
// set the receive buffer size to double default size
socket.setReceiveBufferSize(socket.getReceiveBufferSize() * 2);
System.out.println("Mediator server started on JDK model...");
System.out.println("Socket buffer size: " + socket.getReceiveBufferSize());
}
對于Mina實現:
DatagramAcceptorConfig acceptorConfig = acceptor.getDefaultConfig();
acceptorConfig.setThreadModel(ThreadModel.MANUAL);
// set the acceptor to reuse the address
acceptorConfig.getSessionConfig().setReuseAddress(true);
// set the receive buffer size to double default size
int recBufferSize = acceptorConfig.getSessionConfig().getReceiveBufferSize();
acceptorConfig.getSessionConfig().setReceiveBufferSize(recBufferSize * 2);
此時,相同的INTERVAL,丟包現象明顯減少。
接下來,再測試不同實現的性能差異:
UDP server started on JDK model...
Socket buffer size: 110592
INTERVAL = 100ms,沒有出現丟包,
Process time: 49988
Process time: 49982
Process time: 49984
Process time: 49986
Process time: 49984
INTERVAL = 80ms,仍然沒有丟包,不管Server是不是初次啟動
Process time: 40006
Process time: 40004
Process time: 40003
Process time: 40005
Process time: 40013
UDP Server started on mina model...
Socket buffer size: 110592
INTERVAL = 80ms,Server初次啟動時,經常會出現丟包,當第一次(指服務器初次啟動時)沒有丟包時,隨后基本不丟包,
Process time: 39973
Process time: 40006
Process time: 40007
Process time: 40008
Process time: 40008
INTERVAL = 100ms,沒有出現丟包
Process time: 49958
Process time: 49985
Process time: 49983
Process time: 49988
四、結論
在該要求下,采用JDK和Mina實現性能相當,但是在Server初次啟動時JDK實現基本不會出現丟包,而Mina實現則在Server初次啟動時經常出現丟包現象,在經歷第一次測試后,兩種實現處理時間相近,請求并發量大概為每ms一個請求時,服務器不會出現丟包。
參考: https://help.ubuntu.com/community/VMware/Workstation
下載tar包, rpm包安裝沒成功
sudo apt-get install ia32-libs這句好像沒影響,因為測試機是amd-64
安裝好后,登錄guest OS后,再裝vmware-tools,不要單獨下載,之間vm-install vmware-tools就可以
1、 從Openfire SVN Server中dump出源碼;
2、 Build: ant & ant plugins
3、 將build后的target/openfire作為openfire_home
4、 在target/openfire下建立兩個目錄:src和classes,將dump下來的源碼copy到src路徑下,將classes設置為eclipse編譯后的輸出路徑
5、 將$openfire_home/lib下的openfire.jar中src中存在的部分刪除,剩下的部分作為新的openfire.jar,注意,在ubuntu下之間打開openfire.jar時,直接將/org/jivesoftware/admin, /org/jivesoftware/openfire, /org/jivesoftware/util三個目錄刪除,而對于/org/jivesoftware/database目錄,只將源碼中有的那部分刪除,/org/jivesoftware下面還有一個隱藏目錄/org/jivesoftware/stringprep,不能刪除,接下來,將$openfire_home/lib下的jar包作為工程的Referenced Libraries.
6、 將取出來的工程下src/web/WEB-INF/classes/openfire_init.xml導入到eclipse的查詢路徑里,如將src/web/WEB-INF/classes目錄作為eclipse的源目錄,這樣openfire_init.xml自動copy到$openfire_home/classses下面,將openfire_init.xml中的openfireHome設置為$openfire_home
7、 修改org.jivesoftware.openfire.starter.ServerStarter中的如下兩個field,
private static final String DEFAULT_LIB_DIR = "../lib";
private static final String DEFAULT_ADMIN_LIB_DIR = "../plugins/admin/webapp/WEB-INF/lib";
改成:
private static final String DIR_PREFIX = "$openfire_home"; // to be your own openfire_home
private static final String DEFAULT_LIB_DIR = DIR_PREFIX + "lib";
private static final String DEFAULT_ADMIN_LIB_DIR = DIR_PREFIX + "plugins/admin/webapp/WEB-INF/lib";
1. edit /etc/vsftpd.conf:
listen=YES
anonymous_enable=YES
local_enable=YES
write_enable=YES
local_umask=022
anon_upload_enable=YES
anon_mkdir_write_enable=YES
dirmessage_enable=YES
xferlog_enable=YES
connect_from_port_20=YES
chown_uploads=YES
chown_username=honzeland
ftpd_banner=Welcome to blah FTP service.
secure_chroot_dir=/var/run/vsftpd
pam_service_name=vsftpd
rsa_cert_file=/etc/ssl/certs/ssl-cert-snakeoil.pem
anon_root=/home/ftp
2. Now we must make writable directory for anonymous user.
cd /home/ftp
sudo mkdir opendir
sudo chmod 777 opendir/
Note: 采用proftp作server,安裝配置更容易
zz: http://daoger.javaeye.com/blog/47801
2.調用有簡單返回值的java方法
2.1、
dwr.xml的配置
配置同1.1
<
dwr>
<allow>
<create creator="new" javascript="testClass" >
<param name="class" value="com.
dwr.TestClass" />
<include method="testMethod2"/>
</create>
</allow>
</
dwr>
2.2、javascript中調用
首先,引入javascript腳本
其次,編寫調用java方法的javascript函數和接收返回值的回調函數
Function callTestMethod2(){
testClass.testMethod2(callBackFortestMethod2);
}
Function callBackFortestMethod2(data){
//其中date接收方法的返回值
//可以在這里對返回值進行處理和顯示等等
alert("the return value is " + data);
}
其中callBackFortestMethod2是接收返回值的回調函數
3、調用有簡單參數的java方法
3.1、
dwr.xml的配置
配置同1.1
<
dwr>
<allow>
<create creator="new" javascript="testClass" >
<param name="class" value="com.
dwr.TestClass" />
<include method="testMethod3"/>
</create>
</allow>
</
dwr>
3.2、javascript中調用
首先,引入javascript腳本
其次,編寫調用java方法的javascript函數
Function callTestMethod3(){
//定義要傳到java方法中的參數
var data;
//構造參數
data = “test String”;
testClass.testMethod3(data);
}
4、調用返回JavaBean的java方法
4.1、
dwr.xml的配置
<
dwr>
<allow>
<create creator="new" javascript="testClass" >
<param name="class" value="com.
dwr.TestClass" />
<include method="testMethod4"/>
</create>
<convert converter="bean" match=""com.
dwr.TestBean">
<param name="include" value="username,password" />
</convert>
</allow>
</
dwr>
<creator>標簽負責公開用于Web遠程的類和類的方法,<convertor>標簽則負責這些方法的參數和返回類型。convert元素的作用是告訴
DWR在服務器端Java 對象表示和序列化的JavaScript之間如何轉換數據類型。
DWR自動地在Java和JavaScript表示之間調整簡單數據類型。這些類型包括Java原生類型和它們各自的封裝類表示,還有String、Date、數組和集合類型。
DWR也能把JavaBean轉換成JavaScript 表示,但是出于安全性的原因,要求顯式的配置,<convertor>標簽就是完成此功能的。converter="bean"屬性指定轉換的方式采用JavaBean命名規范,match=""com.
dwr.TestBean"屬性指定要轉換的javabean名稱,<param>標簽指定要轉換的JavaBean屬性。
4.2、javascript中調用
首先,引入javascript腳本
其次,編寫調用java方法的javascript函數和接收返回值的回調函數
Function callTestMethod4(){
testClass.testMethod4(callBackFortestMethod4);
}
Function callBackFortestMethod4(data){
//其中date接收方法的返回值
//對于JavaBean返回值,有兩種方式處理
//不知道屬性名稱時,使用如下方法
for(var property in data){
alert("property:"+property);
alert(property+":"+data[property]);
}
//知道屬性名稱時,使用如下方法
alert(data.username);
alert(data.password);
}
其中callBackFortestMethod4是接收返回值的回調函數
5、調用有JavaBean參數的java方法
5.1、
dwr.xml的配置
配置同4.1
<
dwr>
<allow>
<create creator="new" javascript="testClass" >
<param name="class" value="com.
dwr.TestClass" />
<include method="testMethod5"/>
</create>
<convert converter="bean" match="com.
dwr.TestBean">
<param name="include" value="username,password" />
</convert>
</allow>
</
dwr>
5.2、javascript中調用
首先,引入javascript腳本
其次,編寫調用java方法的javascript函數
Function callTestMethod5(){
//定義要傳到java方法中的參數
var data;
//構造參數,date實際上是一個object
data = { username:"user", password:"password" }
testClass.testMethod5(data);
}
6、調用返回List、Set或者Map的java方法
6.1、
dwr.xml的配置
配置同4.1
<
dwr>
<allow>
<create creator="new" javascript="testClass" >
<param name="class" value="com.
dwr.TestClass" />
<include method="testMethod6"/>
</create>
<convert converter="bean" match="com.
dwr.TestBean">
<param name="include" value="username,password" />
</convert>
</allow>
</
dwr>
注意:如果List、Set或者Map中的元素均為簡單類型(包括其封裝類)或String、Date、數組和集合類型,則不需要<convert>標簽。
6.2、javascript中調用(以返回List為例,List的元素為TestBean)
首先,引入javascript腳本
其次,編寫調用java方法的javascript函數和接收返回值的回調函數
Function callTestMethod6(){
testClass.testMethod6(callBackFortestMethod6);
}
Function callBackFortestMethod6(data){
//其中date接收方法的返回值
//對于JavaBean返回值,有兩種方式處理
//不知道屬性名稱時,使用如下方法
for(var i=0;i<data.length;i++){
for(var property in data){
alert("property:"+property);
alert(property+":"+data[property]);
}
}
//知道屬性名稱時,使用如下方法
for(var i=0;i<data.length;i++){
alert(data[i].username);
alert(data[i].password);
}
}
7、調用有List、Set或者Map參數的java方法
7.1、
dwr.xml的配置
<
dwr>
<allow>
<create creator="new" javascript="testClass" >
<param name="class" value="com.
dwr.TestClass" />
<include method="testMethod7"/>
</create>
<convert converter="bean" match="com.
dwr.TestBean">
<param name="include" value="username,password" />
</convert>
</allow>
<
signatures>
<![CDATA[
import java.util.List;
import com.
dwr.TestClass;
import com.
dwr.TestBean;
TestClass.testMethod7(List<TestBean>);
]]>
</
signatures>
</
dwr>
<
signatures>標簽是用來聲明java方法中List、Set或者Map參數所包含的確切類,以便java代碼作出判斷。
7.2、javascript中調用(以返回List為例,List的元素為TestBean)
首先,引入javascript腳本
其次,編寫調用java方法的javascript函數
Function callTestMethod7(){
//定義要傳到java方法中的參數
var data;
//構造參數,date實際上是一個object數組,即數組的每個元素均為object
data = [
{
username:"user1",
password:"password2"
},
{
username:"user2",
password:" password2"
}
];
testClass.testMethod7(data);
}
注意:
1、對于第6種情況,如果java方法的返回值為Map,則在接收該返回值的javascript回調函數中如下處理:
function callBackFortestMethod(data){
//其中date接收方法的返回值
for(var property in data){
var bean = data[property];
alert(bean.username);
alert(bean.password);
}
}
2、對于第7種情況,如果java的方法的參數為Map(假設其key為String,value為TestBean),則在調用該方法的javascript函數中用如下方法構造要傳遞的參數:
function callTestMethod (){
//定義要傳到java方法中的參數
var data;
//構造參數,date實際上是一個object,其屬性名為Map的key,屬性值為Map的value
data = {
"key1":{
username:"user1",
password:"password2"
},
"key2":{
username:"user2",
password:" password2"
}
};
testClass.testMethod(data);
}
并且在
dwr.xml中增加如下的配置段
<
signatures>
<![CDATA[
import java.util.List;
import com.
dwr.TestClass;
import com.
dwr.TestBean;
TestClass.testMethod7(Map<String,TestBean>);
]]>
</
signatures>
3、由以上可以發現,對于java方法的返回值為List(Set)的情況,
DWR將其轉化為Object數組,傳遞個javascript;對于java方法的返回值為Map的情況,
DWR將其轉化為一個Object,其中Object的屬性為原Map的key值,屬性值為原Map相應的value值。
4、如果java方法的參數為List(Set)和Map的情況,javascript中也要根據3種所說,構造相應的javascript數據來傳遞到java中。
啟動glassfish時,發生如下異常:
xxx@xxx:/opt$ asadmin start-domain domain1
Jan 9, 2008 2:49:18 PM com.sun.enterprise.util.ASenvPropertyReader setSystemProperties
SEVERE: property_reader.unknownHost
java.net.UnknownHostException: xxx: xxx
at java.net.InetAddress.getLocalHost(InetAddress.java:1353)
at com.sun.enterprise.util.net.NetUtils.getCanonicalHostName(NetUtils.java:102)
at com.sun.enterprise.util.ASenvPropertyReader.setSystemProperties(ASenvPropertyReader.java:201)
at com.sun.enterprise.cli.commands.S1ASCommand.<init>(S1ASCommand.java:164)
at com.sun.enterprise.cli.commands.BaseLifeCycleCommand.<init>(BaseLifeCycleCommand.java:101)
at com.sun.enterprise.cli.commands.StartDomainCommand.<init>(StartDomainCommand.java:78)
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:39)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:27)
at java.lang.reflect.Constructor.newInstance(Constructor.java:513)
at java.lang.Class.newInstance0(Class.java:355)
at java.lang.Class.newInstance(Class.java:308)
at com.sun.enterprise.cli.framework.CommandFactory.createCommand(CommandFactory.java:91)
at com.sun.enterprise.cli.framework.CLIMain.invokeCommand(CLIMain.java:160)
at com.sun.enterprise.cli.framework.CLIMain.main(CLIMain.java:79)
Starting Domain domain1, please wait.
Log redirected to /opt/glassfish/domains/domain1/logs/server.log.
Redirecting output to /opt/glassfish/domains/domain1/logs/server.log
Domain domain1 is ready to receive client requests. Additional services are being started in background.
java.net.UnknownHostException: hongzeguo: hongzeguo
CLI156 Could not start the domain domain1.
Solution:
xxx@xxx:/opt$ nslookup xxx
Server: 202.106.46.151
Address: 202.106.46.151#53
** server can't find xxx: NXDOMAIN
Check your /etc/hosts file. Does it have an entry with actual name and ip address of your box?
For example: append follows to /etc/hosts
127.0.0.1 xxx
The following is a simple example of a servlet we will write unit tests for:
The servlet:
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.ServletException;
import java.io.IOException;
import java.util.Map;
public class MyServlet extends HttpServlet {
private MyService myService;
public void setMyService(MyService myService) {
this.myService = myService;
}
public void process(HttpServletRequest request, HttpServletResponse response) {
String action = request.getParameter("action");
if ("update".equals(action)) {
Map parameters = request.getParameterMap();
try {
myService.updateModel(parameters);
} catch (MyException e) {
request.setAttribute("error", e.getMessage());
}
}
request.setAttribute("model", myService.getModel());
getServletContext().getRequestDispatcher("myView.jsp").forward(request, response);
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
process(request, response);
}
}
The unit test:
* if the action is update, does it call myService.updateModel (example is below)
* if myService.updateModel throws an exception does the response contain the exceptions message (not implemented below)
import org.jmock.Mock;
import org.jmock.MockObjectTestCase;
public class MyServletTest extends MockObjectTestCase {
private Mock mockMyService;
private Mock mockRequest;
private Mock mockResponse;
private MyService myService;
private HttpServletRequest request;
private HttpServletResponse response;
private MyServlet myServlet;
protected void setUp() throws java.lang.Exception {
mockMyService = mock(MyService.class);
mockRequest = mock(HttpServletRequest.class);
mockResponse = mock(HttpServletResponse.class);
myService = (MyService) mockMyService.proxy();
request = (HttpServletRequest) mockRequest.proxy();
response = (HttpServletResponse) mockResponse.proxy();
myServlet = new MyServlet();
}
public void testUpdate() {
// SETUP ANY OBJECTS USED ONLY IN THIS TESTCASE
Map parameters = new Hashtable();
// SETUP THE EXPECTED CALLS TO THE MOCK OBJECTS
// setup the mock request object to have the getParameter
// method called once with argument being equal to the
// string action and have it return the string update
mockRequest.expects(once()).method("getParameter").
with(eq("action")).will(returnValue("update"));
// setup the mock request object to have the getParameterMap
// method called once with no arguments and have it
// return an empty map
mockRequest.expects(once()).method("getParameterMap")
.will(returnValue(parameters));
// setup the mock myService object to have the updateModel
// method called once with the parameters map as the
// argument
mockMyService.expects(once()).method("updateModel").with(same(parameters));
// setup the mock myService object to have the getModel
// method called once with no arguments and have it
// return an empty map
mockMyService.expects(once()).method("getModel")
.will(returnValue(parameters));
// setup the mock myService object to have the updateModel
// method called once with the parameters map as the
// argument
mockRequest.expects(once()).method("setAttribute").with(eq("model"), same(parameters));
// RUN THE TEST
// add the myService mock object to the servlet so
// that it can handle calls later
myServlet.setMyService(myService);
myServlet.process(request, response);
}
}
-vmargs
-Xmx512M
-XX:PermSize=64M
-XX:MaxPermSize=128M
zz from http://swik.net/User:dnoble/Distributed+Collaboration+Blog/Pidgin+on+Ubuntu+6.10+(Edgy+Eft)/8ik1
1. Get the source bundle from the Pidgin download sitehttp://pidgin.im/pidgin/download/source/2. Extract the contents
tar jxf pidgin-2.0.0.tar.bz2
cd pidgin-2.0.0
3. Install some prerequisites
sudo apt-get install \
libglib1.2-dev \
libglib2.0-dev \
libgtk2.0-dev
sudo apt-get install \
libnspr-dev \
libnspr4 \
libnspr4-0d \
libnss3 \
libnss3-0d \
libnss-db \
libnss-dev \
libssl0.9.8 \
libssl-dev \
openssl \
ssl-cert
(Thanks to the
Ubuntu forums for tips on the SSL library packages)
4. Configure the buildOverride defaults pointing to older versions of GLib and GTK, then generate the makefiles and other build configuration.
export GLIB_LFLAGS=-lglib-2.0
export GLIB_CFLAGS="-I/usr/include/glib-2.0 -I/usr/lib/glib-2.0/include"
export GTK_LFLAGS="-lgtk-x11-2.0 -lcairo -latk-1.0"
export GTK_CFLAGS="-I/usr/include/gtk-2.0 -I/usr/lib/gtk-2.0/include \
-I/usr/include/cairo -I/usr/include/atk-1.0"
export pango_LIBS=-lpango-1.0
export pango_CFLAGS=-I/usr/include/pango-1.0
export CFLAGS="$GLIB_CFLAGS $GTK_CFLAGS $pango_CFLAGS"
./configure
Add a "--prefix=DIR" option to the
configure command if you prefer to specify a custom installation directory.
*
***************************************************************************
./configure 出現錯誤
checking for LIBXML... no
no
configure: error:
You must have libxml2 >= 2.6.0 development headers installed to build.
于是安裝
sudo apt-get install libxml2-dev
*****************************************************************************
5. Build the software
make
If that doesn't work, redirect the output of "make" to a file and search for the string "errors:" to see what went wrong:
make > OUTPUT 2>&1
6. Install the software
sudo make install
If
you ran the configure script with a custom prefix option pointing to a
directory that you can write to without root privileges, then you can
run "make install" without the "sudo".
7. DoneSo that's it. If you are upgrading from Gaim 1.5, all of your configuration will be copied from the
.gaim directory to
.purple in your home directory. Your log files will also be moved, but a symbolic link will point from the
.gaim/logs directory to the .purple/logs directory.
http://www.onjava.com
O'Reilly的Java網站. 每周都有新文章
http://java.sun.com
官方的Java開發者網站 - 每周都有新文章發表
http://www.developer.com/java
由Gamelan.com 維護的Java技術文章網站
http://www.java.net
Sun公司維護的一個Java社區網站
http://www.ibm.com/developerworks/java
IBM的Developerworks技術網站; 這是其中的Java技術主頁
http://www.javaworld.com
最早的一個Java站點. 每周更新Java技術文章
http://www.javadesktop.org
位于Java.net的一個Java桌面技術社區網站.
http://www.theserverside.com
這是一個討論所有Java服務器端技術的網站.
http://www.jars.com
提供Java評論服務. 包括各種framework和應用程序
http://www.ibiblio.org/javafaq/javafaq.html
comp.lang.java的FAQ站點 - 收集了來自comp.lang.java新聞組的問題和答案的分類目錄.
http://java.sun.com/docs/books/tutorial/
來自SUN公司的官方Java指南 - 對于了解幾乎所有的java技術特性非常有幫助.
http://www.javablogs.com
互聯網上最活躍的一個Java Blog網站.
http://www.java2s.com
Java Codes
一條獵狗將兔子趕出了窩,一直追趕他,追了很久仍沒有捉到。牧羊看到此種情景,譏笑獵狗說"你們兩個之間小的反而跑得快得多。" 獵狗回答說:"你不知道我們兩個的跑是完全不同的!我僅僅為了一頓飯而跑,他卻是為了性命而跑呀!"
目標
這話被獵人聽到了,獵人想:獵狗說的對啊,那我要想得到更多的獵物,得想個好法子。于是,獵人又買來幾條獵狗,凡是能夠在打獵中捉到兔子的,就可以得到幾根骨頭,捉不到的就沒有飯吃.這一招果然有用,獵狗們紛紛去努力追兔子,因為誰都不愿意看著別人有骨頭吃,自已沒的吃.就這樣過了一段時間,問題又出現了。大兔子非常難捉到,小兔子好捉.但捉到大兔子得到的獎賞和捉到小兔子得到的骨頭差不多,獵狗們善于觀察,發現了這個竅門,專門去捉小兔子。慢慢的, 大家都發現了這個竅門。獵人對獵狗說:最近你們捉的兔子越來越小了,為什么?獵狗們說:反正沒有什么大的區別,為什么費那么大的勁去捉那些大的呢?
動力
獵人經過思考后,決定不將分得骨頭的數量與是否捉到兔子掛鉤,而是采用每過一段時間,就統計一次獵狗捉到兔子的總重量.按照重量來評價獵狗,決定一段時間內的待遇。于是獵狗們捉到兔子的數量和重量都增加了。獵人很開心。但是過了一段時間,獵人發現,獵狗們捉兔子的數量又少了,而且越有經驗的獵狗,捉兔子的數量下降的就越利害.于是獵人又去問獵狗。獵狗說"我們把最好的時間都奉獻給了您,主人,但是我們隨著時間的推移會老,當我們捉不到兔子的時候,您還會給我們骨頭吃嗎?"
長期的骨頭
獵人做了論功行賞的決定。分析與匯總了所有獵狗捉到兔子的數量與重量,規定如果捉到的兔子超過了一定的數量后,即使捉不到兔子,每頓飯也可以得到一定數量的骨頭. 獵狗們都很高興,大家都努力去達到獵人規定的數量。一段時間過后,終于有一些獵狗達到了獵人規定的數量.這時,其中有一只獵狗說:我們這么努力,只得到幾根骨頭,而我們捉的獵物遠遠超過了這幾根骨頭.我們為什么不能給自己捉兔子呢?"于是,有些獵狗離開了獵人,自己捉兔子去了。
骨頭與肉兼而有之
獵人意識到獵狗正在流失,并且那些流失的獵狗像野狗一般和自己的獵狗搶兔子。情況變得越來越糟,獵人不得已引誘了一條野狗,問他到底野狗比獵狗強在那里。野狗說:“獵狗吃的是骨頭,吐出來的是肉?。?#8221;,接著又道:“也不是所有的野狗都頓頓有肉吃,大部分最后骨頭都沒的舔!不然也不至于被你誘惑。” 于是獵人進行了改革,使得每條獵狗除基本骨頭外,可獲得其所獵兔肉總量的n,而且隨著服務時間加長,貢獻變大,該比例還可遞增,并有權分享獵人總兔肉的m。就這樣,獵狗們與獵人一起努力,將野狗們逼得叫苦連天,紛紛強烈要求重歸獵狗隊伍。
故事還在繼續
只有永遠的利益,沒有永遠的朋友
日子一天一天地過去,冬天到了,兔子越來越少,獵人們的收成也一天不如一天。而那些服務時間長的老獵狗們老得不能捉到兔子,但仍然在無憂無慮地享受著那些他們自以為是應得的大份食物。終于有一天獵人再也不能忍受,把他們掃地出門,因為獵人更需要身強力壯的獵狗。。。。。
成立公司
被掃地出門的老獵狗們得了一筆不菲的賠償金,于是他們成立了MicroBone公司。他們采用連鎖加盟的方式招募野狗,向野狗們傳授獵兔的技巧,他們從獵得的兔子中抽取一部分作為管理費。當賠償金幾乎全部用于廣告后,他們終于有了足夠多的野狗加盟。公司開始贏利。一年后,他們收購了獵人的家當...
發展公司
MicroBone公司許諾給加盟的野狗能得到公司n的股份。這實在是太有誘惑力了。這些自認為是懷才不遇的野狗們都以為找到了知音:終于做公司的主人了,不用再忍受獵人們呼來喚去的不快,不用再為捉到足夠多的兔子而累死累活,也不用眼巴巴地乞求獵人多給兩跟骨頭而扮得楚楚可憐。這一切對這些野狗來說,這比多吃兩根骨頭更加受用。于是野狗們拖家帶口地加入了MicroBone,一些在獵人門下的年輕獵口也開始蠢蠢欲動,甚至很多自以為聰明實際愚蠢的獵人也想加入。好多同類型的公司象雨后春筍般地成立了,BoneEase, Bone.com, ChinaBone....一時間,森林里熱鬧起來。
F4 的誕生
獵人憑借出售公司的錢走上了老獵狗走過的路,最后千辛萬苦要與MicroBone公司談判的時候,老獵狗出人意料的順利答應了獵人,把 MicroBone公司賣給了獵人。老獵狗們從此不再經營公司,轉而開始寫自轉《老獵狗的一生》,又寫:《如何成為出色的獵狗》,《如何從一只普通獵狗成為一只管理層的獵狗》《獵狗成功秘訣》《成功獵狗500條》《窮獵狗,富獵狗》,并且將老獵狗的故事搬上屏幕,取名《獵狗花園》,四只老獵狗成為了家喻戶曉的明星F4. 收版權費,沒有風險,利潤更高。
總結:干活的總是拿得少的,拿得多的都是不干活的。
Starvation and Deadlock are the two kinds of the process status. If a process stays as starvation or deadlock, that means it is waiting?for getting the needed share resource which has been holding by other processes. When the resources are released , the process status is going to be changed from starvation to run, but for deadlock, it has no chance to get the share resource the process needs because another process which has been holding the share resource needed by the first process, but the second process is staying there to wait for the first process to release the another share resource needed by itself, that is to say, they are waiting for each other's releasing the share resource they need. Deadlock is a innormal status which should be as much avoided as possible by the system designer.
I do think they are the two status of one's life, try to avoid deadlock, starvation is allowed, but do not only wait there to unacitvely get the released resource, go around to look for what you needs, on the way, the important thing is?to learn how to ?abandon the resource you've got. The new resource you really needed may get the chance to?be?in touch?only after the abandonment. Sometimes, it needs a long and boring time to get or understand what is the really love in your heart,?however worthy it is. Remember, don't give up your belief, the root of your prospect.
1. 修改源并更新升級Ubuntu:參見 http://wiki.ubuntu.org.cn/%E5%BF%AB%E9%80%9F%E8%AE%BE%E7%BD%AE%E6%8C%87%E5%8D%97/EdgyEft
?? 注意:基本上可以按照該網頁上的配置來實現,只是Lumaqq就不用裝了
2. 以下軟件安裝可以先通過ubuntu自帶的工具:應用程序 -> 添加/刪除...來搜索關鍵字來查看是否存在
3. qq軟件:eva?? ?? msn:使用gaim中自帶的就可以了
4. 音樂:audacious or beep-media-player
5. 視頻:參見 http://wiki.ubuntu.org.cn/EdgyGuide#head-a12a543ea800098877d88c7a8e2813dfb5253a55
?? 1) totem-xine: sudo apt-get install totem-xine w32codecs libxine-extracodecs
???????注意:totem-xine在打開url時,需要在 打開位置 后,右擊鼠標將選擇的 X輸入法 改成 默認,否則,點擊打開位置后?,totem-xine將會停止相應。
?? 2) vlc
?? 3) Mplayer
?? 4) realplayer
6. ftp client: KFTPgrabber or filezilla
?? 對于KFTPGrabber可以采用apt-get直接安裝,安裝后在 Setting -> Configure KFTPGrabber... -> 常規 -> Bookmarks 中的編碼選擇gb2312,然后在Bookmarks -> 編輯書簽 中每一站點Advanced中的服務器編碼中選擇gb2312
?? 對于filezilla:
在 Windows 上有個好用的 FTP client -- Filezilla,它就可以設定遠端 FTP Server 的編碼,而現在 Filezilla 也有 Linux 版本了,可以到它官方網站下載(http://filezilla-project.org/nightly.php),解開後(用 tar -jxvf)會有一個 filezilla3 的目錄,裡面分別有 bin, share 的目錄,你可以按照下面步驟來安裝:
?? 1) 把 filezilla3/bin/ 目錄下的檔案放在 /usr/local/bin 下(sudo mv filezilla3/bin/* /usr/local/bin/)。
?? 2) 把 filezilla3/share/ 目錄下的檔案放在 /usr/local/share/filezilla 目錄下(sudo mv filezilla3/share/filezilla /usr/local/share/)。
?? 3) 然後在 /usr/share/applications 下新增一個 filezilla.desktop 的檔案,內容如下:
????????? [Desktop Entry]
????????? Encoding=UTF-8
????????? Name=Filezilla 3
????????? Comment=Filezilla FTP Client
????????? Exec=filezilla
????????? Icon=/usr/local/share/filezilla/resources/filezilla.png
????????? Terminal=false
????????? Type=Application
????????? Categories=Application;Network;
? 這樣就可以在選單下啟動 Filezilla 了。
? 而設定遠端伺服器編碼的地方,必須要在站臺管理員裡設定才行,在站點管理 -> 字符集 -> 使用自定義字符集 中選擇gb2312
7. Qterm: 直接apt-get
8. chm 和 pdf瀏覽器采用添加/刪除工具來實現
9. ubuntu默認沒有裝ssh服務,直接apt-get ssh就可以了
10. 更多參考
http://hi.baidu.com/wmywind/blog/item/16cb6c6089a251de8cb10d62.html
這次全國校園招聘,走了很多著名高校,比較下來,我們的面試官普遍認為科大學生相
對技術好能力強,這很讓我感到振奮和自豪。大家臨近畢業了,也基本都已有了自己的
選擇了,但不管來群碩還是去任何一家公司,希望能就我這五年來所看到的有普遍性的
問題給大家一些建議,
1.?善于發現企業好的一面。人總有個特點,就是總是盯著問題不放,而習慣甚至忽略好
的一面。很多剛畢業的學生總是遇到問題就放棄,比如,領導不欣賞重視自己,辭職;
同事人際關系復雜或不夠合群,辭職;工資待遇不夠理想,辭職;企業暫時經歷挫折,
辭職;做的事情不夠感興趣,辭職;當前項目枯燥乏味,辭職;工作辛苦個人時間少,
辭職;…很多畢業生總要經歷若干次辭職之后,心態才能漸漸放平,但這時已經走過很多
彎路。尤其是研究生博士生們,還沒換兩個環境,已經三十多了,這時往往不得不接受
無奈的現實,但因為年齡的緣故在職場上的競爭力已經很有限。畢竟,學校背景只在你
畢業的前兩年起作用。安心一些,耐心一些,樂觀一些,想想對自己最重要的是什么,
沒有十全十美的環境,一般這個環境只要能保證自己最重視的兩樣東西,就足夠好了。
抱著一飛沖天的決心,但腳踏實地地努力,才是成功的關鍵。
2.?不要一味追逐新技術新環境新項目,輕易轉換技術方向。任何真功夫都是要靠無數遍
反反復復的實踐練出來的,你剛學一樣東西的時候覺得很新鮮覺得提高很大,漸漸的你
看不到自己有明顯提高,但如果你還能耐住性子不斷地思考總結實踐學習,這才是你真
正突破的時候。做第一遍到第十遍之間,你能感覺自己的飛速提高,做第一百遍和第一
百一十遍之間,也許你都看不到明顯提高,但要成為真正的高手,最少需要做一千遍以上。
3.?多學以致用,再用實踐指導學習。不要不讀書,也不求多讀書,每個領域讀公認最好
的2,3本書,在實踐中反復回顧把它們徹底吃透,就很好了。很多人工作后一開始讀很多
書,工作了兩年就不讀書了,這樣都不好,讀太多不能消化,不讀不能借鑒別人的知識
經驗,提高會慢。
4.?不要輕易為環境所左右。大學畢業生大概有10%的人是不管在什么環境都偷懶懈怠,
有10%不管在什么環境都很努力,還有80%是隨大流,環境松懈自己也松懈,環境努力自
己也努力。但是要有所成就,或者你是那10%的人,或者你要逼著自己去一個奮斗的環
境。此外,在大多數環境下,你越努力,聽到的閑言碎語越多,受到的阻力和面對的不
公平越多,畢竟木秀于林,風必摧之。不要太過于看著別人怎么說怎么做,堅定執著地
去走自己的路,千萬不要為風言風語所干擾。記住,大多數人所說的,不一定是對的,
不一定是最適合你的。
5.?不要過于計較短期利益,著眼長遠。一般來講人總有得失心,容易為某些短期的不公
平或者利益損失而忿忿不平。但真正能占“大便宜”的人,往往都是肯一次次吃小虧的
人。不要為短期的情緒而阻礙自己前進的腳步。舉個例子,你很努力在項目中取得了不
錯的成績,可是領導卻給另外一個表現明顯不如你的人升職加薪,或者加薪比你多(在
這個社會你們不得不習慣這個現象),于是你就覺得很不公平很郁悶,于是停止努力不
再奮斗(或者換環境,但是換了環境,你就一定能改變這個了嗎?何況,還要從零開始
重新證明自己。一次次換換環境,也會使你失去競爭力。),這時,長遠吃虧的究竟是
誰呢?
6.?不要太急于追求安逸。人生走過的路總是前若干年先快速上升,然后進入穩定。你在
最前面幾年能走到多高,直接決定你以后的位置。誰都想安逸,但太急于追求安逸,一
生反倒很難真正得到安逸。剛畢業的幾年,也是學東西最快的幾年,也是塑造你一生的
職業習慣的幾年,要絕對珍惜。
7.?急于創業。初生牛犢不怕虎,很多畢業生工作不久就充滿熱情去創業。不是沒有成功
的,但實在是太少太少,少到幾乎可以忽略不計。創業有很多不同的玩法,比如接單子
做項目,比如爭取融資,爭取被收購合并,或者爭取上市。但你有沒有想過,這一單做
完,你如何取得下一單?單子做完了,你如何確保按時收到款而不是白條?你如何確保
項目不會因需求變更而嚴重延期(這是項目的天性)導致你耗盡所有流動資金(客戶通
常不回提前付款)從而公司倒閉?你如何減少在項目和項目之間,閑置不創造效益的人
手不斷消耗你的資本?… 風險根本舉不勝舉,但我還是看到一批又一批的同學,同事們
飛蛾撲火般去創業。記住,市場商機,渠道,管理,和有效的財務及收款機制和過硬的
“關系”,都是關鍵,技術反而相對次要。
8.?積累人脈。在前期取得成績,主要依靠你的能力。而將來你走得很高的時候,往往不
取決于你的能力,而在于你的人脈。大家容易忙碌于自己的工作而忽略人脈的積累,如
果你將來希望能取得大的成績,就早一點開始積累。人最愿意幫助的人,是幫助過自己
的人,和在平時始終記得自己的人,而不是在需要幫助時才出現的人。多一些善意,多
一些熱情,多交一些朋友,少一些清高。
希望這對大家有些幫助。
對于TCP套接字,在服務器端通常采用下面的語句來實現:
????ServerSocket welcomeSocket = new ServerSocket(6789);??(1)
????Socket connectionSocket?= welcomeSocket.accept();?????(2)
第(1)句創建了一個能監聽某些客戶機“敲門”聲的門,welcomeSocket在6789端口上監聽;當某些客戶機與welcomeSocket連接時,第(2)句創建了一個新的套接字,稱為connectionSocket,這個套接字也使用6789端口,這時,兩個套接字使用了相同的端口號,因為TCP連接套接字由一個四元組(源IP地址,源端口號,目的IP地址,目的端口號)來標識,UDP套接字(面向無連接的)由一個包含目的IP地址和目的端口號組成的二元組來標識。之后,TCP在客戶端的clientSocket和服務器端的connectionSocket之間建立了一條直接的虛禮管道,該客戶機與服務器可以通過該管道彼此發送字節,并且發送的所有字節將按順序到達對方。隨著connectionSocket的建立,該服務器能繼續使用welcomeSocket監聽其它客戶機的連接請求。
?
一位教授把16張撲克牌放在桌上,如下;?
黑桃?:A、7、Q?????
梅花:?3.4.7.9.J.Q?????
紅心:?2.3.5.Q.K?????
方塊:?A.5??????????
教授從中選出一張,把這張牌的數告訴了他的學生"甲”
把花色告訴了"乙”
然后教授問"甲”說"你知道是哪一張牌嗎?????????????
"甲”我不能確定是哪張牌????????????
"乙”我知道你會這樣說????????????
"甲”現在我知道了????????????
"乙”現在我也知道了
教授高興的點點頭.甲乙二人都是很有強的邏輯推理能力的人,并且都說了實話.?????????
根據以上信息,通過你的推理告訴我這張牌
Override:
(覆蓋)
1
、
Override
發生在繼承中,使得一個子類
Override
其父類的一個
method
。
2
、
Override Rules
:
Keeping the contract
。子類
override
父類的
method
時,必須要保證
overriding method
和父類中的
overridden method
具有相同的對外協議
contract
,即相同的參數列表和兼容的返回類型。
3
、
Arguments must be the same, and return types must be compatible.
4
、
The method can't be less accessible.
考慮如下情況:如果訪問權限變小了,如由父類
Parent
中的
public void test()
變成子類
Son
中的
private void test()
,如果編譯器允許權限變小,在某一類中有如下語句:
Parent?p = new Son()
;
p.test()
;則這些語句能夠通過編譯,當該類加載運行時,將會出現錯誤,因為父類
reference p
指向的是一子類
Son
的對象,而
Son
中的
test()
是
private
,不能在其他類中
invoke
。
??????
另外,上面的語句在編譯器允許權限變小情況下之所以能夠通過編譯,是因為在
“Parent?p = new Son()
;
”
中,聲明了一個父類
Parent
的
reference p
,由于
p
是父類的
reference
,由于父類中的
test()
是
public
,故
“p.test()
;
”
能夠通過編譯;而在運行時,
p
指向的是一個子類的對象,
p.test()
在運行時調用的是子類對象中的
test()
。
Overload
:(重載)
1
、重載:兩個或多個
methods
具有相同的
name
,
和
不同的
argument lists
。
2
、
The return types can be different.
3
、
You can't change ONLY the return type
:不能僅僅改變返回類型,
To overload a method, you MUST change the argument list, although you can change the return type to anything.
4
、
You can vary the access levels in any direction.