置顶随笔
摘要:这篇文章描述了在Eclipse
RCP中引入依赖注射机制的一个简单方法。为了避免污染Eclipse平台的基础设施并且透明的为RCP添加IoC框架,我们使用了动态字节码操作(使用
ObjectWeb ASM类库),Java类加载agent以及Java标注技术的组合。
Eclipse胖客户端平台(Rich Client Platform,RCP)是一个功能强大的软件基础(software
foundation),它基于相互联系并协作的插件,允许开发人员创建通用的应用程序。RCP使得开发人员可以更加关注应用程序的业务逻辑而不是花大量
时间来重新发明轮子去编写大量的应用程序管理逻辑。
控制翻转(IOC)和依赖注射是一种减少程序耦合性的设计模式。它们遵循了一个简单的原则:你不必自己创建对象,你只需要描述如何创建对象。你不必去实例
化或者定位你的组件需要的服务,你只需要去描述哪个组件需要哪个服务,其他组件(通常是容器)来负责将它组装好。这也就是著名的好莱坞原则:不要打电话给
我们,我们会打给你。
这篇文章描述了在Eclipse
RCP中引入依赖注射机制的一个简单方法。为了避免污染Eclipse平台的基础设施并且透明的为RCP添加IoC框架,我们使用了动态字节码操作(使用
ObjectWeb ASM类库),Java类加载agent以及Java标注技术的组合。
什么是Eclipse胖客户端平台?
简单来说,Eclipse胖客户端平台是一组类库,软件框架以及运行环境,它可以用来创建独立运行并且经常需要与网络交互的应用程序。
尽管Eclipse是作为一个集成开发环境(IDE)框架设计的,但是,从3.0版本开始,整个项目已经被重构成各种独立的组件,以便于可以使用这些组件
的一个子集来构建任意的应用程序。这个子集构成了RCP,它包含以下几种组件:基本运行环境,用户接口组件(SWT和JFace),插件以及OSGi层。
图1展示了Eclipse平台的主要组件。
整个Eclipse平台是基于插件和扩展点(extensions
points)这样一个关键概念的。一个插件是可以独立开发和发布的功能的最小单元。它一般会打包成一个jar文件并且通过增加功能来扩充Eclipse
平台(例如,增加一个编辑器,工具栏按钮或者一个编译器)。整个平台就是一组互相联系并通讯的一组插件的集合。一个扩展点是一个已经存在的互相联系的端
点,可以被其他插件用来添加功能(功能:在Eclipse术语中叫做扩展)。扩展和扩展点通过XML配置文件定义并绑定在插件中。
尽管插件已经使用了关注点分离这样一个重要的模式,但是插件之间的强相互联系和通讯会导致他们之间的强依赖关系。一个典型的例子就是需要定位应用程序需要
的各种单例(Singleton)服务,例如数据库连接池,日志处理器,或者用户偏爱(preference)的设置信息的保存等。控制反转和依赖注射是
消除这些依赖的可行解决方案。
控制反转和依赖注射
+控制反转是一种设计模式,它主要关注如何将服务(或者应用程序组件)的定义和服务如何定位它们依赖的服务进行分离。为了完成这种分离,一般都会依赖一个
容器,或者定位框架(locator framework),维护现存服务的列表+提供一种方法来将组件和它们依赖的服务绑定在一起。
+提供一种方法让应用程序代码可以请求一个已经配置好的对象(例如,一个所有依赖都已经满足的对象),这样就可以保证该对象所有相关的服务都已经可用了。
现存的框架一般都使用下面三种基本技术的组合来绑定服务和组件:
+类型一(基于接口):服务对象需要实现一个专门的接口,这个接口为这些服务对象提供了一个对象,服务可以通过这个对象来查询它们的依赖。这是一些早期的
容器使用的模式,例如Excalibur.
+类型二(基于setter):通过JavaBean属性的setter方法将依赖的服务赋值给服务对象。HiveMind和Spring都是通过这种方
式来实现的。
+类型三(基于构造函数):依赖的服务通过构造函数的参数提供(不通过JavaBean属性暴露)。这是PicoContainer使用的唯一方式。HiveMind和Spring也使用了这种方式。
我们将采用第二种方式的变种,通过带标注的方法来提供服务。(示例应用的源代码在资源中可以找到)。声明依赖可以采用以下方式:
@Injected public void aServicingMethod( Service s1,
AnotherService s2) { // save s1 and s2 into class variables // to use
them when needed}
控制反转容器会查找Injected标注并且使用需要的参数来调用这个方法。在我们为Eclipse平台引入IoC的过程中,在服务和可服务对象间建立绑
定关系的代码被封装在一个Eclipse插件中。这个插件定义了一个扩展点
(com.onjava.servicelocator.servicefactory),用来为应用程序提供服务工厂。当一个可服务对象需要配置时,插
件会向工厂请求服务的实例。正如下面的代码,ServiceLocator类将会完成所有的工作。(我们会跳过那些处理扩展点解析的代码,因为这些代码会
很简单)
/** * Injects the requested dependencies into the * parameter
object. It scans the serviceable * object looking for methods tagged
with the * {@link Injected}
annotation.Parameter types are * extracted from the matching method.
An instance * of each type is created from the registered * factories
(see {@link IServiceFactory})。 When *
instances for all the parameter types have been * created the method is
invoked and the next one * is examined. * * @param serviceable the
object to be serviced * @throws ServiceException */
public static void service(Object serviceable) throws ServiceException {
ServiceLocator sl = getInstance();
if (sl.isAlreadyServiced(serviceable)) {
// prevent multiple initializations due to
// constructor hierarchies
System.out.println( "Object " + serviceable + " has already been configured ");
return; }
System.out.println("Configuring " + serviceable);
// Parse the class for the requested services
for (Method m : serviceable.getClass()。getMethods()) {
boolean skip=false;
Injected ann=m.getAnnotation(Injected.class)
; if (ann != null) {
Object[] services = new Object[m.getParameterTypes()。length];
int i = 0;
for(Class<?> klass :m.getParameterTypes()){
IServiceFactory factory = sl.getFactory(klass,ann.optional());
if (factory == null) {
skip = true;
break; }
Object service = factory.getServiceInstance();
// sanity check: verify that the returned
// service's class is the expected one
// from the method
assert(service.getClass()。equals(klass) || klass.isAssignableFrom(service.getClass()));
services[i++] = service ; }
try {
if (!skip)
m.invoke(serviceable, services); }
catch(IllegalAccessException iae) {
if (!ann.optional())
throw new ServiceException( "Unable to initialize services on
" + serviceable + ": " +
iae.getMessage(),iae); }
catch(InvocationTargetException ite) {
if (!ann.optional())
throw new ServiceException( "Unable to initialize services
on " + serviceable + ": " +
ite.getMessage(),ite); } } }
sl.setAsServiced(serviceable);}
既然这个服务工厂返回的服务同样可以是一个可服务对象,这个策略允许定义一种服务层次(但是,目前并不提供对循环依赖的支持)。
ASM and java.lang.instrument Agents
以上介绍的各种注射策略都依赖于容器的存在,依赖容器提供一个入口点来使得应用程序可以使用该入口点来请求已经得到正确配置的对象。但是,我们想在开发我
们的IoC插件的时候使用一种透明的方式,这主要有两个原因:+RCP采用了复杂的classloader以及初始化策略(考虑一下
createExecutableExtension())来维护插件的隔离性以及可见性的限制。我们不想修改或者替代这种策略来引入我们的基于容器的初
始化规则。
+对这样一个入口点(这个例子中,是service
locator插件中定义的service()方法)的显式引用将会强迫应用程序开发人员采用一种显式的模式和逻辑来获取初始化的组件。这样就会有一些应
用-锁定的类库出现在程序代码中。我们想要定义一个合作的插件,这个插件并不需要显示的引用这些代码。
出于以上原因,我们要引入定义在java.lang.instrument包中的Java转换代理,这个包出现在J2SE5.0以及以后的版本中。一个转
换代理是一个实现了java.lang.instrument.ClassFileTransformer接口的对象,这个接口只有唯一一个方法,
transform()。当一个转换器的实例注册到JVM中后,当JVM要创建任何类的时候,这个代理的transform()会被调用。转换器可以在
JVM加载类之前访问这个类的字节码并且可以对其进行修改。
转换代理可以采用-javaagent:jarpath[=options]的形式的命令行参数来注册到JVM,其中jarpath是包含了这个代理类的
JAR文件,options是传递给该代理的一个参数字符串。这个代理JAR文件使用一个特殊的MANIFEST属性来指明真正的代理类,这个代理类必须
定义一个public static void premain(String options, Instrumentation
inst)方法。这个premain()方法将会在应用程序的main方法被调用之前调用,并且将一个真正的变换器注册到传递进来的
java.lang.instrument.Instrumentation类的实例中。
在我们的示例程序中,我们定义一个代理来透明的进行字节码操作并且动态的调用我们的IoC容器(service
vlocator插件)。这个代理会通过检验是否存在Serviceable标注来确定一个可服务对象。然后,修改所有的构造函数来调用IoC容器的方法
在对象初始化时正确地配置和初始化这个对象。
让我们假设我们有一个需要依赖于其他服务的对象(还记得Injected标注吗?):@Serviceablepublic class
ServiceableObject { public ServiceableObject() {
System.out.println("Initializing……"); } @Injected public void
aServicingMethod( Service s1, AnotherService s2) { // ……
omissis …… }}
当它被转换代理操作以后,它的字节码会和下面这个进行正常编译的类的字节码一样:@Serviceablepublic class
ServiceableObject { public ServiceableObject() {
ServiceLocator.service(this); System.out.println("Initializing……");
} @Injected public void aServicingMethod( Service s1,
AnotherService s2) { // …… omissis …… }}
通过这种解决方案,我们不必将对容器的依赖进行硬编码就可以配置一个可服务对象并且使他们可用。开发人员仅仅需要在可服务对象类上使用
Serviceable标注就可以了,变换代理的代码如下:public class IOCTransformer implements
ClassFileTransformer { public byte[] transform( ClassLoader
loader, String className, Class<?>
classBeingRedefined, ProtectionDomain
protectionDomain, byte[] classfileBuffer) throws
IllegalClassFormatException {
System.out.println("Loading " + className); ClassReader
creader = new ClassReader(classfileBuffer); // Parse the
class file ConstructorVisitor cv = new
ConstructorVisitor(); ClassAnnotationVisitor cav = new
ClassAnnotationVisitor(cv); creader.accept(cav,
true); if (cv.getConstructors()。size() > 0) {
System.out.println("Enhancing "+className); // Generate the
enhanced-constructor class ClassWriter cw = new
ClassWriter(false); ClassConstructorWriter writer =
new ClassConstructorWriter( cv.getConstructors(),
cw); creader.accept(writer, false);
return cw.toByteArray(); } else return
null; } public static void premain(String agentArgs,
Instrumentation inst) { inst.addTransformer(new
IOCTransformer()); }}
其中ConstructorVisitor, ClassAnnotationVisitor, ClassWriter以及 ClassConstructorWriter这几个类使用ObjectWeb ASM类库来进行字节码操作。
ASM使用访问者模式来将类数据(包括指令序列)作为事件流处理。当解码一个已经存在的类时,ASM为我们产生事件流,调用我们的方法来处理事件。当生成
一个新类的时候,采用相反的过程:我们产生一个事件流,ASM类库将它转化成一个类。注意我们描述的这个方法并不依赖于特定的字节码类库(我们这里用了
ASM),其他的解决方案,例如BCEL和Javassist同样可以工作。
我们不想深究ASM的内部机制。对于本文的目的来说,知道ConstructorVisitor和ClassAnnotationVisitor对象是用来确定使用了Serviceable标注的类并且收集他们的构造函数就足够了。它们的代码如下:
public class ClassAnnotationVisitor extends ClassAdapter
{ private boolean matches = false; public
ClassAnnotationVisitor(ClassVisitor cv) { super(cv); }
@Override public AnnotationVisitor visitAnnotation( String
desc, boolean visible) { if (visible &&
desc.equals("Lcom/onjava/servicelocator/annot/Serviceable;"))
{ matches = true; } return
super.visitAnnotation(desc, visible); } @Override public
MethodVisitor visitMethod( int access, String name, String
desc, String signature, String[] exceptions) { if
(matches) return super.visitMethod(
access,name,desc,signature,exceptions); else { return
null; } } }public class ConstructorVisitor extends
EmptyVisitor { private Set<Method> constructors; public
ConstructorVisitor() { constructors = new
HashSet<Method>(); } public Set<Method>
getConstructors() { return constructors; }
@Override public MethodVisitor visitMethod( int access,
String name, String desc, String signature, String[]
exceptions) { Type t =
Type.getReturnType(desc); if
(name.indexOf("<init>") != -1 &&
t.equals(Type.VOID_TYPE)) { constructors.add(new
Method(name,desc)); } return
super.visitMethod( access,name,desc,signature,exceptions); }}
对于上述类收集到的每一个构造函数,使用一个ClassConstructorWriter类的实例来修改构造函数的字节码,注入对service
locator插件的调用:com.onjava.servicelocator.ServiceLocator.service(this);
采用ASM方式完成上述工作需要以下代码:// mv is an ASM method visitor,// a class
which allows method manipulationmv.visitVarInsn(ALOAD,
0);mv.visitMethodInsn( INVOKESTATIC,
"com/onjava/servicelocator/ServiceLocator", "service",
"(Ljava/lang/Object;)V");
第一个指令将第二个指令需要使用的this对象的引用放在堆栈上,第二个指令将调用ServiceLocator的一个静态方法。
一个示例Eclipse
RCP应用我们现在已经具有了所有创建应用程序的元素,我们的示例代码用来向用户显示有趣的警句和引用,就像fortune
cookies一样。它包含了四个插件:+service
locator插件,提供Ioc框架的功能+FortuneService插件,提供了管理fortune cookies的服务。
+FortuneInterface插件,发布访问服务需要的公共接口。
+客户端插件,作为Eclipse应用在一个Eclipse视图中显示格式化的警句。
我们采用的IoC设计使得服务的实现和客户端分离,服务的实现可以改变或者修改,而客户端却不受影响。
就像上面几节描述的那样,为了向用户显示警句,service locator将会将客户和服务绑定在一起。FortuneInterface仅仅定义一个公有的接口,用户可以使用它来访问cookie消息。
public interface IFortuneCookie { public String getMessage();}
Fortune插件提供了一个简单的服务工厂来创建IFortuneCookie的实现类的实例。
public class FortuneServiceFactory implements IServiceFactory
{ public Object getServiceInstance() throws ServiceException
{ return new FortuneCookieImpl(); } // …… omissis ……}
工厂作为一个Eclipse扩展注册到service locator,就像它的plugin.xml中描述的一样。
<?xml version="1.0" encoding="UTF-8"?><?eclipse
version="3.0"?><plugin><extension
point="com.onjava.servicelocator.servicefactory">
<serviceFactory
class="com.onjava.fortuneservice.FortuneServiceFactory"
id="com.onjava.fortuneservice.FortuneServiceFactory" name="Fortune
Service Factory"
resourceClass="com.onjava.fortuneservice.IFortuneCookie"/></extension></plugin>
这里,resourceClass属性定义了这个工厂提供的服务类。描述的服务被FortuneClient插件中的Eclipse视图使用。
@Serviceablepublic class View extends ViewPart { public
static final String ID = "FortuneClient.view"; private
IFortuneCookie cookie; @Injected(optional=false) public void
setDate(IFortuneCookie cookie) { this.cookie = cookie; }
public void createPartControl(Composite parent){ Label l = new
Label(parent,SWT.WRAP); l.setText("Your fortune cookie
is:n" + cookie.getMessage()); } public void setFocus() {}}
注意要加上Serviceable和Injected标注来定义对其他服务的依赖,但是不需要引用提供这些服务的代码。最后的结果是createPartControl()可以自由得使用cookie对象而且可以保证cookie是正确初始化过的。
结论
在这篇文章中,我们讨论了如何将一种强大的设计模式(可以简化代码依赖的处理,IoC)与一种可以加速Java客户端应用程序开发的技术合并在一起。虽然
我没有处理更多于这个问题有关的细节,但是我也已经演示了一个示例应用如何将服务和服务客户解耦。我同时也描述了Eclipse插件技术在开发客户端和服
务时如何做到了关注点分离。但是,仍然有很多有趣的元素在等待我们探索,例如清除策略,用来在不需要这个服务的时候清除掉服务,或者在我们的客户插件中使
用mock-up服务来进行单元测试,这些要留给读者去研究了。
Resources +本文的示例代码+Eclipse.org网站上的Eclipse RCP教程+ASM网站上的ASM 2.0介绍+Matrix,与Java共舞+http://www.onjava.com
我究竟能活多久?这是每个人都关心的问题。最近,美国CNN、《时代》周刊等各大媒体纷纷对一个被称为“寿命计算器”的程序进行了报道。
寿命真的是可以计算吗?“寿命计算器”的发明者Thomas Perls博士是美国波士顿大学医学院从事老年医学研究的医生和研究员,日前,他接受了健康时报记者的采访,告诉大家怎样去计算自己的寿命。
数百万人网上“算命”
“寿命计算器”最早出现在1999年。当时只有23个问题,但随着研究的深入,目前已经增加到了40个问题。
丹尼斯·李的预期寿命只有65岁,而相比之下,同样拥有高学历和好的家庭环境的凯特·雷特福的预期寿命却高达105岁。原因是李吸食大麻,并于最近被诊
断出患有糖尿病。滴酒不沾的利奇更是得到了惊人的107岁,因为她现在的生活习惯都是有利于长寿的:徒步旅行,饮食均衡,擅于处理各种压力。
“我的寿命计算程序的最终目的在于:提示你注意良好的生活习惯。”Perls说。
Perls提醒说,寿命计算程序不是一个完美的工具,但它提供了一个蓝图,无论你的分数是60或106,“如果你从现在开始改变其中一个选择,就可以提高自己的分数。”
做完题后的三件事
世卫组织中国代表处营养与食品安全专家张平平建议,做完测试后,你需要做三件事:第一,最好先访问一下医生。在医生的指导下,这种非常个性化的关于寿命
的评估,可以带给你个性化的改善方案。第二,制定健康计划并坚持下来。第三,从小处着手。做完测试,并不是要你马上大刀阔斧地修改自己原有的习惯,而是要
从小处着手,比如肥胖者不必每天非要改吃豆腐,但可以做出合理的适度的调整。
对“算命程序”不要一笑而过
面对这样的一个“算命”程序,许多人也许会想,就是一些会上网的“算命先生”整出的玩意儿,一笑而过,不去当真。其实并非如此。
正如发明者Perls所说,这个测试就是告诉你要从现在开始改变自己。
我们都知道,“生活方式病”很可怕,因为它已经融入了现代生活的方方面面。开私家车上下班、坐电脑前完成一天的工作、餐桌上推杯换盏、灯红酒绿的夜生活里度过夜晚时光……这曾是许多人追求的幸福生活,而今我们享受到了,“生活方式病”却已经开始缠身了。
我们一直强调,要有健康的生活方式。什么是健康的生活方式,其实同样很简单,就是从日常生活点滴做起,从改变吸烟、酗酒等不良的生活习惯做起,从合理安排膳食结构做起。记住,每天前进一小步,健康就能前进一大步。
寿命计算器
Perls把男性的预期寿命设定为86岁,女性则为89岁,随着每个问题的回答,数字会相应加减,最后得到答案。如果你长寿,恭喜你,证明你拥有健康的生活习惯,否则,赶快把烟酒戒了,出去锻炼吧!
1.你已婚。(+3岁)
点评:婚姻让男性的寿命延长3年,对女性则没有影响。
2.你和家人之间联系密切,与朋友经常相聚。(+0.25岁)
点评:和亲朋之间和谐的关系,可以让你健康又长寿。
3.如何评估你目前的压力水平:低(+0.75岁);高(-3岁)
点评:压力过大会短命,善于处理压力可以让寿命增加。
4.你善于减压(+1岁);不善于(-2岁)
点评:减压方法多,女人唠叨,男人的眼泪都可以。
5.每天晚睡3~5个小时(-1岁);6小时以上(+1岁)
点评:出租车上小憩、工作间隙打个盹儿、午休时间小睡一会儿,每天让你的总睡眠时间达6至8小时就行。
6.你接受过多少年的正规教育?16年以上(+0.5年)低于8年(-0.5年)
点评:良好的教育能让你获得更多的健康知识。
7.你一周工作多少小时?低于40个小时(+2岁);40个至60个小时(+1岁)
点评:工作时间一长,就意味着压力增大,疲劳增加,增加工作效率可以缩短工作时间。
8.你对人生逐渐走向衰老感到乐观(+2岁)悲观(-1岁)
点评:乐观与长寿总是结伴而行的。
9.你居住的地方空气质量很好(+0.5岁)
点评:城里人难以选择环境,但可以调节一下自己的生活小环境,比如家里多开窗通风,用绿色植物来调节室内空气。
10.当你在私家车中,你总是会系好安全带(+0.75岁)
11.你每天喝多少杯含有咖啡因的咖啡?
2杯以下(+0.5岁)
3杯以上(-0.5岁)
点评:咖啡能让人提神,但会增加钙质排泄,如果又不注意补钙,就容易造成骨质疏松了。
12.你每天喝2~3杯绿茶(+0.5岁)
点评:喝茶不宜过量,过浓,进餐前半小时不喝茶,孕期、哺乳期妇女、生长发育中的儿童及缺铁性贫血患者不宜饮茶。
13.你吸烟或暴露在二手烟的环境(-4岁)
点评:香烟害人害己,人人喊打,经常被动吸烟的人患肺癌的几率比正常人多出6倍。
14.你每天都吸烟(-0.5岁)
点评:烟民要长寿,第一件事就是戒烟,没有任何借口。
15.你每天吸多少支烟?10支(-5岁);20支(-10岁);40支以上(-15年)
16.你每天饮用啤酒超过3杯,或含酒精的饮品超过3杯,或4杯白酒。(-7岁)
点评:酒伤身还是养身,因人而异,因量而异。
17.你每天服用一片阿司匹林(+2岁)
点评:如果在医生的建议下,你能每天服用81mg阿司匹林,可以提高听力和大脑健康,有助于延缓或避免心脏病或中风的发生。
18.阳光下你会涂抹防晒油来保护皮肤吗?很少(-1岁)会做好防护(+0.5岁)
点评:适量的紫外线能促进钙质的吸收,对预防骨质疏松、佝偻病有好处,但过量的紫外线会大大增加患皮肤癌的危险,还会增加皱纹。
19.你没有从事危险性行为,也不注射违法药物。(+10岁)
20.你每天都用牙线洁牙吗?是的(+1岁);不是(-1岁)
点评:如果能经常使用牙线,就可以减少牙周炎的发生,不刷牙则会减寿一年。
21.你一周吃多少次快餐和熟食。从来不吃(+4岁);5次以上(-2岁)
点评:快餐在营养学家眼中是高热、高脂、高蛋白的“垃圾食品”,会导致肥胖、糖尿病、癌症等各种慢性疾病。
22.你很少吃烧烤的鱼,家禽或肉类(+1岁)
23.你每天会补充钙(+0.5岁)
点评:每天摄取更多的钙或每天服用1500mg的钙片,可以让寿命增加。
24.如果在正餐之间吃零食,通常你会选择干果(+0.5岁)
点评:干果可以美肤、健脑、保护心脑血管健康、抗衰老。
25.你常吃大量的甜食,如冰淇淋,蛋糕,糖果等(-1岁)
点评:吃甜食过多,会引起高脂血症、动脉硬化、肥胖症、高血压病、冠心病、糖尿病和骨质疏松等疾病,还会促发乳腺癌,加速细胞的老化,使人体环境适应能力差等。
26.我每天都吃得很多,肥胖(-5岁)
27.你不会把铁作为营养素的一部分来补充(+2岁)
点评:降低体内的铁质很可能会减缓老化过程,并让人能够避免跟老化有关的疾病,可以让寿命增加。
28.你一周有多少天能达到至少锻炼30分钟?每周7天(+5岁);每周三天(+3岁);我很少锻炼(-1年)
点评:养成运动习惯很重要,实在达不到30分钟,那就每天利用零星时间锻练3分钟也会有点效果,可以做做腹式呼吸、转转脖子、扭扭腰。
29.你排便不规律(-0.5年)
点评:每天清晨起来一杯凉白开,就能解决这个问题。
30.你的总胆固醇水平高于180mg/dl(5mmol/L)(-2年)
31.你心脏的收缩压是多少?低于120(+2岁);高于230(-5年至15年)
32.你心脏的舒张压低于80(+7岁)
33.你每年都做血糖检测(+0.5年)
点评:18岁以上定期测血压,30岁以上定期测血脂,40岁以上男性每年都应该测血糖。
34.你的心脏病两年前发作过,但后来也没有采取任何措施来预防它再次发作(-2岁)
35.你的直系亲属中从来没有患有糖尿病者或心脏病者(+2岁)
36.直系亲属中有三位或更多的人患有癌症。(-1岁)
37.你母亲活到90岁以上(+2岁)
38.你父亲活到90岁以上(+2岁)
39.你的祖父母或曾祖父母中有达到或超过98岁高龄的(+2岁)
40.你没有借助任何人工生育手段生育最后一个孩子时是多少岁?35~43岁(+2岁)
点评:40岁或以后才怀孕的妇女,要比年轻时怀孕的女性更长寿,因为晚育可能意味着更年期的推迟,对女性荷尔蒙的产生有积极作用。
2008年1月16日
关于
Ruby &Ruby on Rails 的一些书及论坛网站
归纳以来,Ruby有以下优点:
◆解释器
Ruby是解释型语言,其程序无需编译即可轻松执行。
◆变量无类型
Ruby的变量没有类型,因此不必为静态的类型匹配而烦恼。相应地,错误检查功能也变弱了。
◆不需要变量声明
所有变量均无需声明即可立即使用。另外,从变量名即可判断出是何种变量(局部变量,全局变量,实例变量)。
◆语法简单
语法比较简单,类似Algol系语法。
◆不需要内存管理
具有垃圾回收(Garbage Collect,GC)功能,能自动回收不再使用的对象。
◆一切都是对象
Ruby从一开始就被设计成纯粹的面向对象语言,因此以整数等基本数据类型为首的所有东西都是对象,它们都有发送信息的统一接口。
◆类,继承,方法
Ruby当然具有面向对象语言的基本功能。
◆特殊方法
可向某对象添加方法。例如,可以把GUI按钮被按下时的动作作为方法记述下来,还可以用它来进行原型库(prototypebase)的面向对象编程(有人这么干吧)。
◆用模块进行混合插入(Mixin)
Ruby故意舍弃了多重继承,但拥有混合插入功能。使用模块来超越类的界限来共享数据和方法等。
◆迭代器
该功能可以将循环抽象化。
◆闭包
可以将某过程片段对象化。对象化后的该过程片段就称作闭包。
◆功能强大的字符串操作/正则表达式
以Perl为样板创造出了功能强大的字符串操作和正则表达式检索功能。
◆拥有超长整数
添加超长整数功能后,可以计算非常大的整数。例如计算400的阶乘也轻而易举。
◆具有错误处理功能
错误处理功能可以使您编写代码处理出错情况。
◆可以直接访问OS
Ruby可以使用(UNIX的)绝大部分的系统调用。单独使用Ruby也可以进行系统编程。
◆动态加载
若OS支持的话,可以在运行时读入对象文件。
但Ruby也有下列缺点:
◆因为Ruby是解释型语言,所以速度较慢
◆静态检查比较少
--------------------------------------------------------
优点:
1 采用虚拟机实现跨平台
2 支持垃圾回收
3 脚本语言,灵活,容易扩展
4 支持面向对象
5 基于脚本语言,易于同Linux Shell进行交互
6 拥有功能强大和完善的标准类库
7 拥有良好的交互式运行环境:IRB,方便进行开发和调试
8 语言内置对规则表达式(Regular Express)的支持
9 直观的Block语法来实现循环遍历和函数回调
10 开放源代码
11 支持异常机制
12 同Perl和Python相比更容易用C语言扩展(来自Ruby官方网站)
13 支持动态载入类库
14 因为是解释型语言,不需要编译,修改后马上就可以执行
15 拥有一个完善的Web开发框架Rails
缺点
1 性能不如纯静态编译语言,例如C,但可以通过用C语言来扩展来解决
2 完全没有类型检查,灵活但容易隐藏潜在的问题
3 使用Module来实现多重继承,不如JAVA所采用的接口(Interface)方式清晰,而且由于没有严格的类型
检查,所以框架层的设计无法对实现有很严格的约束
4 没有很完善的开发,重构的工具,虽然现在Eclipse已经提供Ruby的开发插件,但还没有实现重构,跳
转和自动代码生成等很有用的功能,主要原因是由于Ruby支持动态类型
5 依然保留部分非面向对象的语法,例如全局变量,全局方法
6 允许在扩展代码中随意替换现有类的方法定义或变量,非常灵活,但可能会因为错误地修改了框架逻辑
而引发难以发现的错误,但可以用freeze方法来组织被修改
7 不支持真正的多线程,多线程环境是通过在虚拟机环境中模拟出来的,不能充分发挥多CPU的功能,如
果一个线程在执行底层调用被Block住则整个应用程序也被Block住。
8 对Unicode和多国语言的支持不好(来自Martin上海演讲实录3:细数Ruby语言优缺点)
2008年1月7日
摘要:这篇文章描述了在Eclipse
RCP中引入依赖注射机制的一个简单方法。为了避免污染Eclipse平台的基础设施并且透明的为RCP添加IoC框架,我们使用了动态字节码操作(使用
ObjectWeb ASM类库),Java类加载agent以及Java标注技术的组合。
Eclipse胖客户端平台(Rich Client Platform,RCP)是一个功能强大的软件基础(software
foundation),它基于相互联系并协作的插件,允许开发人员创建通用的应用程序。RCP使得开发人员可以更加关注应用程序的业务逻辑而不是花大量
时间来重新发明轮子去编写大量的应用程序管理逻辑。
控制翻转(IOC)和依赖注射是一种减少程序耦合性的设计模式。它们遵循了一个简单的原则:你不必自己创建对象,你只需要描述如何创建对象。你不必去实例
化或者定位你的组件需要的服务,你只需要去描述哪个组件需要哪个服务,其他组件(通常是容器)来负责将它组装好。这也就是著名的好莱坞原则:不要打电话给
我们,我们会打给你。
这篇文章描述了在Eclipse
RCP中引入依赖注射机制的一个简单方法。为了避免污染Eclipse平台的基础设施并且透明的为RCP添加IoC框架,我们使用了动态字节码操作(使用
ObjectWeb ASM类库),Java类加载agent以及Java标注技术的组合。
什么是Eclipse胖客户端平台?
简单来说,Eclipse胖客户端平台是一组类库,软件框架以及运行环境,它可以用来创建独立运行并且经常需要与网络交互的应用程序。
尽管Eclipse是作为一个集成开发环境(IDE)框架设计的,但是,从3.0版本开始,整个项目已经被重构成各种独立的组件,以便于可以使用这些组件
的一个子集来构建任意的应用程序。这个子集构成了RCP,它包含以下几种组件:基本运行环境,用户接口组件(SWT和JFace),插件以及OSGi层。
图1展示了Eclipse平台的主要组件。
整个Eclipse平台是基于插件和扩展点(extensions
points)这样一个关键概念的。一个插件是可以独立开发和发布的功能的最小单元。它一般会打包成一个jar文件并且通过增加功能来扩充Eclipse
平台(例如,增加一个编辑器,工具栏按钮或者一个编译器)。整个平台就是一组互相联系并通讯的一组插件的集合。一个扩展点是一个已经存在的互相联系的端
点,可以被其他插件用来添加功能(功能:在Eclipse术语中叫做扩展)。扩展和扩展点通过XML配置文件定义并绑定在插件中。
尽管插件已经使用了关注点分离这样一个重要的模式,但是插件之间的强相互联系和通讯会导致他们之间的强依赖关系。一个典型的例子就是需要定位应用程序需要
的各种单例(Singleton)服务,例如数据库连接池,日志处理器,或者用户偏爱(preference)的设置信息的保存等。控制反转和依赖注射是
消除这些依赖的可行解决方案。
控制反转和依赖注射
+控制反转是一种设计模式,它主要关注如何将服务(或者应用程序组件)的定义和服务如何定位它们依赖的服务进行分离。为了完成这种分离,一般都会依赖一个
容器,或者定位框架(locator framework),维护现存服务的列表+提供一种方法来将组件和它们依赖的服务绑定在一起。
+提供一种方法让应用程序代码可以请求一个已经配置好的对象(例如,一个所有依赖都已经满足的对象),这样就可以保证该对象所有相关的服务都已经可用了。
现存的框架一般都使用下面三种基本技术的组合来绑定服务和组件:
+类型一(基于接口):服务对象需要实现一个专门的接口,这个接口为这些服务对象提供了一个对象,服务可以通过这个对象来查询它们的依赖。这是一些早期的
容器使用的模式,例如Excalibur.
+类型二(基于setter):通过JavaBean属性的setter方法将依赖的服务赋值给服务对象。HiveMind和Spring都是通过这种方
式来实现的。
+类型三(基于构造函数):依赖的服务通过构造函数的参数提供(不通过JavaBean属性暴露)。这是PicoContainer使用的唯一方式。HiveMind和Spring也使用了这种方式。
我们将采用第二种方式的变种,通过带标注的方法来提供服务。(示例应用的源代码在资源中可以找到)。声明依赖可以采用以下方式:
@Injected public void aServicingMethod( Service s1,
AnotherService s2) { // save s1 and s2 into class variables // to use
them when needed}
控制反转容器会查找Injected标注并且使用需要的参数来调用这个方法。在我们为Eclipse平台引入IoC的过程中,在服务和可服务对象间建立绑
定关系的代码被封装在一个Eclipse插件中。这个插件定义了一个扩展点
(com.onjava.servicelocator.servicefactory),用来为应用程序提供服务工厂。当一个可服务对象需要配置时,插
件会向工厂请求服务的实例。正如下面的代码,ServiceLocator类将会完成所有的工作。(我们会跳过那些处理扩展点解析的代码,因为这些代码会
很简单)
/** * Injects the requested dependencies into the * parameter
object. It scans the serviceable * object looking for methods tagged
with the * {@link Injected}
annotation.Parameter types are * extracted from the matching method.
An instance * of each type is created from the registered * factories
(see {@link IServiceFactory})。 When *
instances for all the parameter types have been * created the method is
invoked and the next one * is examined. * * @param serviceable the
object to be serviced * @throws ServiceException */
public static void service(Object serviceable) throws ServiceException {
ServiceLocator sl = getInstance();
if (sl.isAlreadyServiced(serviceable)) {
// prevent multiple initializations due to
// constructor hierarchies
System.out.println( "Object " + serviceable + " has already been configured ");
return; }
System.out.println("Configuring " + serviceable);
// Parse the class for the requested services
for (Method m : serviceable.getClass()。getMethods()) {
boolean skip=false;
Injected ann=m.getAnnotation(Injected.class)
; if (ann != null) {
Object[] services = new Object[m.getParameterTypes()。length];
int i = 0;
for(Class<?> klass :m.getParameterTypes()){
IServiceFactory factory = sl.getFactory(klass,ann.optional());
if (factory == null) {
skip = true;
break; }
Object service = factory.getServiceInstance();
// sanity check: verify that the returned
// service's class is the expected one
// from the method
assert(service.getClass()。equals(klass) || klass.isAssignableFrom(service.getClass()));
services[i++] = service ; }
try {
if (!skip)
m.invoke(serviceable, services); }
catch(IllegalAccessException iae) {
if (!ann.optional())
throw new ServiceException( "Unable to initialize services on
" + serviceable + ": " +
iae.getMessage(),iae); }
catch(InvocationTargetException ite) {
if (!ann.optional())
throw new ServiceException( "Unable to initialize services
on " + serviceable + ": " +
ite.getMessage(),ite); } } }
sl.setAsServiced(serviceable);}
既然这个服务工厂返回的服务同样可以是一个可服务对象,这个策略允许定义一种服务层次(但是,目前并不提供对循环依赖的支持)。
ASM and java.lang.instrument Agents
以上介绍的各种注射策略都依赖于容器的存在,依赖容器提供一个入口点来使得应用程序可以使用该入口点来请求已经得到正确配置的对象。但是,我们想在开发我
们的IoC插件的时候使用一种透明的方式,这主要有两个原因:+RCP采用了复杂的classloader以及初始化策略(考虑一下
createExecutableExtension())来维护插件的隔离性以及可见性的限制。我们不想修改或者替代这种策略来引入我们的基于容器的初
始化规则。
+对这样一个入口点(这个例子中,是service
locator插件中定义的service()方法)的显式引用将会强迫应用程序开发人员采用一种显式的模式和逻辑来获取初始化的组件。这样就会有一些应
用-锁定的类库出现在程序代码中。我们想要定义一个合作的插件,这个插件并不需要显示的引用这些代码。
出于以上原因,我们要引入定义在java.lang.instrument包中的Java转换代理,这个包出现在J2SE5.0以及以后的版本中。一个转
换代理是一个实现了java.lang.instrument.ClassFileTransformer接口的对象,这个接口只有唯一一个方法,
transform()。当一个转换器的实例注册到JVM中后,当JVM要创建任何类的时候,这个代理的transform()会被调用。转换器可以在
JVM加载类之前访问这个类的字节码并且可以对其进行修改。
转换代理可以采用-javaagent:jarpath[=options]的形式的命令行参数来注册到JVM,其中jarpath是包含了这个代理类的
JAR文件,options是传递给该代理的一个参数字符串。这个代理JAR文件使用一个特殊的MANIFEST属性来指明真正的代理类,这个代理类必须
定义一个public static void premain(String options, Instrumentation
inst)方法。这个premain()方法将会在应用程序的main方法被调用之前调用,并且将一个真正的变换器注册到传递进来的
java.lang.instrument.Instrumentation类的实例中。
在我们的示例程序中,我们定义一个代理来透明的进行字节码操作并且动态的调用我们的IoC容器(service
vlocator插件)。这个代理会通过检验是否存在Serviceable标注来确定一个可服务对象。然后,修改所有的构造函数来调用IoC容器的方法
在对象初始化时正确地配置和初始化这个对象。
让我们假设我们有一个需要依赖于其他服务的对象(还记得Injected标注吗?):@Serviceablepublic class
ServiceableObject { public ServiceableObject() {
System.out.println("Initializing……"); } @Injected public void
aServicingMethod( Service s1, AnotherService s2) { // ……
omissis …… }}
当它被转换代理操作以后,它的字节码会和下面这个进行正常编译的类的字节码一样:@Serviceablepublic class
ServiceableObject { public ServiceableObject() {
ServiceLocator.service(this); System.out.println("Initializing……");
} @Injected public void aServicingMethod( Service s1,
AnotherService s2) { // …… omissis …… }}
通过这种解决方案,我们不必将对容器的依赖进行硬编码就可以配置一个可服务对象并且使他们可用。开发人员仅仅需要在可服务对象类上使用
Serviceable标注就可以了,变换代理的代码如下:public class IOCTransformer implements
ClassFileTransformer { public byte[] transform( ClassLoader
loader, String className, Class<?>
classBeingRedefined, ProtectionDomain
protectionDomain, byte[] classfileBuffer) throws
IllegalClassFormatException {
System.out.println("Loading " + className); ClassReader
creader = new ClassReader(classfileBuffer); // Parse the
class file ConstructorVisitor cv = new
ConstructorVisitor(); ClassAnnotationVisitor cav = new
ClassAnnotationVisitor(cv); creader.accept(cav,
true); if (cv.getConstructors()。size() > 0) {
System.out.println("Enhancing "+className); // Generate the
enhanced-constructor class ClassWriter cw = new
ClassWriter(false); ClassConstructorWriter writer =
new ClassConstructorWriter( cv.getConstructors(),
cw); creader.accept(writer, false);
return cw.toByteArray(); } else return
null; } public static void premain(String agentArgs,
Instrumentation inst) { inst.addTransformer(new
IOCTransformer()); }}
其中ConstructorVisitor, ClassAnnotationVisitor, ClassWriter以及 ClassConstructorWriter这几个类使用ObjectWeb ASM类库来进行字节码操作。
ASM使用访问者模式来将类数据(包括指令序列)作为事件流处理。当解码一个已经存在的类时,ASM为我们产生事件流,调用我们的方法来处理事件。当生成
一个新类的时候,采用相反的过程:我们产生一个事件流,ASM类库将它转化成一个类。注意我们描述的这个方法并不依赖于特定的字节码类库(我们这里用了
ASM),其他的解决方案,例如BCEL和Javassist同样可以工作。
我们不想深究ASM的内部机制。对于本文的目的来说,知道ConstructorVisitor和ClassAnnotationVisitor对象是用来确定使用了Serviceable标注的类并且收集他们的构造函数就足够了。它们的代码如下:
public class ClassAnnotationVisitor extends ClassAdapter
{ private boolean matches = false; public
ClassAnnotationVisitor(ClassVisitor cv) { super(cv); }
@Override public AnnotationVisitor visitAnnotation( String
desc, boolean visible) { if (visible &&
desc.equals("Lcom/onjava/servicelocator/annot/Serviceable;"))
{ matches = true; } return
super.visitAnnotation(desc, visible); } @Override public
MethodVisitor visitMethod( int access, String name, String
desc, String signature, String[] exceptions) { if
(matches) return super.visitMethod(
access,name,desc,signature,exceptions); else { return
null; } } }public class ConstructorVisitor extends
EmptyVisitor { private Set<Method> constructors; public
ConstructorVisitor() { constructors = new
HashSet<Method>(); } public Set<Method>
getConstructors() { return constructors; }
@Override public MethodVisitor visitMethod( int access,
String name, String desc, String signature, String[]
exceptions) { Type t =
Type.getReturnType(desc); if
(name.indexOf("<init>") != -1 &&
t.equals(Type.VOID_TYPE)) { constructors.add(new
Method(name,desc)); } return
super.visitMethod( access,name,desc,signature,exceptions); }}
对于上述类收集到的每一个构造函数,使用一个ClassConstructorWriter类的实例来修改构造函数的字节码,注入对service
locator插件的调用:com.onjava.servicelocator.ServiceLocator.service(this);
采用ASM方式完成上述工作需要以下代码:// mv is an ASM method visitor,// a class
which allows method manipulationmv.visitVarInsn(ALOAD,
0);mv.visitMethodInsn( INVOKESTATIC,
"com/onjava/servicelocator/ServiceLocator", "service",
"(Ljava/lang/Object;)V");
第一个指令将第二个指令需要使用的this对象的引用放在堆栈上,第二个指令将调用ServiceLocator的一个静态方法。
一个示例Eclipse
RCP应用我们现在已经具有了所有创建应用程序的元素,我们的示例代码用来向用户显示有趣的警句和引用,就像fortune
cookies一样。它包含了四个插件:+service
locator插件,提供Ioc框架的功能+FortuneService插件,提供了管理fortune cookies的服务。
+FortuneInterface插件,发布访问服务需要的公共接口。
+客户端插件,作为Eclipse应用在一个Eclipse视图中显示格式化的警句。
我们采用的IoC设计使得服务的实现和客户端分离,服务的实现可以改变或者修改,而客户端却不受影响。
就像上面几节描述的那样,为了向用户显示警句,service locator将会将客户和服务绑定在一起。FortuneInterface仅仅定义一个公有的接口,用户可以使用它来访问cookie消息。
public interface IFortuneCookie { public String getMessage();}
Fortune插件提供了一个简单的服务工厂来创建IFortuneCookie的实现类的实例。
public class FortuneServiceFactory implements IServiceFactory
{ public Object getServiceInstance() throws ServiceException
{ return new FortuneCookieImpl(); } // …… omissis ……}
工厂作为一个Eclipse扩展注册到service locator,就像它的plugin.xml中描述的一样。
<?xml version="1.0" encoding="UTF-8"?><?eclipse
version="3.0"?><plugin><extension
point="com.onjava.servicelocator.servicefactory">
<serviceFactory
class="com.onjava.fortuneservice.FortuneServiceFactory"
id="com.onjava.fortuneservice.FortuneServiceFactory" name="Fortune
Service Factory"
resourceClass="com.onjava.fortuneservice.IFortuneCookie"/></extension></plugin>
这里,resourceClass属性定义了这个工厂提供的服务类。描述的服务被FortuneClient插件中的Eclipse视图使用。
@Serviceablepublic class View extends ViewPart { public
static final String ID = "FortuneClient.view"; private
IFortuneCookie cookie; @Injected(optional=false) public void
setDate(IFortuneCookie cookie) { this.cookie = cookie; }
public void createPartControl(Composite parent){ Label l = new
Label(parent,SWT.WRAP); l.setText("Your fortune cookie
is:n" + cookie.getMessage()); } public void setFocus() {}}
注意要加上Serviceable和Injected标注来定义对其他服务的依赖,但是不需要引用提供这些服务的代码。最后的结果是createPartControl()可以自由得使用cookie对象而且可以保证cookie是正确初始化过的。
结论
在这篇文章中,我们讨论了如何将一种强大的设计模式(可以简化代码依赖的处理,IoC)与一种可以加速Java客户端应用程序开发的技术合并在一起。虽然
我没有处理更多于这个问题有关的细节,但是我也已经演示了一个示例应用如何将服务和服务客户解耦。我同时也描述了Eclipse插件技术在开发客户端和服
务时如何做到了关注点分离。但是,仍然有很多有趣的元素在等待我们探索,例如清除策略,用来在不需要这个服务的时候清除掉服务,或者在我们的客户插件中使
用mock-up服务来进行单元测试,这些要留给读者去研究了。
Resources +本文的示例代码+Eclipse.org网站上的Eclipse RCP教程+ASM网站上的ASM 2.0介绍+Matrix,与Java共舞+http://www.onjava.com
2008年1月2日
我究竟能活多久?这是每个人都关心的问题。最近,美国CNN、《时代》周刊等各大媒体纷纷对一个被称为“寿命计算器”的程序进行了报道。
寿命真的是可以计算吗?“寿命计算器”的发明者Thomas Perls博士是美国波士顿大学医学院从事老年医学研究的医生和研究员,日前,他接受了健康时报记者的采访,告诉大家怎样去计算自己的寿命。
数百万人网上“算命”
“寿命计算器”最早出现在1999年。当时只有23个问题,但随着研究的深入,目前已经增加到了40个问题。
丹尼斯·李的预期寿命只有65岁,而相比之下,同样拥有高学历和好的家庭环境的凯特·雷特福的预期寿命却高达105岁。原因是李吸食大麻,并于最近被诊
断出患有糖尿病。滴酒不沾的利奇更是得到了惊人的107岁,因为她现在的生活习惯都是有利于长寿的:徒步旅行,饮食均衡,擅于处理各种压力。
“我的寿命计算程序的最终目的在于:提示你注意良好的生活习惯。”Perls说。
Perls提醒说,寿命计算程序不是一个完美的工具,但它提供了一个蓝图,无论你的分数是60或106,“如果你从现在开始改变其中一个选择,就可以提高自己的分数。”
做完题后的三件事
世卫组织中国代表处营养与食品安全专家张平平建议,做完测试后,你需要做三件事:第一,最好先访问一下医生。在医生的指导下,这种非常个性化的关于寿命
的评估,可以带给你个性化的改善方案。第二,制定健康计划并坚持下来。第三,从小处着手。做完测试,并不是要你马上大刀阔斧地修改自己原有的习惯,而是要
从小处着手,比如肥胖者不必每天非要改吃豆腐,但可以做出合理的适度的调整。
对“算命程序”不要一笑而过
面对这样的一个“算命”程序,许多人也许会想,就是一些会上网的“算命先生”整出的玩意儿,一笑而过,不去当真。其实并非如此。
正如发明者Perls所说,这个测试就是告诉你要从现在开始改变自己。
我们都知道,“生活方式病”很可怕,因为它已经融入了现代生活的方方面面。开私家车上下班、坐电脑前完成一天的工作、餐桌上推杯换盏、灯红酒绿的夜生活里度过夜晚时光……这曾是许多人追求的幸福生活,而今我们享受到了,“生活方式病”却已经开始缠身了。
我们一直强调,要有健康的生活方式。什么是健康的生活方式,其实同样很简单,就是从日常生活点滴做起,从改变吸烟、酗酒等不良的生活习惯做起,从合理安排膳食结构做起。记住,每天前进一小步,健康就能前进一大步。
寿命计算器
Perls把男性的预期寿命设定为86岁,女性则为89岁,随着每个问题的回答,数字会相应加减,最后得到答案。如果你长寿,恭喜你,证明你拥有健康的生活习惯,否则,赶快把烟酒戒了,出去锻炼吧!
1.你已婚。(+3岁)
点评:婚姻让男性的寿命延长3年,对女性则没有影响。
2.你和家人之间联系密切,与朋友经常相聚。(+0.25岁)
点评:和亲朋之间和谐的关系,可以让你健康又长寿。
3.如何评估你目前的压力水平:低(+0.75岁);高(-3岁)
点评:压力过大会短命,善于处理压力可以让寿命增加。
4.你善于减压(+1岁);不善于(-2岁)
点评:减压方法多,女人唠叨,男人的眼泪都可以。
5.每天晚睡3~5个小时(-1岁);6小时以上(+1岁)
点评:出租车上小憩、工作间隙打个盹儿、午休时间小睡一会儿,每天让你的总睡眠时间达6至8小时就行。
6.你接受过多少年的正规教育?16年以上(+0.5年)低于8年(-0.5年)
点评:良好的教育能让你获得更多的健康知识。
7.你一周工作多少小时?低于40个小时(+2岁);40个至60个小时(+1岁)
点评:工作时间一长,就意味着压力增大,疲劳增加,增加工作效率可以缩短工作时间。
8.你对人生逐渐走向衰老感到乐观(+2岁)悲观(-1岁)
点评:乐观与长寿总是结伴而行的。
9.你居住的地方空气质量很好(+0.5岁)
点评:城里人难以选择环境,但可以调节一下自己的生活小环境,比如家里多开窗通风,用绿色植物来调节室内空气。
10.当你在私家车中,你总是会系好安全带(+0.75岁)
11.你每天喝多少杯含有咖啡因的咖啡?
2杯以下(+0.5岁)
3杯以上(-0.5岁)
点评:咖啡能让人提神,但会增加钙质排泄,如果又不注意补钙,就容易造成骨质疏松了。
12.你每天喝2~3杯绿茶(+0.5岁)
点评:喝茶不宜过量,过浓,进餐前半小时不喝茶,孕期、哺乳期妇女、生长发育中的儿童及缺铁性贫血患者不宜饮茶。
13.你吸烟或暴露在二手烟的环境(-4岁)
点评:香烟害人害己,人人喊打,经常被动吸烟的人患肺癌的几率比正常人多出6倍。
14.你每天都吸烟(-0.5岁)
点评:烟民要长寿,第一件事就是戒烟,没有任何借口。
15.你每天吸多少支烟?10支(-5岁);20支(-10岁);40支以上(-15年)
16.你每天饮用啤酒超过3杯,或含酒精的饮品超过3杯,或4杯白酒。(-7岁)
点评:酒伤身还是养身,因人而异,因量而异。
17.你每天服用一片阿司匹林(+2岁)
点评:如果在医生的建议下,你能每天服用81mg阿司匹林,可以提高听力和大脑健康,有助于延缓或避免心脏病或中风的发生。
18.阳光下你会涂抹防晒油来保护皮肤吗?很少(-1岁)会做好防护(+0.5岁)
点评:适量的紫外线能促进钙质的吸收,对预防骨质疏松、佝偻病有好处,但过量的紫外线会大大增加患皮肤癌的危险,还会增加皱纹。
19.你没有从事危险性行为,也不注射违法药物。(+10岁)
20.你每天都用牙线洁牙吗?是的(+1岁);不是(-1岁)
点评:如果能经常使用牙线,就可以减少牙周炎的发生,不刷牙则会减寿一年。
21.你一周吃多少次快餐和熟食。从来不吃(+4岁);5次以上(-2岁)
点评:快餐在营养学家眼中是高热、高脂、高蛋白的“垃圾食品”,会导致肥胖、糖尿病、癌症等各种慢性疾病。
22.你很少吃烧烤的鱼,家禽或肉类(+1岁)
23.你每天会补充钙(+0.5岁)
点评:每天摄取更多的钙或每天服用1500mg的钙片,可以让寿命增加。
24.如果在正餐之间吃零食,通常你会选择干果(+0.5岁)
点评:干果可以美肤、健脑、保护心脑血管健康、抗衰老。
25.你常吃大量的甜食,如冰淇淋,蛋糕,糖果等(-1岁)
点评:吃甜食过多,会引起高脂血症、动脉硬化、肥胖症、高血压病、冠心病、糖尿病和骨质疏松等疾病,还会促发乳腺癌,加速细胞的老化,使人体环境适应能力差等。
26.我每天都吃得很多,肥胖(-5岁)
27.你不会把铁作为营养素的一部分来补充(+2岁)
点评:降低体内的铁质很可能会减缓老化过程,并让人能够避免跟老化有关的疾病,可以让寿命增加。
28.你一周有多少天能达到至少锻炼30分钟?每周7天(+5岁);每周三天(+3岁);我很少锻炼(-1年)
点评:养成运动习惯很重要,实在达不到30分钟,那就每天利用零星时间锻练3分钟也会有点效果,可以做做腹式呼吸、转转脖子、扭扭腰。
29.你排便不规律(-0.5年)
点评:每天清晨起来一杯凉白开,就能解决这个问题。
30.你的总胆固醇水平高于180mg/dl(5mmol/L)(-2年)
31.你心脏的收缩压是多少?低于120(+2岁);高于230(-5年至15年)
32.你心脏的舒张压低于80(+7岁)
33.你每年都做血糖检测(+0.5年)
点评:18岁以上定期测血压,30岁以上定期测血脂,40岁以上男性每年都应该测血糖。
34.你的心脏病两年前发作过,但后来也没有采取任何措施来预防它再次发作(-2岁)
35.你的直系亲属中从来没有患有糖尿病者或心脏病者(+2岁)
36.直系亲属中有三位或更多的人患有癌症。(-1岁)
37.你母亲活到90岁以上(+2岁)
38.你父亲活到90岁以上(+2岁)
39.你的祖父母或曾祖父母中有达到或超过98岁高龄的(+2岁)
40.你没有借助任何人工生育手段生育最后一个孩子时是多少岁?35~43岁(+2岁)
点评:40岁或以后才怀孕的妇女,要比年轻时怀孕的女性更长寿,因为晚育可能意味着更年期的推迟,对女性荷尔蒙的产生有积极作用。