钓鱼要有耐心作文(通用5篇)
1.钓鱼要有耐心作文 篇一
正如美国的贝弗里奇所言:“每一点滴的进展都是缓慢而艰巨的,一个人一次只能着手解决一项有限的目标。”是啊,什么事情都不是一蹴而就的,而是一个循序渐进的过程,一个人只有脚踏实地,一步一个脚印,才能搭起一座通往成功的桥梁,而没有耐心的人,最终只能一事无成。
“不积跬步,无以至千里;不积小溪,无以成江海。”只有一点一滴地积累、等待和忍受,才能获得成功。就像丑小鸭蜕变成白天鹅、毛毛虫蜕变成花蝴蝶一样,都需要一个漫长而难熬的过程。如果没有耐心从而半途而废,就会像《小猫钓鱼》里哪三心二意的小猫一样,一条鱼都钓不着。
人生道路上,是满布着艰难、坎坷的。遇到困难时,不能知难而退,而是要迎难而进,不屈不挠,想尽一切办法去扫除障碍、克服困难。而做到这些的首要条件就是一定要具备耐心,否则,幸运之神不会降临到你的身上。如果你没有耐心去等待成功的到来,那么,你只好用一生的耐心去面对失败,只有耐心、自强不息的人,才能挖掘出“智慧”的钥匙,从而打开通向“成功”的大门。
耐心,不可忽视,它是人类最宝贵的财富,拥有它,你就会拥有别人无法拥有的一切。正如英国大哲学家培根所言:“无论何人,若是失去耐心,就是失去灵魂。”因此,我们要时刻以耐心为伴,要把它当作我们最好的朋友,因为我们离它不可。
2.钓鱼要有耐心作文 篇二
夏至刚过,暑气渐盛,万物呈现出一幅蓬勃旺盛的景象。趁着端午假期,爸爸妈妈带我参加了春晚小农夫的活动,一大早我们就来到了半山耕云农庄。
带着满心的期待,才踏入农庄动物园,迎面的马儿就跟我们“打招呼”,好像在说:你们好啊!欢迎到农庄来。这里的动物可真多呀!憨厚的.老牛、呆萌的驴、乖巧的羊、吵吵闹闹的鸡鸭,它们有的在嬉戏玩耍,有的在打架斗殴,仿佛是在争夺谁是这片土地的主人?
在农民伯伯的指导下,我们全副武装好,兴致勃勃,蹦蹦跳跳地来到菜园子里学习种菜。指导我们种菜的,是一位专业的阿姨。阿姨简单介绍之后,就开始有条不紊地给我们做示范。但是我早已经按捺不住自己激动的心情,急急忙忙就拿起小锄头照猫画虎学着阿姨开始种菜。我用蛮力三下五除二就刨出了一串坑,又把菜苗当成飞镖一样嗖,嗖全抛进坑里,进一个,我就开怀大笑,兴致越来越高,我索性学着天女洒花般在每个坑里快速洒上了一些土,转眼间大功告成,但是放眼一看菜苗像是生病一样,横七竖八倒在土里!
正在我自鸣得意之际,阿姨轻轻拍拍我的肩膀,很有耐心地对我说:小朋友,这样没有耐心种出的菜,到收获的季节你可能就一无所获啦。种菜时坑要挖得深一些,土要埋得要多一些,苗很嫩,要轻拿轻放且勿折断,水要浇透。阿姨,一边说着,一边示范给我看!阿姨种下的菜苗和我种下的形成鲜明对比,它们都精神十足地挺立着,一瓢水浇下去,仿佛还能看到菜苗茎叶都舒展开了。
我认真看完阿姨种菜的每一个步骤,轻轻挑出之前急匆匆种下的菜苗,把坑慢慢地挖深挖齐,然后蹲下身子,小心翼翼地用左手扶着小嫩苗,右手在把坑边的土扒到菜根上,再用双手把土压实,让小菜苗一棵棵地挺立起来,看它们多精神啊!菜栽完,我又拎来一桶水,又耐心地把苗浇了两遍水,菜苗又变得更绿了。
3.教育孩子要有爱心耐心信心 篇三
家庭教育对孩子一生的成长起着至关重要的作用。家庭教育不得当,很可能使孩子成为问题少年,像进入所谓“行走学校”的那些学员;也可能使孩子在遇到突发事件时做出过激行为,比如西安音乐学院的药家鑫。作为父母,要想给孩子提供良好的家庭教育,就必须要对孩子有爱心、耐心和信心。对孩子有爱心,相信100%的父母都敢拍着自己的胸脯说这句话,但表达爱心的方法是否得当,这个问题值得每个家长深思。很多父母受工作的影响,很难全身心地关注孩子。教育孩子需要长期的努力,几天甚至几个星期不关注孩子,一般不会有太明显的变化,而工作则是很现实的事情,一天不操心,工作成绩就可能受影响。所以很多家长在遇到精力分配矛盾时,往往选择后者。去年6月,乌鲁木齐一所“行走学校”的女学员李某在试图逃离学校时坠楼身亡。记者前去采访时,一名把孩子送入该学校的家长说,如果孩子能够重新回到自己肚子里,重新成长一次,自己一定会把所有的爱全部给他。还有很多父母为了给孩子提供较好的生活条件和教育环境,长年在外打工,把赚到的钱一分不差地寄给孩子。其实这样并不利于孩子的成长,没有父母陪伴的家庭是不完整的家庭,很容易使孩子形成性格缺陷。有一个单亲家庭的孩子,用自己的零花钱攒
够了父亲一天的工资,然后央求父亲:“我给你一天的工资,你明天能不能陪我到公园玩一天?”我想每个做父母的听了这个故事,心理都会感慨万千。与物质条件比起来,孩子更需要父母的陪伴。
对孩子一定要有耐心。好奇是孩子的天性,遇到任何事情都要问个为什么。很多家长前期都能耐心地解答孩子的问题,但随着时间的推移,相当一部分家长会慢慢失去耐心,用简单的敷衍来打发孩子,心情不好时可能还会批评孩子多事。这种做法不仅打击孩子的积极性,还有可能引发或加剧两代人之间的隔阂。此外,对一些已经表现出叛逆行为的孩子,很多家长在处理时也表现的不够有耐心。对于青少年来讲,叛逆心理属于正常现象,这时父母应该多与孩子沟通交流,了解孩子的内心世界,通过亲情的感化和事理的引导,校正孩子的思想和行为。然而相当一部分家长没有足够的耐心去做这些工作,只是重复着简单的说教,达不到目的时便严加训斥,甚至拳脚相加,结果使家庭关系更加紧张。我上大学时,我弟弟高二辍学。后来他跟我说,离开学校那天,他脑子里只有一个念头:以后我有了孩子,我绝对不会天天在他耳边唠叨好好读书、好好上学。我一直觉得,如果那时父母能想办法和弟弟耐心地聊一聊,也许不会是这种结局。
4.班主任工作要有爱心、细心和耐心 篇四
219班班主任郑拥民
班主任工作是一门艺术。我虽然从事班主任工作十余年了,但是还没有摸出一条很好的道来。有些时候很羡慕那些成功的班主任,如方文建老师、吴图强老师等,总觉得他们工作很轻松、师生关系又融洽。但我由于性格原因不喜欢采用高压也不会说教,一直被动地用“勤”持着。我也深切的知道,这样毕竟不是个办法,得有属于我自已的班级管理方法。
在近几年中,我体会到教育孩子的不易;认真听取了我校多次组织的《班主任经验介绍》;学习了学校推荐的几本教育专著;特别是何校长多次提到的“教育是慢的艺术”时常萦绕在我的耳边。渐渐地使我对班主任工作有了新的看法,通过一年的实践有了以下几点粗浅的体会。
一、爱心是班主任工作的基础
首先,教师要把学生当孩子一样来呵护和宠爱,不是当大人一样去批评与苛求。高中阶段的学生正处于青春期,生理和心理在成熟与幼稚之间徘徊,他们好奇,辨别能力差,容易冲动,叛逆性极强。这是处在这个年龄阶段的孩子所反映出来的特殊的生理特征。作为班主任应该允许存在,但只要通过心灵的交流,耐心细致的正面引导,逐渐在改变就可以了。将心比心,对待我们自已的孩子何尝不是这样做的呢?
其次,每个学生所面对的家庭环境都是不相同的,错位的家庭教育和扭曲的家庭关爱正滋生了学生的弱点:其一,他们都注重自我,张扬个性,总是以“我”为中心,忽视周围他人的感受;其二,他们自私狭隘,集体意
识缺乏;其三,自律意识差,自省能力差;其四,劳动观念淡薄,依赖性强,独立性差。作为班主任我们应深入其成长环境中,以社会、生存、竞争的角度去关爱他们,引导他们也同样能去关爱周围的一切。有了爱就有了理解、支持、鼓励与合作,班级同学间就能和谐相处了,学生的集体荣誉感就会增强。
第三,当今社会正处在物质文明高速发展时期,常有一些非社会主义甚至敌视社会主义的价值观念、生活方式与文艺作品等引进来,从而削弱心理、思想尚不成熟学生的防御能力,潜移默化地改变着这些学生的思想意识和价值观念。现代学生的思想价值观念具有明显的多元性、表面性、矛盾性和不稳定性,这些都是班主任最困惑的问题。因而,迫切需要运用爱的力量营造积极向上的价值观念,影响这些学生。
二、细心是班主任工作的源泉
班主任作为班级工作的实施者、引领者,肩负着几十名学生全面发展、健康成长的重任,大至管理教育教学工作,引导学生的人生发展,小至管理粉笔、地面上的瓜子壳、窗户上的灰尘、学生的吃饭、睡觉。除了这些,班主任还应有一颗纤细如发的心。要做到勤观察、勤交流、勤家访、勤鼓励、勤表扬、勤记录”。
平时,细心观察学生的学习、情绪、身体、表现;多与学生交流、谈心,了解他们的喜怒哀乐、爱好兴趣;多家访,了解学生的家庭情况,分析思想动态;关注学生点滴的进步,及时鼓励、表扬;有教育心得多记录、多总结,积累经验……这样对学生各方面情况均了然于心,才能因材施教。
不厌其烦,不厌其细,时时抓,细细抓,是班主任工作不可欠缺的。
三、耐心是做好班主任工作的保证
班主任工作千头万绪,细小琐碎。有时确实让班主任焦头烂额,然而终止一个坏习惯和培养一个好习惯,都需要有足够的耐心。因为一种正确的观点,往往要经过多次反复才能化为学生自己的认识。因此,在差生的转化过程中,我们决不能操之过急,希望一步到位,或“一蹴而就”,“一劳永逸”。要允许反复,并在反复中耐心地进行诱导教育。在教育差生的过程中我们要有足够的思想准备,做到宽严相济,持之以恒,耐心细致地做好他们的教育工作。决不能对差生揭老底,算旧账,全盘否定他们已有的进步。我的做法是后进生实行“周谈”;优生实行“月谈”。指出他们的进步和不足,收到了很好的效果。
班主任工作,可说酸甜苦辣样样滋味皆在其中。尽管工作尚不够尽善尽美,但确已尽心尽职了。我总想,何时才有自已治班的特色呢?……
5.钓鱼要有耐心作文 篇五
[img=http://hi.csdn.net/attachment/201003/17/1442_12687963784zxy.jpg][/img]
想来学习Java也有两个年头了,永远不敢说多么精通,但也想谈谈自己的感受,写给软件学院的同仁们,帮助大家在技术的道路上少一点弯路。说得伟大一点是希望大家为软件学院争气,其实最主要的还是大家自身的进步提升——
Java学习杂谈一
1. 关于动态加载机制——
学习Java比C++更容易理解OOP的思想,毕竟C++还混合了不少面向过程的成分。很多人都能背出来Java语言的特点,所谓的动态加载机制等等。当然概念往往是先记住而后消化的,可有多少人真正去体会过动态加载的机制,试图去寻找过其中的细节呢? 提供大家一个方法:
在命令行窗口运行Java程序的时候,加上这个很有用的参数:
java –verbose *.class
这样会清晰的打印出被加载的类文件,大部分是jdk自身运行需要的,最后几行会明显的看到自己用到的那几个类文件被加载进来的顺序。即使你声明了一个类对象,不实例化也不会加载,说明只有真正用到那个类的实例即对象的时候,才会执行加载。这样是不是大家稍微能明白一点动态加载了呢?^_^
2. 关于寻找class文件原理——
建议大家在入门的时候在命令行窗口编译和运行,不要借助JCreator或者Eclipse等IDE去帮助做那些事情。尝试自己这样做:
javac
-classpath yourpath *.java java
-classpath yourpath *.class
也许很多人都能看懂,设置classpath的目的就是告诉编译器去哪里寻找你的class文件.不过至少笔者今日才弄懂JVM去查询类的原理,编译器加载类要依靠classloader,而classloader有3个级别,从高到低分别是BootClassLoader(名字可能不准确), ExtClassLoader, AppClassLoader.这3个加载器分别对应着编译器去寻找类文件的优先级别和不同的路径:BootClassLoader对应jre/classes路径,是编译器最优先寻找class的地方
ExtClassLoader对应jre/lib/ext路径,是编译器次优先寻找class的地方
AppClassLoader对应当前路径,所以也是编译器默认找class的地方
其实大家可以自己写个程序简单的测试,对任何class,例如A,调用new A().getClass().getClassLoader().toString()打印出来就可以看到,把class文件放在不同的路径下再次执行,就会看到区别。特别注意的是如果打印出来是null就表示到了最高级BootClassLoader, 因为它是C++编写的,不存在Java对应的类加载器的名字。
寻找的顺序是一种向上迂回的思想,即如果本级别找不到,就只能去本级别之上的找,不会向下寻找。不过似乎从Jdk1.4到Jdk1.6这一特点又有改变,没有找到详细资料。所以就不举例子了。告诉大家设计这种体系的是Sun公司曾经的技术核心宫力先生,一个纯种华人哦!^_^
这样希望大家不至于迷惑为什么总报错找不到类文件,不管是自己写的还是导入的第三方的jar文件(J2ee中经常需要导入的)。
3. 关于jdk和jre——
大家肯定在安装JDK的时候会有选择是否安装单独的jre,一般都会一起安装,我也建议大家这样做。因为这样更能帮助大家弄清楚它们的区别:
Jre 是java runtime environment, 是java程序的运行环境。既然是运行,当然要包含jvm,也就是大家熟悉的虚拟机啦,还有所有java类库的class文件,都在lib目录下打包成了jar。大家可以自己验证。至于在windows上的虚拟机是哪个文件呢?
学过MFC的都知道什么是dll文件吧,那么大家看看jre/bin/client里面是不是有一个jvm.dll呢?那就是虚拟机。
Jdk 是java development kit,是java的开发工具包,里面包含了各种类库和工具。当然也包括了另外一个Jre.那么为什么要包括另外一个Jre呢?而且jdk/jre/bin同时有client和server两个文件夹下都包含一个jvm.dll。
说明是有两个虚拟机的。这一点不知道大家是否注意到了呢?
相信大家都知道jdk的bin下有各种java程序需要用到的命令,与jre的bin目录最明显的区别就是jdk下才有javac,这一点很好理解,因为jre只是一个运行环境而已。与开发无关,正因为如此,具备开发功能的jdk自己的jre下才会同时有client性质的jvm和server性质的jvm,而仅仅作为运行环境的jre下只需要client性质的jvm.dll就够了。
记得在环境变量path中设置jdk/bin路径麽?这应该是大家学习Java的第一步吧,老师会告诉大家不设置的话javac和java是用不了的。确实jdk/bin目录下包含了所有的命令。可是有没有人想过我们用的java命令并不是jdk/bin目录下的而是jre/bin目录下的呢?不信可以做一个实验,大家可以把jdk/bin目录下的java.exe剪切到别的地方再运行java程序,发现了什么?一切OK!
那么有人会问了?我明明没有设置jre/bin目录到环境变量中啊?
试想一下如果java为了提供给大多数人使用,他们是不需要jdk做开发的,只需要jre能让java程序跑起来就可以了,那么每个客户还需要手动去设置环境变量多麻烦啊?所以安装jre的时候安装程序自动帮你把jre的java.exe添加到了系统变量中,验证的方法很简单,大家看到了系统环境变量的path最前面有―%SystemRoot%system32;%SystemRoot%;‖这样的配置,那么再去Windows/system32下面去看看吧,发现了什么?有一个java.exe。
如果强行能够把jdk/bin挪到system32变量前面,当然也可以迫使使用jdk/jre里面的java,不过除非有必要,我不建议大家这么做。使用单独的jre跑java程序也算是客户环境下的一种测试。
这下大家应该更清楚jdk和jre内部的一些联系和区别了吧?
PS: 其实还有满多感想可以总结的,一次写多了怕大家扔砖头砸死我,怪我太罗唆。大家应该更加踏实更加务实的去做一些研究并互相分享心得,大方向和太前沿的技术讨论是必要的但最好不要太多,毕竟自己基础都还没打好,什么都讲最新版本其实是进步的一大障碍!
Java 学习杂谈
(二)鉴于上回写的一点感想大家不嫌弃,都鼓励小弟继续写下去,好不容易等到国庆黄金周,实习总算有一个休息的阶段,于是这就开始写第二篇了。希望这次写的仍然对志同道合的朋友们有所帮助。上回讲了Java动态加载机制、classLoader原理和关于jdk和jre三个问题。这次延续着讲一些具体的类库——
1. 关于集合框架类
相信学过Java的各位对这个名词并不陌生,对 java.util.*这个package肯定也不陌生。不知道大家查询API的时候怎么去审视或者分析其中的一个package,每个包最重要的两个部分就是interfaces和classes,接口代表了它能做什么,实现类则代表了它如何去做。关注实现类之前,我们应该先理解清楚它的来源接口,不管在j2se还是j2ee中,都应该是这样。那么我们先看这三个接口:List、Set、Map。
也许有些人不太熟悉这三个名字,但相信大部分人都熟悉ArrayList,LinkedList,TreeSet,HashSet,HashMap,Hashtable等实现类的名字。它们的区别也是满容易理解的,List放可以重复的对象集合,Set放不可重复的对象组合,而Map则放
到底Vector和ArrayList,Hashtable和HashMap有什么区别?
很多面试官喜欢问这个问题,其实更专业一点应该这样问:新集合框架和旧集合框架有哪些区别?新集合框架大家可以在这些包中找since jdk1.2的,之前的如vector和Hashtable都是旧的集合框架包括的类。那么区别是?
a.新集合框架的命名更加科学合理。例如List下的ArrayList和LinkedList
b.新集合框架下全部都是非线程安全的。建议去jdk里面包含的源代码里面自己去亲自看看vector和ArrayList的区别吧。当然如果是jdk5.0之后的会比较难看一点,因为又加入了泛型的语法,类似c++的template语法。
那么大家是否想过为什么要从旧集合框架默认全部加锁防止多线程访问更新到新集合框架全部取消锁,默认方式支持多线程?(当然需要的时候可以使用collections的静态方法加锁达到线程安全)
笔者的观点是任何技术的发展都未必是遵循它们的初衷的,很多重大改变是受到客观环境的影响的。大家知道Java的初衷是为什么而开发的麽?是为嵌入式程序开发的。记得上一篇讲到classLoader机制麽?那正是为了节约嵌入式开发环境下内存而设计的。而走到今天,Java成了人们心中为互联网诞生的语言。互联网意味着什么?多线程是必然的趋势。客观环境在变,Java技术也随着飞速发展,导致越来越脱离它的初衷。据说Sun公司其实主打的是J2se,结果又是由于客观环境影响,J2se几乎遗忘,留在大家谈论焦点的一直是j2ee。
技术的细节这里就不多说了,只有用了才能真正理解。解释这些正是为了帮助大家理解正在学的和将要学的任何技术。之后讲j2ee的时候还会再讨论。
多扯句题外话:几十年前的IT巨人是IBM,Mainframe市场无人可比。微软如何打败IBM?正是由于硬件飞速发展,对个人PC的需求这个客观环境,让微软通过OS称为了第二个巨人。下一个打败微软的呢?Google。如何做到的?如果微软并不和IBM争大型机,Google借着互联网飞速发展这个客观环境作为决定性因素,避开跟微软争OS,而是走搜索引擎这条路,称为第3个巨人。那么第4个巨人是谁呢?很多专家预言将在亚洲或者中国出现,Whatever,客观环境变化趋势才是决定大方向的关键。当然笔者也希望会出现在中国,^_^~~
2. 关于Java设计模式
身边的很多在看GOF的23种设计模式,似乎学习它无论在学校还是在职场,都成了一种流行风气。我不想列举解释这23种Design Pattern,我写这些的初衷一直都是谈自己的经历和看法,希望能帮助大家理解。
首先我觉得设计模式只是对一类问题的一种通用解决办法,只要是面向对象的编程预言都可以用得上这23种。理解它们最好的方法就是亲自去写每一种,哪怕是一个简单的应用就足够了。如果代码实现也记不住的话,记忆它们对应的UML图会是一个比较好的办法,当然前提是必须了解UML。
同时最好能利用Java自身的类库帮助记忆,例如比较常用的观察者模式,在java.util.*有现成的Observer接口和Observable这个实现类,看看源代码相信就足够理解观察者模式了。再比如装饰器模式,大家只要写几个关于java.io.*的程序就可以完全理解什么是装饰器模式了。有很多人觉得刚入门的时候不该接触设计模式,比如图灵设计丛书系列很出名的那本《Java设计模式》,作者: Steven John Metsker,大部分例子老实说令现在的我也很迷惑。但我仍然不同意入门跟学习设计模式有任何冲突,只是我们需要知道每种模式的概念的和典型的应用,这样我们在第一次编写 FileOutputStream、BufferedReader、PrintWriter的时候就能感觉到原来设计模式离我们如此之近,而且并不是多么神秘的东西。
另外,在学习某些模式的同时,反而更能帮助我们理解java类库的某些特点。例如当你编写原型(Prototype)模式的时候,你必须了解的是 java.lang.Cloneable这个接口和所有类的基类Object的clone()这个方法。即深copy和浅copy的区别:
Object.clone()默认实现的是浅copy,也就是复制一份对象拷贝,但如果对象包含其他对象的引用,不会复制引用,所以原对象和拷贝共用那个引用的对象。
深copy当然就是包括对象的引用都一起复制啦。这样原对象和拷贝对象,都分别拥有一份引用对象。如果要实现深copy就必须首先实现 java.lang.Cloneable接口,然后重写clone()方法。因为在Object中的clone()方法是protected签名的,而 Cloneable接口的作用就是把protected放大到public,这样clone()才能被重写。
那么又有个问题了?如果引用的对象又引用了其他对象呢?这样一直判断并复制下去,是不是显得很麻烦?曾经有位前辈告诉我的方法是重写clone方法的时候直接把原对象序列化到磁盘上再反序列化回来,这样不用判断就可以得到一个深copy的结果。如果大家不了解序列化的作法建议看一看 ObjectOutputStream和ObjectInputStream
归根结底,模式只是思想上的东西,把它当成前人总结的经验其实一点都不为过。鼓励大家动手自己去写,例如代理模式,可以简单的写一个Child类,Adult类。Child要买任何东西由Adult来代理实现。简单来说就是Adult里的buy()内部实际调用的是Child的buy(),可是暴露在main函数的却是Adult.buy()。这样一个简单的程序就足够理解代理模式的基本含义了。
Java 杂谈
(三)这已经笔者写的第三篇Java杂记了,庆幸前两篇一直得到论坛朋友们的支持鼓励,还望大家继续指正不足之处。笔者也一直渴望通过这样方式清醒的自审,来寻找自己技术上的不足之处,希望和共同爱好Java的同仁们一起提高。
前两次分别讲述了关于jvm、jdk、jre、collection、classLoader和一些Design Pattern的自我理解。这次仍然不准备开始过渡到j2ee中,因为觉得还有一些琐碎的j2se的问题没有总结完毕。
1. 关于Object类理解
大家都知道Object是所有Java类的基类,意味着所有的Java类都会继承了Object的11个方法。建议大家去看看Object的 11个成员函数的源代码,就会知道默认的实现方式。比如equals方法,默认实现就是用“==”来比较,即直接比较内存地址,返回true 或者 false。而toString()方法,返回的串组成方式是——
“getClass().getName()+ ”@“ + Integer.toHexString(hashCode())”
其实不用我过多的解释,大家都能看懂这个串的组成。接下来再看看hashCode(): public native int hashCode();
由于是native方法,跟OS的处理方式相关,源代码里仅仅有一个声明罢了。我们有兴趣的话完全可以去深究它的hashCode到底是由OS怎么样产生的呢?但笔者建议最重要的还是先记住使用它的几条原则吧!首先如果equals()方法相同的对象具有相通的hashCode,但equals()对象不相通的时候并不保证hashCode()方法返回不同的整数。而且下一次运行同一个程序,同一个对象未必还是当初的那个hashCode()哦。
其余的方法呢?nofigy()、notifyAll()、clone()、wait()都是native方法的,说明依赖于操作系统的实现。最后一个有趣的方法是finalize(),类似C++的析构函数,签名是protected,证明只有继承扩展了才能使用,方法体是空的,默示什么也不做。它的作用据笔者的了解仅仅是通知JVM此对象不再使用,随时可以被销毁,而实际的销毁权还是在于虚拟机手上。那么它真的什么也不做麽?未必,实际上如果是线程对象它会导致在一定范围内该线程的优先级别提高,导致更快的被销毁来节约内存提高性能。其实从常理来说,我们也可以大概这样猜测出jvm做法的目的。
2. 关于重载hashCode()与Collection框架的关系
笔者曾经听一位搞Java培训多年的前辈说在他看来hashCode方法没有任何意义,仅仅是为了配合证明具有同样的hashCode会导致equals 方法相等而存在的。连有的前辈都犯这样的错误,其实说明它还是满容易被忽略的。那么hashCode()方法到底做什么用?
学过数据结构的课程大家都会知道有一种结构叫hash table,目的是通过给每个对象分配一个唯一的索引来提高查询的效率。那么Java也不会肆意扭曲改变这个概念,所以hashCode唯一的作用就是为支持数据结构中的哈希表结构而存在的,换句话说,也就是只有用到集合框架的 Hashtable、HashMap、HashSet的时候,才需要重载hashCode()方法,这样才能使得我们能人为的去控制在哈希结构中索引是否相等。笔者举一个例子:
曾经为了写一个求解类程序,需要随机列出1,2,3,4组成的不同排列组合,所以笔者写了一个数组类用int[]来存组合结果,然后把随机产生的组合加入一个HashSet中,就是想利用HashSet不包括重复元素的特点。可是HashSet怎么判断是不是重复的元素呢?当然是通过 hashCode()返回的结果是否相等来判断啦,可做一下这个实验:
int[] A = {1,2,3,4};int[] B = {1,2,3,4};
System.out.println(A.hashCode());System.out.println(B.hashCode());
这明明是同一种组合,却是不同的hashCode,加入Set的时候会被当成不同的对象。这个时候我们就需要自己来重写hashCode()方法了,如何写呢?其实也是基于原始的hashCode(),毕竟那是操作系统的实现,找到相通对象唯一的标识,实现方式很多,笔者的实现方式是:
首先重写了toString()方法:
return A[0]―+‖ A[1]―+‖ A[2]―+‖ A[3];//显示上比较直观
然后利用toString()来计算hashCode():
return this.toString().hashCode();
这样上述A和B返回的就都是‖1234‖,在测试toString().hashCode(),由于String在内存中的副本是一样的,‖1234‖.hashCode()返回的一定是相同的结果。
说到这,相信大家能理解得比我更好,今后千万不要再误解hashCode()方法的作用。
3. 关于Class类的成员函数与Java反射机制
很早刚接触Java就听很多老师说过Java的动态运行时机制、反射机制等。确实它们都是Java的显著特点,运行时加载笔者在第一篇介绍过了,现在想讲讲反射机制。在Java中,主要是通过java.lang包中的Class类和Method类来实现内存反射机制的。
熟悉C++的人一定知道下面这样在C++中是做不到的: 运行时以字符串参数传递一个类名,就可以得到这个类的所有信息,包括它所有的方法,和方法的详细信息。还可以实例化一个对象,并通过查到的方法名来调用该对象的任何方法。这是因为Java的类在内存中除了C++中也有的静态动态数据区之外,还包括一份对类自身的描述,也正是通过这描述中的信息,才能帮助我们才运行时读取里面的内容,得到需要加载目标类的所有信息,从而实现反射机制。大家有没有想过当我们需要得到一个JavaBean的实例的时候,怎么知道它有哪些属性呢?再明显简单不过的例子就是自己写一个JavaBean的解析器:
a.通过Class.forName(―Bean的类名‖)得到Class对象,例如叫ABeanClass b.通过ABeanClass的getMethods()方法,得到Method[]对象
c.按照规范所有get方法名后的单词就代表着该Bean的一个属性
d.当已经知道一个方法名,可以调用newInstance()得到一个实例,然后通过invoke()方法将方法的名字和方法需要用的参数传递进去,就可以动态调用此方法。
当然还有更复杂的应用,这里就不赘述,大家可以参考Class类和Method类的方法。
4. 坦言Synchronize的本质
Synchronize大家都知道是同步、加锁的意思,其实它的本质远没有大家想得那么复杂。声明Synchronize的方法被调用的时候,锁其实是加载对象上,当然如果是静态类则是加在类上的锁,调用结束锁被解除。它的实现原理很简单,仅仅是不让第二把锁再次被加在同一个对象或类上,仅此而已。一个简单的例子足以说明问题:
class A{
synchronized void f(){} void g(){} }
当A的一个对象a被第一个线程调用其f()方法的时候,第二个线程不能调用a的synchronized方法例如f(),因为那是在试图在对象上加第二把锁。但调用g()却是可以的,因为并没有在同一对象上加两把锁的行为产生。
这样大家能理解了麽?明白它的原理能更好的帮助大家设计同步机制,不要滥用加锁。
PS:下篇笔者计划开始对J2ee接触到的各个方面来进行总结,谈谈自己的经验和想法。希望大家还能一如既往的支持笔者写下去,指正不足之处。
Java杂谈
(四)不知不觉已经写到第四篇了,论坛里面不断的有朋友鼓励我写下去。坚持自己的作风,把一切迷惑不容易理清楚的知识讲出来,讲到大家都能听懂,那么自己就真的懂了。最近在公司实习的时候Trainer跟我讲了很多经典事迹,对还未毕业的我来说是笔不小的财富,我自己的信念是:人在逆境中成长的速度要远远快过顺境中,这样来看一切都能欣然接受了。
好了,闲话不说了,第三篇讲的是反射机制集合框架之类的,这次打算讲讲自己对反序列化和多线程的理解。希望能对大家学习Java起到帮助——
1.关于序列化和反序列化
应该大家都大概知道Java中序列化和反序列化的意思,序列化就是把一个Java对象转换成二进制进行磁盘上传输或者网络流的传输,反序列化的意思就是把这个接受到的二进制流重新组装成原来的对象逆过程。它们在Java中分别是通过ObjectInputStream和 ObjectInputStream这两个类来实现的(以下分别用ois和oos来简称)。
oos的writeObject()方法用来执行序列化的过程,ois的readObject()用来执行反序列化的过程,在传输二进制流之前,需要讲这两个高层流对象连接到同一个Channel上,这个Channel可以是磁盘文件,也可以是socket底层流。所以无论用哪种方式,底层流对象都是以构造函数参数的形式传递进oos和ois这两个高层流,连接完毕了才可以进行二进制数据传输的。例子:
可以是文件流通道
file = new File(―C:/data.dat‖);
oos = new ObjectOutputStream(new FileOutputStream(file));ois = new ObjectInputStream(new FileInputStream(file));
或者网络流通道
oos = new ObjectOutputStream(socket.getOutputStream());ois = new ObjectInputStream(socket.getInputStream());
不知道大家是否注意到oos总是在ois之前定义,这里不希望大家误解这个顺序是固定的么?回答是否定的,那么有顺序要求么?回答是肯定的。原则是什么呢?
原则是互相对接的输入/输出流之间必须是output流先初始化然后再input流初始化,否则就会抛异常。大家肯定会问为什么?只要稍微看一看这两个类的源代码文件就大概知道了,output流的任务很简单,只要把对象转换成二进制往通道中写就可以了,但input流需要做很多准备工作来接受并最终重组这个Object,所以ObjectInputStream的构造函数中就需要用到output初始化发送过来的header信息,这个方法叫做 readStreamHeader(),它将会去读两个Short值用于决定用多大的缓存来存放通道发送过来的二进制流,这个缓存的size因jre的版本不同是不一样的。所以output如果不先初始化,input的构造函数首先就无法正确运行。
对于上面两个例子,第一个顺序是严格的,第二个因为oos和ois连接的已经不是对方了,而是socket另外一端的流,需要严格按照另外一方对接的output流先于对接的input流打开才能顺利运行。
这个writeObject和readObject本身就是线程安全的,传输过程中是不允许被并发访问的。所以对象能一个一个接连不断的传过来,有很多人在运行的时候会碰到EOFException, 然后百思不得其解,去各种论坛问解决方案。其实笔者这里想说,这个异常不是必须声明的,也就是说它虽然是异常,但其实是正常运行结束的标志。EOF表示读到了文件尾,发送结束自然连接也就断开了。如果这影响到了你程序的正确性的话,请各位静下心来看看自己程序的业务逻辑,而不要把注意力狭隘的聚集在发送和接受的方法上。因为笔者也被这样的bug困扰了1整天,被很多论坛的帖子误解了很多次最后得出的教训。如果在while循环中去readObject,本质上是没有问题的,有对象数据来就会读,没有就自动阻塞。那么抛出EOFException一定是因为连接断了还在继续read,什么原因导致连接断了呢?一定是业务逻辑哪里存在错误,比如NullPoint、ClassCaseException、ArrayOutofBound,即使程序较大也没关系,最多只要单步调适一次就能很快发现bug并且解决它。
难怪一位程序大师说过:解决问题90%靠经验,5%靠技术,剩下5%靠运气!真是金玉良言,笔者大概查阅过不下30篇讨论在while循环中使用 readObject抛出EOFExceptionde 的帖子,大家都盲目的去关注解释这个名词、反序列化的行为或反对这样写而没有一个人认为EOF是正确的行为,它其实很老实的在做它的事情。为什么大家都忽略了真正出错误的地方呢?两个字,经验!
2.关于Java的多线程编程
关于Java的线程,初学或者接触不深的大概也能知道一些基本概念,同时又会很迷惑线程到底是怎么回事?如果有人认为自己已经懂了不妨来回答下面的问题:
a.A对象实现Runnable接口,A.start()运行后所谓的线程对象是谁?是A么?
b.线程的wait()、notify()方法到底是做什么时候用的,什么时候用?
c.为什么线程的suspend方法会被标注过时,不推荐再使用,线程还能挂起么?
d.为了同步我们会对线程方法声明Synchronized来加锁在对象上,那么如果父类的f()方法加了Synchronized,子类重写f()方法必须也加Synchronized么?如果子类的f()方法重写时声明Synchronized并调用super.f(),那么子类对象上到底有几把锁呢?会因为竞争产生死锁么?
呵呵,各位能回答上来几道呢?如果这些都能答上来,说明对线程的概念还是满清晰的,虽说还远远不能算精通。笔者这里一一做回答,碍于篇幅的原因,笔者尽量说得简介一点,如果大家有疑惑的欢迎一起讨论。
首先第一点,线程跟对象完全是两回事,虽然我们也常说线程对象。但当你用run()和start()来启动一个线程之后,线程其实跟这个继承了 Thread或实现了Runnable的对象已经没有关系了,对象只能算内存中可用资源而对象的方法只能算内存正文区可以执行的代码段而已。既然是资源和代码段,另外一个线程当然也可以去访问,main函数执行就至少会启动两个线程,一个我们称之为主线程,还一个是垃圾收集器的线程,主线程结束就意味着程序结束,可垃圾收集器线程很可能正在工作。
第二点,wait()和sleep()类似,都是让线程处于阻塞状态暂停一段时间,不同之处在于wait会释放当前线程占有的所有的锁,而 sleep不会。我们知道获得锁的唯一方法是进入了Synchronized保护代码段,所以大家会发现只有Synchronized方法中才会出现 wait,直接写会给警告没有获得当前对象的锁。所以notify跟wait配合使用,notify会重新把锁还给阻塞的线程重而使其继续执行,当有多个对象wait了,notify不能确定唤醒哪一个,必经锁只有一把,所以一般用notifyAll()来让它们自己根据优先级等竞争那唯一的一把锁,竞争到的线程执行,其他线程只要继续wait。
从前Java允许在一个线程之外把线程挂起,即调用suspend方法,这样的操作是极不安全的。根据面向对象的思想每个对象必须对自己的行为负责,而对自己的权力进行封装。如果任何外步对象都能使线程被挂起而阻塞的话,程序往往会出现混乱导致崩溃,所以这样的方法自然是被毙掉了啦。
最后一个问题比较有意思,首先回答的是子类重写f()方法可以加Synchronized也可以不加,如果加了而且还内部调用了super.f()的话理论上是应该对同一对象加两把锁的,因为每次调用Synchronized方法都要加一把,调用子类的f首先就加了一把,进入方法内部调用父类的 f又要加一把,加两把不是互斥的么?那么调父类f加锁不就必须永远等待已经加的锁释放而造成死锁么?实际上是不会的,这个机制叫重进入,当父类的f方法试图在本对象上再加一把锁的时候,因为当前线程拥有这个对象的锁,也可以理解为开启它的钥匙,所以同一个线程在同一对象上还没释放之前加第二次锁是不会出问题的,这个锁其实根本就没有加,它有了钥匙,不管加几把还是可以进入锁保护的代码段,畅通无阻,所以叫重进入,我们可以简单认为第二把锁没有加上去。
总而言之,Synchronized的本质是不让其他线程在同一对象上再加一把锁。
[size=5]Java杂谈(五)[/size]
本来预计J2se只讲了第四篇就收尾了,可是版主厚爱把帖子置顶长期让大家浏览让小弟倍感责任重大,务必追求最到更好,所以关于J2se一些没有提到的部分,决定再写几篇把常用的部分经验全部写出来供大家讨论切磋。这一篇准备讲一讲Xml解析包和Java Swing,然后下一篇再讲java.security包关于Java沙箱安全机制和RMI机制,再进入J2ee的部分,暂时就做这样的计划了。如果由于实习繁忙更新稍微慢了一些,希望各位见谅!
1. Java关于XML的解析
相信大家对XML都不陌生,含义是可扩展标记语言。本身它也就是一个数据的载体以树状表现形式出现。后来慢慢的数据变成了信息,区别是信息可以包括可变的状态从而针对程序硬编码的做法变革为针对统一接口硬编码而可变状态作为信息进入了XML中存储。这样改变状态实现扩展的唯一工作是在XML中添加一段文本信息就可以了,代码不需要改动也不需要重新编译。这个灵活性是XML诞生时候谁也没想到的。
当然,如果接口要能提取XML中配置的信息就需要程序能解析规范的XML文件,Java中当然要提高包对这个行为进行有利支持。笔者打算讲到的两个包是org.w3c.dom和javax.xml.parsers和。(大家可以浏览一下这些包中间的接口和类定义)
Javax.xml.parsers包很简单,没有接口,两个工厂配两个解析器。显然解析XML是有两种方式的:DOM解析和SAX解析。本质上并没有谁好谁不好,只是实现的思想不一样罢了。给一个XML文件的例子:
A Cat
所谓DOM解析的思路是把整个树状图存入内存中,需要那个节点只需要在树上搜索就可以读到节点的属性,内容等,这样的好处是所有节点皆在内存可以反复搜索重复使用,缺点是需要消耗相应的内存空间。
自然SAX解析的思路就是为了克服DOM的缺点,以事件触发为基本思路,顺序的搜索下来,碰到了Element之前触发什么事件,碰到之后做什么动作。由于需要自己来写触发事件的处理方案,所以需要借助另外一个自定义的Handler,处于org.xml.sax.helpers包中。它的优点当然是不用整个包都读入内存,缺点也是只能顺序搜索,走完一遍就得重来。
大家很容易就能猜到,接触到的J2ee框架用的是哪一种,显然是DOM。因为类似Struts,Hibernate框架配置文件毕竟是很小的一部分配置信息,而且需要频繁搜索来读取,当然会采用DOM方式(其实SAX内部也是用DOM采用的结构来存储节点信息的)。现在无论用什么框架,还真难发现使用SAX来解析XML的技术了,如果哪位仁兄知道,请让笔者也学习学习。
既然解析方式有了,那么就需要有解析的存储位置。不知道大家是否发现org.w3c.dom这个包是没有实现类全部都是接口的。这里笔者想说一下Java如何对XML解析是Jdk应该考虑的事,是它的责任。而w3c组织是维护定义XML标准的组织,所以一个XML结构是怎么样的由w3c说了算,它不关心Java如何去实现,于是乎规定了所有XML存储的结构应该遵循的规则,这就是org.w3c.dom里全部的接口目的所在。在笔者看来,简单理解接口的概念就是实现者必须遵守的原则。
整个XML对应的结构叫Document、子元素对应的叫做Element、还有节点相关的Node、NodeList、Text、Entity、CharacterData、CDATASection等接口,它们都可以在XML的语法中间找到相对应的含义。由于这里不是讲解XML基本语法,就不多介绍了。如果大家感兴趣,笔者也可以专门写一篇关于XML的语法规则帖与大家分享一下。
2. Java Swing
Swing是一个让人又爱又恨的东西,可爱之处在于上手很容易,较AWT比起来Swing提供的界面功能更加强大,可恨之处在于编复杂的界面工作量实在是巨大。笔者写过超过3000行的Swing界面,感觉用户体验还不是那么优秀。最近又写过超过6000行的,由于功能模块多了,整体效果还只是一般般。体会最深的就一个字:累!所以大家现在都陆续不怎么用Swing在真正开发的项目上了,太多界面技术可以取代它了。笔者去写也是迫于无奈组里面大家都没写过,我不入地域谁入?
尽管Swing慢慢的在被人忽略,特别是随着B/S慢慢的在淹没C/S,笔者倒是很愿意站出来为Swing正身。每一项技术的掌握绝不是为了流行时尚跟风。真正喜欢Java的朋友们还是应该好好体会一下Swing,相信在校的很多学生也很多在学习它。很可能从Jdk 1.1、1.2走过来的很多大学老师可能是最不熟悉它的。
Swing提供了一组轻组件统称为JComponent,它们与AWT组件的最大区别是JComponent全部都是Container,而Container的特点是里面可以装载别的组件。在Swing组件中无论是JButton、JLabel、JPanel、JList等都可以再装入任何其他组件。好处是程序员可以对Swing组件实现―再开发‖,针对特定需求构建自己的按钮、标签、画板、列表之类的特定组件。
有轻自然就有重,那么轻组件和重组件区别是?重组件表现出来的形态因操作系统不同而异,轻组件是Swing自己提供GUI,在跨平台的时候最大程度的保持一致。
那么在编程的时候要注意一些什么呢?笔者谈谈自己的几点经验:
a.明确一个概念,只有Frame组件才可以单独显示的,也许有人会说JOptionPane里面的静态方法就实现了单独窗口出现,但追寻源代码会发现其实现实出来的Dialog也需要依托一个Frame窗体,如果没有指定就会默认产生一个然后装载这个Dialog显示出来。
b.JFrame是由这么几部分组成:
最底下一层JRootPane,上面是glassPane(一个JPanel)和layeredPane(一个JLayeredPane),而layeredPane又由contentPane(一个JPanel)和menuBar构成。我们的组件都是加在contentPane上,而背景图片只能加在layeredPane上面。至于glassPane是一个透明的覆盖了contentPane的一层,在特定效果中将被利用到来记录鼠标坐标或掩饰组件。
c.为了增强用户体验,我们会在一些按钮上添加快捷键,但Swing里面通常只能识别键盘的Alt键,要加入其他的快捷键,必须自己实现一个ActionListener。
d.通过setLayout(null)可以使得所有组件以setBounds()的四个参数来精确定位各自的大小、位置,但不推荐使用,因为好的编程风格不应该在Swing代码中硬编码具体数字,所有的数字应该以常数的形式统一存在一个静态无实例资源类文件中。这个静态无实例类统一负责Swing界面的风格,包括字体和颜色都应该包括进去。
e.好的界面设计有一条Golden Rule: 用户不用任何手册通过少数尝试就能学会使用软件。所以尽量把按钮以菜单的形式(不管是右键菜单还是窗体自带顶部菜单)呈现给顾客,除非是频繁点击的按钮才有必要直接呈现在界面中。
其实Swing的功能是相当强大的,只是现在应用不广泛,专门去研究大概是要花不少时间的。笔者在各网站论坛浏览关于Swing的技巧文章还是比较可信的,自己所学非常有限,各人体会对Swing各个组件的掌握就是一个实践积累的过程。笔者只用到过以上这些,所以只能谈谈部分想法,还望大家见谅!
Java杂谈
(六)这篇是笔者打算写的J2se部分的最后一篇了,这篇结束之后,再写J2ee部分,不知道是否还合适写在这个版块?大家可以给点意见,谢谢大家对小弟这么鼓励一路写完前六篇Java杂谈的J2se部分。最后这篇打算谈一谈Java中的RMI机制和JVM沙箱安全框架。
1. Java中的RMI机制
RMI的全称是远程方法调用,相信不少朋友都听说过,基本的思路可以用一个经典比方来解释:A计算机想要计算一个两个数的加法,但A自己做不了,于是叫另外一台计算机B帮忙,B有计算加法的功能,A调用它就像调用这个功能是自己的一样方便。这个就叫做远程方法调用了。
远程方法调用是EJB实现的支柱,建立分布式应用的核心思想。这个很好理解,再拿上面的计算加法例子,A只知道去call计算机B的方法,自己并没有B的那些功能,所以A计算机端就无法看到B执行这段功能的过程和代码,因为看都看不到,所以既没有机会窃取也没有机会去改动方法代码。EJB正式基于这样的思想来完成它的任务的。当简单的加法变成复杂的数据库操作和电子商务交易应用的时候,这样的安全性和分布式应用的便利性就表现出来优势了。
好了,回到细节上,要如何实现远程方法调用呢?我希望大家学习任何技术的时候可以试着依赖自己的下意识判断,只要你的想法是合理健壮的,那么很可能实际上它就是这么做的,毕竟真理都蕴藏在平凡的生活细节中。这样只要带着一些薄弱的Java基础来思考RMI,其实也可以想出个大概来。
a)需要有一个服务器角色,它拥有真正的功能代码方法。例如B,它提供加法服务
b)如果想远程使用B的功能,需要知道B的IP地址
c)如果想远程使用B的功能,还需要知道B中那个特定服务的名字
我们很自然可以想到这些,虽然不完善,但已经很接近正确的做法了。实际上RMI要得以实现还得意于Java一个很重要的特性,就是Java反射机制。我们需要知道服务的名字,但又必须隐藏实现的代码,如何去做呢?答案就是:接口!
举个例子:
public interface Person(){
public void sayHello();}
Public class PersonImplA implements Person{
public PersonImplA(){}
public void sayHello(){ System.out.println(―Hello!‖);}
}
Public class PersonImplB implements Person{
public PersonImplB(){}
public void sayHello(){ System.out.println(―Nice to meet you!‖);}
}
客户端:Person p = Naming.lookup(―PersonService‖);
p.sayHello();
就这几段代码就包含了几乎所有的实现技术,大家相信么?客户端请求一个say hello服务,服务器运行时接到这个请求,利用Java反射机制的Class.newInstance()返回一个对象,但客户端不知道服务器返回的是ImplA还是ImplB,它接受用的参数签名是Person,它知道实现了Person接口的对象一定有sayHello()方法,这就意味着客户端并不知道服务器真正如何去实现的,但它通过了解Person接口明确了它要用的服务方法名字叫做sayHello()。
如此类推,服务器只需要暴露自己的接口出来供客户端,所有客户端就可以自己选择需要的服务。这就像餐馆只要拿出自己的菜单出来让客户选择,就可以在后台厨房一道道的按需做出来,它怎么做的通常是不让客户知道的!(祖传菜谱吧,^_^)
最后一点是我调用lookup,查找一个叫PersonService名字的对象,服务器只要看到这个名字,在自己的目录(相当于电话簿)中找到对应的对象名字提供服务就可以了,这个目录就叫做JNDI(Java命名与目录接口),相信大家也听过的。
有兴趣的朋友不妨自己做个RMI的应用,很多前辈的博客中有简单的例子。提示一下利用Jdk的bin目录中rmi.exe和rmiregistry.exe两个命令就可以自己建起一个服务器,提供远程服务。因为例子很容易找,我就不自己举例子了!
2. JVM沙箱&框架
RMI罗唆得太多了,实在是尽力想把它说清楚,希望对大家有帮助。最后的最后,给大家简单讲一下JVM框架,我们叫做Java沙箱。Java沙箱的基本组件如下:
a)类装载器结构
b)class文件检验器
c)内置于Java虚拟机的安全特性
d)安全管理器及Java API
其中类装载器在3个方面对Java沙箱起作用: a.它防止恶意代码去干涉善意的代码
b.它守护了被信任的类库边界
c.它将代码归入保护域,确定了代码可以进行哪些操作
虚拟机为不同的类加载器载入的类提供不同的命名空间,命名空间由一系列唯一的名称组成,每一个被装载的类将有一个名字,这个命名空间是由Java虚拟机为每一个类装载器维护的,它们互相之间甚至不可见。
我们常说的包(package)是在Java虚拟机第2版的规范第一次出现,正确定义是由同一个类装载器装载的、属于同一个包、多个类型的集合。类装载器采用的机制是双亲委派模式。具体的加载器框架我在Java杂谈
(一)中已经解释过了,当时说最外层的加载器是AppClassLoader,其实算上网络层的话AppClassLoader也可以作为parent,还有更外层的加载器URLClassLoader。为了防止恶意攻击由URL加载进来的类文件我们当然需要分不同的访问命名空间,并且制定最安全的加载次序,简单来说就是两点:
a.从最内层JVM自带类加载器开始加载,外层恶意同名类得不到先加载而无法使用
b.由于严格通过包来区分了访问域,外层恶意的类通过内置代码也无法获得权限访问到内层类,破坏代码就自然无法生效。
附:关于Java的平台无关性,有一个例子可以很明显的说明这个特性:
一般来说,C或C++中的int占位宽度是根据目标平台的字长来决定的,这就意味着针对不同的平台编译同一个C++程序在运行时会有不同的行为。然而对于Java中的int都是32位的二进制补码标识的有符号整数,而float都是遵守IEEE 754浮点标准的32位浮点数。
PS: 这个小弟最近也没时间继续研究下去了,只是想抛砖引玉的提供给大家一个初步认识JVM的印象。有机会了解一下JVM的内部结构对今后做Java开发是很有好处的。
Java杂谈
(七)--接口& 组件、容器
终于又静下来继续写这个主题的续篇,前六篇主要讲了一些J2se方面的经验和感受,眼下Java应用范围已经被J2ee占据了相当大的一块领域,有些人甚至声称Java被J2ee所取代了。不知道大家如何来理解所谓的J2ee(Java2 Enterprise Edition),也就是Java企业级应用?
笔者的观点是,技术的发展是顺应世界变化的趋势的,从C/S过渡到B/S模式,从客户端的角度考虑企业级应用或者说电子商务领域不在关心客户端维护问题,这个任务已经交给了任何一台PC都会有的浏览器去维护;从服务器端的角度考虑,以往C/S中的TCP/IP协议实现载体ServerSocket被Web Server Container所取代,例如大家都很熟悉的Tomcat、JBoss、WebLogic等等。总之一切的转变都是为了使得Java技术能更好的为人类生产生活所服务。
有人会问,直接去学J2ee跳过J2se行否?笔者是肯定不赞成的,实际上确实有人走这条路,但笔者自身体会是正是由于J2se的基础很牢固,才会导致在J2ee学习的道路上顺风顺水,知识点上不会有什么迷惑的地方。举个简单的例子吧:
笔者曾经跟大学同学讨论下面这两种写法的区别:
ArrayList list = new ArrayList();//笔者不说反对,但至少不赞成List list = new ArrayList();//笔者支持
曾经笔者跟同学争论了几个小时,他非说第一种写法更科学,第二种完全没有必要。我无法完全说服他,但笔者认为良好的习惯和意识是任何时候都应该针对接口编程,以达到解耦合和可扩展性的目的。下面就以接口开始进入J2ee的世界吧:
1.J2ee与接口
每一个版本的J2ee都对应着一个确定版本的JDK,J2ee1.4对应Jdk1.4,现在比较新的是JDK5.0,自然也会有J2EE 5.0。其实笔者一直在用的是J2EE1.4,不过没什么关系,大家可以下任何一个版本的J2ee api来稍微浏览一下。笔者想先声明一个概念,J2ee也是源自Java,所以底层的操作依然调用到很多J2se的库,所以才建议大家先牢牢掌握J2se的主流技术。
J2ee api有一个特点,大家比较熟悉的几个包java.jms、javax.servlet.http、javax.ejb等都以interface居多,实现类较少。其实大家真正在用的时候百分之六十以上都在反复的查着javax.servlet.http这个包下面几个实现类的api函数,其他的包很少问津。笔者建议在学习一种技术之前,对整体的框架有一个了解是很有必要的,J2ee旨在通过interface的声明来规范实现的行为,任何第三方的厂商想要提供自己品牌的实现前提也是遵循这些接口定义的规则。如果在从前J2se学习的道路上对接口的理解很好的话,这里的体会将是非常深刻的,举个简单的例子:
public interface Mp3{
public void play();
public void record();
public void stop();
}
如果我定义这个简单的接口,发布出去,规定任何第三方的公司想推出自己的名字为Mp3的产品都必须实现这个接口,也就是至少提供接口中方法的具体实现。这个意义已经远远不止是面向对象的多态了,只有厂商遵循J2ee的接口定义,世界上的J2ee程序员才能针对统一的接口进行程序设计,最终不用改变代码只是因为使用了不同厂商的实现类而有不同的特性罢了,本质上说,无论哪一种厂商实现都完成了职责范围内的工作。这个就是笔者想一直强调的,针对接口编程的思想。
接口到底有什么好处呢?我们这样设想,现在有AppleMp3、SonyMp3、SamsungMp3都实现了这个Mp3的接口,于是都有了play、record、stop这三个功能。我们将Mp3产品座位一个组件的时候就不需要知道它的具体实现,只要看到接口定义知道这个对象有3个功能就可以使用了。那么类似下面这样的业务就完全可以在任何时间从3个品牌扩展到任意个品牌,开个玩笑的说,项目经理高高在上的写完10个接口里的方法声明,然后就丢给手下的程序员去写里面的细节,由于接口已经统一(即每个方法传入和传出的格式已经统一),经理只需关注全局的业务就可以天天端杯咖啡走来走去了,^_^:
public Mp3 create();
public void copy(Mp3 mp3);
public Mp3 getMp3();
最后用一个简单的例子说明接口:一个5号电池的手电筒,可以装入任何牌子的5号电池,只要它符合5号电池的规范,装入之后任何看不到是什么牌子,只能感受到手电筒在完成它的功能。那么生产手电筒的厂商和生产5号电池的厂商就可以完全解除依赖关系,可以各自自由开发自己的产品,因为它们都遵守5号电池应有的形状、正负极位置等约定。这下大家能对接口多一点体会了么?
2.组件和容器
针对接口是笔者特意强调的J2ee学习之路必备的思想,另外一个就是比较常规的组件和容器的概念了。很多教材和专业网站都说J2EE的核心是一组规范与指南,强调J2ee的核心概念就是组件+容器,这确实是无可厚非的。随着越来越多的J2ee框架出现,相应的每种框架都一般有与之对应的容器。
容器,是用来管理组件行为的一个集合工具,组件的行为包括与外部环境的交互、组件的生命周期、组件之间的合作依赖关系等等。J2ee包含的容器种类大约有Web容器、Application Client容器、EJB容器、Applet客户端容器等。但在笔者看来,现在容器的概念变得有点模糊了,大家耳熟能详是那些功能强大的开源框架,比如Hibernate、Struts2、Spring、JSF等,其中Hibernate就基于JDBC的基础封装了对事务和会话的管理,大大方便了对数据库操作的繁琐代码,从这个意义上来说它已经接近容器的概念了,EJB的实体Bean也逐渐被以Hibernate为代表的持久化框架所取代。
组件,本意是指可以重用的代码单元,一般代表着一个或者一组可以独立出来的功能模块,在J2ee中组件的种类有很多种,比较常见的是EJB组件、DAO组件、客户端组件或者应用程序组件等,它们有个共同特点是分别会打包成.war,.jar,.jar,.ear,每个组件由特定格式的xml描述符文件进行描述,而且服务器端的组件都需要被部署到应用服务器上面才能够被使用。
稍微理解完组件和容器,还有一个重要的概念就是分层模型,最著名的当然是MVC三层模型。在一个大的工程或项目中,为了让前台和后台各个模块的编程人员能够同时进行工作提高开发效率,最重要的就是实现层与层之间的耦合关系,许多分层模型的宗旨和开源框架所追求的也就是这样的效果。在笔者看来,一个完整的Web项目大概有以下几个层次:
a)表示层(Jsp、Html、javascript、Ajax、Flash等等技术对其支持)
b)控制层(Struts、JSF、WebWork等等框架在基于Servlet的基础上支持,负责把具体的请求数据(有时卸载重新装载)导向适合处理它的模型层对象)
c)模型层(笔者认为目前最好的框架是Spring,实质就是处理表示层经由控制层转发过来的数据,包含着大量的业务逻辑)
d)数据层(Hibernate、JDBC、EJB等,由模型层处理完了持久化到数据库中)
当然,这仅仅是笔者个人的观点,仅仅是供大家学习做一个参考,如果要实现这些层之间的完全分离,那么一个大的工程,可以仅仅通过增加人手就来完成任务。虽然《人月神话》中已经很明确的阐述了增加人手并不能是效率增加,很大程度上是因为彼此做的工作有顺序上的依赖关系或者说难度和工作量上的巨大差距。当然理想状态在真实世界中是不可能达到的,但我们永远应该朝着这个方向去不断努力。最开始所提倡的针对接口来编程,哪怕是小小的细节,写一条List list= = new ArrayList()语句也能体现着处处皆使用接口的思想在里面。Anyway,这只是个开篇,笔者会就自己用过的J2ee技术和框架再细化谈一些经验
Java杂谈
(八)--Servlet/Jsp
终于正式进入J2ee的细节部分了,首当其冲的当然是Servlet和Jsp了,上篇曾经提到过J2ee只是一个规范和指南,定义了一组必须要遵循的接口,核心概念是组件和容器。曾经有的人问笔者Servlet的Class文件是哪里来的?他认为是J2ee官方提供的,我举了一个简单的反例:稍微检查了一下Tomcat5.0里面的Servlet.jar文件和JBoss里面的Servlet.jar文件大小,很明显是不一样的,至少已经说明了它们不是源自同根的吧。其实Servlet是由容器根据J2ee的接口定义自己来实现的,实现的方式当然可以不同,只要都遵守J2ee规范和指南。
上述只是一个常见的误区罢了,告诉我们要编译运行Servlet,是要依赖于实现它的容器的,不然连jar文件都没有,编译都无法进行。那么Jsp呢?Java Server Page的简称,是为了开发动态网页而诞生的技术,其本质也是Jsp,在编写完毕之后会在容器启动时经过编译成对应的Servlet。只是我们利用Jsp的很多新特性,可以更加专注于前后台的分离,早期Jsp做前台是满流行的,毕竟里面支持Html代码,这让前台美工人员可以更有效率的去完成自己的工作。然后Jsp将请求转发到后台的Servlet,由Servlet处理业务逻辑,再转发回另外一个Jsp在前台显示出来。这似乎已经成为一种常用的模式,最初笔者学习J2ee的时候,大量时间也在编写这样的代码。
尽管现在做前台的技术越来越多,例如Flash、Ajax等,已经有很多人不再认为Jsp重要了。笔者觉得Jsp带来的不仅仅是前后端分离的设计理念,它的另外一项技术成就了我们今天用的很多框架,那就是Tag标签技术。所以与其说是在学习Jsp,不如更清醒的告诉自己在不断的理解Tag标签的意义和本质。
1. Servlet以及Jsp的生命周期
Servlet是Jsp的实质,尽管容器对它们的处理有所区别。Servlet有init()方法初始化,service()方法进行Web服务,destroy()方法进行销毁,从生到灭都由容器来掌握,所以这些方法除非你想自己来实现Servlet,否则是很少会接触到的。正是由于很少接触,才容易被广大初学者所忽略,希望大家至少记住Servlet生命周期方法都是回调方法。回调这个概念简单来说就是把自己注入另外一个类中,由它来调用你的方法,所谓的另外一个类就是Web容器,它只认识接口和接口的方法,注入进来的是怎样的对象不管,它只会根据所需调用这个对象在接口定义存在的那些方法。由容器来调用的Servlet对象的初始化、服务和销毁方法,所以叫做回调。这个概念对学习其他J2ee技术相当关键!
那么Jsp呢?本事上是Servlet,还是有些区别的,它的生命周期是这样的:
a)一个客户端的Request到达服务器->
b)判断是否第一次调用-> 是的话编译Jsp成Servlet
c)否的话再判断此Jsp是否有改变-> 是的话也重新编译Jsp成Servlet
d)已经编译最近版本的Servlet装载所需的其他Class
e)发布Servlet,即调用它的Service()方法
所以Jsp号称的是第一次Load缓慢,以后都会很快的运行。从它的生命的周期确实不难看出来这个特点,客户端的操作很少会改变Jsp的源码,所以它不需要编译第二次就一直可以为客户端提供服务。这里稍微解释一下Http的无状态性,因为发现很多人误解,Http的无状态性是指每次一张页面显示出来了,与服务器的连接其实就已经断开了,当再次有提交动作的时候,才会再次与服务器进行连接请求提供服务。当然还有现在比较流行的是Ajax与服务器异步通过xml交互的技术,在做前台的领域潜力巨大,笔者不是Ajax的高手,这里无法为大家解释。
2. Tag标签的本质
笔者之前说了,Jsp本身初衷是使得Web应用前后台的开发可以脱离耦合分开有效的进行,可惜这个理念的贡献反倒不如它带来的Tag技术对J2ee的贡献要大。也许已经有很多人开始使用Tag技术了却并不了解它。所以才建议大家在学习J2ee开始的时候一定要认真学习Jsp,其实最重要的就是明白标签的本质。
Html标签我们都很熟悉了,有 、
、、说得明白一点的话还是举几个简单的例子说明一下吧:
顺便罗唆一句,Web Project的目录结构是相对固定的,因为容器会按照固定的路径去寻找它需要的配置文件和资源,这个任何一本J2ee入门书上都有,这里就不介绍了。了解Tag的本质还要了解它的工作原理,所以大家去J2ee的API里找到并研究这个包:javax.servlet.jsp.tagext。它有一些接口,和一些实现类,专门用语开发Tag,只有自己亲自写出几个不同功能的标签,才算是真正理解了标签的原理。别忘记了自己开发的标签要自己去完成配置文件,容器只是集成了去哪里寻找jsp标签对应class的路径,自己写的标签库当然要告诉容器去哪里找啦。
说了这么多,我们为什么要用标签呢?完全在Jsp里面来个 <% %> 就可以在里面任意写Java代码了,但是长期实践发现页面代码统一都是与html同风格的标记语言更加有助于美工人员进行开发前台,它不需要懂Java,只要Java程序员给个列表告诉美工什么标签可以完成什么逻辑功能,他就可以专注于美工,也算是进一步隔离了前后台的工作吧!
3. 成就Web框架
框架是什么?曾经看过这样的定义:与模式类似,框架也是解决特定问题的可重用方法,框架是一个描述性的构建块和服务集合,开发人员可以用来达成某个目标。一般来说,框架提供了解决某类问题的基础设施,是用来创建解决方案的工具,而不是问题的解决方案。
正是由于Tag的出现,成就了以后出现的那么多Web框架,它们都开发了自己成熟实用的一套标签,然后由特定的Xml文件来配置加载信息,力图使得Web应用的开发变得更加高效。下面这些标签相应对很多人来说相当熟悉了:
它们分别来自Struts和JSF框架,最强大的功能在于控制转发,就是MVC三层模型中间完成控制器的工作。Struts-1实际上并未做到真正的三层隔离,这一点在Struts-2上得到了很大的改进。而Jsf向来以比较完善合理的标签库受到人们推崇。
今天就大概讲这么多吧,再次需要强调的是Servlet/Jsp是学习J2ee必经之路,也是最基础的知识,希望大家给与足够的重视!
Java杂谈
(九)--Struts
J2ee的开源框架很多,笔者只能介绍自己熟悉的几个,其他的目前在中国IT行业应用得不是很多。希望大家对新出的框架不要盲目的推崇,首先一定要熟悉它比旧的到底好在哪里,新的理念和特性是什么?然后再决定是否要使用它。
这期的主题是Struts,直译过来是支架。Struts的第一个版本是在2001年5月发布的,它提供了一个Web应用的解决方案,如何让Jsp和servlet共存去提供清晰的分离视图和业务应用逻辑的架构。在Struts之前,通常的做法是在Jsp中加入业务逻辑,或者在Servlet中生成视图转发到前台去。Struts带着MVC的新理念当时退出几乎成为业界公认的Web应用标准,于是当代IT市场上也出现了众多熟悉Struts的程序员。即使有新的框架再出来不用,而继续用Struts的理由也加上了一条低风险,因为中途如果开发人员变动,很容易的招进新的会Struts的IT民工啊,^_^!
笔者之前说的都是Struts-1,因为新出了Struts-2,使得每次谈到Struts都必须注明它是Struts-1还是2。笔者先谈比较熟悉的Struts-1,下次再介绍一下与Struts-2的区别:
1. Struts框架整体结构
Struts-1的核心功能是前端控制器,程序员需要关注的是后端控制器。前端控制器是是一个Servlet,在Web.xml中间配置所有Request都必须经过前端控制器,它的名字是ActionServlet,由框架来实现和管理。所有的视图和业务逻辑隔离都是应为这个ActionServlet,它就像一个交通警察,所有过往的车辆必须经过它的法眼,然后被送往特定的通道。所有,对它的理解就是分发器,我们也可以叫做Dispatcher,其实了解Servlet编程的人自己也可以写一个分发器,加上拦截request的Filter,其实自己实现一个struts框架并不是很困难。主要目的就是让编写视图的和后台逻辑的可以脱离紧耦合,各自同步的完成自己的工作。
那么有了ActionServlet在中间负责转发,前端的视图比如说是Jsp,只需要把所有的数据Submit,这些数据就会到达适合处理它的后端控制器Action,然后在里面进行处理,处理完毕之后转发到前台的同一个或者不同的视图Jsp中间,返回前台利用的也是Servlet里面的forward和redirect两种方式。所以到目前为止,一切都只是借用了Servlet的API搭建起了一个方便的框架而已。这也是Struts最显著的特性——控制器。
那么另外一个特性,可以说也是Struts-1带来的一个比较成功的理念,就是以xml配置代替硬编码配置信息。以往决定Jsp往哪个servlet提交,是要写进Jsp代码中的,也就是说一旦这个提交路径要改,我们必须改写代码再重新编译。而Struts提出来的思路是,编码的只是一个逻辑名字,它对应哪个class文件写进了xml配置文件中,这个配置文件记录着所有的映射关系,一旦需要改变路径,改变xml文件比改变代码要容易得多。这个理念可以说相当成功,以致于后来的框架都延续着这个思路,xml所起的作用也越来越大。
大致上来说Struts当初给我们带来的新鲜感就这么多了,其他的所有特性都是基于方便的控制转发和可扩展的xml配置的基础之上来完成它们的功能的。
下面将分别介绍Action和FormBean,这两个是Struts中最核心的两个组件。
2. 后端控制器Action
Action就是我们说的后端控制器,它必须继承自一个Action父类,Struts设计了很多种Action,例如DispatchAction、DynaValidationAction。它们都有一个处理业务逻辑的方法execute(),传入的request, response, formBean和actionMapping四个对象,返回actionForward对象。到达Action之前先会经过一个RequestProcessor来初始化配置文件的映射关系,这里需要大家注意几点:
1)为了确保线程安全,在一个应用的生命周期中,Struts框架只会为每个Action类创建一个Action实例,所有的客户请求共享同一个Action实例,并且所有线程可以同时执行它的execute()方法。所以当你继承父类Action,并添加了private成员变量的时候,请记住这个变量可以被多个线程访问,它的同步必须由程序员负责。(所有我们不推荐这样做)。在使用Action的时候,保证线程安全的重要原则是在Action类中仅仅使用局部变量,谨慎的使用实例变量。局部变量是对每个线程来说私有的,execute方法结束就被销毁,而实例变量相当于被所有线程共享。
2)当ActionServlet实例接收到Http请求后,在doGet()或者doPost()方法中都会调用process()方法来处理请求。RequestProcessor类包含一个HashMap,作为存放所有Action实例的缓存,每个Action实例在缓存中存放的属性key为Action类名。在RequestProcessor类的processActionCreate()方法中,首先检查在HashMap中是否存在Action实例。创建Action实例的代码位于同步代码块中,以保证只有一个线程创建Action实例。一旦线程创建了Action实例并把它存放到HashMap中,以后所有的线程会直接使用这个缓存中的实例。
3) 元素的
3. 数据传输对象FormBean
Struts并没有把模型层的业务对象直接传递到视图层,而是采用DTO(Data Transfer Object)来传输数据,这样可以减少传输数据的冗余,提高传输效率;还有助于实现各层之间的独立,使每个层分工明确。Struts的DTO就是ActionForm,即formBean。由于模型层应该和Web应用层保持独立。由于ActionForm类中使用了Servlet API,因此不提倡把ActionForm传递给模型层,而应该在控制层把ActionForm Bean的数据重新组装到自定义的DTO中,再把它传递给模型层。它只有两个scope,分别是session和request。(默认是session)一个ActionForm标准的生命周期是:
1)控制器收到请求->
2)从request或session中取出ActionForm实例,如不存在就创建一个-> 3)调用ActionForm的reset()方法-> 4)把实例放入session或者request中-> 5)将用户输入表达数据组装到ActionForm中-> 6)如眼张方法配置了就调用validate()方法->
7)如验证错误就转发给 属性指定的地方,否则调用execute()方法
validate()方法调用必须满足两个条件:
1)ActionForm 配置了Action映射而且name属性匹配
2) 元素的validate属性为true
如果ActionForm在request范围内,那么对于每个新的请求都会创建新的ActionForm实例,属性被初始化为默认值,那么reset()方法就显得没有必要;但如果ActionForm在session范围内,同一个ActionForm实例会被多个请求共享,reset()方法在这种情况下极为有用。
4. 验证框架和国际化
Struts有许多自己的特性,但是基本上大家还是不太常用,说白了它们也是基于JDK中间的很多Java基础包来完成工作。例如国际化、验证框架、插件自扩展功能、与其他框架的集成、因为各大框架基本都有提供这样的特性,Struts也并不是做得最好的一个,这里也不想多说。Struts的验证框架,是通过一个validator.xml的配置文件读入验证规则,然后在validation-rules.xml里面找到验证实现通过自动为Jsp插入javascript来实现,可以说做得相当简陋。弹出来的javascript框不但难看还很多冗余信息,笔者宁愿用formBean验证或者Action的saveErrors(),验证逻辑虽然要自己写,但页面隐藏/浮现的警告提示更加人性化和美观一些。
至于Struts的国际化,其实无论哪个框架的国际化,java.util.Locale类是最重要的Java I18N类。在Java语言中,几乎所有的对国际化和本地化的支持都依赖于这个类。如果Java类库中的某个类在运行的时候需要根据Locale对象来调整其功能,那么就称这个类是本地敏感的(Locale-Sensitive),例如java.text.DateFormat类就是,依赖于特定Locale。
创建Locale对象的时候,需要明确的指定其语言和国家的代码,语言代码遵从的是ISO-639规范,国家代码遵从ISO-3166规范,可以从
http:// http://。
这里很明显的一点是不存在FormBean的作用域封装,直接可以从Action中取得数据。这里有一个Strut-2配置的web.xml文件:
注意到以往的servlet变成了filter,ActionServlet变成了FilterDispatcher,*.do变成了/*。filter配置定义了名称(供关联)和filter的类。filter mapping让URI匹配成功的的请求调用该filter。默认情况下,扩展名为 “.action ”。这个是在default.properties文件里的 “struts.action.extension ”属性定义的。
default.properties是属性定义文件,通过在项目classpath路径中包含一个名为―struts.properties‖的文件来设置不同的属性值。而Struts-2的默认配置文件名为struts.xml。由于1和2的action扩展名分别为.do和.action,所以很方便能共存。我们再来看一个Struts-2的action代码:
public class MyAction {
public String execute()throws Exception {
//do the work return “success ”;
}
}
很明显的区别是不用再继承任何类和接口,返回的只是一个String,无参数。实际上在Struts-2中任何返回String的无参数方法都可以通过配置来调用action。所有的参数从哪里来获得呢?答案就是Inversion of Control技术(控制反转)。笔者尽量以最通俗的方式来解释,我们先试图让这个Action获得reuqest对象,这样可以提取页面提交的任何参数。那么我们把request设为一个成员变量,然后需要一个对它的set方法。由于大部分的action都需要这么做,我们把这个set方法作为接口来实现。
public interface ServletRequestAware {
public void setServletRequest(HttpServletRequest request);
}
public class MyAction implements ServletRequestAware {
private HttpServletRequest request;
public void setServletRequest(HttpServletRequest request){
this.request = request;
}
public String execute()throws Exception {
// do the work directly using the request
return Action.SUCCESS;
}
}
那么谁来调用这个set方法呢?也就是说谁来控制这个action的行为,以往我们都是自己在适当的地方写上一句action.setServletRequest(…),也就是控制权在程序员这边。然而控制反转的思想是在哪里调用交给正在运行的容器来决定,只要利用Java反射机制来获得Method对象然后调用它的invoke方法传入参数就能做到,这样控制权就从程序员这边转移到了容器那边。程序员可以减轻很多繁琐的工作更多的关注业务逻辑。Request可以这样注入到action中,其他任何对象也都可以。为了保证action的成员变量线程安全,Struts-2的action不是单例的,每一个新的请求都会产生一个新的action实例。
那么有人会问,到底谁来做这个对象的注入工作呢?答案就是拦截器。拦截器又是什么东西?笔者再来尽量通俗的解释拦截器的概念。大家要理解拦截器的话,首先一定要理解GOF23种设计模式中的Proxy模式。
A对象要调用f(),它希望代理给B来做,那么B就要获得A对象的引用,然后在B的f()中通过A对象引用调用A对象的f()方法,最终达到A的f()被调用的目的。有没有人会觉得这样很麻烦,为什么明明只要A.f()就可以完成的一定要封装到B的f()方法中去?有哪些好处呢?
1)这里我们只有一个A,当我们有很多个A的时候,只需要监视B一个对象的f()方法就可以从全局上控制所有被调用的f()方法。
2)另外,既然代理人B能获得A对象的引用,那么B可以决定在真正调A对象的f()方法之前可以做哪些前置工作,调完返回前可有做哪些后置工作。
讲到这里,大家看出来一点拦截器的概念了么?它拦截下一调f()方法的请求,然后统一的做处理(处理每个的方式还可以不同,解析A对象就可以辨别),处理完毕再放行。这样像不像对流动的河水横切了一刀,对所有想通过的水分子进行搜身,然后再放行?这也就是AOP(Aspect of Programming面向切面编程)的思想。
Anyway,Struts-2只是利用了AOP和IoC技术来减轻action和框架的耦合关系,力图到最大程度重用action的目的。在这样的技术促动下,Struts-2的action成了一个简单被框架使用的POJO(Plain Old Java Object)罢了。实事上AOP和IoC的思想已经遍布新出来的每一个框架上,他们并不是多么新的技术,利用的也都是JDK早已可以最到的事情,它们代表的是更加面向接口编程,提高重用,增加扩展性的一种思想。Struts-2只是部分的使用这两种思想来设计完成的,另外一个最近很火的框架Spring,更大程度上代表了这两种设计思想,笔者将于下一篇来进一步探讨Spring的结构。
PS: 关于Struts-2笔者也没真正怎么用过,这里是看了网上一些前辈的帖子之后写下自己的学习体验,不足之处请见谅!
Java杂谈
(十一)--Spring
笔者最近比较忙,一边在实习一边在寻找明年毕业更好的工作,不过论坛里的朋友非常支持小弟继续写,今天是周末,泡上一杯咖啡,继续与大家分享J2ee部分的学习经验。今天的主题是目前很流行也很好的一个开源框架-Spring。
引用《Spring2.0技术手册》上的一段话:
Spring的核心是个轻量级容器,它是实现IoC容器和非侵入性的框架,并提供AOP概念的实现方式;提供对持久层、事务的支持;提供MVC Web框架的实现,并对于一些常用的企业服务API提供一致的模型封装,是一个全方位的应用程序框架,除此之外,对于现存的各种框架,Spring也提供了与它们相整合的方案。
接下来笔者先谈谈自己的一些理解吧,Spring框架的发起者之前一本很著名的书名字大概是《J2ee Development without EJB》,他提倡用轻量级的组件代替重量级的EJB。笔者还没有看完那本著作,只阅读了部分章节。其中有一点分析觉得是很有道理的:
EJB里在服务器端有Web Container和EJB Container,从前的观点是各层之间应该在物理上隔离,Web Container处理视图功能、在EJB Container中处理业务逻辑功能、然后也是EBJ Container控制数据库持久化。这样的层次是很清晰,但是一个很严重的问题是Web Container和EJB Container毕竟是两个不同的容器,它们之间要通信就得用的是RMI机制和JNDI服务,同样都在服务端,却物理上隔离,而且每次业务请求都要远程调用,有没有必要呢?看来并非隔离都是好的。
再看看轻量级和重量级的区别,笔者看过很多种说法,觉得最有道理的是轻量级代表是POJO + IoC,重量级的代表是Container + Factory。(EJB2.0是典型的重量级组件的技术)我们尽量使用轻量级的Pojo很好理解,意义就在于兼容性和可适应性,移植不需要改变原来的代码。而Ioc与Factory比起来,Ioc的优点是更大的灵活性,通过配置可以控制很多注入的细节,而Factory模式,行为是相对比较封闭固定的,生产一个对象就必须接受它全部的特点,不管是否需要。其实轻量级和重量级都是相对的概念,使用资源更少、运行负载更小的自然就算轻量。
话题扯远了,因为Spring框架带来了太多可以探讨的地方。比如它的非侵入性:指的是它提供的框架实现可以让程序员编程却感觉不到框架的存在,这样所写的代码并没有和框架绑定在一起,可以随时抽离出来,这也是Spring设计的目标。Spring是唯一可以做到真正的针对接口编程,处处都是接口,不依赖绑定任何实现类。同时,Spring还设计了自己的事务管理、对象管理和Model2 的MVC框架,还封装了其他J2ee的服务在里面,在实现上基本都在使用依赖注入和AOP的思想。由此我们大概可以看到Spring是一个什么概念上的框架,代表了很多优秀思想,值得深入学习。笔者强调,学习并不是框架,而是框架代表的思想,就像我们当初学Struts一样……
1.Spring MVC
关于IoC和AOP笔者在上篇已经稍微解释过了,这里先通过Spring的MVC框架来给大家探讨一下Spring的特点吧。(毕竟大部分人已经很熟悉Struts了,对比一下吧)
众所周知MVC的核心是控制器。类似Struts中的ActionServlet,Spring里面前端控制器叫做DispatcherServlet。里面充当Action的组件叫做Controller,返回的视图层对象叫做ModelAndView,提交和返回都可能要经过过滤的组件叫做Interceptor。
让我们看看一个从请求到返回的流程吧:
(1)前台Jsp或Html通过点击submit,将数据装入了request域
(2)请求被Interceptor拦截下来,执行preHandler()方法出前置判断
(3)请求到达DispathcerServlet
(4)DispathcerServlet通过Handler Mapping来决定每个reuqest应该转发给哪个后端控制器Controller
(5)各式各样的后端控制器Controller来处理请求,调用业务层对象来处理业务逻辑,然后返回一个ModelAndView对象
(6)当Controller执行完毕,Interceptor会调用postHandle来做后置处理
(7)ModelAndView代表了呈现画面是使用的Model数据对象和View对象,由于只能返回一个对象所有起了这个名字封装这两个对象。
(8)由ViewResolver对象来解析每个返回的ModelAndView对象应该呈现到哪一个视图(Jsp/Html等)中(包括Exception Resolver)
(9)当View绘制完成之后Interceptor又会跳出来执行它的afterCompletion方法做善后处理。当然Interceptor的行为完全是配置的而不是强制的。
这样一个完整的流程就这样结束了,个人感觉Spring的MVC框架稍显复杂,不像Struts-1那么容易上手。不管是Controller、Model、ViewRosovler、Handle Mapping还是View,Spring MVC框架都已经为你提供了多种实现,想最大程度的减少程序员的编码,增加框架的适用性。大家有兴趣可以继续深入研究哈!
2.Spring AOP
记得最初笔者请教他人Spring是一个什么东西的时候,每个人都会提到AOP这个词语。笔者在上一篇已经解释过AOP基本原理,这次来跟大家说说Spring的AOP编程吧。不同的AOP框架会有其对AOP概念不同的实现方式,主要的差别在于所提供的Pointcut、Aspects的丰富程度,以及它们如何被织入应用程序、代理的方式等等。先熟悉一下AOP中的几个重要概念:
(1)Cross-cutting:横切,说白了就是需要统一处理的集合(2)Aspects:将散落各处的横切收集起来,设计成各个独立可重用的对象称为Aspects。
(3)Advice: 对横切的具体实现,即等待插入一段逻辑。
(4)Joinpoint:Advice插入流程的时机点。
(5)Pointcut: 用于选择Joinpoint的程序结构,可以通过Annotation或者XML实现。
(6)Weave: Advice被应用至对象之上的过程称之为织入,有编译期、类加载期、运行期三种时间点策略。
如果你采用实现接口的方式,Spring会在执行时期适用java的动态代理,如果不实现接口,Spring会使用CGLIB产生代理类。AOP的概念很大很泛,而Spring只使用了其中的部分特性,毕竟Spring的目标是轻量级框架,比如它只支持对Method的Joinpoint,而不支持对Field的Joinpoint,理由是为了封装性。
其实我们可以把概念看得简单一点,AOP的目的是减少冗余代码,增强对较大项目的全局监控。Spring利用AOP可以规定一个集合和一套规则,在这个集合里所有的方法被invoke即调用的时候,都必须按照那套规则走一遍。那么首先对其中10个方法都要用到的处理代码就只用写一遍,如果是这10个方法来了就织入这段代码;其次,按照规则,也许所有的牵扯某个模块的方法调用的时候,我都需要做日志或者进行验证,那么我只要立足于这个集合的入口和出口,管他从哪里来去哪里,都能被有效的监控。我监控的可能不止是某个方法单独的行为,我还可以加入对流程控制的监控规则。例如是论坛,我规定注册了才能登录,而登录后才能发帖回帖下资源,于是所有这类流程都会被收集到我眼皮地下通过。
PS:笔者最近忙于找工作的事,没有太多经历在论坛跟大家整理自己的笔记。最近也只是接触Spring的MVC比较多,对于Spring的其他特性,还没有更多的去实践,所以仅仅是泛泛而谈,只是介绍一个印象罢了。还是那句话,我们学习一个框架不是如何使用,而是它所带来的优秀的思想和理念,这比如何使用这个框架更有意义得多
Java杂谈
(十二)--JVM
本来这次应该讲讲ORM的几个框架,但是笔者还没有完全总结出来,所以这里先插入一次学习JVM的心得。作为一个Java程序员,如果不了解JVM的工作原理,就很难从底层去把握Java语言和Java程序的运作机制。这里先推荐一个最权威的讲解JVM的文档,大家只要查过Java API的可以在里面的一个叫―API, Language, and Virtual Machine Document‖的标题下看到四个子标题,第一个是我们最熟悉的Java API Specification,很少会有人注意到第三和第四个子标题,分别是―The Java Language Specification‖和―The Java Machine Specification‖后面都带有(Download)字样,JVM的那个URL直接链接到http://java.sun.com/docs/books/vmspec/2nd-edition/这里地址。我们可以下载到一份非常权威详细的讲解JVM原理的官方文档。笔者业余时间花了1个星期来阅读,这里把自己的收获跟大家来分享一下,大概从这么几个方面来谈一谈:
1. JVM的实现机制
Java虚拟机就是一个小的计算机,有自己的指令集,有自己的文件系统,管理内部的表和数据,负责读取class文件里面字节码,然后转换成不同操作系统的CPU指令,从而使得Java程序在不同的操作系统上顺利的跑起来。所以Window的JVM能把字节码转换成Window系统的指令集,Linux的JVM能把字节码转换成Linux系统的字节,同理还有Solaris,它们彼此之间是不能通用的。最早一款的原型虽然是Sun公司开发的,但发展到现在其实任何厂商都可以自己去实现一个虚拟机,用来读取字节码转换成OS指令。甚至我们可以认为JVM跟Java编程语言都没有关系,因为你自己哪怕用记事本写一串字节码,也可以让JVM来解析运行,只要你的字节码能通过JVM的验证。
JVM的验证其实是很严格的,这里只讲一些有趣的地方。大家还记得Java的图标是一个杯咖啡麽?究其历史我们也许可以查出为什么,但还有更显而易见的方式是JVM怎么判断一个文件是否是class文件?JVM的做法是读取前4个字节转换成16进制数,判断是否等于0xCAFEBABE这个数。注意到这个单词了麽?―cafebabe‖,代表着国外一种咖啡品牌,似乎叫做Peet’s coffee-baristas之类。创造Java的人为了方便记忆,选择了这样一个16进制数作为标准class文件的头,所以任何class文件都必须具有这4个字节的头部。我们可以用DataInput这个接口的实现类来验证一下,读取任何一个class文件的第一个int,int在Java里面是四个字节。转换成16进制一定会是0xcafebabe的。
所以这里想告诉大家的是,JVM其实并没有那么神秘,我们完全可以理解它的构造。
2. Java相关的基础概念
配合JVM的结构,在Java语言中也会有很多特点比较鲜明的地方。比如对数值计算从来不会检查位溢出。任何变量存储的二进制即使位全部为1了仍然可以加,全部为0了仍然可以减。大家只要稍微测试一下就知道了,看这几个例子:
int max = Integer.MAX_VALUE;
int min = Integer.MIN_VALUE;
max+1 == min;//true
min-1 == max;//true
0.0/0.0 //得到―NaN‖(Not a number)
1/0.0 //Infinity
-1/0.0 //-Infinity
1或-1/0 //ArithmeticException唯一的异常情况
看完这几个例子,大家是否能更好的把握Java的数值运算呢?Java完全遵照IEEE-754的标准来定义单双精度浮点数以及其他的数值存储方式。
另外Java里面有一个概念叫做Daemon Thread(守护线程),知道它的存在主要是为了理解虚拟机的生命周期。当我们运行java命令,从main函数进入的那一刻起,虚拟机就开始启动运行了。Main所在的主线程也会启动起来,它属于非守护线程。与之同时一些守护线程也会同时启动,最典型的守护线程代表就是GC(垃圾收集器)线程。JVM虚拟机什么时候退出呢?是在所有的非守护线程结束的那一刻,JVM就exit。注意这个时候守护线程并未退出,很可能还要继续完成它的本职工作之后才会结束,但虚拟机的生命周期已经提前于它结束了。
3. JVM内部的基本概念
虚拟机内部还有一些概念,全部列举是不现实的,太繁琐也没有意义。除非您真的想自己去做一个JVM。笔者只列举部分概念:
首先我们来看一个叫做ReturnAddress的变量,它是JVM用来存储方法出口或者说进行跳转的依据,把任何地址存入这个变量就一定会按照这个地址来跳转。我们需要注意的就是finally有比方法return更高的赋值给ReturnAddress的优先级。同时存在方法return和finally return的话,一定是按照finally里面的return为准。
JVM有自己的Heap,能被所有线程共享,存储着所有的对象,内存是动态被分配的。对于每个线程,拥有自己的Stack,栈里面存储的单位叫做Frame(桢)。桢里面就记录着零时变量、对象引用地址、方法返回值等数据。JVM还有一个叫做Method Area的地方,存储着一段一段的可执行代码,每一段就是一个方法体,也能被所有线程共享。所以我们说一个线程其实从run方法跑起来,跟它的类中声明的其他方法是两个概念。因为其他的方法包括的所有的对象,这个时候都充当为资源被线程使用。
JVM有自己管理内存的方案,因为它具有文件系统的功能,我们可以看成一个小型的数据库,内部有许许多多不同的表。表的字段可能是另外一张表的地址,也可以直接就是一个存储数据值的地址值。JVM所有对运行时候类的解析验证计算等管理工作,实际上都是在管理这些表的变动,如果我们从数据库的角度来看,JVM所做的就是根据你的代码来操作那么多个表最后返回给你结果的过程。里面的表结构包括class的表、field表、method表、attribute表等。
4. JVM的指令集
JVM有自己的指令集,笔者从前也看过一些计算机组成结构和汇编语言的数,建议大家也稍微看看,了解设计一个高效可用的计算机指令集是多么复杂又多么重要的过程。对于JVM的指令集,职责是管理好Java程序编译出来的字节码,相对而言指令集的名称就多少和Java语言相关了,比如指令集里就有sastore,、saload表示array里面short的存和取、类似还有d2i表示从double转换成int、monitorenter表示进入synchronized块加锁、getstatic和putstatic表示对静态标量的存取、jsr和ret等跳转指令……
为了便于记忆,设计JVM指令集的人们约定f开头的跟float有关,d跟double有关,i跟int有关,s跟short有关,a跟array有关。有兴趣的可以细读文档里面的每一个指令的作用。因为只是作为初步了解,这里就不多说了。
5. 一些Java关键字的实现原理
文档还很详细的列举了很多加载、初始化、加锁等操作的过程。笔者觉得比较有用的第一是记住Java里面只有Array不是由ClassLoader加载的对象,其他的对象全部都必须由一个ClassLoader来加载。另外package的概念除了类似于C++的namespace,是一种命名空间之外,底层的实现是规定同一个package下的类必须由同一个类加载器来加载,所以package的概念还可以认为是被同一个类加载器加载的类。
另外在多线程中,有很多细节值得去体会。每个线程有自己的Working memory,它们从能被共享的Main Memory中去读数据、修改、然后再存回去。笔者一直认为线程就是数据库里面事务的前身或者说祖先。我们只要稍微比较一下它们的行为,就会发现很多一致性。事务也是操作被事务共享的表数据,你改完我改,顺序不一致就会出现脏数据,而线程同样会出现脏数据。我们对线程加的锁策略,同样在事务中也有适用。当然多事务的情况显然比多线程更加复杂,但我们只要理解了多线程,相信对学习数据库事务的效果也是非常有帮助的。Java里面除了synchronized能够帮助同步多线程之外,还有一个弱同步的操作关键字是volatile,它产生在变量上的约束在文档中也有详细的说明。因为很复杂,考虑到篇幅笔者就不打算解释一遍了。
好了,又是新的一篇结束了。大概再有一两篇笔者大学关于Java所学就差不多说完了。不足之处大家尽管提出来,笔者愿意接受各种职责批评,因为笔者认为失败的教训往往比成功更加助人成长。这个帖子一直以来得到那么多朋友的大力支持和鼓励,笔者在这里真诚的说一声谢谢!因为笔者即将毕业投入茫茫人海去从草根阶层开始挣扎,最近冷静的想了很多,即使毕业了,要提高的不止是技术,还包括很多综合素质,也许并不能马上找到如意的团队和工作岗位,只能承认自己是弱势群体,有时不得不向现实的生活低头,不知道今后是否还有这闲心去写学习笔记,去坚持走分享的道路。其实很多人我认为也很有心去分享,但被现实的生活束缚了手脚。所以也期望还呆在学校里的大学生们好好努力的珍惜那份无忧虑的心境和安静的环境,好好充实自己吧!
Java杂谈
(十三)——ORM
这是最后一篇Java杂谈了,以ORM框架的谈论收尾,也算是把J2ee的最后一方面给涵盖到了,之所以这么晚才总结出ORM这方面,一是笔者这两周比较忙,另一方面也想善始善终,仔细的先自己好好研究一下ORM框架技术,不想草率的敷衍了事。
其实J2ee的规范指南里面就已经包括了一些对象持久化技术,例如JDO(Java Data Object)就是Java对象持久化的新规范,一个用于存取某种数据仓库中的对象的标准化API,提供了透明的对象存储,对开发人员来说,存储数据对象完全不需要额外的代码(如JDBC API的使用)。这些繁琐的工作已经转移到JDO产品提供商身上,使开发人员解脱出来,从而集中时间和精力在业务逻辑上。另外,JDO很灵活,因为它可以在任何数据底层上运行。JDBC只是面向关系数据库(RDBMS)JDO更通用,提供到任何数据底层的存储功能,比如关系数据库、文件、XML以及对象数据库(ODBMS)等等,使得应用可移植性更强。我们如果要理解对象持久化技术,首先要问自己一个问题:为什么传统的JDBC来持久化不再能满足大家的需求了呢?
笔者认为最好是能用JDBC真正编写过程序了才能真正体会ORM的好处,同样的道理,真正拿Servlet/Jsp做过项目了才能体会到Struts、Spring等框架的方便之处。很幸运的是笔者这两者都曾经经历过,用混乱的内嵌Java代码的Jsp加Servlet转发写过完整的Web项目,也用JDBC搭建过一个完整C/S项目的后台。所以现在接触到新框架才更能体会它们思想和实现的优越之处,回顾从前的代码,真是丑陋不堪啊。^_^
回到正题,我们来研究一下为什么要从JDBC发展到ORM。简单来说,传统的JDBC要花大量的重复代码在初始化数据库连接上,每次增删改查都要获得Connection对象,初始化Statement,执行得到ResultSet再封装成自己的List或者Object,这样造成了在每个数据访问方法中都含有大量冗余重复的代码,考虑到安全性的话,还要加上大量的事务控制和log记录。虽然我们学习了设计模式之后,可以自己定义Factory来帮助减少一部分重复的代码,但是仍然无法避免冗余的问题。其次,随着OO思想深入人心,连典型的过程化语言Perl等都冠冕堂皇的加上了OO的外壳,何况是Java中繁杂的数据库访问持久化技术呢?强调面向对象编程的结果就是找到一个桥梁,使得关系型数据库存储的数据能准确的映射到Java的对象上,然后针对Java对象来设计对象和方法,如果我们把数据库的Table当作Class,Record当作Instance的话,就可以完全用面向对象的思想来编写数据层的代码。于是乎,Object Relationship Mapping的概念开始普遍受到重视,尽管很早很早就已经有人提出来了。
缺点我们已经大概清楚了,那么如何改进呢?对症下药,首先我们要解决的是如何从Data Schema准备完美的映射到Object Schema,另外要提供对数据库连接对象生命周期的管理,对事务不同粒度的控制和考虑到扩展性后提供对XML、Properties等可配置化的文件的支持。到目前为止,有很多框架和技术在尝试着这样做。例如似乎是封装管理得过了头的EJB、很早就出现目前已经不在开发和升级了的Apache OJB、首先支持Manual SQL的iBATIS,还有公认非常优秀的Hibernate等等。在分别介绍它们之前,我还想反复强调这些框架都在试图做什么:
毕竟Java Object和数据库的每一条Record还是有很大的区别,就是类型上来说,DB是没有Boolean类型的。而Java也不得不用封装类(Integer、Double等)为了能映射上数据库中为null的情况,毕竟Primitive类型是没有null值的。还有一个比较明显的问题是,数据库有主键和外键,而Java中仍然只能通过基本类型来对应字段值而已,无法规定Unique等特征,更别提外键约束、事务控制和级联操作了。另外,通过Java Object预设某Field值去取数据库记录,是否在这样的记录也是不能保证的。真的要设计到完全映射的话,Java的Static被所有对象共享的变量怎么办?在数据库中如何表现出来……
我们能看到大量的问题像一座座大山横在那些框架设计者们面前,他们并不是没有解决办法,而是从不同的角度去考虑,会得到很多不同的解决方案,问题是应该采取哪一种呢?甚至只有等到真正设计出来了投入生产使用了,才能印证出当初的设想是否真的能为项目开发带来更多的益处。笔者引用一份文档中提到一个健壮的持久化框架应该具有的特点:
A robust persistence layer should support----
1.Several types of persistence mechanism 2.Full encapsulation of the persistence mechanism.3.Multi-object actions
4.Transactions Control
5.Extensibility
6.Object identifiers
7.Cursors: logical connection to the persistence mechanism
8.Proxies: commonly used when the results of a query are to be displayed in a list
9.Records: avoid the overhead of converting database records to objects and then back to records
10.Multi architecture
11.Various database version and/or vendors
12.Multiple connections
13.Native and non-native drivers
14.Structured query language queries(SQL)
现在来简短的介绍一下笔者用过的一些持久化框架和技术,之所以前面强调那么多共通的知识,是希望大家不要盲从流行框架,一定要把握它的本质和卓越的思想好在哪里。
1. Apache OJB
OJB代表Apache Object Relational Bridge,是Apache开发的一个数据库持久型框架。它是基于J2ee规范指南下的持久型框架技术而设计开发的,例如实现了ODMG 3.0规范的API,实现了JDO规范的API,核心实现是Persistence Broker API。OJB使用XML文件来实现映射并动态的在Metadata layer听过一个Meta-Object-Protocol(MOP)来改变底层数据的行为。更高级的特点包括对象缓存机制、锁管理机制、Virtual 代理、事务隔离性级别等等。举个OJB Mapping的简单例子ojb-repository.xml:
jdbc-type=‖INTEGER‖ primarykey=‖true‖ autoincrement=‖true‖/> jdbc-type=‖INTEGER‖ primarykey=‖true‖ autoincrement=‖true‖/> 2. iBATIS iBATIS最大的特点就是允许用户自己定义SQL来组配Bean的属性。因为它的SQL语句是直接写入XML文件中去的,所以可以最大程度上利用到SQL语法本身能控制的全部特性,同时也能允许你使用特定数据库服务器的额外特性,并不局限于类似SQL92这样的标准,它最大的缺点是不支持枚举类型的持久化,即把枚举类型的几个对象属性拼成与数据库一个字段例如VARCHAR对应的行为。这里也举一个Mapping文件的例子sqlMap.xml: select * from Test where TestId=#value# Update Tests set Name=#name#, Date=‖date‖ where TestId=#testId# 3. Hibernate Hibernate无疑是应用最广泛最受欢迎的持久型框架,它生成的SQL语句是非常优秀。虽然一度因为不能支持手工SQL而性能受到局限,但随着新一代Hibernate 3.x推出,很多缺点都被改进,Hibernate也因此变得更加通用而时尚。同样先看一个Mapping文件的例子customer.hbm.xml来有一个大概印象: Customers_CustomerId_Seq … Hibernate有很多显著的特性,最突出的就是它有自己的查询语言叫做HQL,在HQL中select from的不是Table而是类名,一方面更加面向对象,另外一方面通过在hibernate.cfg.xml中配置Dialect为HQL可以使得整个后台与数据库脱离耦合,因为不管用那种数据库我都是基于HQL来查询,Hibernate框架负责帮我最终转换成特定数据库里的SQL语句。另外Hibernate在Object-Caching这方面也做得相当出色,它同时管理两个级别的缓存,当数据被第一次取出后,真正使用的时候对象被放在一级缓存管理,这个时候任何改动都会影响到数据库;而空闲时候会把对象放在二级缓存管理,虽然这个时候与数据库字段能对应上但未绑定在一起,改动不会影响到数据库的记录,主要目的是为了在重复读取的时候更快的拿到数据而不用再次请求连接对象。其实关于这种缓存的设计建议大家研究一下Oracle的存储机制(原理是相通的),Oracle牺牲了空间换来时间依赖于很健壮的缓存算法来保证最优的企业级数据库访问速率。 以上是一些Mapping的例子,真正在Java代码中使用多半是继承各个框架中默认的Dao实现类,然后可以通过Id来查找对象,或者通过Example来查找,更流行的是更具Criteria查找对象。Criteria是完全封装了SQL条件查询语法的一个工具类,任何一个查询条件都可以在Criteria中找到方法与之对应,这样可以在Java代码级别实现SQL的完全控制。另外,现在许多ORM框架的最新版本随着JDk 5.0加入Annotation特性都开始支持用XDoclet来自动根据Annotation来生成XML配置文件了。 笔者不可能详细的讲解每一个框架,也许更多的人在用Hibernate,笔者是从OJB开始接触ORM技术的,它很原始却更容易让人理解从JDBC到ORM的过渡。更多的细节是可以从官方文档和书籍中学到的,但我们应该更加看中它们设计思想的来源和闪光点,不是盲从它们的使用方法。 到这里全部Java杂谈这个主题就正式结束了,笔者也可以长叹一口气,拿到毕设题目后也该开始忙自己的毕业设计了。最近看了很多Spring AOP的资料,实习下班回家还会研究Ajax和极限编程,如果有时间了话,会把自己的学习笔记拿出来分享的。衷心希望大家都能不断的进步,在喜欢的道路上不回头的走下去……^_^ 另外,大家也回帖也够累的,呵呵,就让此帖沉下去吧,现在重写看到这些,觉得好多好多地方没说清楚或者有漏洞,自己都觉得挺惭愧的不过我觉得,任何人在学习一门技术的道路上都是要带着错误去前进的,而且要用于带着错误和疑惑向前走,适当的时候停下来抖一抖,抖掉身上累赘的错误然后再继续前进,最终达到精通的高峰!畏首畏尾只会阻碍人前进……笔者脸大,所以坚持写下来了,呵呵…… 【钓鱼要有耐心作文】推荐阅读: 做事要有耐心名人事例08-22 耐心的老师作文400字08-28 爱心诚心耐心09-15 耐心倾听10-23 爱心耐心11-01 让细心耐心成为习惯07-08 耐心的近义词什么06-21 面试举例说明你有耐心06-17 爱心__耐心__细心07-01 耐心词语的相关同义词07-25