2008年2月19日
#
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月薪这样的小概率事件,当然最好轮到自己
红军长征两万五、打成右派反革命、胼手胝足牺牲尊严去奋斗,最好留给祖辈父辈和别人
自然,不是每个吃过苦的人都会得到回报
但是,任何时代,每一个既得利益者身后,都有他的祖辈父辈奋斗挣扎乃至流血付出生命的身影
羡慕别人有个好爸爸,没什么不可以
问题是,你的下一代,会有一个好爸爸吗?
至于问到为什么不能有同样的赢面概率?我只能问:为什么物种竞争中,人和猴子不能有同样的赢面概率?
物竞天择。猴子的灵魂不一定比你卑微,但你身后有几十万年的类人猿进化积淀。
七、入对行跟对人
在中国,大概很少有人是一份职业做到底的,虽然如此,第一份工作还是有些需要注意的地方,有两件事情格外重要,第一件是入行,第二件事情是跟人。第一份工作对人最大的影响就是入行,现代的职业分工已经很细,我们基本上只能在一个行业里成为专家,不可能在多个行业里成为专家。很多案例也证明即使一个人在一个行业非常成功,到另外一个行业,往往完全不是那么回事情,“你想改变世界,还是想卖一辈子汽水?”是乔布斯邀请百事可乐总裁约翰·斯考利加盟苹果时所说的话,结果这位在百事非常成功的约翰,到了苹果表现平平。其实没有哪个行业特别好,也没有哪个行业特别差,或许有报道说哪个行业的平均薪资比较高,但是他们没说的是,那个行业的平均压力也比较大。看上去很美的行业一旦进入才发现很多地方其实并不那么完美,只是外人看不见。
说实话,我自己都没有发大财,所以我的建议只是让人快乐工作的建议,不是如何发大财的建议,我们只讨论一般普通打工者的情况。我认为选择什么行业并没有太大关系,看问题不能只看眼前。比如,从前年开始,国家开始整顿医疗行业,很多医药公司开不下去,很多医药行业的销售开始转行。其实医药行业的不景气是针对所有公司的,并非针对一家公司,大家的日子都不好过,这个时候跑掉是非常不划算的,大多数正规的医药公司即使不做新生意撑个两三年总是能撑的,大多数医药销售靠工资撑个两三年也是可以撑的,国家不可能永远捏着医药行业不放的,两三年以后光景总归还会好起来的,那个时候别人都跑了而你没跑,那时的日子应该会好过很多。有的时候觉得自己这个行业不行了,问题是,再不行的行业,做得人少了也变成了好行业,当大家都觉得不好的时候,往往却是最好的时候。大家都觉得金融行业好,金融行业门槛高不说,有多少人削尖脑袋要钻进去,竞争激励,进去以后还要时时提防,一个疏忽,就被后来的人给挤掉了,压力巨大,又如何谈得上快乐?也就未必是“好”工作了。
太阳能这个东西至今还不能进入实际应用的阶段,但是中国已经有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及同事、好朋友给我的支持,鼓励及协助,这篇文字化我心声的文章与好友分享。