123
 123

Tue 07 September, 2010

Channel Image23:07 Showcase of Interesting Navigation Designs» Smashing Magazine Feed

  

Everyone is always looking for interesting and effective ways to organize their website and allow users to move about and find things. But there’s a fine line between unexpected and unusable. Three points to consider in any navigation scheme are consistency, user expectations and contextual clues.

Foundation Six Portfolio

If page is long and provides different levels of navigation, will users be able to find their way through the site and use proper navigation quickly? Forcing visitors to use certain keystrokes to navigate, rather than what they're used to, might be novel, but is that effective if you have to explain instructions prominently on your home page? Here are some examples for your reading pleasure.

Channel Image21:30 Automatically download log files from hosting control panel» Antezeta Web Marketing
I’m currently using an Italian hosting service, Webperte, for my Italian blog. The service is fine, except for one minor frustration: they don’t support ftp retrieval of the web server access logs used by log based web analytics systems such as Google’s Urchin. The official solution is to log in to a hosting control panel, [...]
Channel Image18:50 The Look That Says Book» A List Apart
Hyphenation and justification: It’s not just for print any more. Armed with good taste, a special unicode font character called the soft hyphen, and a bit o’ JavaScript jiggery, you can justify and hyphenate web pages with the best of them. Master the zero width space. Use the Hyphenator.js library to bottle fame, brew glory, and put a stopper in death. Create web pages that hyphenate and justify on the fly, even when the layout reflows in response to changes in viewport size.
Channel Image18:50 Strategic Content Management» A List Apart
Any web project more complex than a blog requires custom CMS design work. It’s tempting to use familiar tools and try to shoehorn content in—but we can’t select the appropriate tool until we’ve figured out the project’s specific needs. So what should a CMS give us, apart from a bunch of features? How can we choose and customize a CMS to fit a project’s needs? How can content strategy help us understand what those needs really are? And what happens a day, a week, or a year after we’ve installed and customized the CMS?
Channel Image09:23 Keynotopia Wireframing Set: Free Wireframing Templates for Apple Keynote» Smashing Magazine Feed

  

Lately, Apple Keynote has been gaining popularity among designers as a wireframing and prototyping tool. Features like multiple slide masters, styles, grouping, animation and hyperlinks make it ideal for crafting interactive prototypes and UI narratives. Today's freebie, Keynotopia, is a free set of interface elements for Keynote that makes it possible for anyone to create these prototypes in minutes. All elements are hand-crafted in Apple Keynote, and organized in nested groups for easier manipulation and customization. The templates can be used in Keynote 09 and 08 and are designed by Amir Khella.

Wireframe templates for Apple Keynote

Start with a blank presentation, and create a new slide for each application screen. Then copy/paste elements from the wireframe templates into your slides, and edit their labels, sizes and colors. To save time, group elements together, and use master slides to share common interface and navigation components across multiple screens. Finally, add hyperlinks to enable user interaction, and use slide transitions to create cool interface animations.

Mon 06 September, 2010

Channel Image16:20 Fight The System: Battling Bureaucracy» Smashing Magazine Feed

  

If you work as part of an in-house Web team, you have my sympathy. If that in-house team is within a large organization, then doubly so. Being part of an in-house Web team sucks. Trust me, I know. I worked at IBM for three years and now spend most of my days working alongside battle-weary internal teams.

Web designer trying to hang himself

It's hardly surprising that most in-house teams are worn down and depressed. They face almost insurmountable challenges. Too often, a website becomes a battleground for pre-existing departmental conflicts. Political power plays can manifest themselves in fights over home page real estate or conflicts over website ownership. After all, is the website an IT function or a marketing tool?

Sat 04 September, 2010

Fri 03 September, 2010

Channel Image18:49 3PAR 争夺战» DBA Notes

这两天看到消息,Dell 在对 3PAR 争夺战中退出,HP 宣布获胜。当然,代价也是不菲,总收购价格大约 20 亿美元。

3PAR 这家公司刚进入国内我就有所接触,因为该公司在美国有很多证券、金融行业的客户,加之我上一家雇主就是做这个方面的,所以我非常想了解并引入 3PAR 的一些成功的经验,并且研究了一下高端的几款产品特性(refer: 3PAR 存储架构解析 ),最后还吃了一下螃蟹,在 3PAR 上实现了一套 Oracle 11g RAC 集群。所以,我算得上国内少数真正用过 3PAR 的了吧。考虑到以后再也不会接触这些所谓高端存储,还是有必要写点东西做个纪念。

应该说,3PAR 这产品的确有独到之处。首先是性能上看,通过特定硬件架构,充分利用了机械硬盘的特点,进而保证 I/O 响应时间,这是硬指标,真是非常贴近金融类的用户需求。 Thin Provisioning 技术也是实实在在的可在产品环境使用的,不像个别存储厂商只是一些功能的包装,跟风炒作概念,忽悠客户。让人感慨的是,3PAR 最近有有些生不逢时或是走向末路,上市时也恰好是金融危机来临之际,核心业务一下子受到非常大的影响,这是商业层面上的;另一方面,3PAR 的技术在机械硬盘时代几乎独步存储武林,但到了 SSD 的时代,则有武功被废的可能。尽管也宣称支持 SSD,但毕竟在机械硬盘时代的优势将不复存在了。追求高 IOPS ,更小 I/O 响应时间的用户用 PC 服务器 + SSD 就能很好的满足要求了。

HP 收购 3PAR 的意图其实比较明显,那就是弥补自己在高端产品上的缺陷。最近几年,IBM 在推收购来的 XIV ,HP 也有 EVA 系列的存储,和 3PAR 的一些设计理念都是很相近的,不过都只能算是中端存储,算不得高端产品。据我所知,EVA 似乎市场表现一般。收购 3PAR 后,估计 EVA 产品线将最终消亡。业内其实都知道,HP 自己一直没有高端存储产品,一直是 OEM Hitachi Data Systems(HDS) 的高端产品,后来和 Oracle 合作 Exadata ,Oracle 收购 Sun 之后也不再和 HP 合作,对 HP 来说,如果在未来几年,要在存储领域有所作为,收购是最为便捷的办法--也是高层最不用动脑筋就能使用的办法。

尽管 HP 有收购 3PAR 的足够理由,但我觉得这笔收购未必能对 HP 带来多大价值。如果给 Dell 可能会更好一些,Dell 可能将 3PAR 用来主打中高端存储市场。

据说 3PAR 的 CEO 获益比三位联合创始人的都要高,这就是商业运作的力量。3PAR 发展到现在,历史已经有 10 年了,在看到一些有创造力的公司成功之前,也要想到创业的艰辛,成功只是少数,失败是多数。(3PAR 的命名是这样的:3 表示三个人,P 代表Jeffrey Price;A 代表Ashok Singhal;R 则为 Robert Rogers,已于 2001 年离开. 所以,这家公司的名字应该都用大写字母才是)

从技术发展和业界的需求来看,这些 SAN 中高端存储已经临近黄昏。也不可能在云存储方面有什么进一步的想象力,有的话,也是空想。当然,这是另外的话题了。

--EOF--
Channel Image18:27 Web Design Checkmate: Using Chess For Success in Web Design» Smashing Magazine Feed

  

The business of building websites is one of constant change, adaptation and strategy. The way designers and developers build websites is often informed by the methods of others and their own trial and error. In light of this, we can draw a number of parallels — some philosophical, to a certain extent — between Web professionals and one of the oldest and most popular board games of all time (counting traditional and digital games). This game is chess.

Screenshot

In this article, we’ll explore the relationship between the game of chess and the Web industry. We’ll learn fundamental lessons from the pawn, rook, knight, bishop, queen and king, and we’ll highlight the factors — both offline and online — that determine best practices. The game is beloved by many professionals, so it seems fitting to apply its great strategy and elegance to the digital age; certain practices might help you lead a more successful working life.

Thu 02 September, 2010

Channel Image23:07 Showcase Of Appetizing Restaurant Websites» Smashing Magazine Feed

  

They say the first bite is taken with the eye. If so, these appetizing restaurant websites succeed in whetting our appetites, inviting us to a savoury next bite. In these designs, color scheme and introductory copy show vastly different aspects of the restaurant experience. Moody warm tones create atmosphere, vibrant greens underscore freshness, and earthy colors communicate a relaxed, friendly attitude.

Showcase of Appetizing Restaurant Websites

Because customers are increasingly using mobile browsers to make decisions on the spot, restaurant websites are doing a better job of communicating core information quickly. Similarly, full Flash websites with no mobile alternatives are seeing some decline. Especially interesting is how these businesses are improving their online menus by replacing PDF-only downloads with Web-optimized alternatives that are more readable and easier to navigate.

Channel Image12:34 盗梦空间(Inception)» DBA Notes

看过之后才会相信,电影《盗梦空间》(Inception) 如潮的好评名副其实,个人强烈推荐。即使有人介绍过剧情,自己观看的时候仍然是不一样的。这就是经典的魅力吧。

整部电影还是给人颇有些"庄生晓梦"的感觉。我和 Laura 开玩笑,是不是现在就在梦里?毕竟都凌晨三点钟了。情节繁复,循环,仿佛博尔赫斯的小说,但影片倒是并没有故弄玄虚,虽然开头部分信息量较大,一旦进入情节,后面的节奏控制得非常好。对于情节的设定,计算机同行不妨把把梦当作虚拟机好了,只是下一层的虚拟机会更慢而已。或许你会为电影中的爱情故事感慨,关于父子亲情难道就不感人么?

有一处场景明显是借鉴了荷兰艺术家埃舍尔(M.C. Escher)的作品,"不可能"的图形:

Escher Ascending and_Descending

杭州万象城的 IMAX 厅,九月一号凌晨看的首映,买票的时候有些犹豫,午夜场,第二天还要上班,熬夜值不值?回家睡了一觉又杀去,整个放映厅差不多都坐满了,杭州影迷精神头可谓十足。将近三个小时的电影,看完后毫无困意。

--EOF--
Channel Image09:07 Chrome usage creeps up on Microsoft» News | BuilderAU.com.au
Internet Explorer's growth slowed once again, and Chrome shook off its slump in August, new statistics show.
Channel Image07:16 iCandies Icon Set: 60 Free Icons For Your User Interfaces and Apps» Smashing Magazine Feed

  

Today we are glad to release iCandies Icon Set, a set with 60 high quality icons in 64×64px, 48×48px and 32×32px, available in .EPS, .AI and .PNG. The set is designed by the talented folks from IconEden on a sole purpose of giving your projects a sleek and geeky style or provide crisp, attractive icons for your modern and fashionable-looking interfaces. All the icons in this pack — 60 icons in total — are designed in Round Rectangle shape.

iCandies Icon Set

You can use the set for all of your projects for free and without any restrictions. You can freely use it for both your private and commercial projects, including software, online services, templates and themes. The set may not be resold, sublicensed or rented. Please link to this article if you want to spread the word.

Wed 01 September, 2010

Channel Image23:15 The Case For Open-Source Design: Can Design By Committee Work?» Smashing Magazine Feed

  

In celebrating the merits of free software and the excitement over this radical networked production method, an important truth is left unspoken. Networked collaboration shines in the low levels of network protocols, server software and memory allocation, but user interface has consistently been a point of failure. How come the networked collaboration that transformed code production and encyclopedia-writing fails to translate to graphic and interface design?

Screenshot

The following is an investigation into the difficulties of extending the open-source collaboration model from coding to its next logical step: interface design. While we'll dive deep into the practical difference between these two professional fields, the article might also serve as a note of caution to think before rushing to declare the rise of "open-source architecture," "open-source university," "open-source democracy" and so on.

Channel Image15:49 深入理解JVM» JavaEye论坛最新精华讨论贴

1   Java技术与Java虚拟机

说起Java,人们首先想到的是Java编程语言,然而事实上,Java是一种技术,它由四方面组成: Java编程语言、Java类文件格式、Java虚拟机和Java应用程序接口(Java API)。它们的关系如下图所示:

图1   Java四个方面的关系

运行期环境代表着Java平台,开发人员编写Java代码(.java文件),然后将之编译成字节码(.class文件)。最后字节码被装入内存, 一旦字节码进入虚拟机,它就会被解释器解释执行,或者是被即时代码发生器有选择的转换成机器码执行。从上图也可以看出Java平台由Java虚拟机和 Java应用程序接口搭建,Java语言则是进入这个平台的通道,用Java语言编写并编译的程序可以运行在这个平台上。这个平台的结构如下图所示:

在Java平台的结构中, 可以看出,Java虚拟机(JVM) 处在核心的位置,是程序与底层操作系统和硬件无关的关键。它的下方是移植接口,移植接口由两部分组成:适配器和Java操作系统, 其中依赖于平台的部分称为适配器;JVM 通过移植接口在具体的平台和操作系统上实现;在JVM 的上方是Java的基本类库和扩展类库以及它们的API, 利用Java API编写的应用程序(application) 和小程序(Java applet) 可以在任何Java平台上运行而无需考虑底层平台, 就是因为有Java虚拟机(JVM)实现了程序与操作系统的分离,从而实现了Java 的平台无关性。

那么到底什么是Java虚拟机(JVM)呢?通常我们谈论JVM时,我们的意思可能是:

  1. 对JVM规范的的比较抽象的说明;
  2. 对JVM的具体实现;
  3. 在程序运行期间所生成的一个JVM实例。

对JVM规范的的抽象说明是一些概念的集合,它们已经在书《The Java Virtual Machine Specification》(《Java虚拟机规范》)中被详细地描述了;对JVM的具体实现要么是软件,要么是软件和硬件的组合,它已经被许多生产厂 商所实现,并存在于多种平台之上;运行Java程序的任务由JVM的运行期实例单个承担。在本文中我们所讨论的Java虚拟机(JVM)主要针对第三种情 况而言。它可以被看成一个想象中的机器,在实际的计算机上通过软件模拟来实现,有自己想象中的硬件,如处理器、堆栈、寄存器等,还有自己相应的指令系统。

JVM在它的生存周期中有一个明确的任务,那就是运行Java程序,因此当Java程序启动的时候,就产生JVM的一个实例;当程序运行结束的时候,该实例也跟着消失了。下面我们从JVM的体系结构和它的运行过程这两个方面来对它进行比较深入的研究。

2   Java虚拟机的体系结构

刚才已经提到,JVM可以由不同的厂商来实现。由于厂商的不同必然导致JVM在实现上的一些不同,然而JVM还是可以实现跨平台的特性,这就要归功于设计JVM时的体系结构了。

我们知道,一个JVM实例的行为不光是它自己的事,还涉及到它的子系统、存储区域、数据类型和指令这些部分,它们描述了JVM的一个抽象的内部体系 结构,其目的不光规定实现JVM时它内部的体系结构,更重要的是提供了一种方式,用于严格定义实现时的外部行为。每个JVM都有两种机制,一个是装载具有 合适名称的类(类或是接口),叫做类装载子系统;另外的一个负责执行包含在已装载的类或接口中的指令,叫做运行引擎。每个JVM又包括方法区、堆、 Java栈、程序计数器和本地方法栈这五个部分,这几个部分和类装载机制与运行引擎机制一起组成的体系结构图为:

图3   JVM的体系结构

JVM的每个实例都有一个它自己的方法域和一个堆,运行于JVM内的所有的线程都共享这些区域;当虚拟机装载类文件的时候,它解析其中的二进制数据 所包含的类信息,并把它们放到方法域中;当程序运行的时候,JVM把程序初始化的所有对象置于堆上;而每个线程创建的时候,都会拥有自己的程序计数器和 Java栈,其中程序计数器中的值指向下一条即将被执行的指令,线程的Java栈则存储为该线程调用Java方法的状态;本地方法调用的状态被存储在本地 方法栈,该方法栈依赖于具体的实现。

下面分别对这几个部分进行说明。

执行引擎处于JVM的核心位置,在Java虚拟机规范中,它的行为是由指令集所决定的。尽管对于每条指令,规范很详细地说明了当JVM执行字节码遇 到指令时,它的实现应该做什么,但对于怎么做却言之甚少。Java虚拟机支持大约248个字节码。每个字节码执行一种基本的CPU运算,例如,把一个整数 加到寄存器,子程序转移等。Java指令集相当于Java程序的汇编语言。

Java指令集中的指令包含一个单字节的操作符,用于指定要执行的操作,还有0个或多个操作数,提供操作所需的参数或数据。许多指令没有操作数,仅由一个单字节的操作符构成。

虚拟机的内层循环的执行过程如下: 
do{ 
取一个操作符字节; 
根据操作符的值执行一个动作; 
}while(程序未结束)

由于指令系统的简单性,使得虚拟机执行的过程十分简单,从而有利于提高执行的效率。指令中操作数的数量和大小是由操作符决定的。如果操作数比一个字节大,那么它存储的顺序是高位字节优先。例如,一个16位的参数存放时占用两个字节,其值为:

第一个字节*256+第二个字节字节码。

指令流一般只是字节对齐的。指令tableswitch和lookup是例外,在这两条指令内部要求强制的4字节边界对齐。

对于本地方法接口,实现JVM并不要求一定要有它的支持,甚至可以完全没有。Sun公司实现Java本地接口(JNI)是出于可移植性的考虑,当然 我们也可以设计出其它的本地接口来代替Sun公司的JNI。但是这些设计与实现是比较复杂的事情,需要确保垃圾回收器不会将那些正在被本地方法调用的对象 释放掉。

Java的堆是一个运行时数据区,类的实例(对象)从中分配空间,它的管理是由垃圾回收来负责的:不给程序员显式释放对象的能力。Java不规定具体使用的垃圾回收算法,可以根据系统的需求使用各种各样的算法。

Java方法区与传统语言中的编译后代码或是Unix进程中的正文段类似。它保存方法代码(编译后的java代码)和符号表。在当前的Java实现 中,方法代码不包括在垃圾回收堆中,但计划在将来的版本中实现。每个类文件包含了一个Java类或一个Java界面的编译后的代码。可以说类文件是 Java语言的执行代码文件。为了保证类文件的平台无关性,Java虚拟机规范中对类文件的格式也作了详细的说明。其具体细节请参考Sun公司的Java 虚拟机规范。

Java虚拟机的寄存器用于保存机器的运行状态,与微处理器中的某些专用寄存器类似。Java虚拟机的寄存器有四种:

  1. pc: Java程序计数器;
  2. optop: 指向操作数栈顶端的指针;
  3. frame: 指向当前执行方法的执行环境的指针;。
  4. vars: 指向当前执行方法的局部变量区第一个变量的指针。

在上述体系结构图中,我们所说的是第一种,即程序计数器,每个线程一旦被创建就拥有了自己的程序计数器。当线程执行Java方法的时候,它包含该线程正在被执行的指令的地址。但是若线程执行的是一个本地的方法,那么程序计数器的值就不会被定义。

Java虚拟机的栈有三个区域:局部变量区、运行环境区、操作数区。

局部变量区

每个Java方法使用一个固定大小的局部变量集。它们按照与vars寄存器的字偏移量来寻址。局部变量都是32位的。长整数和双精度浮点数占据了两 个局部变量的空间,却按照第一个局部变量的索引来寻址。(例如,一个具有索引n的局部变量,如果是一个双精度浮点数,那么它实际占据了索引n和n+1所代 表的存储空间)虚拟机规范并不要求在局部变量中的64位的值是64位对齐的。虚拟机提供了把局部变量中的值装载到操作数栈的指令,也提供了把操作数栈中的 值写入局部变量的指令。

运行环境区

在运行环境中包含的信息用于动态链接,正常的方法返回以及异常捕捉。

动态链接

运行环境包括对指向当前类和当前方法的解释器符号表的指针,用于支持方法代码的动态链接。方法的class文件代码在引用要调用的方法和要访问的变 量时使用符号。动态链接把符号形式的方法调用翻译成实际方法调用,装载必要的类以解释还没有定义的符号,并把变量访问翻译成与这些变量运行时的存储结构相 应的偏移地址。动态链接方法和变量使得方法中使用的其它类的变化不会影响到本程序的代码。

正常的方法返回

如果当前方法正常地结束了,在执行了一条具有正确类型的返回指令时,调用的方法会得到一个返回值。执行环境在正常返回的情况下用于恢复调用者的寄存器,并把调用者的程序计数器增加一个恰当的数值,以跳过已执行过的方法调用指令,然后在调用者的执行环境中继续执行下去。

异常捕捉

异常情况在Java中被称作Error(错误)或Exception(异常),是Throwable类的子类,在程序中的原因是:①动态链接错,如无法找到所需的class文件。②运行时错,如对一个空指针的引用。程序使用了throw语句。

当异常发生时,Java虚拟机采取如下措施:

  • 检查与当前方法相联系的catch子句表。每个catch子句包含其有效指令范围,能够处理的异常类型,以及处理异常的代码块地址。
  • 与异常相匹配的catch子句应该符合下面的条件:造成异常的指令在其指令范围之内,发生的异常类型是其能处理的异常类型的子类型。如果找到了匹 配的catch子句,那么系统转移到指定的异常处理块处执行;如果没有找到异常处理块,重复寻找匹配的catch子句的过程,直到当前方法的所有嵌套的 catch子句都被检查过。
  • 由于虚拟机从第一个匹配的catch子句处继续执行,所以catch子句表中的顺序是很重要的。因为Java代码是结构化的,因此总可以把某个方 法的所有的异常处理器都按序排列到一个表中,对任意可能的程序计数器的值,都可以用线性的顺序找到合适的异常处理块,以处理在该程序计数器值下发生的异常 情况。
  • 如果找不到匹配的catch子句,那么当前方法得到一个"未截获异常"的结果并返回到当前方法的调用者,好像异常刚刚在其调用者中发生一样。如果 在调用者中仍然没有找到相应的异常处理块,那么这种错误将被传播下去。如果错误被传播到最顶层,那么系统将调用一个缺省的异常处理块。

操作数栈区

机器指令只从操作数栈中取操作数,对它们进行操作,并把结果返回到栈中。选择栈结构的原因是:在只有少量寄存器或非通用寄存器的机器(如 Intel486)上,也能够高效地模拟虚拟机的行为。操作数栈是32位的。它用于给方法传递参数,并从方法接收结果,也用于支持操作的参数,并保存操作 的结果。例如,iadd指令将两个整数相加。相加的两个整数应该是操作数栈顶的两个字。这两个字是由先前的指令压进堆栈的。这两个整数将从堆栈弹出、相 加,并把结果压回到操作数栈中。

每个原始数据类型都有专门的指令对它们进行必须的操作。每个操作数在栈中需要一个存储位置,除了long和double型,它们需要两个位置。操作 数只能被适用于其类型的操作符所操作。例如,压入两个int类型的数,如果把它们当作是一个long类型的数则是非法的。在Sun的虚拟机实现中,这个限 制由字节码验证器强制实行。但是,有少数操作(操作符dupe和swap),用于对运行时数据区进行操作时是不考虑类型的。

本地方法栈,当一个线程调用本地方法时,它就不再受到虚拟机关于结构和安全限制方面的约束,它既可以访问虚拟机的运行期数据区,也可以使用本地处理 器以及任何类型的栈。例如,本地栈是一个C语言的栈,那么当C程序调用C函数时,函数的参数以某种顺序被压入栈,结果则返回给调用函数。在实现Java虚 拟机时,本地方法接口使用的是C语言的模型栈,那么它的本地方法栈的调度与使用则完全与C语言的栈相同。

3   Java虚拟机的运行过程

上面对虚拟机的各个部分进行了比较详细的说明,下面通过一个具体的例子来分析它的运行过程。

虚拟机通过调用某个指定类的方法main启动,传递给main一个字符串数组参数,使指定的类被装载,同时链接该类所使用的其它的类型,并且初始化它们。例如对于程序:

class HelloApp 
{
 public static void main(String[] args) 
 {
  System.out.println("Hello World!"); 
  for (int i = 0; i < args.length; i++ )
  {
   System.out.println(args[i]);
  }
 }
}

编译后在命令行模式下键入: java HelloApp run virtual machine

将通过调用HelloApp的方法main来启动java虚拟机,传递给main一个包含三个字符串"run"、"virtual"、"machine"的数组。现在我们略述虚拟机在执行HelloApp时可能采取的步骤。

开始试图执行类HelloApp的main方法,发现该类并没有被装载,也就是说虚拟机当前不包含该类的二进制代表,于是虚拟机使用 ClassLoader试图寻找这样的二进制代表。如果这个进程失败,则抛出一个异常。类被装载后同时在main方法被调用之前,必须对类 HelloApp与其它类型进行链接然后初始化。链接包含三个阶段:检验,准备和解析。检验检查被装载的主类的符号和语义,准备则创建类或接口的静态域以 及把这些域初始化为标准的默认值,解析负责检查主类对其它类或接口的符号引用,在这一步它是可选的。类的初始化是对类中声明的静态初始化函数和静态域的初 始化构造方法的执行。一个类在初始化之前它的父类必须被初始化。整个过程如下:

图4:虚拟机的运行过程

4   结束语

本文通过对JVM的体系结构的深入研究以及一个Java程序执行时虚拟机的运行过程的详细分析,意在剖析清楚Java虚拟机的机理。



作者: 帅的被神砍 
声明: 本文系JavaEye网站发布的原创文章,未经作者书面许可,严禁任何网站转载本文,否则必将追究法律责任!

已有 30 人发表回复,猛击->>这里<<-参与讨论


JavaEye推荐



Channel Image11:35 Google wants to talk» News | BuilderAU.com.au
Google's Mike Cohen won't be satisfied until anyone who wants to talk to their computer can do so without laughing at the hideous translation or sighing in frustration.

Tue 31 August, 2010

Channel Image19:40 Desktop Wallpaper Calendar: September 2010» Smashing Magazine Feed

  

Desktop wallpapers can serve as an excellent source of inspiration. However, if you use some specific wallpaper for a long period of time, it becomes harder to draw inspiration out of it. That’s why we have decided to supply you with smashing wallpapers over 12 months. And to make them a little bit more distinctive from the usual crowd, we’ve decided to embed calendars for the upcoming month. So if you need to look up some date, isn’t it better to show off a nice wallpaper with a nice calendar instead of launching some default time application?

Smashing Wallpaper — September 10

This post features 75 free desktop wallpapers, created by designers across the globe. Both versions with a calendar and without a calendar can be downloaded for free.

Please notice:

  • all images can be clicked and lead to the preview of the wallpaper;
  • you can feature your work in our magazine by taking part in our desktop wallpaper calendar series. We are regularly looking for creative designers and artists to be featured on Smashing Magazine. Are you one of them?

So what wallpapers have we received for September 2010?

Channel Image15:24 Google promises to prioritise your inbox» News | BuilderAU.com.au
Google is set to introduce a new feature for Gmail users in the coming week that sorts emails based on how important they are to the reader called "priority inbox".
Channel Image11:08 瞧咱办公室的拓扑图» JavaEye论坛最新精华讨论贴

前几天的《Swing版小小网管》让我想起前阵子做过的一个企业网管项目。客户是一个工厂,搞生产制造的。人并不多,四、五十人,多数是车间工人。办公室的也就二十多人,网络结构并不复杂:ADSL宽带接入,加上几个AP进行信号扩展;台式机、服务器、笔记本电脑,加上零星的手机上网,仅此而已。大伙知道:做企业网管是比较艰难的,工作量大,吃力不讨好,竞争激烈,卖不了几个钱,不容易啊!但是为了能够在项目中多点筹码,界面还是要做的精益求精才行。

 

先到工厂详细调研了网络结构图,并绘制了一个简单的草图结构(不好意思,写字越来越难看了,还不如我小学时候的水平,都是电脑键盘给害的哈):


 

 

其中ADSL和几个无线AP是网络主干,各个办公室的结构都是星形结构,通过网线进行汇聚,通过AP进行互联。我问工厂的车间主任为啥这样搞,他说使用AP的原因是工厂正在进行改造,几个办公室之间距离较远,隔着一个大院子,网线铺设不便;另外几个车间里头也有电脑需要联网,在嘈杂混乱火星直冒的车间里头,弄网线就更麻烦了。

 

要在网管里面呈现和监控这个网络,对俺来说不算难事。不过我需要一个好看一点的流量监控的图表。首先通过SNMP获取AP中的iftable中的端口,并获取其实时流量,然后放置在拓扑图的link对象中。为了不给网络造成太多负担,网管默认每5秒钟获取一次数值,并存储连续100个数值,多余的抛弃。

 

绘制chart的图并不复杂,整个chart呈一个长矩形状,分为三段(老、中、轻),用不同的颜色进行渲染。

 

 

public void paintIcon(Component c, Graphics g, int x, int y) {
    Graphics2D g2d = (Graphics2D) g;

    Rectangle bounds = getBounds();

    g2d.setColor(background);
    g2d.fill(bounds);

    Point location = bounds.getLocation();
    int startX = location.x;
    int startY = location.y + chartHeight;
    GeneralPath path = new GeneralPath();
    path.moveTo(startX, startY);

    int[] values = getChartDataValues();

    for (int i = 0; i < values.length; i++) {
        path.lineTo(startX + i, startY - values[i]);
    }
    path.lineTo(startX + chartWidth, startY);
    path.closePath();

    g2d.setColor(chartColor1);
    g2d.fill(path);

    //clip center part, paint with color2.
    Shape oldClip = g2d.getClip();
    Rectangle clip = new Rectangle(bounds.x + chartWidth / 4, bounds.y, chartWidth / 2, chartHeight);
    g2d.setClip(clip);
    g2d.setColor(chartColor2);
    g2d.fill(path);
    g2d.setClip(oldClip);

    g2d.setColor(Color.lightGray);
    g2d.draw(bounds);

    g2d.setColor(Color.darkGray);
    g2d.setFont(font);

    int textX = location.x + 5;
    int textY = location.y + chartHeight / 2 + font.getSize() / 2;
    g2d.drawString("Sum", textX, textY);

    int speed = 100;
    if (getElementUI().getElement() instanceof MyLink) {
        speed = ((MyLink) getElementUI().getElement()).getSpeed();
    }
    String text = NumberFormat.getInstance().format(speed) + " kbit/s";
    Rectangle2D textBounds = g2d.getFontMetrics().getStringBounds(text, g2d);
    textX = (int) (location.x + chartWidth - textBounds.getWidth() - 5);
    g2d.drawString(text, textX, textY);
}

 

 显示效果如下:


 

另外,对连线的效果也进行了一些处理。用直线连接无疑太土气了,来点曲线增加一点趣味。曲线用一个对称的抛物线来处理,根据不同的角度进行自动调整:

 

 

public GeneralPath getPath() {
    Point from = this.getFromPoint();
    Point to = this.getToPoint();
    Point middle = new Point((from.x + to.x) / 2, (from.y + to.y) / 2);
    boolean wider = (Math.abs(from.x - to.x) > Math.abs(from.y - to.y));
    GeneralPath myPath = new GeneralPath();
    myPath.moveTo(from.x, from.y);
    if (wider) {
        myPath.quadTo(middle.x, from.y, middle.x, middle.y);
        myPath.quadTo(middle.x, to.y, to.x, to.y);
    } else {
        myPath.quadTo(from.x, middle.y, middle.x, middle.y);
        myPath.quadTo(to.x, middle.y, to.x, to.y);
    }

    return myPath;
}

 

其实我发现,虽然咱写程序的号称数学都必须很非常贼NB,可是真正的几何和数学真的都忘的差不多了,不知道你还能不能随手写下二次方程的根,椭圆的几何方程,或者球体的面积公式?反正我是不行了。
 
不管咋说,俺还是九牛二虎之下整出了几个花里胡哨的path出来。毫无技术含量,你可别被俺忽悠了:

接下来,再把SNMP获得的数据放入link中的chart呈现。为了避免泄密的麻烦,附件的demo俺去掉了这些业务代码,用一个thread模拟代替了:

    Thread thread = new Thread() {

        private Vector links = null;

        private Vector getLinks() {
            if (links == null) {
                links = new Vector();
                Iterator it = network.getDataBox().iterator();
                while (it.hasNext()) {
                    Object o = it.next();
                    if (o instanceof MyLink) {
                        MyLink link = (MyLink) o;
                        links.add(link);
                    }
                }
            }
            return links;
        }

        @Override
        public void run() {
            while (true) {
                Iterator it = getLinks().iterator();
                while (it.hasNext()) {
                    Object o = it.next();
                    if (o instanceof MyLink) {
                        MyLink link = (MyLink) o;
                        createRandomValue(link);
                        if (TWaverUtil.getRandomInt(10) == 1) {
                            link.setSpeed(TWaverUtil.getRandomInt(10000));
                        }
                    }
                }
                try {
                    Thread.sleep(100);
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
        }

        private void createRandomValue(MyLink link) {
            int value = link.getLastChartValue();
            int change = TWaverUtil.getRandomInt(3) - 1;
            value = value + change;

            value = Math.min(value, 100);
            value = Math.max(value, 0);

            link.addChartValue(value);
        }
    };
    thread.start();
}
 
 再到google上物色几个清爽的图标。


对了,在结束之际,突然想起一件事:AP上的晃悠悠的电线丝,可不是icon的一部反,而是咱draw上去的!这个小亮点咱得说说:主要思路就是new一个path,模拟其曲线的路径,然后在Swing的paint时候给附加上去。代码如下:

 

    @Override
    public void paintBody(Graphics2D g2d) {
        super.paintBody(g2d);

        if (((MyNode) getElement()).isWireVisible()) {
            Object oldValue = g2d.getRenderingHint(RenderingHints.KEY_ANTIALIASING);
            g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
            g2d.setColor(wireColor);
            g2d.setStroke(TWaverConst.BASIC_STROKE);
            g2d.draw(createWireShape());
            g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, oldValue);
        }
    }

    private Shape createWireShape() {
        Point point = this.getHotspot();
        int x = point.x - 25;
        int y = point.y + 23;

        GeneralPath path = new GeneralPath();
        path.moveTo(x, y);
        path.curveTo(x + 20, y - 20, x + 50, y - 10, x + 48, y);
        path.curveTo(x + 48, y + 15, x + 10, y + 10, x + 12, y - 5);
        path.curveTo(x + 12, y - 20, x + 50, y - 20, x + 53, y);
        path.quadTo(x + 53, y + 3, x + 51, y + 5);
        return path;
    }

这下终于完整了。看看“晃悠悠的铁丝”效果:




 
接下来,在程序里面整合一下,整个效果就出来了:

 

 


 

为了和大家共同学习和交流,附上的源代码是从项目中抽取了Swing展示部分,去掉了所有业务逻辑,仅仅为了和大家共享Swing的展示能力,以及拓扑图的制作思路。虽然不是惊世骇俗美轮美奂,但是俺比较喜欢这种简洁、清新的风格。

 

老规矩,有代码共享。在这里可以下载全部源代码、可执行文件、图标资源等内容

 

祝大家编程愉快!



作者: xiaozhonghua 
声明: 本文系JavaEye网站发布的原创文章,未经作者书面许可,严禁任何网站转载本文,否则必将追究法律责任!

已有 27 人发表回复,猛击->>这里<<-参与讨论


JavaEye推荐



Mon 30 August, 2010

Channel Image22:31 Blogging For Web Designers: Editorial Calendars and Style Guides» Smashing Magazine Feed

  

A few years ago, you might not have pointed out during a meeting with a potential client that you maintained a blog. Over time, though, blogs have evolved from the being a personal hobby to a serious work tool. In fact, today, web designers are supposed to know much more than just how to design and build websites. Customer's expectations have increased, and unless you are in position to choose your favourite clients, meeting them requires hard work.

Calendar

Hence, it's important to keep learning about the variety of design-related fields every single day — be it marketing, psychology, business, copywriting, publishing or blogging. This article doesn't cover "traditional" web design discipline as we know it, but goes a bit beyond it, exploring various writing, blogging and online publishing strategies. Apart from that, we present some useful writing style guides that may help you educate your clients on their copy for their upcoming project.

Channel Image15:05 Web Analytics and the Missing Right Clicks Conundrum» Antezeta Web Marketing
A web marketing professional naturally thinks a lot about the incredible diversity of a site’s visitor demographics. Old and young, male and female, well educated and not, affluent and not… but how much thought has been given to the right-clickers? No, not the right-handed, the right-clickers. Right-clickers are those who right-click on a link to [...]
Channel Image15:04 Comparison of Google Analytics / Urchin Tracking Scripts» Antezeta Web Marketing
With the advent of Google Analytics asynchronous tracking code, many sites need to review the automatic tagging code they’re using to track items such as downloaded files and outgoing link clicks. Unfortunately Google doesn’t offer an official library for this purpose; each Google Analytics or Urchin administrator is on their own in selecting an extended [...]
Channel Image04:00 Academica: Free WordPress 3.0+ Theme For Educational Websites» Smashing Magazine Feed

  

In this post we release a yet another freebie: Academica WordPress Theme, a free WordPress theme designed specifically for educational websites such as universities, schools etc. It's a flexible and versatile free theme that can be easily customized and branded for any university, academy or non-profit organization. The theme is designed by ProudThemes (the server is currently not working) and released for Smashing Magazine and its readers. As usual, the theme is free to use in private and commerical projects.

Screenshot

The theme was developed for WordPress 3.0+, allows enabling/disabling of a jQuery-based content slider on the homepage for showing your photos, has 9 sidebar widget areas and 3 custom widgets, 3 custom page templates and 2 custom post templates and provides dynamic image resizing (TimThumb script). The theme is released under GPL. You can use it for all your projects for free and without any restrictions. Please link to this article if you want to spread the word. You may modify the theme as you wish.

Fri 27 August, 2010

Channel Image07:16 Dell ponders possible Streak stuff up» News | BuilderAU.com.au
Dell is responding to concerns in the open-source community that it didn't comply with the rules governing Android software used in its Streak tablet.

Thu 26 August, 2010

Channel Image18:21 从main.c开始走进Ruby-异常» JavaEye论坛最新精华讨论贴

这一阵子真没时间,9月上旬更没时间,头大.

前天写面试题目的时候遇到了setjmp和longjmp这两个方法,

于是就想到R uby的异常处理是如何实现的,顺道研究下.

其他的Ruby相关的实现现在真没时间写.但肯定要写,因为我喜欢R ,不是一般的喜欢.

 

===============================

 

兵马未动,粮草先行.

 

我想看看raise怎么实现的,但当我在irb中敲入了raise后,我却不知道在gdb中该对哪个方法下断点.

冒然出击肯定是盲目的,就像菲律宾特警解救香港游客人质一样,一点思路都没有.

那好吧,我还是要做点预习功课,翻开代码去找raise对应的C方法.

 

 

通过阅读eval.c的代码,发现里面有如下方法:

 

 

void
Init_eval(void)
{
    rb_define_virtual_variable("$@", errat_getter, errat_setter);
    rb_define_virtual_variable("$!", errinfo_getter, 0);

    rb_define_global_function("raise", rb_f_raise, -1);
    rb_define_global_function("fail", rb_f_raise, -1);
/*
    ..........
*/
}
 

其中raise这个字符串对应的方法是: rb_f_raise,

 

 

static VALUE
rb_f_raise(int argc, VALUE *argv)
{
    VALUE err;
    if (argc == 0) {
	err = get_errinfo();
	if (!NIL_P(err)) {
	    argc = 1;
	    argv = &err;
	}
    }
    rb_raise_jump(rb_make_exception(argc, argv));
    return Qnil;		/* not reached */
}
 

同时我们也可以看到其他方法的底层实现,比如:

 

  • $@是获得出错信息的所在代码行,通过errat_getter实现
  • $!是获得出错信息,通过errinfo_getter实现

我们既然要理解Ruby异常机制,那么我们就要对rb_f_raise进行仔细的分析.

现在我设计这样一个场景:

 

  1. 在irb中通过raise抛出一个异常
  2. 在gdb中对rb_f_raise设置断点
  3. 执行 irb中的raise代码
  4. 在gdb中step进入rb_f_raise及其里面,通过bt观察它的调用栈以及最底层的实现方式.

 

看我如下操作:

 

irb 写道
>> raise ArgumentError,"Debug Ruby from main.c"
ArgumentError: Debug Ruby from main.c
from (irb):15
from /usr/local/ruby-1.9.1/bin/irb:12:in `<main>'

 

gdb 写道
(gdb) b rb_f_raise
Breakpoint 10 at 0x1000289dc: file eval.c, line 467.
(gdb) c
Continuing.
 

 

irb 写道
>> raise ArgumentError,"Debug Ruby from main.c"

 

 

gdb 写道
Breakpoint 10, rb_f_raise (argc=2, argv=0x100400298) at eval.c:467
467 if (argc == 0) {
 

这个时候我们已经断到了rb_f_raise,下面就是要step进入其中,看看一个语言如何设计对异常的处理.

 

我没有把每一步的代码都贴出来,而是走到了最里面,当我遇到以下两个函数的时候,我认为我们就可以大概知道Ruby的异常处理是怎么实现的了.

 

 

int	_setjmp(jmp_buf);
void	_longjmp(jmp_buf, int);
 

深入过C++和Java实现的同学肯定也见过这两个函数,他们的作用就是:

 

  • setjmp:收集当前堆栈信息并且保存到jmp_buf中,返回值是longjmp,如果不是从longjmp返回的,则返回0.
  • longjmp:跳转到jmp_buf设定的位置,并将int返回给setjmp.

下面还有setjmp和longjmp的解释.

 

我出过一道面试题目,就是关于用setjmp.h中的方法实现异常处理的,在这里可以当作一个简单应用的例子,如下:

 

 

#include <setjmp.h>
jmp_buf jb;
int ret = setjmp(jb);
switch(ret)
{
case 0:/*ok*/;break;
case 1:exc1_handler();break;
case 2:exc2_handler();break;
default:default_handler();
}
 
if(a==b){/*do ok*/}
else{/*raise exception*/longjmp(jb,1)}

 

知道了这两个函数,我们就可以开心一下了,原来好多语言都使用它们去实现异常处理阿,只是功能丰富程度不同,但通过了解上面这些,至少我们知道了一个大概的思路,给我们自己的语言设计启发了不少.

 

话题再转回R uby的调试中,在断到longjmp的时候,我使用bt命令查看了一下调用栈,可以比较清楚的看到当在Ruby层面执行raise语句的时候,它是通过调用哪些关键函数来实现异常处理功能的.

 

gdb 写道
#0 rb_sourcefile () at vm.c:754
#1 0x00000001000274cd in rb_longjmp (tag=6, mesg=4304442240) at eval.c:358
#2 0x00000001000277e8 in rb_raise_jump (mesg=<value temporarily unavailable, due to optimizations>) at eval.c:530
#3 0x0000000100028a07 in rb_f_raise (argc=<value temporarily unavailable, due to optimizations>, argv=<value temporarily unavailable, due to optimizations>) at eval.c:474
 

 

 

整个raise的流程差不多就这样,我在对其中个别几个函数再剖析一下:

 

rb_make_exception是在rb_raise_jump的时候调用的,

他的作用就是生成一个异常对象给rb去raise.

现在我们来看一下这个exception的生成过程.

 

 

VALUE
rb_make_exception(int argc, VALUE *argv)
{
    VALUE mesg;
    ID exception;
    int n;

    mesg = Qnil;
    switch (argc) {
      case 0:
	break;
      case 1:
	if (NIL_P(argv[0]))
	    break;
	mesg = rb_check_string_type(argv[0]);
	if (!NIL_P(mesg)) {
	    mesg = rb_exc_new3(rb_eRuntimeError, mesg);
	    break;
	}
	n = 0;
	goto exception_call;

      case 2:
      case 3:
	n = 1;
      exception_call:
	CONST_ID(exception, "exception");
	if (!rb_respond_to(argv[0], exception)) {
	    rb_raise(rb_eTypeError, "exception class/object expected");
	}
	mesg = rb_funcall(argv[0], exception, n, argv[1]);
	break;
      default:
	rb_raise(rb_eArgError, "wrong number of arguments");
	break;
    }
    if (argc > 0) {
	if (!rb_obj_is_kind_of(mesg, rb_eException))
	    rb_raise(rb_eTypeError, "exception object expected");
	if (argc > 2)
	    set_backtrace(mesg, argv[2]);
    }

    return mesg;
}

 

罗列这么多代码的确很不好意思,因为不是每行都需要说明一下,但我又怕只取部分代码片段的话,会有人看不明白.

 

 

mesg = rb_check_string_type(argv[0]);

这里是将错误信息argv转换为String

 

 

mesg = rb_exc_new3(rb_eRuntimeError, mesg);

这里将mesg变成一个异常的对象,而在此之前它还只是个String.

我们看到更改mesg Ruby类型的时候都不需要加强制转换,就是因为这是在C层面,一切R uby对象都是VALUE.

 

rb_exc_new3是通过rb_funcall(etype, rb_intern("new"), 1, str);来实现的.

我们又看到了rb_intern方法,这个方法是把ruby的方法名字转换为id,然后找到该id对应的该函数的C的实现,再执行C的实现.这是C中使用Ruby函数的一个通用思路.

 

 

CONST_ID(exception, "exception");

这个宏定义如下:

 

 

#define CONST_ID_CACHE(result, str)			\
    {							\
	static ID rb_intern_id_cache;			\
	if (!rb_intern_id_cache)			\
	    rb_intern_id_cache = rb_intern2(str, strlen(str));	\
	result rb_intern_id_cache;			\
    }
#define CONST_ID(var, str) \
    do CONST_ID_CACHE(var =, str) while (0)

 其中static类型的rb_intern_id_cache用来保存exception的C实现,这样再次访问这个方法的时候就不用使用rb_intern2去取了.

 

 

贴一下setjmp和longjmp的解释:

 

setjmp longjmp 写道
setjmp|longjmp

  与刺激的abort()和exit()相比,goto语句看起来是处理异常的更可行方案。不幸的是,goto是本地的:它只能跳到所在函数内部的标号上,而不能将控制权转移到所在程序的任意地点(当然,除非你的所有代码都在main体中)。
  为了解决这个限制,C函数库提供了setjmp()和longjmp()函数,它们分别承担非局部标号和goto作用。头文件<setjmp.h>申明了这些函数及同时所需的jmp_buf数据类型。
  原理非常简单:
  1.setjmp(j)设置“jump”点,用正确的程序上下文填充jmp_buf对象j。这个上下文包括程序存放位置、栈和框架指针,其它重要的寄存器和内存数据。当初始化完jump的上下文,setjmp()返回0值。
  2. 以后调用longjmp(j,r)的效果就是一个非局部的goto或“长跳转”到由j描述的上下文处(也就是到那原来设置j的setjmp()处)。当作为长跳转的目标而被调用时,setjmp()返回r或1(如果r设为0的话)。(记住,setjmp()不能在这种情况时返回0。)
  通过有两类返回值,setjmp()让你知道它正在被怎么使用。当设置j时,setjmp()如你期望地执行;但当作为长跳转的目标时,setjmp()就从外面“唤醒”它的上下文。你可以用longjmp()来终止异常,用setjmp()标记相应的异常处理程序。
 

作者: CharlesCui 
声明: 本文系JavaEye网站发布的原创文章,未经作者书面许可,严禁任何网站转载本文,否则必将追究法律责任!

已有 7 人发表回复,猛击->>这里<<-参与讨论


JavaEye推荐



Channel Image16:49 Swing版小小网管» JavaEye论坛最新精华讨论贴

JavaEye上不少朋友是做网管系统的。一个典型的网络管理系统,需要具备FCAPS几个标准模块,而网络的自动发现和拓扑展示是核心之一。很多人不喜欢Java的Swing,而本文就用一个很小很小的例子,来模拟一个小小的网络管理程序,希望能给大家一点启发。虽然很小,它却可以完成一个简单的局域网自动发现搜索、多线程、ICMP和SNMP的ping、节点的生成、拓扑的展示、自动布局等功能。继续改巴改巴也许还有点使用价值也未可知。

 

如果不喜欢研究代码,就当它是一个趣味程序吧!你可以在公司的网络里面搜索一把,把同事的机器都挖出来,看看你们公司的网络结构是怎样的;如果喜欢研究代码,可以看看相关SNMP、多线程和拓扑图展示的部分,虽然很简单,就当看肥皂剧消遣了。

 

Ping和SNMP PING

这个程序的自动发现比较简单,就是对所在的网段进行便利搜索。首先,获得本机的网址以及所在的网段。例如,如果本机的地址是192.168.1.122,那么所在的网段自然就是192.168.1.0。然后,将这个网段中所有可能存在的IP地址进行拆分,并通过多线程进行任务分配,一个一个的Ping。

 

 

    public static boolean ping(String ip) {
        try {
            InetAddress ipaddress = InetAddress.getByName(ip);
            return ipaddress.isReachable(2000);
        } catch (Exception ex) {
            ex.printStackTrace();
            return false;
        }
    }
 

 

用Java来Ping机器,有两个做法。一个是传统的调用命令行执行Ping命令的做法。这种做法的好处是速度快,比较可靠。缺点是,不同的操作系统,甚至Windows的不同版本,其执行和返回结果格式都可能不同,造成跨平台的不便以及代码的啰嗦。第二个方法自然就是使用大家都熟知的Java 5提供的InetAddress的isReachable方法。这个方法本来应当很好,可是在实际使用中就会发现,它不大灵光。超时时间设置短了吧,就ping不through;长了把,又贼慢。网上不少人都反映和抱怨这个问题。仔细研究这个isReachable,会发现更多的问题。1、它不是线程安全的。也就是说,为了提高速度而使用多线程进行多节点并行ping,会导致不安全的返回结果。这个问题挺致命。2、这个函数并非使用ICMP的ping,而是仅仅用TCP连一下7号端口而已:

 

 写道
InetAddress.isReachable() doesn't use ICMP, it just tries to open TCP port 7 at the target. You can use ICMP in Java with the JPCAP library if you can find it, but you can't multithread it for reasons which are discussed above - basically, ICMP is not thread-safe

 

又慢又线程不安全就比较不爽了。还可以使用上面提到的JPCAP这个库来完成。这个库的地址是:

http://netresearch.ics.uci.edu/kfujii/jpcap/doc/

 

不管怎么说,一个小小的ping还是挺麻烦。不过本例子由于仅仅是示例小程序,还是使用了isReachable方法,简化代码。

 

在ping通一个机器后,接下来再使用SNMP进行ping。做过SNMP网管的朋友知道,所谓SNMP Ping其实就是用SNMP去get一个非常基本的OID看对方有无反应。如果能够返回数据,说明这是一个SNMP节点,可以通过SNMP配合MIB库去获取更多的业务数据。例如磁盘、CPU、内存、端口力量等等基本的信息,都有相关的SNMP MIB进行定义。

 

这个例子使用了Westhawk's SNMP stack这个SNMP协议栈,一个轻量的、Java的、开源的、免费的SNMP协议栈,实现了SNMPv1、SNMPv2c以及SNMPv3 (包括MD5和SHA1以及DES, AES加密算法)。地址在这里:

http://snmp.westhawk.co.uk/

 

使用Westhawk's SNMP做一个简单的get操作如下:

 

 

            SnmpContextv2c context = new SnmpContextv2c(ip, 161);
            context.setCommunity("public");

            BlockPdu pdu = new BlockPdu(context);
            pdu.setRetryIntervals(new int[]{1000});
            String sysUpTime = "1.3.6.1.2.1.1.3.0";
            pdu.addOid(sysUpTime);
            Object result = pdu.getResponseVariable();

代码中用v2c,并假设community是public,超时时间1秒。获取sysUpTime也就是设备启动时间。如果有返回,认为节点存在且SNMP协议已启动。

 

 

本例子就ping这么多。如果做一个真正的综合设备网管,可以先获得设备的标识OID,判断其设备厂商和型号,然后加载对应设备支持的MIB进行复杂的监控。

 

多线程任务

由于一个网段需要ping的地址很多,一个线程会很慢。所以这个例子中使用很多线程并发进行。例如192.168.1.*里面有254个可能节点,就用10个线程去分头ping然后汇总。这个让人想起网络蚂蚁。于是就做了一个类似网络蚂蚁的界面。


其中,每个球是一个可能存在的节点地址。每个红色的球是一个线程正在ping这个节点。灰色的球是已经被ping过证明不存在或无法ping通的地址。绿色球是已经ping通,存在的节点。

 

通过调节线程的数量,可以掌握网络发现的速度。一般这254个节点,可以在30秒到60秒内完成。

 

拓扑呈现

拓扑呈现用TWaver就行了。每次发现一个存在的节点,往Network中new一个Node,设置一个图标即可。同时,在网段节点(一个云形图标的节点)和计算机节点创建一个连线。

 

同时,把拓扑图network组件的弹簧布局打开。这样,每次节点加入,都会像弹簧一样被自动布局到合适的位置,比较动感、有视觉效果。

 

            network.getSpringLayouter().setMovableFilter(new MovableFilter() {

                public boolean isMovable(Element element) {
                    return element != centerNode;
                }
            });
            network.getSpringLayouter().start();
            network.getSpringLayouter().setLinkRepulsionFactor(2);
 

 

 

另外,一旦ping通,我们在节点上就显示一个windows图标;如果snmp能ping通,再显示一个齿轮的图标。显示效果如下:


显示图标的代码很简单:

 

 

        ResizableNode node = new ResizableNode(ipaddress);
        node.setImage("/demo/main/snmp/images/node.png");
        node.addAttachment("winxp");
        node.putAttachmentPosition(TWaverConst.POSITION_TOPLEFT);
        if (snmpPingOK) {
            node.addAttachment("snmp");
        }
 

 

此外,可以通过windows的“net view hostname”的命令来查看一个机器的共享信息。我们做一个右键菜单,将执行命令结果显示出来:

 


显示结果如下:

 


 

结果显示,这台test计算机上有“move”、“SharedDocs”两个共享目录,以及三个共享打印机。实现的代码如下:

 

 

import java.io.*;

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

import twaver.*;
import twaver.network.*;

public class SnmpPopupMenuFactory implements PopupMenuFactory {

    private TNetwork network = null;

    public SnmpPopupMenuFactory(TNetwork network) {
        this.network = network;
    }

    public JPopupMenu getPopupMenu(DataBoxSelectionModel dataBoxSelectionModel, Point point) {
        if (network.getDataBox().getSelectionModel().size() == 1) {
            Element element = network.getDataBox().getSelectionModel().lastElement();
            if (element instanceof ResizableNode) {
                final Node node = (Node) element;

                JPopupMenu menu = new JPopupMenu();
                JMenuItem item = new JMenuItem("View this computer");
                item.addActionListener(new ActionListener() {

                    public void actionPerformed(ActionEvent e) {
                        String result = executeCommand("net view \\\\" + node.getName());
                        if (result != null && !result.trim().isEmpty()) {
                            JOptionPane.showMessageDialog(network, result);
                        } else {
                            JOptionPane.showMessageDialog(network, "No information available.");
                        }
                    }
                });
                menu.add(item);

                return menu;
            }
        }
        return null;
    }

    private static String executeCommand(String command) {
        try {
            Process p = Runtime.getRuntime().exec(command);
            InputStreamReader ir = new InputStreamReader(p.getInputStream());
            LineNumberReader input = new LineNumberReader(ir);

            String result = null;
            String line = input.readLine();
            while (line != null) {
                if (result == null) {
                    result = line;
                } else {
                    if (!line.trim().equalsIgnoreCase("")) {
                        result = result + "\n" + line.trim();
                    }
                }
                line = input.readLine();
            }
            return result;
        } catch (IOException ex) {
            ex.printStackTrace();
        }
        return null;
    }

    //可以用这两行代码来测试test机器的返回结果。
    public static void main(String[] args) {
        String result = executeCommand("net view \\\\test");
        System.out.println(result);
    }
}
 

 

 

链路探测与告警

在所有的节点被探索结束并放入界面后,我们可以起一个线程,周期性对每个节点进行ping。一旦无法ping通,生成告警,显示在拓扑图中。

 

 

Thread linkCheckThread = new Thread() {

            @Override
            public void run() {
                while (true) {
                    try {
                        Thread.sleep(3000);
                        if (!network.getDataBox().isEmpty()) {
                            Collection elements = network.getDataBox().getAllElements();
                            Iterator it = elements.iterator();
                            while (it.hasNext()) {
                                final Element element = (Element) it.next();
                                if (element instanceof ResizableNode) {
                                    final String ipaddress = element.getID().toString();
                                    final boolean pingOK = ping(ipaddress);

                                    SwingUtilities.invokeLater(new Runnable() {

                                        public void run() {
                                            Alarm alarm = new Alarm();
                                            if (!pingOK) {
                                                alarm.setAlarmSeverity(AlarmSeverity.CRITICAL);
                                            } else {
                                                if (element.getAlarmState().isEmpty()) {
                                                    return;
                                                }
                                                alarm.setAlarmSeverity(AlarmSeverity.CLEARED);
                                            }
                                            alarm.setElementID(ipaddress);
                                            alarm.setProbableCause(AlarmProbableCause.LINE_INTERFACE_FAILURE);
                                            box.getAlarmModel().addAlarm(alarm);
                                        }
                                    });
                                }
                            }
                        }
                    } catch (InterruptedException ex) {
                        ex.printStackTrace();
                    }
                }
            }
        };
        linkCheckThread.start();
    }

 

 将告警放置在一个告警表格中:



 同时,让告警表和拓扑图共享一个DataBox,于是告警就会在拓扑中显示:

 


 

最终效果以及源代码下载

这是用这个小程序探索我们办公室的网络结构。你的呢?也可以发上来看看!

 




源代码、第三方lib包、可执行包、run.bat都在附件中,请大家自行下载。请确保安装了JAVA 6。解压后双击run.bat即可。在弹出的对话框中点击start按钮即可进行网络自动发现。

 

源代码和可执行文件点击下载

 

GOOD LUCK & HAVE FUN!

 

 





作者: xiaozhonghua 
声明: 本文系JavaEye网站发布的原创文章,未经作者书面许可,严禁任何网站转载本文,否则必将追究法律责任!

已有 24 人发表回复,猛击->>这里<<-参与讨论


JavaEye推荐



Channel Image08:42 Internet Explorer 9 info leaked pre-launch» News | BuilderAU.com.au
Thanks to Microsoft's Russian subsidiary, the world now has a pretty good idea of what Internet Explorer 9 will look like.

Wed 25 August, 2010

Channel Image23:31 用if、Exception还是assert? ——看JavaEye论坛帖子:《面试题:用Exception异常还是if判断》的感想» JavaEye论坛最新精华讨论贴

用if、Exception还是assert? 
          ——看JavaEye论坛帖子:《面试题:用Exception异常还是if判断》的感想

今天在JavaEye上看到一个很火的帖子,题目是:面试题:用Exception异常还是if判断http://www.javaeye.com/topic/745640。内容大概是: 
    当参数不合法的时候,究竟是if else 判断后返回一个值还是直接来个Exception ? 
    如果是 if else 的话,有什么好处 ? 
    如果是 exception 的话,又有什么好处 ? 
    或者是根据不同情况来定?

 

实际中应该使用if else或Exception的都有,因而下面的回复也是各抒己见,很多人都有一定的道理。在此先列出几个个人觉得比较靠谱的回复:

 

1)sam_chi:看情况吧,如果能在方法里面处理不影响方法功能的话使用if else处理,如果参数错误导致方法不能正常工作,那么就得抛异常了,Java提供了java.lang.IllegalArgumentException,可以直接new一个抛出去,这是一个RuntimeException,不需要try..catch。

2)mercyblitz:if-else 方式的好处在于更贴近与逻辑思维,性能优于Exception。相对于Exception,其缺点是,不适合OOP,语义不明显,不易于错误错误跟踪或错误提示较少,并且类型比较单一(比如利用C语言的原生类型)或者难以统一(比如C语言结构和宏定义)。 
exception方法的好处在于是业务逻辑和异常处理分离(代码相对清晰),try中处理业务,catch中处理异常情况。在API设计中,可以设计Exception Handler来处理异常,使得层次分明。同时,更好的OOP的封装和多态性。缺点在于性能相对差。

3)fireaap:根据情况来选择使用if...else,还是exception。选择的依据就是,你的方法的职责。也可以说是你方法的契约。

4)konser:sam_chi 说的很对,首先大家要明白什么是异常,异常就是程序不能正常执行,错误的调用api的方法,资源失败等等原因 。程序逻辑又是一回事。为什么要把异常和逻辑判断混合起来做对比?  补充一下如果,当参数不合法的时候抛出IllegalArgumentException . 
如果是 if else 的话,有什么好处 ? 
参数不合法程序都不能正常执行了那你说有啥好处? 
如果是 exception 的话,又有什么好处 ? 
面向对象的思维,抛出每个异常时可根据不同异常以不同方式进行处理 。缺点是创建消耗内存,效率低。

5)IcedCoffee:java规范的定义是说异常不要参与控制流程,你不能把异常作为一种正常的控制流程作为程序的一部分,这样是不对的. 
面试官正真要考的就是这个.. 
没有哪个公司会用异常来处理参数验证..

6)liupopo:先要明确异常是做什么的,异常是程序执行时的一些非正常的情况,如果有不正确的数据、逻辑等会抛出异常。 
if else是逻辑判断,控制程序流程。 
断言是预先决断应该是什么样的值,不应该是什么类型等明确的条件 
从定义上不太好区分这些怎么去用,但可以从使用场景去考虑,下面的可能不十分准确,但大家可以参考一下: 
异常:是程序员写给其他程序员的,比方说我写了一个方法,如果其他人使用我这个方法的时候有可能不按我想的路子使用,可能会引起我的方法产生不正常的处理,这时候我要使用异常,而且可以决定产生异常了是由我这个方法本身进行处理(catch语句中处理)还是交给调用者进行处理(直接把异常抛出) 
if else 就是我写的方法,要进行逻辑判断,就用它,没什么好说的吧。 
断言可用处大了,是我在调用别人写的方法,或者我处理程序时对通过其他途径得到的数据(如调用别人方法的返回值(好象不建议用断言),通过参数传入的值)等进行假设它就是某值时使用的。是方便开发、调试提供的一个语法元素

7)JonyUabka:1对可预见的运行时异常当进行捕捉并处理,比如空指针等。通常,对空指针的判断不是使用捕捉NullPointException的方式,而是在调用该对象之前使用判断语句进行直接判断,如: 
//若不对list是否为null进行检查,则在其为null时会抛出空指针异常 

if(null != list && 0 < list.size()){ 
for(int i = 0; i &lt; list.size(); i++){ 
} 
} 

 2对于经常发生的可预计事件不要采用异常 
3不要使用异常实现控制结构。 
4通常的思想是只对错误采用异常处理:逻辑和编程错误,设置错误,被破坏的数据,资源耗尽,等等。 
对于java的编码规范,有具体描述。我想考官希望得到的是这个方向的答案。

8)maomaolingyu: 
if else 
优点: 
A 逻辑自己控制,清晰 
缺点: 
A 当情况复杂时 需要过多的if else .导致逻辑难以理解 
B 容易存在漏洞,导致错误或者不期望的结果 
Exception 
优点: 
A 异常通过异常链传播且会被jvm自动捕捉,减少逻辑代码 
B 异常很容易确定代码问题段,方便测试 
缺点: 
A 当异常发生但是没有正确捕获时,会异常抛出到用户页面.

引用了这么多,下面谈谈本人的一点看法,不对之处敬请指正。

首先,对于这个问题,不应该回答:用Exception异常或者if判断,而应该视情况决定使用Exception还是if。个人觉得面试官比较看重的是你对异常的理解,回答的关键应该是两者的优缺点与适用场合上,顺带可以总结一些实际中你是如何处理异常的。对于有人说使用断言(assert),还说是标准答案,本人实在不赞同。就像有人回答说:题目问的是:用Exception异常还是if判断,却来个用断言,太离题了吧。而且这么回答的话,下面的问题怎么答?难道直接说他们都不好,assert好?

本人觉得liupopo说的靠谱。下面结合一些代码来说明一些问题。

在此之前,就像不少人说的一样,先要明确什么是Exception。《The Java Language Specification 3.0》中有这么一句描述:When a program violates the semantic constraints of the Java programming language, the Java virtual machine signals this error to the program as an exception.大意是:当程序违反Java语言的语义约束时,Java虚拟机会将这个错误(异常)发送给程序。

下面进入正题:

 

1、看Java API如何处理的

本人查看了DateFormat、Integer等类的源码,分析如下:

1.1 public final String format(Date date)方法

该方法的参数date只有一种情况是不正确的:null。然而,阅读源码发现并没有判断该参数是否为null。那么当为null时(违反了Java语言的语义),异常NullPointerException自然而然是JVM自动抛出的,而不是API通过throw抛出的。

1.2 public static Integer valueOf(String s, int radix) throws NumberFormatException方法

该方法有两个参数,第一个String类型的参数有可能为null,而方法声明抛出NumberFormatException异常,因而有一段if判断: 
        if (s == null) { 
            throw new NumberFormatException("null"); 
        } 
对于radix参数有不少限制,因而源码中进行了几个if判断: 
        if (radix &lt; Character.MIN_RADIX) { 
            throw new NumberFormatException("radix " + radix + 
                            " less than Character.MIN_RADIX"); 
        }

        if (radix > Character.MAX_RADIX) { 
            throw new NumberFormatException("radix " + radix + 
                            " greater than Character.MAX_RADIX"); 
        } 
后面还有几个其他的判断。

总结:在论坛中,有人提到在JDK源码中,经常可以看见类似 
        if (s == null) { 
            throw new NumberFormatException("null"); 
        } 
这样的代码。借用liupopo的话,JDK中的方法是写给Java程序员调用的,而且Java有自己内部的一套异常机制。比如,对于format()方法抛出的NullPointerException异常,JDK不可能自己进行处理,出现该异常是:这个方法的时候没按它的路子使用,我们要使用该方法就应该确保传入的Date参数不是null,即需要进行if判断,对于valueOf方法一样如此,调用方法前就应该对是否为null值进行if判断(或确保非null)。从JDK的处理radix参数的方法我们可以看出,if经常与Exception结合使用,if用于处理逻辑,Exception用于告诉用户“有问题”。所以,如果你写的代码是给其他人使用的,你需要考虑是否抛出异常,这个时候对于参数的判断更多的应该使用JDK这种方式:对于非法参数,if判断后,包装为统一的Exception抛出,当然异常也有可能没必要抛出,而是自己处理(各种开源框架中都有这样情况)。

2、现在推荐的Java异常机制

Java API在设计的时候大量使用了checked exception,当时很推荐这种方式。然而,用过Spring的人都知道,Spring中大量使用了unchecked exception即RuntimeException。《thinking in java》的作者Bruce Echel说:“当少量代码时,checked异常无疑是十分优雅的构思,并有助于避免了许多潜在的错误。但是经验表明,对大量代码来说结果正好相反”。很多时候对于Java API抛出的异常,程序员都不知道怎么处理,比如SQLException,大家无非就是catch,然后e.printStackTrace(),或者什么也不做,这样导致程序中很多的try...catch...,既然如此,那么抛出checked exception就没有必要了,而且抛出uncheck exception不会污染接口。对于异常处理的问题很是有必要研究研究的。

既然推荐使用uncheck exception,结合Exception本身的特点,在写应用程序(不是提供给其他开发人员的API)时,参数的判断应该更多的使用if,毕竟参数错误的情况不会很多。

另外,有人建议使用Java自己的IllegalArgumentException异常进行判断,当然这有时候也是可行的。比如,有些时候参数没法活很难通过if来判断(因为我们不知道非法参数会是什么),这个时候可以使用该异常。

其实,很多时候,对参数合法性检查时,需要考虑方法的返回值。比如,接收一个Date类型参数,然后返回一个对应的String类型在页面上显示(该参数不是必须的,比如生日),这个时候,如果Date类型参数为null,我们不应该抛出NullPointerException,而应该用if进行如下判断: 
        if(date==null) { 
            return ""; 
        } 
返回空字符串很合理,这样页面就会显示空白(相当于没填)。其他情况,如返回值为boolean类型,很可能参数非法时会返回false。视具体情况而定。

3、关于断言(assert)

论坛回复中有人力推assert来判断参数合法性。不知道是没有理解题目的意思还是对断言本身不够了解,亦或对断言情有独钟。对于该面试题,可以肯定的是,用断言(assert)绝对是错误的。断言只是程序员测试和调试的一个工具,发布后一般也不会开启的,而且,Java规范也建议这么做。而参数是否合法性却总是存在的,所以用断言是完全错误的。

关于Java的assert,本人几乎没使用过,也很少见人使用。对程序进行测试或调试,更多的是使用JUnit或IDE的Debug功能,他们已经工作的很好了。在网上找到一篇很有意思的文章,他是痛批java的assert(Java陷阱之assert关键字),感兴趣的可以读读。个人不完全同意其观点,Java引入assert应该有其好处。

在这里把《The Java Language Specification 3.0》中对assert的描述做个总结。

3.1 assert的语法

assert语句有两种形式语法: 
    assert 表达式1 ; 
    assert 表达式1 : 表达式2 ; 
其中,表达式1的值必须是boolean类型,否则会编译错误。这里boolean类型有两种形式:1)var1==var2;2)var=true。其中第二种形式var是boolean类型,一般应该使用第一种形式,而第二种形式往往是程序员失误造成的。表达式2的值可以是除了void之外的任何类型。

3.2 断言开启时机

默认情况下断言没有开启。可以选择在哪个类上启用断言(断言启用没有继承性)。在类字段初始化器和静态初始化器初始化之前,类的类装载器根据配置决定启用或禁用断言。一旦类初始化完成后,断言的状态(启用或禁用)没法再改变。

然而,在规范中提到了一种例外情况:类层次结构之间的循环情况(实际中几乎不会这么做)。例子程序如下: 
        public class Foo { 
            public static void main(String[] args) { 
                Baz.testAsserts(); 
                // Will execute after Baz is initialized. 
            } 
        }

        class Bar { 
            static { 
                Baz.testAsserts(); 
                // Will execute before Baz is initialized! 
            } 
        }

        class Baz extends Bar { 
            static void testAsserts() { 
                boolean enabled = false; 
                assert enabled = true; 
                System.out.println("Asserts " + (enabled ? "enabled" : "disabled")); 
            } 
        } 
调试运行会发现,在assert没有开启的情况下,第一次输出的结果却显示assert已开启,当然第二条语句显示没有开启(注意,如果assert没开启,assert enabled = true这条语句是没有效果的)。这是说明,当断言语句在类初始化之前执行时,结果就如同开启了assert。(Polaris在调试时,发现一个奇怪的现象:就是第一次执行testAsserts方法时,单步调试,assert enabled = true;语句似乎跳过去了,不过结果却显示该语句是执行了的。很奇怪。)

关于断言的开启方法,控制台开启,IDE开启可以上网搜索,在此不介绍。

3.3 使用断言的注意事项

Java规范中对于断言语句的执行有详细的说明,有兴趣的朋友可以参阅http://java.sun.com/docs/books/jls/third_edition/html/statements.html#14.10

关于assert抛出的AssertionError,规范上建议不要捕获。

由于断言有可能没有开启,因而程序千万不要假定断言中的表达式会被计算,即不能依赖断言的结算结果(表达式1的值),所以,表达式1可能会产生一定的副作用:

1)assert语句虽然可以有副作用,如上面提到的表达式1返回boolean类型的第二种形式,然而这通常是不合适的,因为这可能导致依赖此值的程序在assert启用或禁用时有不同结果;

2)不要使用断言作为公共方法的参数检查,公共方法的参数永远都要执行。这一点可以很好的解释为什么说使用断言来判断参数合法性是错误的,尽管断言有时候可以用于检查传递给私有方法的参数。

Java规范在讲解断言副作用时,还说到了erroneous arguments should result in an appropriate runtime exception (such as IllegalArgumentException, IndexOutOfBoundsException or NullPointerException),即错误的参数检查应该产生一个合适的运行时异常。

在网上看到有总结使用断言的情况:(列举出来供参考)

1)可以在预计正常情况下程序不会到达的地方放置断言 :assert false  
2)断言可以用于检查传递给私有方法的参数。(对于公有方法,因为是提供给外部的接口,所以必须在方法中有相应的参数检验才能保证代码的健壮性)  
3)使用断言测试方法执行的前置条件和后置条件 
4)使用断言检查类的不变状态,确保任何情况下,某个变量的状态必须满足。(如age属性应大于0小于某个合适值)

3.4 Polaris的建议

要进行调试或测试,可以使用IDE或JUnit。

说了这么多希望对大家有个参考作用,同时不对之处敬请批评指正。



作者: polaris1119 
声明: 本文系JavaEye网站发布的原创文章,未经作者书面许可,严禁任何网站转载本文,否则必将追究法律责任!

已有 16 人发表回复,猛击->>这里<<-参与讨论


JavaEye推荐



Channel Image07:49 Google's licensing server hacked» News | BuilderAU.com.au
A licensing system meant to ensure Android applications were properly downloaded has been hacked, and Google is looking into the issue.
Channel Image07:45 Mozilla Firefox 4 beta 4 attacks tab mess» News | BuilderAU.com.au
Mozilla released its fourth beta of Firefox 4 on Tuesday, bringing a new feature that addresses one of the biggest challenges in the new era of the browser: reclaiming control over the burgeoning number of tasks that now happen on the web.

Tue 24 August, 2010

Channel Image09:12 OpenSolaris board disbands» News | BuilderAU.com.au
An end has come to a major part of Sun Microsystems' attempt to transform Solaris from a proprietary version of Unix to an open-source operating system built by others.

Mon 23 August, 2010

Channel Image07:54 Apple looks to kill jailbroken devices» News | BuilderAU.com.au
Apple is apparently ramping up its battle to prevent iPhone and iPod owners from jailbreaking their devices.

Sun 22 August, 2010

Fri 20 August, 2010

Channel Image12:22 去大公司还是去小公司工作?» DBA Notes

去大公司还是小公司工作?这个问题问大多数 IT 人都会选择前者。如果换一个问法,去大公司还是去初创公司(Startup)工作?或许有极小一部分人能改变一下决定。

对于 IT 人来说,选择到大公司工作的理由可能有:觉得"钱多人傻,干嘛不去?";有人认为 "机会多,有发展"; 也有人被小公司的眉毛胡子一把抓吓怕了,认为大公司"正规一些"。或是觉得"有挑战,起码几千万用户,数千台机器,数亿 PageView,想想就激动";也有人说,"有很多牛人在那,能学到东西"...

的确,大公司有很多好处,比如相对完善的工作流程;成熟的开发体系、培训体系;专人专事,不会让你身兼多职;旱涝保收,起码不会遇到开不出工资来,甚至福利也不错。有人说,够了,我们工作不就是为了这些么?不一定。相对完善的工作流程,意味着你的工作可能会是模式化的,革新就会少很多;成熟的开发体系、培训体系,未必能释放你的全部生产力,新技术也未必能尽快发挥威力;专业人做专业事,看起来是好事,但同时也意味着你的职业风险,专业分的越细,意味着你更依赖于整个体系,自己做不了什么事情,脱离这个环境可能一无是处;至于工资和待遇,大家应该明白,在当前的形势下,你依赖工资可能永远买不起房子,或者是一辈子的工资只能交给银行...

大公司的确有光环,要清楚那光环并不属于你的。大公司有牛人,可能牛人内心也很苦闷。大公司还有个很明显的优点就是能调动的资源的确大,是小公司没法比的。但一定要明确一点,那些资源如果不能被你所充分使用,其实和没有是一样的。在大企业做事情,似乎都是大项目,大战略,所以,芝麻绿豆大的事情也会提升到某个高度,召集一群人反复开会、讨论、分析、论证,对技术人的天性是一种无情的摧残。

回过头来,我们说小公司。如果小公司的老板是依赖于某些潜规则赚钱,那么你尽快想办法离开,在这样的小公司工作越久,对你伤害越大,除非你将来也要做个这样的小老板。如果你所在的公司虽小,但是有活力,有潜力,目光远大但不冒进。虽然短期内赚不到太多钱,但能让你受到很大的锻炼。绝对不要忘了在小公司发展,实际上你的潜在收益更大。记得王健硕说过类似如下的观点:小公司总有一天会淘汰现在的大公司,自己也会变成大公司,否则的话,我们现在看到的都是那些恐龙级的企业了。新陈代谢是这个社会发展的必然规律。如果你觉得对小公司尤其是初创公司有过失望的话,哪或许是你根本没去尝试做一些改变,你能确保你到了大公司一切都让自己变好?

隔岸观景是人的普遍心态,尤其是技术人员,始终觉得有些没有尝试过的环境会更好一些,比如在小公司做久了,一厢情愿的觉得大公司会很美好,但不知道加入到那些大公司后,你还会坚持自己当初的想法?你的短板在大公司得到了锻炼没有?你的技能提升了没有?你的视野得到了拓宽?人要想让自己变化,不能依赖于周围环境,驱动力来自自身,只能靠你自己。

最近在 丁香园 招聘技术人员的过程中,我发现了另外一个有趣的现象,很多人居然会选择去外包型公司(最离奇的一个理由是可以去练习外语)。在我看来,这似乎是最糟糕的职业途径了。在外包公司工作,公司把你发放到客户企业去(常常是那些大公司,因为自己的员工都去开会了,没有人写代码作一些边缘项目),工作起来的确压力不大,按照一些套路完成那些企业非核心业务就行了,不管你做什么事情,很难会产生归属感,你到底是属于哪家公司的人?你做的事情得到的回报,除了薪水还有其它的么?我的建议是,外包公司对你的职业生涯只会有害无益。

去小公司还是大公司? 亦或是去外包公司?我说的这些或许并不能改变有些人的固有想法,没关系,多想一下也是好的。

PS. 这篇文章是重读范凯的去跨国公司还是去创业公司?有感而发。

--EOF--
Channel Image08:44 Google's Chrome store open for developers» News | BuilderAU.com.au
Google's Chrome web store is now available for developers to test out, and the company confirmed a few details that leaked out earlier this week.
Channel Image08:39 Gmail search reaches into Google Docs» News | BuilderAU.com.au
Google is broadening the reach of Gmail Search with the help of a Labs tool that allows users to find emails and documents, the company announced on Wednesday.

Thu 19 August, 2010

Channel Image20:39 (六)用JAVA编写MP3解码器——帧数据结构» JavaEye论坛最新精华讨论贴

      MP3文件按帧(frame)依次存储,解码时也是逐帧解码,所以我们应该首先弄清MP3帧内的数据的封装形式。帧的结构如下图所示:

 

      图中sync表示帧同步字,每一帧以同步字开始;side info表示帧边信息;main_data_end是帧边信息结构中的一个成员。

      每一帧依次存放帧头(header)、帧边信息(side information)、主信息(main information)。其中的主信息由增益因子(scale factor)和主数据(main data)组成。

      每一帧的帧头长4字节,如果帧头信息中的protection_bit为零,帧头之后有2字节的循环冗余校检(CRC)数据。帧头在前面已经讲解过了,不再赘述。

      帧边信息的长度计算方法见《(二)用JAVA编写MP3解码器——帧头信息解码》内Header.java第207行。

      主信息的长度计算方法见《(二)用JAVA编写MP3解码器——帧头信息解码》内Header.java第218行。

      从上图可以看出,某一帧的主信息可能不在它本身这一帧内,可能有一部分存放在上一帧内,也有可能有一部分数据存放在下一帧内。例如上图中第3帧的主信息就存放在3个相邻的帧内、第1帧的数据包含相邻的三帧的主数据。这样使某些帧主信息太小的帧(如上图中的第1帧)不至于帧的长度太短,提高存储效率。

      对固定码率(CBR)编码的MP3,每一帧的长度几乎相等,由于有的帧有附加位而多一字节;对平均码率(ABR)编码的MP3,每一帧的长度也几乎相等;对变化码率(VBR)编码的MP3来说,每一帧的长度不同,长度相差会比较大,但最长的帧不超过1732字节。

      VBR编码的MP3的第一帧没有存储以哈夫曼编码的主数据,存储的是VBR编码信息,其中TOC域存放的是帧百分比和文件长度的对应关系,我们知道VBR编码的MP3每帧长度不一定相同,但每帧解码成为PCM数据长度是相同的,即每帧的播放时长相同。利用TOC域的数据就使文件按时间轴拖放播放时,定位到正确的文件位置成为可能。

      采用标准的立体声编码的帧,每一帧的主信息被划分为两个粒度组(granule),每个粒度组又分为两个声道(channel)。知道了MP3帧数据的封装形式,就不难理解后文中多处出现的for(gr...)和for(ch...)循环语句了。

 

      Layer3.java内的解码一帧MP3(MPEG 1.0/2.0/2.5 Audio Layer 3)定义的数据结构和初始化解码器用到的许多常量如下:

/*
* Layer3.java -- 解码Layer3
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
package decoder;

public final class Layer3 implements ILayer123 {
	private static Header objHeader;
	private static Synthesis objFilter;
	private static BitStream objInBitStream;
	private static int intWhichChannel;
	private static int intMaxGr;
	private static int intChannels;
	private static int intFirstChannel;
	private static int intLastChannel;
	private static int[] intSfbIdxLong;
	private static int[] intSfbIdxShort;
	private static boolean boolIntensityStereo;	//强度立体声:true

	public Layer3(BitStream bs,Header h, Synthesis filter, int wch) {
		objInBitStream = bs;
		objHeader = h;
		intChannels = objHeader.getChannels();
		intWhichChannel = wch;
		intMaxGr = (objHeader.getVersion() == Header.MPEG1) ? 2 : 1;
		objFilter = filter;
		objSI = new SideInfo();
		objHuffBits = new HuffmanBits(objInBitStream);
		objSideBS = new BitStream(36);
		scfL = new int[2][23];
		scfS = new int[2][3][13];
		is = new int[32 * 18 + 4];
		xr = new float[2][32][18];
		intWidthLong = new int[22];
		intWidthShort = new int[13];
		floatRawOut = new float[36];
		floatPrevBlck = new float[2][32][18];
		cs = new float[] { 0.857492925712f, 0.881741997318f, 0.949628649103f,
				0.983314592492f, 0.995517816065f, 0.999160558175f,
				0.999899195243f, 0.999993155067f };
		ca = new float[] { -0.5144957554270f, -0.4717319685650f,
				-0.3133774542040f, -0.1819131996110f, -0.0945741925262f,
				-0.0409655828852f, -0.0141985685725f, -0.00369997467375f };

		int i;
		/*
		 * floatPowIS[]:用于查表求 v^(4/3),v是经哈夫曼解码出的一个(正)值,该值的范围是0..8191
		 */
		floatPowIS = new float[8207];
		for (i = 0; i < 8207; i++)
			floatPowIS[i] = (float) Math.pow(i, 4.0 / 3.0);

		/*
		 * pow_2[] -- 用于查表求 2 ^ exp, exp 是长块(long block)逆量化的指数
		 */
		floatPow2 = new float[256 + 118 + 4];
		for (i = -256; i < 118 + 4; i++)
			floatPow2[i + 256] = (float) Math.pow(2.0, -0.25 * (i + 210));
		//initMDCT();
		if (intChannels == 2)
			switch (intWhichChannel) {
			case Decoder.CH_LEFT:
				intFirstChannel = intLastChannel = 0;
				break;
			case Decoder.CH_RIGHT:
				intFirstChannel = intLastChannel = 1;
				break;
			case Decoder.CH_BOTH:
			default:
				intFirstChannel = 0;
				intLastChannel = 1;
				break;
			}
		else
			intFirstChannel = intLastChannel = 0;

		//---------------------------------------------------------------------
		//待解码文件的不同特征用到不同的变量.初始化:
		//---------------------------------------------------------------------
		int intSfreq = objHeader.getSampleFrequency();
		intSfreq += (objHeader.getVersion() == Header.MPEG1) ? 0 : ((objHeader.getVersion() == Header.MPEG2) ? 3 : 6);
		/*
		 * ANNEX B,Table 3-B.8. Layer III scalefactor bands
		 */
		switch (intSfreq) {
		case 0:
			/* MPEG 1, sampling_frequency=0, 44.1kHz */
			intSfbIdxLong = new int[] { 0, 4, 8, 12, 16, 20, 24, 30, 36, 44,
					52, 62, 74, 90, 110, 134, 162, 196, 238, 288, 342, 418, 576 };
			intSfbIdxShort = new int[] { 0, 4, 8, 12, 16, 22, 30, 40, 52, 66,
					84, 106, 136, 192 };
			break;
		case 1:
			/* MPEG 1, sampling_frequency=1, 48kHz */
			intSfbIdxLong = new int[] { 0, 4, 8, 12, 16, 20, 24, 30, 36, 42,
					50, 60, 72, 88, 106, 128, 156, 190, 230, 276, 330, 384, 576 };
			intSfbIdxShort = new int[] { 0, 4, 8, 12, 16, 22, 28, 38, 50, 64,
					80, 100, 126, 192 };
			break;
		case 2:
			/* MPEG 1, sampling_frequency=2, 32kHz */
			intSfbIdxLong = new int[] { 0, 4, 8, 12, 16, 20, 24, 30, 36, 44,
					54, 66, 82, 102, 126, 156, 194, 240, 296, 364, 448, 550,
					576 };
			intSfbIdxShort = new int[] { 0, 4, 8, 12, 16, 22, 30, 42, 58, 78,
					104, 138, 180, 192 };
			break;

		case 3:
			/* MPEG 2, sampling_frequency=0, 22.05kHz */
			intSfbIdxLong = new int[] { 0, 6, 12, 18, 24, 30, 36, 44, 54, 66,
					80, 96, 116, 140, 168, 200, 238, 284, 336, 396, 464, 522,
					576 };
			intSfbIdxShort = new int[] { 0, 4, 8, 12, 18, 24, 32, 42, 56, 74,
					100, 132, 174, 192 };
			break;
		case 4:
			/* MPEG 2, sampling_frequency=1, 24kHz */
			intSfbIdxLong = new int[] { 0, 6, 12, 18, 24, 30, 36, 44, 54, 66,
					80, 96, 114, 136, 162, 194, 232, 278, 330, 394, 464, 540,
					576 };
			intSfbIdxShort = new int[] { 0, 4, 8, 12, 18, 26, 36, 48, 62, 80,
					104, 136, 180, 192 };
			break;
		case 5:
			/* MPEG 2, sampling_frequency=2, 16kHz */
			intSfbIdxLong = new int[] { 0, 6, 12, 18, 24, 30, 36, 44, 54, 66,
					80, 96, 116, 140, 168, 200, 238, 284, 336, 396, 464, 522,
					576 };
			intSfbIdxShort = new int[] { 0, 4, 8, 12, 18, 26, 36, 48, 62, 80,
					104, 134, 174, 192 };
			break;
		case 6:
			/* MPEG 2.5, sampling_frequency=0, 11.025kHz */
			intSfbIdxLong = new int[] { 0, 6, 12, 18, 24, 30, 36, 44, 54, 66,
					80, 96, 116, 140, 168, 200, 238, 284, 336, 396, 464, 522,
					576 };
			intSfbIdxShort = new int[] { 0, 4, 8, 12, 18, 26, 36, 48, 62, 80,
					104, 134, 174, 192 };
			break;
		case 7:
			/* MPEG 2.5, sampling_frequency=1, 12kHz */
			intSfbIdxLong = new int[] { 0, 6, 12, 18, 24, 30, 36, 44, 54, 66,
					80, 96, 116, 140, 168, 200, 238, 284, 336, 396, 464, 522,
					576 };
			intSfbIdxShort = new int[] { 0, 4, 8, 12, 18, 26, 36, 48, 62, 80,
					104, 134, 174, 192 };
			break;
		case 8:
			/* MPEG 2.5, sampling_frequency=2, 8kHz */
			intSfbIdxLong = new int[] { 0, 12, 24, 36, 48, 60, 72, 88, 108,
					132, 160, 192, 232, 280, 336, 400, 476, 566, 568, 570, 572,
					574, 576 };
			intSfbIdxShort = new int[] { 0, 8, 16, 24, 36, 52, 72, 96, 124,
					160, 162, 164, 166, 192 };
			break;
		}
		for (i = 0; i < 22; i++)
			intWidthLong[i] = intSfbIdxLong[i + 1] - intSfbIdxLong[i];
		for (i = 0; i < 13; i++)
			intWidthShort[i] = intSfbIdxShort[i + 1] - intSfbIdxShort[i];
		//---------------------------------------------------------------------
		//强度立体声:
		boolIntensityStereo = objHeader.isIStereo();
		if(boolIntensityStereo) {
			if (objHeader.getVersion() == Header.MPEG1)	//MPEG 1.0
				is_coef = new float[] { 0.0f, 0.211324865f, 0.366025404f, 0.5f,
						0.633974596f, 0.788675135f, 1.0f };
			else	//MPEG 2.0/2.5
				lsf_is_coef = new float[][] {
						{ 0.840896415f, 0.707106781f, 0.594603558f, 0.5f,
								0.420448208f, 0.353553391f, 0.297301779f,
								0.25f, 0.210224104f, 0.176776695f,
								0.148650889f, 0.125f, 0.105112052f,
								0.088388348f, 0.074325445f },
						{ 0.707106781f, 0.5f, 0.353553391f, 0.25f,
								0.176776695f, 0.125f, 0.088388348f, 0.0625f,
								0.044194174f, 0.03125f, 0.022097087f,
								0.015625f, 0.011048543f, 0.0078125f,
								0.005524272f } };
		}
		//-----------------------------------------------------------------
		//MPEG 2.0/2.5
		if (objHeader.getVersion() != Header.MPEG1) {
			i_slen2 = new int[256];		 // MPEG 2.0 slen for intensity stereo
			n_slen2 = new int[512];		 // MPEG 2.0 slen for 'normal' mode
			slen_tab2 = new byte[][][] {
					{ { 6, 5, 5, 5 }, { 6, 5, 7, 3 }, { 11, 10, 0, 0 },
							{ 7, 7, 7, 0 }, { 6, 6, 6, 3 }, { 8, 8, 5, 0 } },
					{ { 9, 9, 9, 9 }, { 9, 9, 12, 6 }, { 18, 18, 0, 0 },
							{ 12, 12, 12, 0 }, { 12, 9, 9, 6 },
							{ 15, 12, 9, 0 } },
					{ { 6, 9, 9, 9 }, { 6, 9, 12, 6 }, { 15, 18, 0, 0 },
							{ 6, 15, 12, 0 }, { 6, 12, 9, 6 }, { 6, 18, 9, 0 } } };
			int j, k, l, n;
			for (i = 0; i < 5; i++)
				for (j = 0; j < 6; j++)
					for (k = 0; k < 6; k++) {
						n = k + j * 6 + i * 36;
						i_slen2[n] = i | (j << 3) | (k << 6) | (3 << 12);
					}
			for (i = 0; i < 4; i++)
				for (j = 0; j < 4; j++)
					for (k = 0; k < 4; k++) {
						n = k + j * 4 + i * 16;
						i_slen2[n + 180] = i | (j << 3) | (k << 6) | (4 << 12);
					}
			for (i = 0; i < 4; i++)
				for (j = 0; j < 3; j++) {
					n = j + i * 3;
					i_slen2[n + 244] = i | (j << 3) | (5 << 12);
					n_slen2[n + 500] = i | (j << 3) | (2 << 12) | (1 << 15);
				}
			for (i = 0; i < 5; i++)
				for (j = 0; j < 5; j++)
					for (k = 0; k < 4; k++)
						for (l = 0; l < 4; l++) {
							n = l + k * 4 + j * 16 + i * 80;
							n_slen2[n] = i | (j << 3) | (k << 6) | (l << 9);
						}
			for (i = 0; i < 5; i++)
				for (j = 0; j < 5; j++)
					for (k = 0; k < 4; k++) {
						n = k + j * 4 + i * 20;
						n_slen2[n + 400] = i | (j << 3) | (k << 6) | (1 << 12);
					}
		}
	}

	// 解码帧边信息等代码...
}

  

      需要根据MP3编码的不同特征而初始化为不同常量的部分在构造函数内初始化,这些常量值及含义在手册内都查得到。用到的常量、变量很多,看了让人挠头。构造函数中的形参中有3个类class BitStream、class Header、class Synthesis变量,这三个类的实例先于class Layer的对象实例被初始化。解码的输入是位流对象(BitStream),解码输到PCM缓冲区(在class Synthesis内定义);形参int wch表示输出哪一个声道。

      解码的过程就是由经过编码的MP3文件还原成PCM数据,再将PCM数据送入音频输出模块播放。我把解码一帧MP3划分为10个步骤,这些步骤在下文中陆续写出来,各个步骤内也将谈到class Layer3内定义的变量,将下文解码帧边信息等各步骤的代码放在class Layer3内就封装好了解码一帧MP3的一个类了。

 

上一篇:(五)用JAVA编写MP3解码器——解析文件信息

下一篇:(七)用JAVA编写MP3解码器——解码帧边信息

 

【本程序下载地址】http://jmp123.sourceforge.net/



作者: lfp001 
声明: 本文系JavaEye网站发布的原创文章,未经作者书面许可,严禁任何网站转载本文,否则必将追究法律责任!

已有 10 人发表回复,猛击->>这里<<-参与讨论


JavaEye推荐



Channel Image20:14 Google Trends for Websites, now with less data» Antezeta Web Marketing
So who isn’t in love with Google’s competitive data tools for web marketing? Of all the sources of public web analytics, Google is potentially the most accurate. Why? No one else has the breadth of web data that Google has. Not comScore nor Nielsen. Not Hitwise. Not quantcast, compete nor Alexa. Forget their admittedly impressive [...]
Channel Image14:42 高仿真版山寨QQ之YY2010实现(谁说SWING做不了漂亮的UI)» JavaEye论坛最新精华讨论贴

    操作系统:Windows XP Prefessional SP2
    服务器软件:Openfire 3.6.4
    数据库:MSSQL server


    服务器安装过程请见 www.jivesoft.org
    YY客户端下载 http://yyn.googlecode.com/files/YY_windows_2_0_0.exe

    email:285264911@qq.com

    详细服务器搭建步骤:http://blog.ccidnet.com/blog-htm-do-showone-uid-2702-type-blog-itemid-13189376.html

大家帮忙提提意见,有什么好的功能可以应用到IM上,我们不仅要仿,而且要超越,要创新,所以需要一些独特的功能。至于大家强烈要求的开源,我会保留一些界面源码,将大部分功能代码都开放,大家一起协作,发挥自己的擅长,以插件形式嵌入,这样可以不会让系统肥大,目前我正在整理文档和API,大家请持续关注。有人说建议提供一个测试服务器用于交流,我也除了技术没什么钱,所以希望大家有服务器的能否联系我共享一下,谢谢!

YY版本日后我会每个月更新一次,希望大家持续关注!

大家在用网盘的时候,会一直都是等待界面,你可以注册一个share开头的帐号,部署在服务器上,其他客户端只要添加这个网盘为好友,就能登陆网盘,这个网盘机器人你添加后是不会显示在列表的。

持续更新:


08/25/2010 非常感谢网友[ ♡。衣襟耀眼 ] 提供一台服务器:http://im.honk.cc


































































以下是历史版本,2009版本的,其实ui设计也很别致,不过最终还是弃用了,原因是没人关注








 



作者: zzc0000 
声明: 本文系JavaEye网站发布的原创文章,未经作者书面许可,严禁任何网站转载本文,否则必将追究法律责任!

已有 183 人发表回复,猛击->>这里<<-参与讨论


JavaEye推荐



Channel Image08:27 Open-source devs irked by Oracle decision» News | BuilderAU.com.au
Following Oracle's decision to end open distribution of source codes for its Solaris enterprise operating system, the company now faces backlash from the open-source community which is likely to damage its relationship with developers in the long-run, industry watchers noted.
Channel Image08:01 Toshiba Android tablet set for September» News | BuilderAU.com.au
Digitimes has reported plans from Toshiba to produce an Android-based touchscreen tablet computer, due out in September or October of 2010.

Wed 18 August, 2010

Channel Image12:11 从main.c开始走进Ruby-登上调试Ruby之旅» JavaEye论坛最新精华讨论贴

我想更深入的了解Ruby内部的实现,出发点或许过于天真,

 

我想了解下这门语言的实现,从中或许可以学习到某些思路,
比如:

  • 如果我们要设计另外一种动态语言该如何去下手,
  • 如何将其他语言的特性融合进Ruby或者我们要设计的语言,
  • 特定领域的特定语言该如何设计(不要一门又广又全的语言,但又不是DSL)。


题目是《从main.c开始走进Ruby》,那我们需要以下的准备工作

  1. Ruby的源码,我采用的是ruby-1.9.2,
  2. gdb(神器)
  3. 一个IDE,eclipse(可选)

得到源码之后,首先要对其进行编译,不能采用默认的编译方式,

要在执行./configure的时候加上如下参数

 

  • CFLAGS="-ggdb -g3 -gdwarf-2"

或者在Makefile中添加如下标志参数:

  • optflags = -O
  • debugflags = -ggdb -g3 -gdwarf-2

这样编译的Ruby包含了充足的调试信息,同时还保留了Ruby源码中大量使用的宏信息,

比如我们在gdb中显示如下的宏:

(gdb) info macro RTEST
Defined at ./include/ruby/ruby.h:349
  included at /home/zheng.cuizh/ruby-1.9.2-rc2/ruby.c:18
#define RTEST(v) (((VALUE)(v) & ~Qnil) != 0)
 

如果编译参数没有添加,那么编译出来的代码是没办法看到macro信息的。

 

在编译好Ruby后,需要使用make install安装,安装好后就可以登上调试Ruby之旅了。

 

调试Ruby有几种方法,只要能在你知道的地方下个断点,并且Ruby解析器能够执行到该断点即可,

我想到以下两种方式:

  1. ruby -e "h=Hash.new"
  2. irb

对于第一种方式,我们直接使用gdb ruby启动即可,然后在gdb的console中设置属性及断点:

  • gdb ruby
  • set args -e "h=Hash.new"
  • b main

对于第二种方式,我们先执行irb,然后再开启一个终端,通过ps ax|grep irb|grep -v grep|awk '{print $1}' 找到irb的进程id,然后用gdb attach到该进程即可:

  • irb
  • gdb - <irb_pid>
  • b rb_hash_initialize
  • c
  • h=Hash.new#irb中输入

这样的话就会在初始化Hash对象的时候被断到,然后就可以继续调试了。

 

个人推荐采用第二种方式来调试Ruby解析器。

 

 

这里我要多说一下,第一种方式的最后一步b main的意思是对main()这个位于main.c中的入口函数。

 

(gdb) list main
18
19      RUBY_GLOBAL_SETUP
20
21      int
22      main(int argc, char **argv)
23      {
24      #省略
30
31          ruby_sysinit(&argc, &argv);
32          {
33              RUBY_INIT_STACK;
34              ruby_init();
35              return ruby_run_node(ruby_options(argc, argv));
36          }
37      }
(gdb)
Line number 38 out of range; main.c has 37 lines.

 

Ruby是一种解释型语言,它的代码都是没编译过的字符串,

  • 在Ruby解析器执行起来后,

vm_exec(th);

  • 通过读入Ruby代码文件,动态解析

    PREPARE_PARSE_MAIN({
        tree = load_file(parser, opt->script, 1, opt);
    });

从而完成边解析边执行的过程。第一种方法的main.c函数就是ruby解析器的入口,当我们在那里下断点后,必定能将Ruby断下来。

在main.c35行这句话:return ruby_run_node(ruby_options(argc, argv));

我们需要在gdb里面通过键入s跟进两次,ruby_run_node和ruby_options这两个函数都需要步入,他们都很重要。其中ruby_run_node会调用vm_exec来执行语法树。

 

 

至此,我们的准备工作就完成了。很简单的步骤,却很重要。

 

之后的工作就是不断的跟踪Ruby各种类型的实现机制,简单的描述就是在irb里面不停尝试各种类型的new操作,同时在gdb里面不断的对不同类型rb_object_initialize下断点,然后步入每一个想知道的方法中,对某些变量设置观察点,打印出结构体,再步进。

 

真感谢Ruby有个irb这么好用的工具,也是由于动态语言的缘故,代码可以通过eval来执行。

有了irb,我们就可以交互式调试没段代码了。

 

 

 

 

这是走进Ruby的开始,我也刚刚做好准备工作,很多Ruby内部的实现我还没有读懂,我边读边写,不过我要在我读懂了之后再写出来,由于悟性的问题,我可能写的很慢或者有错误的地方,而且思路可能还会很跳跃^-^,希望高手能指点一下。

 


Ruby有好多预定以变量,多的让我不可能记住,这里有个网页,记录了和Ruby有关的一些关键字:
http://www.tutorialspoint.com/ruby/ruby_predefined_constants.htm

我们在使用Ruby的argv参数数组时,知道$0是当前进程的程序名称,
比如我们在启动的irb中执行$0,就会返回"irb",
这个$0是在这里被保存的:

rb_argv0 = rb_str_new4(rb_progname);



rb_progname是什么,debug跟踪进去可以看到如下定义:

#define rb_progname (GET_VM()->progname)
#define GET_VM() ruby_current_vm



这俩段宏可以得到当前Ruby解析器的名字,这个名字是保存在下面这个结构体中的,

下面这个是ruby vm_core.h中的解析器结构体,

typedef struct rb_vm_struct {
    VALUE self;

    rb_thread_lock_t global_vm_lock;

    struct rb_thread_struct *main_thread;
    struct rb_thread_struct *running_thread;

    st_table *living_threads;
    VALUE thgroup_default;

    int running;
    int thread_abort_on_exception;
    unsigned long trace_flag;
    volatile int sleeper;

    /* object management */
    VALUE mark_object_ary;

    VALUE special_exceptions[ruby_special_error_count];

    /* load */
    VALUE top_self;
    VALUE load_path;
    VALUE loaded_features;
    struct st_table *loading_table;

    /* signal */
    struct {
    VALUE cmd;
    int safe;
    } trap_list[RUBY_NSIG];

    /* hook */
    rb_event_hook_t *event_hooks;

    int src_encoding_index;

    VALUE verbose, debug, progname;
    VALUE coverages;

    struct unlinked_method_entry_list_entry *unlinked_method_entry_list;

#if defined(ENABLE_VM_OBJSPACE) && ENABLE_VM_OBJSPACE
    struct rb_objspace *objspace;
#endif
} rb_vm_t;




和python等其他脚本语言类似,脚本语言无法真实的实现native thread来做并行运算,可以通过下面这个成员看出端倪:

全局解释器锁

rb_thread_lock_t global_vm_lock;

 

在调试过程中要善用bt这个命令,通过bt我们可以看到某个函数的调用栈,配合eclipse等IDE(没有IDE的话就用layout查看代码),可以很好的分析出代码的走向,一个实例创建的过程。

 

比如在以irb方式调试的时候,刚刚断入irb的进程,执行bt后看如下输出:

 

(gdb) bt
#0  0x000000304360d5cb in read () from /lib64/libpthread.so.0
#1  0x0000000000527803 in rb_thread_blocking_region (func=0x42b0e0 <internal_read_func>, data1=0x7fff42460860, ubf=0x527960 <ubf_select>, data2=0xb58bf0) at thread.c:1117
#2  0x000000000042bae8 in io_fillbuf (fptr=0xe88a40) at io.c:587
#3  0x0000000000437004 in rb_io_getbyte (io=<value optimized out>) at io.c:3101
#4  0x0000000000517ee5 in vm_call0 (th=0xb58bf0, recv=15222400, id=1928, argc=0, argv=0x0, me=0xc28600) at vm_eval.c:78
#5  0x000000000051b27b in rb_funcall (recv=15222400, mid=0, n=0) at vm_eval.c:234
#6  0x00002aaaaaeb29bd in readline_getc (input=0x3042d516a0) at readline.c:120
#7  0x0000003043e25eba in rl_read_key () from /usr/lib64/libreadline.so.5
#8  0x0000003043e14921 in readline_internal_char () from /usr/lib64/libreadline.so.5
#9  0x0000003043e14d65 in readline () from /usr/lib64/libreadline.so.5
#10 0x000000000041773d in rb_protect (proc=0x2aaaaaeb2940 <readline_get>, data=15701216, state=0x7fff42460e6c) at eval.c:718
#11 0x00002aaaaaeb28ae in readline_readline (argc=2, argv=<value optimized out>, self=<value optimized out>) at readline.c:255
#12 0x000000000050cc95 in vm_call_method (th=0xb58bf0, cfp=0x2af8e53e16c8, num=2, blockptr=0x1, flag=8, id=0, me=0xe217b0, recv=15222560) at vm_insnhelper.c:401
#13 0x00000000005103b7 in vm_exec_core (th=0xb58bf0, initial=<value optimized out>) at insns.def:1006
#14 0x0000000000516c99 in vm_exec (th=0x0) at vm.c:1145
#15 0x000000000051c3c8 in invoke_block_from_c (th=0xb58bf0, block=0xe9d9c0, self=15225000, argc=0, argv=0x0, blockptr=0x0, cref=0x0) at vm.c:557
#16 0x000000000051c77f in rb_vm_invoke_proc (th=0xb58bf0, proc=0xe9d9c0, self=15225000, argc=0, argv=0x2af8e52e2250, blockptr=0x0) at vm.c:603
#17 0x000000000050cc95 in vm_call_method (th=0xb58bf0, cfp=0x2af8e53e18d8, num=0, blockptr=0x1, flag=0, id=0, me=0xc52000, recv=15307040) at vm_insnhelper.c:401
#18 0x00000000005103b7 in vm_exec_core (th=0xb58bf0, initial=<value optimized out>) at insns.def:1006
#19 0x0000000000516c99 in vm_exec (th=0x0) at vm.c:1145
#20 0x000000000051c3c8 in invoke_block_from_c (th=0xb58bf0, block=0x2af8e53e1c18, self=15213640, argc=0, argv=0x0, blockptr=0x0, cref=0x0) at vm.c:557
#21 0x000000000051c888 in loop_i () at vm.c:587
#22 0x00000000004179a9 in rb_rescue2 (b_proc=0x51c850 <loop_i>, data1=0, r_proc=0, data2=0) at eval.c:646
#23 0x00000000005092e9 in rb_f_loop (self=15213640) at vm_eval.c:816
#24 0x000000000050cc95 in vm_call_method (th=0xb58bf0, cfp=0x2af8e53e1bf0, num=0, blockptr=0x2af8e53e1c19, flag=8, id=0, me=0xbe21a0, recv=15213640) at vm_insnhelper.c:401
#25 0x00000000005103b7 in vm_exec_core (th=0xb58bf0, initial=<value optimized out>) at insns.def:1006
#26 0x0000000000516c99 in vm_exec (th=0x0) at vm.c:1145
#27 0x000000000051c3c8 in invoke_block_from_c (th=0xb58bf0, block=0x2af8e53e1d20, self=15213640, argc=1, argv=0x0, blockptr=0x0, cref=0x0) at vm.c:557
#28 0x000000000051c82e in catch_i (tag=4080910, data=<value optimized out>) at vm.c:587
#29 0x00000000005084fd in rb_catch_obj (tag=4080910, func=0x51c7f0 <catch_i>, data=0) at vm_eval.c:1522
#30 0x0000000000509198 in rb_f_catch (argc=<value optimized out>, argv=<value optimized out>) at vm_eval.c:1498
#31 0x000000000050cc95 in vm_call_method (th=0xb58bf0, cfp=0x2af8e53e1cf8, num=1, blockptr=0x2af8e53e1d21, flag=8, id=0, me=0xbe1e20, recv=15213640) at vm_insnhelper.c:401
#32 0x00000000005103b7 in vm_exec_core (th=0xb58bf0, initial=<value optimized out>) at insns.def:1006
#33 0x0000000000516c99 in vm_exec (th=0x0) at vm.c:1145
#34 0x000000000051c3c8 in invoke_block_from_c (th=0xb58bf0, block=0x2af8e53e1ed8, self=12129600, argc=1, argv=0x0, blockptr=0x0, cref=0x0) at vm.c:557
#35 0x000000000051c82e in catch_i (tag=3247374, data=<value optimized out>) at vm.c:587
#36 0x00000000005084fd in rb_catch_obj (tag=3247374, func=0x51c7f0 <catch_i>, data=0) at vm_eval.c:1522
#37 0x0000000000509198 in rb_f_catch (argc=<value optimized out>, argv=<value optimized out>) at vm_eval.c:1498
---Type <return> to continue, or q <return> to quit---
#38 0x000000000050cc95 in vm_call_method (th=0xb58bf0, cfp=0x2af8e53e1eb0, num=1, blockptr=0x2af8e53e1ed9, flag=8, id=0, me=0xbe1e20, recv=12129600) at vm_insnhelper.c:401
#39 0x00000000005103b7 in vm_exec_core (th=0xb58bf0, initial=<value optimized out>) at insns.def:1006
#40 0x0000000000516c99 in vm_exec (th=0x0) at vm.c:1145
#41 0x0000000000516f74 in rb_iseq_eval_main (iseqval=12097560) at vm.c:1386
#42 0x0000000000417bcf in ruby_exec_internal (n=0xb89818) at eval.c:214
#43 0x000000000041a396 in ruby_run_node (n=<value optimized out>) at eval.c:261
#44 0x0000000000416e5d in main (argc=2, argv=0x7fff424632c8) at main.c:35

 

第一行read函数就是irb和使用者交互的方法,

最后一行的main就是Ruby解析器的入口函数,

从下到上就是Ruby解析器整个执行过程。

不过我还没弄明白loop_i这个函数的作用。

 

先写到这里,下一章我也不知道会写什么,因为前提是我看懂了什么^-^



作者: CharlesCui 
声明: 本文系JavaEye网站发布的原创文章,未经作者书面许可,严禁任何网站转载本文,否则必将追究法律责任!

已有 8 人发表回复,猛击->>这里<<-参与讨论


JavaEye推荐



Tue 17 August, 2010

Channel Image23:17 我们要写怎么样的系统» JavaEye论坛最新精华讨论贴
编程也好久了。越来越觉得一个做一个好的程序员不是一件简单的事情,只有努力努力再努力。
经常看到一些很烂的代码,总是想着怎么要把它改善一下。今天,看了javaeye的你的代码写的很烂的帖子,心里一阵的寒啊。有那么多的人在说:

在没有了解整个程序的解决方案之前,你不可能就那么轻易的判断代码的好和坏。
在没有了解清楚前请不要轻易下结论,任何一段代码能跑起来都有它的一定道理。
恩,得用历史的发展的观念来看代码,不排除有特别的烂的代码,但是除非你读懂了整个过程,否则不要仅仅针对一段代码评价它的好坏。
千万别轻易否定别人的设计! 也许他考虑的东西比你想的多得多, 我们不能太自我了。
你几乎无法在短时间、局部的环境中体会到10年前编写这段代码的人的思路。
是的,每个人都有自己的设计思路……不应该轻易的去否定别人的作品!!

对,我承认上面说的都有道理。

好吧,那我们程序员是干什么的,我们为什么做程序员,如果有一天我们看到自己的系统都是一脸惭愧,那作为程序员自身的意义何在呢。

我很同意在没有了解整个程序的解决方案之前,你不可能就那么轻易的判断代码的好和坏。
但是事分两说,如果一个成熟的程序员不能很快的看出一个系统的大概架构,那这个系统就是有问题的。很多时候,要费劲心机才明白一小段程序的意义,为什么,因为代码不好,没有自我解释性。本来看代码应该是只看一个架构,看一个领域模型,就差不多了,基本上靠猜应该八九不离十了。但是实际生活中呢,太多方法名和内容不符合的了,搞的人都怕怕了。

代码应该还是有好坏之分的吧,你看到一个7层的try catch不晕,你看到一个17个参数的方法不晕,你看到一个方法前条件约束2,3页的不晕,ok,我服了你了。

即使是整个程序的解决方案之前,你不可能就那么轻易的判断代码的好和坏。这句我还是有保留的,一个类写在哪里,一个方法出现在哪里,都是有意义的,一段代码的好坏,的确依赖于对整个方案的理解,但是,同样的,一段好的代码,本身就应该可以解释很多事情。

常见的理由,有时间紧,资源有限,功能性的需求优先级高,历史条件(这个听的最多了)。我们不是有持续重构的利器吗,不要觉得一个方法的注释,一个变量的名字,不重要,想想看,写代码和读代码的时间的比例,我们一天写几行代码,一天要读多少行代码,当你读着读着想打人,骂人的时候,多想想自己不要做这样的人。

看了这个帖子有点激动,有些言语过激了。



作者: zhang_xzhi_xjtu 
声明: 本文系JavaEye网站发布的原创文章,未经作者书面许可,严禁任何网站转载本文,否则必将追究法律责任!

已有 17 人发表回复,猛击->>这里<<-参与讨论


JavaEye推荐



Channel Image19:56 Good Help is Hard to Find» A List Apart
Help content gets no respect. For one thing, it is content, and our horse-before-cart industry is only now beginning to seriously tackle content strategy. For another, we assume that our site is so usable, nobody will ever need the help content anyway. Typically, no one is in charge of the help content and no strategy exists to keep it up to date. On most sites, help content is hard to find, poorly written, blames the user, and turns a mildly frustrating experience into a lousy one. It's time to rethink how we approach this part of our site. Done well, help content offers tremendous potential to earn customer loyalty. By learning to plan for and create useful help content, we can turn frustrated users into our company's biggest fans.
Channel Image19:56 Apps vs. the Web» A List Apart
There's an app for that, and you're the folks who are creating it. But should you design a web-based application, or an iPhone app? Each approach has pluses and minuses—not to mention legions of religiously rabid supporters. Apple promotes both approaches (they even gave the web a year-long head start before beginning to sell apps in the store), and the iPhone's Safari browser supports HTML5 and CSS3 and brags a fast JavaScript engine. Yet many companies and individuals with deep web expertise choose to create iPhone apps instead of web apps that can do the same thing. Explore both approaches and learn just about everything you'll need to know if you choose to create an iPhone app—from the lingo, to the development process, to the tricks that can smooth the path of doing business with Apple.
Channel Image13:05 The First Day of School» Raible Designs
Summer isn't over, but my kids' summer vacation is. Today, Abbie and Jack went to their first day of school for the year. I've never seen them more excited, except maybe on Christmas or their birthdays. While taking pictures this morning, I told Jack to smile like he was playing Wii. I was expecting a huge smile, but instead got the pose below. :)

Pretend like you're playing Wii Jack!

I remember loving the first day of school when I was a kid. It's great to see Abbie and Jack doing the same.

Abbie and Jack, entering 1st and 2nd grade

I especially like the thought of the things that follow the beginning of the school year: Broncos Football, DU Hockey and (my favorite) Ski Season. It's gonna be a great year.

Related: The First Day of School, 3 years ago.

Channel Image12:03 Microsoft Internet Explorer 15 years on» News | BuilderAU.com.au
Thanks to corporate use and ties to Windows, Internet Explorer has remained dominant in the browser space ever since it won the first browser wars with Netscape a decade ago.
Channel Image07:30 Next Ubuntu to have multi-touch» News | BuilderAU.com.au
The next version of Ubuntu will get multi-touch interface abilities, catching the Linux operating system up to Windows and Mac OS X in at least one domain.

Mon 16 August, 2010

Channel Image21:01 谁是下个JVM王者语言?» JavaEye论坛最新精华讨论贴
                                                          The next big JVM language?

There’s an interesting thread of comments related to a blog post by Stephen Colebourne, who is giving a talk at this year’s JavaOne entitled “Next Big JVM language.” In particular, he and others note that the Fantom language could be the answer (I find this interesting as Fantom really wasn’t even on my radar. Until now.). Moreover, many of the threads claim Scala to be the next big language. It seems people still prefer static typing over dynamic-ness. Either way, I got the distinct impression, based upon those individuals that left comments, which, by no means reflects the community at large, that Groovy isn’t it.

Principally, the arguments against Groovy can be summarized as its lack of performance (compared to Scala, for instance). Not to be outdone, a few folks brought up Groovy++ (which attempts to add a bit of static-ness to Groovy ostensibly to increase performance). Nevertheless, the comments are quite interesting to read if for anything that Fantom is gaining mind share perhaps at the cost of other more mainstream alternatives like Groovy.

 

    这几年,关于谁是下一个JVM上的王者语言之争已经不是新鲜事情了。看了上面的文章,您是不是又所感慨呢?

 

笔者对部分上述语言接触过,下面发表一下意见:

 

Groovy:在这些上述语言中,我认为它是最接近于Java的语言,并且提供了闭包和动态类型等特性。学习曲线不陡峭,并且学习资源很多。可惜性能让人失望,因此,一直不被笔者考虑作为实现语言。当年的GoG就是模仿RoR来实现快速Web开发,方便地解析XML文件,集成Spring和ORM,这些让SSHer欣喜不易。本人虽然没有深入研究,不过规约大于配置的开发模式,着实让我眼前一亮。不过,动态语言Groovy并没有让我看到它的光明的未来。说到Groovy的未来,当然还看SpringSource发展路线。也许正如上文所说,Groovy++才是正确的方向。

 

Scala:这门函数型语言能够让Groovy发明者后悔,能够让twitter放弃Ruby(至少部分放弃),转而拥抱它。这些足以证明Scala是一门优秀语言,静态类型安全,提供闭包功能,简化并发编程,弥补Java的不足。不过它的语法确实比较晦涩,我想这就是它最大的伤痛。Scala前途还是比较光明的。

 

Fantom:实际上,笔者还没有接触过,只是看了下:http://fantom.org/doc/docIntro/WhyFantom.html 。它给我第一个感觉是,像雾像雨又像风(C++、Java和其他语言的混合体)。先不说它是否简单,不过它还是有特斯色的。当年,Java号称跨平台的语言,虽然目前.NET可以跨平台(如果您的是HelloWorld工程的话,不必担心风险)。可是Fantom确实跨语言加跨平台的,这个是很强大啦。目前,Java和.NET的跨语言不过是建立在JVM和CLR平台上。Java和.NET两大平台通讯还是需要靠第三方协议(比如WebServices)。Fantom其他的方面没有什么特色,并且学习资源太少。

 

Clojure:第一次知晓这门语言,是通过看庄周梦蝶 的Clojure的blog。本人正在学习中,这门语言让我想起了“伊波兰式”(操作符号在操作数之前),并且执行语句皆在"()"里面,真如其名-封闭(其实没有这个英文单词,只有closure)。尽管这门函数语言,语法上非常不贴近于Java,但是简洁的代码足以替代臃肿的Java代码,即使和Scala比较,也不是同一个级别的。不过和Scala一样,提供优雅的函式和并发编程,不过纠结的语法确实不适合传统C语言系的开发人员。不过我还是比较看好它,试想一旦掌握后,那是多么“暴力”的语言。

 

JavaScript:从感情和熟悉程度上,笔者偏好JS。众所周知,JS作为浏览器内建语言,提供了灵活的前端页面控制。语法上,从普通的函数编程发展到prototype和闭包,并且通俗易懂;功能上,从DOM操作,到Ajax,再到目前的WebSocket;实现上,从客户端程序到服务端程序。目前的Node.js(在V8平台)可以实现服务端I/O操作,那么,实现Web服务端不在话下。在Web2.0时代,熟悉JS的人要比熟悉Java人还要多。同时,Java 6.0提供了JavaScript解释引擎,.NET就更不用说了。综上分析,下一个JVM王者语言很有可能就是JavaScipt。

 

Java:这么多年来,Java是最成功的语言之一。无论哪门语言都有缺点,然而越是出名,越是招骂。Java 7的闭包、模块化和GC优化等功能能不能成功地拯救Java,就看JVM产商的啦。我相信,大家都期待Java能过王者回归。

 

似乎上述语言都有希望称为JVM的王者语言。从JVM实现来看,JVM还是非常强大的。

 

下文推崇Groovy和JRuby,以供参考:

 

                                                                                  Top five scripting languages on the JVM
   Groovy and JRuby lead a strong field, with Scala, Fantom, and Jython following behind

 

   Anyone who has followed software development tools during the last decade knows that the term "Java" refers to a pair of technologies: the Java programming language and the Java Virtual Machine (JVM). The Java language is compiled into bytecodes that run on the JVM. Through this design, Java delivers its vaunted portability.

The language and the JVM, however, have been increasingly moving in opposite directions. The language has grown more complex, while the JVM has become one of the fastest and most efficient execution platforms available. On many benchmarks, Java equals the performance of binary code generated by compiled languages such as C and C++. The increasing complexity of the language and the remarkable performance, portability, and scalability of the JVM have created an opening for a new generation of programming languages. These languages lack Java's syntax overload (often referred to disparagingly as its "ceremony") -- that is, the amount of excess code that needs to be cranked out before the code that does the actual work can be written -- but take advantage of the JVM.

......

 

 

PS:笔者学识和技术有限,不足的地方请指正。

 



作者: mercyblitz 
声明: 本文系JavaEye网站发布的原创文章,未经作者书面许可,严禁任何网站转载本文,否则必将追究法律责任!

已有 50 人发表回复,猛击->>这里<<-参与讨论


JavaEye推荐



Channel Image14:14 Oracle puts OpenSolaris to rest» News | BuilderAU.com.au
Software giant Oracle has closed the book on developer distributions of the open enterprise operating system, OpenSolaris.
Channel Image08:34 基本的封装---Ajax之一» JavaEye论坛最新精华讨论贴

Ajax,或许已经是老掉牙的话题。我学习总结一下。大概会有6篇,从基本的ajax直至高级的ajax应用。最后会形成一个实用的Ajax工具库。创建一个基本的Ajax应用不需要太多的代码,大概三个步骤。

 

1,创建Ajax的核心对象XMLHttpRequest


因为浏览器之间的不兼容,IE7之前的版本并没有原生的XMLHttpRequest对象却实现为ActiveX对象。
互联网及各种书籍中有着多种创建方式,有的复杂很多行代码,有的则简洁很少代码。当然复杂的考虑的情形更多一些。
如下几乎将IE中所有的情况都考虑到了

 

function cretaeXHR(){
	try{ return new XMLHttpRequest();}catch(e){}
	try{ return new ActiveXObject('Msxml2.XMLHTTP.6.0');}catch(e){}
	try{ return new ActiveXObject('Msxml2.XMLHTTP.4.0');}catch(e){}
	try{ return new ActiveXObject('Msxml2.XMLHTTP.3.0');}catch(e){}
	try{ return new ActiveXObject('Msxml2.XMLHTTP');}catch(e){}
	try{ return new ActiveXObject('MSXML3.XMLHTTP');}catch(e){}
	try{ return new ActiveXObject('MSXML.XMLHTTP');}catch(e){}
	try{ return new ActiveXObject('Microsoft.XMLHTTP');}catch(e){}
	try{ return new ActiveXObject('MSXML2.ServerXMLHTTP');}catch(e){}
	return null;
}

 

代码较少的采用对象特性判断,

 

var xhr = window.XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject('Microsoft.XMLHTTP');

这里采用精简方式,暂不考虑创建异常的情况。

 

2,发送请求

xhr.open
xhr.send

 

3,处理响应

xhr.onreadystatechange = function(){
	if(xhr.readyState == 4){
		if(xhr.status == 200){//当然你可以把200~300之间或304的都理解成响应成功
			//callback
		}
	}
}
 

嗯,写到这里没什么特别的,所有的书籍上都是这么几个步骤来着。对于初学者来说,要将这几个步骤很好的封装一下形成一个良好的模块还是很困难的。全局变量满天飞不知道怎么去组织代码,偶刚学的时候就是这样的。现在想想是对一门语言没有足够的掌握,尤其是闭包。

这里采用 单例模式 封装成一个对象,即只有一个全局的变量将其赋值给Ajax,该对象有一个request方法。
request有两个参数,第一个为请求的url(必要的),字符串类型,第二个opt为配置参数(可选的),对象类型。
结果处理使用内部私有的_onStateChange函数。

完整代码如下

 

/**
 * 执行基本ajax请求,返回XMLHttpRequest
 * Ajax.request(url,{
 * 		async 	是否异步 true(默认)
 * 		method 	请求方式 POST or GET(默认)
 * 		data 	请求参数 (键值对字符串)
 * 		success 请求成功后响应函数,参数为xhr
 * 		failure 请求失败后响应函数,参数为xhr
 * });
 *
 */
var Ajax = 
function(){
	function request(url,opt){
		function fn(){}
		var async   = opt.async !== false,
			method  = opt.method 	|| 'GET',
			data    = opt.data 		|| null,
			success = opt.success 	|| fn,
			failure = opt.failure 	|| fn;
			method  = method.toUpperCase();
		if(method == 'GET' && data){
            url += (url.indexOf('?') == -1 ? '?' : '&') + data;
			data = null;
        }
		var xhr = window.XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject('Microsoft.XMLHTTP');
		xhr.onreadystatechange = function(){
			_onStateChange(xhr,success,failure);
		};
		xhr.open(method,url,async);
		if(method == 'POST'){
			xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded;');
		}
		xhr.send(data);
		return xhr;	
	}
	function _onStateChange(xhr,success,failure){
		if(xhr.readyState == 4){
			var s = xhr.status;
			if(s>= 200 && s < 300){
				success(xhr);
			}else{
				failure(xhr);
			}
		}else{}
	}
	return {request:request};	
}();
 

如下请求后台的一个servlet,发送参数name=jack,age=20,默认使用异步,GET方式。

 

Ajax.request('servlet/ServletJSON',{
		data : 'name=jack&age=20',
		success : function(xhr){
			//to do with xhr
		},
		failure : function(xhr){
			//to do with xhr
		}
	}
);
 

以上是一个简单封装,用了不到40行的代码。这里的请求参数data只能是键值字符串,有时候希望可以js对象,以便可以更灵活的传参,下一篇将从改善参数开始。

 





作者: zhouyrt 
声明: 本文系JavaEye网站发布的原创文章,未经作者书面许可,严禁任何网站转载本文,否则必将追究法律责任!

已有 17 人发表回复,猛击->>这里<<-参与讨论


JavaEye推荐



Sun 15 August, 2010

Channel Image22:09 Web 产品的改进» DBA Notes

这一段时间来,一直在考虑快速改进产品的事情。Web 产品的改进是个麻烦事情。远不是收集一大推需求列表,然后三下五除二修改上线那么简单。

产品的改进,也是个不停作选择的过程,不要被用户牵着走,尤其注意那些专家型用户的意见,是很好的参考,要尊重他们,尽快给他们反馈,但未必要全盘采纳。如果真的存在易用性问题,用户会从更广范的角度不停的反馈给你。不能看竞争对手的,因为他们可能在跟你学呢。

改进的前后,数据收集的丰富性将影响最后改进的结果。但不能完全跟着数据走,因为你收集的数据可能不是准确的。此外要记住,不要事先定一些数据上的指标,比如注册增长多少啦,流失率减少多少啦,没有意义。有些改进,内部数据上未必会好看,但用户会叫好。要知道,越大的公司,数据越是用来唬人的。反正决策层没几个人懂数据。

针对产品改进,要想做出正确的决定,只有对产品本身的熟悉程度是不够的。其实越了解自己的产品,越容易做出有失偏颇的决定。而如果能对用户加深了解,会有助于做正确的事情。但必须承认,了解用户可不那么容易。

既然是改进,就不是翻新,就不要做大项目,"大山临盆"的事情常有,只是失败过后没有人好意思说。快速上线,快速反馈,不但用户容易接受,团队也会从中受益。

好的技术人,能让产品变得更好。而团队越大,产品会变得越糟。三个和尚没水喝的故事永远都适用。

说到底,都是可意会不可言传的废话。如人饮水,冷暖自知。

--EOF--

灾难究竟要多么惨痛,才能不让它再次发生?我的朋友们阿,答案在风中飘扬。

Sat 14 August, 2010

Channel Image19:12 招募:项目助理» 刘润


==============================
职位:项目助理(Project Assistant)
上级:刘润(runliu at microsoft.com)
类别:外服派驻微软员工(i- contract employee)
==============================

Responsibilities:

1. coordinator of internal readiness to drive LMTC (Local Microsoft Technology Center) business-oriented People Readiness. Daily job includes:

- Sales training (for LMTC sales)
- Solution camp (for LMTC sales and presales)
- Quarterly management meeting (for LMTC mgmt team)
- Project Camp (for LMTC project managers)
- SE camp (for LMTC implementation engineers)
- Weekly Friday Forum (for LMTC all employees, technical, non-technical)
- Technology interest group (for LMTC technical persons)
- and other internal readiness events.

2. online operation platform administrator helping LMTCs collaboration.

- The platform is the key management system to manage/collaborate/measure LMTC business and management.
- Helping on maintain the system smoothly running.
- Helping manager on improving LMTC overall technical, sales and professional skills, in order to improve business results (revenue and customer satisfaction).
- Update, credit system, data accuracy and etc. 1000+ LMTC employees are using this platform as daily working tool.

3. supporting LMTC marketing event execution as well.

Requirements:

- 3-5 years working experience, BA degree, IT background preferred, good English read/written skill.
- Passionate, self-motivate, self-driven.
- Strong project management skill, strong execution skill, strong problem solving skill.
- Smart, ability to deal in ambiguous situations, good communication skill.

Fri 13 August, 2010

Thu 12 August, 2010

Channel Image23:19 朴实的C++设计» JavaEye论坛最新精华讨论贴
朴实的C++设计

(这篇文章写于 2008 年底,“去年”指的是 2007 年。)

去年8月入职,培训了4个月,12月进入现在这个部门,到现在工作正好一年了。工作内容是软件开发,具体地说,用C++开发一个网络应用(TCP not Web),这是我们的外汇交易系统的一个部件。这半年来,和一两位同事合作把原有的一个C++程序重写了一遍,并增加了很多新功能,重写后的代码不长,不到15000行,代码质量与性能大大提高。实际上,重写只花了三个月,9月我们交付了第一个版本,实现了原来的主要功能,吞吐量提高4倍。后面这三个月我们在增加新功能,并准备交付第二个版本。这个项目让我对C++的使用有了新的体会,那就是“实用当头,朴实为贵,好用才是王道”。

C++是一门(最)复杂的编程语言,语言虽复杂,不代表一定要用复杂的方式来使用它。对于一个金融交易系统,正确性是首要的,价格/数量/交割日期弄错了就会赔钱。在编写代码时,我们特别注意把代码写得尽量简单直白,让人一看就懂。为了控制代码的复杂度,我们采用了基于对象的风格,也就是具体类加全局函数,把C++程序写得如C语言一般清晰,同时使用一些C++特性和库来减少代码。

项目中基本没有用到面向对象,或者说没有用到继承和多态的那种面向对象,不一定非得有基类和派生类的设计才是好设计。引入基类和派生类,或许能带来灵活性,但是代码就不如原来透彻了。在不需要这种灵活性的场合,干嘛要付出这样的代价呢?我宁愿花一天时间把几千行 C 代码弄懂,也不愿在几十个类组成的继承体系里绕来绕去浪费脑力。定义并使用清晰一致的接口很重要,但“接口”不一定非得是抽象基类,一个类的成员函数就是它的接口。如果看头文件就能明白这个类在干什么、该怎么用固然很好,如果不明白,打开实现文件,东西都在那儿摆着呢,一望而知。没必要非得用个抽象的接口类把使用者和实现隔开,再把实现隐藏起来,这除了让查找并理解代码变麻烦之外没有任何好处。一个进程内部的解耦意义不大,相反,函数调用是最直接有效的通信方式。或许采用接口类/实现类的一个可能的好处是依赖注入,便于单元测试。经过权衡比较,我们发现针对各个类写测试的意义不大。另外,如果用白盒测试,那么功能代码和测试代码就得同步更新,会增加不少工作量,碍手碍脚。

程序里边有一处用到了继承,因为它能简化设计。这是一个strategy,涉及一个基类和3、4个派生类,所有的类都没有数据成员,只有虚函数。这几个类的代码加起来不到200行。这个设计不是一开始就有的,而是在项目进行了一大半的时候,我们发现代码里有若干处针对请求类型的switch/case,于是我们提炼出了一个strategy,把好几处switch/case替换为了strategy对象的虚函数调用,从而简化了代码。这里我们纯粹把OO当做函数指针表来用的。

程序里还有几处用了模板,甚至是type traits,这都是为了简化代码,少敲键盘。这些代码都藏在一个角落里,对外只暴露出一个全局函数的接口,使用者不会被其困扰。

项目里,我们惟一仰赖的C++特性是确定性析构,即一个对象在离开其作用域之后会保证调用析构函数。我们利用这点大大简化了代码,并确保资源和内存的回收。在我看来,确定性析构是C++区别其他主流开发语言(Java/C#/C/动态脚本语言)的最主要特性。

为了确保正确性,我们另外用Java写了一个测试夹具(test harness)来测试我们这个C++程序。这个测试夹具模拟了所有与我们这个C++程序打交道的其他程序,能够测试各种正常或异常的情况。基本上任何代码改动和bug修复都在这个夹具中有体现。如果要新加一个功能,会有对应的测试用例来验证其行为。如果发现了一个bug,先往夹具里加一个或几个能复现bug的测试用例,然后修复代码,让测试通过。我们积累了几百个测试用例,这些用例表示了我们对程序行为的预期,是一份可以运行的文档。每次代码改动提交之前,我们都会执行一遍测试,以防低级错误发生。

我们让每个类有明确的职责范围,一个类代表一个概念,不能像个杂货铺一样什么都装。在增加或修改功能的时候,仔细考虑在哪儿下手才最合理。必要时可以动大手脚,而不是每次都选择最简单的修补方式,那样只会使代码越来越臭,积重难返,重蹈上一个版本的覆辙。有时我们会提炼出一个新的类,把原来分散在多个类里的代码集中到一起,从而优化结构。我们有测试夹具保障,并不担心修改会破坏什么。

设计不是一开始就形成的,而是随着项目进展逐步演化出来。我们的设计是基于类的,而不是基于类的继承体系。我们是在写应用,不是在写框架,在C++里用那么多继承对我们没好处。一开始我们只有三四个类,实现了基本的报价功能,然后增加了一个类,实现了下单功能。这时我们把报价和下单的共同数据结构提炼成一个新的类,作为原来两个类的成员(而不是基类!),并把解析客户输入的代码移到这个类里。我们的原则是,可以有特别简单的类,但不宜有特别复杂的类,更不能有大怪兽。一个类太大,我们就看看能不能把它拆成两个,把责任分开。两个类有共同的代码逻辑,我们会考虑提炼出一个工具类来用,输入数据的验证就是这么提炼出来的一个类。勿以善小而不为,所以始终能让代码保持清晰易懂。

让代码保持清晰,给我们带来了显而易见的好处。错误更容易暴露,在发布前每多修复一个错误,发布后就少一次半夜被从被窝里叫醒查错的机会:)

不要因为某个技术流行而去用它,除非它确实能降低程序的复杂性。毕竟,软件开发的首要技术使命是控制复杂度,防止脑袋爆掉。对于继承要特别小心,这条贼船上去就下不来,除非你是继承boost::noncopyable 讲解面向对象的书里,总会举一些用继承的精巧的例子,比如矩形、正方形、圆形继承自形状,飞机和麻雀继承自“能飞的”,这不意味着继承处处适用。我认为在C++这样需要自己管理内存和对象生命期的语言里,大规模使用面向对象、继承、多态多是自讨苦吃。还不如用C语言的思路来设计,在局部用一用继承来代替函数指针表。而GoF的《设计模式》与其说是常见问题的解决方案,不如说是绕过(work around)C++语言限制的技巧。当然,也是一些人挂在嘴边用来忽悠别人或麻痹自己的灵丹妙药。


作者: Solstice 
声明: 本文系JavaEye网站发布的原创文章,未经作者书面许可,严禁任何网站转载本文,否则必将追究法律责任!

已有 55 人发表回复,猛击->>这里<<-参与讨论


JavaEye推荐



Channel Image21:59 用hpricot/nokogiri做各种欢乐的事. .» JavaEye论坛最新精华讨论贴
  话说之前某运营MM在某大型商城做反盗版活动.活动的要求还蛮简单的. 需求总结如下:

  1.抓取某商家的所有item
  2.与博库.当当.亚马逊三家去做比对.如果没有这本书就说明是盗版(好吧.我承认这个方法.. ...)
  3.输出所有的情况.

  嗯. boss给她们手动各种抓.. 这个肯定不能行啊. 好吧.我就各种接下活来.

  拿到需求怎么办. 尤其是拿到mm的各种需求怎么办. 肾上腺素一上来你就知道该怎么办了.

  各种hpricot/nokogiri下. 好吧. 首先你需要的是什么. 嗯 .是各种搜索页面.

  这里各种收集了下:

 
dang_url = "http://search.dangdang.com/search.php?key="
amzn_url = "http://www.amazon.cn/s/ref=nb_sb_noss?url=search-alias%3Daps&field-keywords="
boku_url = "http://www.bookuu.com/search/book_search.jsp?category=sm&keyword="

ltyx_url = "http://ltyx.mall.taobao.com/?search=y&pageNum="
  


  好吧. 以上网址均来自互联网. 切勿跨省追捕.

 
require 'hpricot'
require 'open-uri'
require 'iconv'
require 'cgi'

inner_ltyx_items = []

#pagenum 1..2  :)
for i in 1..2 do
  #sleep for g_w..you know.
  sleep rand(3)
  Hpricot(open(ltyx_url + i.to_s))./('//a[@class="permalink"]').each do |item|
    #puts item.inner_text
    inner_ltyx_items << item.inner_text
  end
end

inner_ltyx_items.each do |item|
  dang_doc = Hpricot(open(dang_url + CGI.escape(item)))
  boku_doc = Hpricot(open(boku_url + CGI.escape(item)))
  amzn_doc = Hpricot(open(amzn_url + CGI.escape(item)))
  sleep rand(3)

  puts "#{item}"
  {:"当当" =>[dang_doc,'//div[@class="allsearch_right_list_sorry"]'],
    :"博库" =>[boku_doc,'//div[@id="no-pages-box"]'],
    :"卓越" => [amzn_doc,'//h1[@id="noResultsTitle"]']}.each do |name,map|
    begin
      puts "--#{name}检索失败!" if map[0]./(map[1]).first.inner_text
    rescue 
      puts "#{name}检索到您的商品."
    end
  end
end


  好吧 .上面的代码在真正检索商品的时候利用了三大书城的搜索失败tag. 这里算是小hack. 当博库没有搜索到结果的时候会出现"no-pages-box".当当卓越类似.当然在我代码最后rescue的时候就会出现  异常竟然为检索的商品的乱象也就不奇怪了..
  当中的sleep rand(i) 应该就不用再解释一遍了.然后开头hack了页面的num.当然你也可以从hpricot/nokogiri里面取.不麻烦的.
  当然.最好的搭档工具莫过于firebug. 要不然哪里来那么多selector.

  上面的代码运行是没有问题的 当然有缺陷.

  1. 当这个某商家的 item 命名不规范时. 在三大书城是很难搜索到相同的物品的.这里我不知道该说他们的搜索做的差.还是我这边其实应该做分词.
  2. bot is evil.

  当然就这样收手我是不干的.. 各种顺便做个比价吧..

  比价是怎么回事. 好吧. 你应该懂的.

  想比价. 先拿item .这里你自己找去吧. 在下面我对比了某mall跟当当的价钱.

 
# eg. %D6%DC%B1%CA%B3%A9 
doc = Nokogiri::HTML(open('http://search.dangdang.com/search.php?key=%D6%DC%B1%CA%B3%A9'))

doc = Nokogiri::HTML(open('http://list.mall.taobao.com/search_product.htm?q=%D6%DC%B1%CA%B3%A9'))


{:mall =>'//div[@class="now-price"]', :dangdang =>'//span[@class="red"]'}.each do |name,selector|
  begin
    puts name.to_s+doc.xpath(selector).first.content.to_s
  rescue 
    puts name.to_s+" : it matchs none!"
  end
end


  当然比价跟上面的查盗版会遇到相同的item命名问题.嗯.其实这个还真是有点问题.不过同时抓amazon 跟 当当的话. 就会好很多.因为命名会比较规范.

  其次. bot is evil.

  嗯. 各种evil. 结果在某日的某日. 又有身在国外在玩具厂打学工的mm说自己接到一个$500的活.某厂boss给他一份各种Primary.College.School的名单.然后她的工作就是找到这些学校的email. 然后整理成册. 用途就是用作发各种垃圾邮件.. 好吧. . 嗯. 国外的钱真的比较好挣. 这时候联想到 时薪75~120的澳洲freelancer哥. 算了一下..这时候我应该超越他了.可惜我只是帮忙.没有报酬.当然超越这种事也只是YY而已.
 
  分析了一下 boss给MM 的学校名册. 她的内容格式类似下面这样. 然后分为四个文件.

 
Art Coordinator
St Mary's School
97 Railway Street
Altona, VIC 3018

Art Coordinator
St Paul's Primary
716 Fourteenth Street
Mildura, VIC 3500

Art Coordinator
St Peter's College
Cranbourne-Frankston Rd
Cranbourne, VIC 3977


  嗯. 学校只分这三种. college primary 跟school . 废话不多说.拿出我们有用的学校名字就够了. .其余的位置什么的是用不到的. 这回我们用google干点$500的事.

 
["private school NSW.txt","private school SA.txt","private school VIC.txt","private school WA.txt"].each do |name|
	File.open("FIX_#{name}",'w').puts(File.open(name).readlines.grep(/School|Primary|College/))
end


  修理下四个文件.然后加上Fix头.  直接grep出这三种就好. 嗯. 效果很好.

 
Carey Baptist Grammar School
Christian College Geelong
ELTHAM College of Education


  然后利用强大到墙外的google .. 我们需要的是 google出我们的 各种school. 然后在后面加上 email 关键字. 然后在结果里面淘出我们的结果.

  事先我先试了一下. 发现效果尚可. 大概5中3的样子. 这生意可以做.而且澳洲的学校都比较乖.不会用 #  AT 或者 pic 来代替 email中字符的情况出现.

  用firebug划拨了几个要点.就要开始超越freelancer哥的旅程了. 加上各种分析文件什么的大概两小时吧.(嗯.这时间实在是有点长. 不过算是超越了).得到了下面的东西.

 
require 'rubygems'
require 'nokogiri'
require 'open-uri'
require 'cgi'

google_url = "http://www.google.com/search?q="
["FIX_private school NSW.txt","FIX_private school SA.txt","FIX_private school VIC.txt","FIX_private school WA.txt"].each do |name|
	File.open(name).readlines.each do |line|
		flag = ""
		s = CGI.escape(line+" email")
		sleep 8+rand(3)
		begin
			doc = Nokogiri::HTML(open(google_url+s))			
			doc.xpath('//li/div[@class="s"]').each do |link|
				email = link.content.gsub(/<\/?em>/,"")[/[a-z0-9!#\$%&'*+\/=?^_`{|}~-]+(?:\.[a-z0-9!#\$%&'*+\/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+(?:[A-Z]{2}|com|org|net|edu|gov|mil|biz|info|mobi|name|aero|asia|jobs|museum)/]
				if email
					puts "#{name}_RESULT :   "+email
					File.open("#{name}".gsub("FIX","RESULT"),'w+') {|f| f.puts(email)}
					break
				end
				flag = email
			end
		rescue
			puts "has error occur!"
		end
		if !flag
			puts "#{name}_NOT FOUND :   "+line
			File.open("#{name}".gsub("FIX","NOT_FOUND"),'w+') {|f| f.puts(line) }
		end
		STDOUT.flush
	end
end


  好吧.我承认中间那段验证email的regex是抄的. 得冒(霓虹语来着). it works. .
 
  由于google有关键字加红的问题. 所以在取到content之后先需要gsub掉<em></em>标签.然后调用email的正则. 嗯. 就大概得到了结果.

  当然在最后我们需要记录没有被找到的学校名称.

  当然 .Google大神的话我们sleep的时间要比较长. 我选择了 8 + rand 3的方式. 嗯.比较保守.当然. 你也可以选择被大神强制输captcha .或者直接ban掉.  事实证明.我还是有极少的部分超时了. 打出了简易error log.嗯.不过已经非常出色了.

  嗯. 成果类似下面.

 
FIX_private school NSW.txt_NOT FOUND :   Australian Technical College
FIX_private school NSW.txt_RESULT :   admin@avondaleschool.nsw.edu
FIX_private school NSW.txt_RESULT :   registrar@bankstowngrammar.nsw.edu


  $500.你值得拥有.

  hpricot/nokogiri 是可以做到简单的抓取重要数据.但是如果有强大的ban未知爬虫的能力的站话.就要换用mechanize了.

  mechanize的能力就是模拟各种浏览器.携带cookie什么的. =>传送门<=

  主要我还不是那么高端的人. 暂时还用不到. 据说要爬本站(也就是Javaeye)的话.是会付出惨重代价的. 尤其你还用这么低端的爬虫.

  发挥想象力.是有很多东西可以玩的.而且写爬虫会上瘾的.



  Don't remember ! bot is evil . evil   . evi. .e ..

 

 

作者: Saito 
声明: 本文系JavaEye网站发布的原创文章,未经作者书面许可,严禁任何网站转载本文,否则必将追究法律责任!

已有 3 人发表回复,猛击->>这里<<-参与讨论


JavaEye推荐



Tue 10 August, 2010

Channel Image01:39 卧虎藏龙之G4开发平台首次曝光» JavaEye论坛最新精华讨论贴


恩 2天的时间访问人数突破1W了,嘿嘿!非常感谢诸大侠们的宝贵意见和给予的肯定和鼓励!! 
这个轮子的前世与今生
先说说这个轮子的大概情况。她是一个开放源代码的项目,是一个面向企业计算环境的基础开发平台,用于在企业计算环境下进行系统集成与行业应用快速开发。姑且称之为:eRedG4(易道系统集成与应用开发平台)吧。所谓G4一为等我发布V1.0的时候此轮子就造了快满4年了,二为此轮子已经是第四代了。(此前已经有3个夭折版本由于本人经验能力方面的问题导致技术选型不得当在我开发过程中被我先后不断推翻否定掉了。现在这个版本大的基线确切的说是2009年01月才定的型,当然以前版本的部分基础类库是沿用了的)。目前为止,已经完成了整个开发平台工作量70%左右。

为什么要发此文
偶06年毕业参加工作,当时Java水平比现在菜得多,记得实习时候经常有代码要带我的师傅白帮忙,挺感谢他的,算是我到公司的入门师傅吧。后来由于我对编程有着极大的兴趣,对技术的掌握有了突飞猛进的提高(其实是起点太低:)),慢慢的就不满足于在公司框架上天天重复着复制粘贴的操作了,凭着一股牛劲十足的偏执与激情终于在07年8月份下定决心开始着手eRedG4平台的研发工作。3年的捣腾之路让偶饱尝了各种辛酸与痛楚,当然也带来了无数次完美封装或排错后的高潮与冲动。就这样一路辛酸痛楚着、高潮冲动着坚持了3年。无论是得意时、落魄时、高兴时、伤心时都从不敢懈怠。。。本想一鼓作气的把她干完,但偶最近感觉 身心疲惫,精神恍惚,唯恐在爱机前壮志未酬一命呜呼掉岂不悲哉。加之入职以来都过度偏向技术了,业务知识的不足已经严重影响了我对公司产品和项目的整体把握和驾驭。刚好公司近来有个大集中大整合的项目,正好是我梳理业务提升对公司业务领域掌握层次的大好时机,所以决定把G4平台研发的工作放慢脚步推迟到 2011年9.21发布1.0全功能版本。故写此文算是对G4做个阶段性总结,也希望广大网友们尽管拍砖,使劲拍,拍到头破血流为止。。。
郑重声明

此项目不含任何商业气息也不会在任何时候对其进行商业化包装,纯粹是一个程序员凭借激情与耐力的一个作品。版本发布后回提供完整的代码和文档供同学们参考、修改、使用、或者进行二次开发后包装为你自己的产品!所以今天请诸好汉们在此聚室而谋,多拍砖头,好让偶在以后下一步的工作能够超着正确的方向推进。尽快发布版本,因为偶快扛不住了~~~~ ~

继续对eRedG4平台做一个梗概性的介绍

这段就看一下我的《eRedG4开发指南》的目录截图就能有个大概了解了,打字好辛苦。

分别再介绍一下每一章里面的东东,

 

(1)、基础类库(eRedCCL)

eRed公共组件库(eRed Common Component Library)是eRedG4平台底层基础组件库。为eRedG4平台的其它组件提供了一系列的基础服务。她包括如下一些内容:

1)、常用数据结构、(已实现)

2)、XML处理器、(已实现)

3)、JSON处理器、(已实现)

4)、Properties处理器、(已实现)

5)、模板引擎、(已实现)

6)、eRedServer开发调试服务器(Based Jetty)(已实现)

7)、全能序列号发生器(已实现,参考升值照搬了开源项目E3的相关代码)

8)、辅助工具类等。(已实现)

eRedCCL不但可以作为eRedG4平台的基础组件,也可以独立打包作为一个独立组件发布给其它Java应用使用。

(2)、业务模型框架(eRedBMF)

eRedBMF(eRed Business Model Frame)。eRed业务模型框架是面向企业计算环境基础开发平台(eRedG4)的核心组件之一。主要负责企业计算模型下一系列关键技术的实现。使得应用软件开发人员在基于 eRedG4平台构建应用系统的时候把更多的精力放在企业计算模型上。而不用关心技术细节。把所有的技术细节交给我们来做,让你去做你最擅长的事。
1)、基于Spring的业务处理对象托管模型(已实现)

2)、基于iBatis2.X的数据持久化方案(已实现)

3)、基于SpringJDBCTemplate的数据持久化方案(待开发)

4)、基于Spring的声明式事物和编程式事物机制实现(已实现)

5)、基于AOP模型的日志审计组件(已实现)

6)、基于AOP模型的业务异常组件(已实现)

7)、“企业服务总线”实现。基本想法为提供一个灵活可配置的服务路由控制器。可以将业务逻辑组件方便的以CXF、 Hessian、SprigHttpInvoker三种RPC方式发布为服务,并在三种服务发布方式之间灵活切换和路由。此组件的定位还在摸索中,希望大家拍砖!准备要用的ESB实现框架也在考察论证中,望大家发表建议!(论证中,待开发)

 

(3)、富浏览器端开发框架(eRedRIF)

时间晚了,写快点。只能简单说一下了。我的想法就是一个应用提供2套界面开发方案。一套基于web浏览器的,也就是这里 说的eRedRIF;一套基于SWT/JFace和EclipseRCP技术的富桌面客户端开发框架(eRedRCF)。偶的最高理想是设计一套统一这两套UI的标记描述语言,统一UI开发方法或过程然后在编译时可以根据引擎随便驱动出一套UI界面。当然了,这个似乎有点像共产主义,暂时不纳入日程。否则就重蹈大跃进覆辙了。目前的定位还是两套供选择的UI独立开发和部署,但为可能发生的UI方案更换提供便利性。

先说说eRedRIF

1)、引入Struts1.X。但仅仅是用于充当MVC模型中控制器的作用。其他作用大大弱化。(已实现)
2)、UI主要依赖ExtJS,+ 部分自定义JSP标签。(已实现)

(本来想把EXTJS全面封装为JspTag,但经过反复论证后放弃了此念头)

3)、基于JasperReport封装客户端Applet打印功能和PDF导出功能。(已实现)

4)、基于FusionChartsFree封装动画图表功能。(已实现)

5)、基于JXL的Excel导入和Excel导出功能(导出支持自定义模板标记语言哦,很方便的)(正在开发,即将杀青)
      补充:已经开发完毕(2010-08-20):详情点击 http://www.javaeye.com/topic/733339?page=9#1646449

6)、可能还有一些Ext缺乏或不完善的UI组件会在后期继续封装成JspTag。

上一段JSP代码截图你就有个大概印象了。

(只是部分UI封装为JspTag,ExtJS使用原生,但后面提供了代码生成器)

 

其他效果统一放在最后面的插图了,省事点呵呵~~~~~~

 

(4)、富桌面客户端开发框架(eRedRCF)
富桌面客户端开发框架是基于IBM早期推出的 Swt/Jface图形库和现在的EclipseRCP技术构建的Java桌面应用。她的崛起必将刷新Java桌面的市场份额。对于这套技术我已经跟进很久了,很看好。国外已经有很多成功案例。据不可靠报道:国内电信、电力等高端行业也逐渐开始了尝试和探索。。。偶也不甘落后,准备在这块上做点文章。目前尚未设计开发,可能得放到明年去了。

可能还有很多同学对这套技术还不怎么熟悉,可以通过这个链接了解一下:

http://www.eclipse.org/community/rcpcp.php

下面上个图片,来个直观感受吧!图片是网上看到的一个做得还马虎点的RCP应用!上面的链接有很多老外的案例。可以去那看!


(5)、权限参考模型(eRedARM)

eRed权限参考模型参照了NIST (美国国家标准与技术研究院)提出的标准RBAC模型,并在此模型的基础上进行了一些删减和变异。使其更加符合中国特色。哎不多说了,最后我会放几个图大家一看便知。(已实现)

 

(6)、辅助开发插件(eRedG4.Builder)(未开发,但有很多前期预演工作,将很快实现)

辅助开发插件为基于eclipse并与其无缝结合的一个开发助手。可以生成一大堆符合eRedG4开发平台标准的一大堆文件。比如:

1)、根据库表结构生成一堆领域实体对象。

2)、根据库表结构生成一大堆iBatis的SQLMAP文件。

3)、根据UI界面生成向导生成各种常用的ExtJS代码。

4)、根据代码生成向导2分钟之内生成一个可运行的对单表操作CRUD功能模块。

还是上个图吧,大概样子就是这样的:

 
 哎,累死偶了,终于写完了 最后来一组写真集就睡觉了!



 

 

 

 

 

 

 

 



 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 









作者: eredlab 
声明: 本文系JavaEye网站发布的原创文章,未经作者书面许可,严禁任何网站转载本文,否则必将追究法律责任!

已有 91 人发表回复,猛击->>这里<<-参与讨论


JavaEye推荐



Sun 08 August, 2010

Channel Image11:04 [个人管理]一位技术人员成长的烦恼及我的分析» JavaEye论坛最新精华讨论贴
上次和JavaEye朋友rubys聊天,他现在遇到了一些技术和职业困惑。他对技术似乎蛮感兴趣,于是就有了下面的对话。

提升技术的关键
问:很想成为技术高手,最重要的,是不是要看大量书,做很多项目?
答:我看很多人买了一堆书,做了无数的项目,还是技术平平,因为买了书没读,后五年做项目,是在前三年水平循环。
能力提升靠学习,学习的持久动力来源于兴趣。
有兴趣,买书后会看、会思考;做项目时会总结。
有兴趣,就会勤奋、专注、有耐心。
有兴趣,就会不断总结方法。而方法,是提升学习效率和工作效率的关键。
所以,兴趣最关键。

关于阅读量
问:很多技术高手都看书近百本,我如何做到?
答:你知道他们花了多少年吗?可能是10年,平均一年才10本,这个你能够做到吧?

关于表达能力
问:我感觉自己和同事交流时,总表达不清自己的观点,我怎样提升自己的口才?
答:这个和口才没多大关系,深刻理解了问题的本质,表达自然会流畅。

关于技术大牛
问:我知道很多技术大牛,想很快赶上他们,怎么做?
答:你知道他们一般多大年龄?可能35吧。你现在23岁,耐心积累和等待吧。

关于浮躁
问:我最近看什么都没耐心,看东西也总是似懂非懂,是不是我太浮躁了?
答:人的心态,都会经历一个过程:宁静、浮躁再到宁静静,你现在处于浮躁期很正常。
你上学时,一学期啥都不做,只学10本书,自然心很静。现在,要看的东西很多,你驾驭不了,自然会无所适从。坚持几年,各知识点都梳理差不多,学习方法也成熟了,你自然会静下来。

关于遗忘
问:我现在学什么都容易忘记,很有挫败感,究竟怎么了?
答:打个比方,你将200颗五颜六色的玻璃珠丢到桶里,你要随便找出某一颗,是不是很难?如果按颜色穿成20串呢?如果再用一根横线,将20串再串起来呢?是不是再找起来很容易。
学习有个过程,从点到线到网,你现在处于点的积累阶段。积累一定程度,要串起来就很快,那时,你会有顿悟和触类旁通的感觉。
耐心等待吧。

关于学习的梯度
问:我想成为技术大牛,但我感觉业务很重要,以后还想搞管理,我该怎么做?
答:你现在才大学毕业一年,忘记业务和管理吧,专心搞技术。
如果你现在就专注业务,你会发现技术只是一种工具。
如果你现在就专注管理,你会发现,原来技术可以请人做,原来领导只关注进度和预算,于是你的技术可能会踌躇不前。
技术需要一种偏执和狂热,它是你以后转向业务和管理的基础,敲门砖。

关于科班出生
问:我是大专毕业,总感觉自己不如那些本科生,我考个本科证如何?
答:举个例子吧,我是学化学的,当年暑假,自学计算机网络和TCP/IP时,每天学习和实践不下10小时,40天,共400小时。
计算机科班的,18次课,课外18小时,加起来50多小时,就说这一门,他们怎么和我比?
另外,我所有计算机课都是自学,毕业后养成了自学的习惯和方法,后劲比一般人足。
所以,把你那宝贵的考证时间,用来做点实事吧。

关于技术含量
问:我现在做外包项目,没啥技术含量,学不到东西,咋办?
答:最没技术含量的,是做对日外包的Coding,jsp命名都是101.jsp、215.jsp,方法名也替你写好了。
但我见过从中走出的大牛,别人一周花3天干完活,其它时间研究日本人写的框架和设计文档。
软件开发这行很特殊,没有人可以限制你腾飞的冲动。

关于技术更新速度
问:IT技术变化太快,半年不学习就更不上时代了,好累啊。
答:当你学习EJB/RMI、CORBA、DCOM、SOAP/REST、DWR/JsonRPC时,你有没有发现,它们都根源于古老的RPC,对Windows RPC漏洞不陌生吧?把RPC原理彻底搞懂,这些技术就学起来很快。


关于阅读框架源码
问:我看很多框架动辄上10万行代码,我看一点,实在看不懂就放弃了,你当时是怎么看的?
答:不要先钻进代码堆,而是了解设计。比如,struts核心是Command模式,JBPM思想是Graph Oriented Programming,两个框架的内核都不到2000行,几个核心类搞懂了,其它类都是顺藤摸瓜。
提示:了解设计,先研究包结构,如继承关系。

关于面试
问:我想进重技术的公司,面试官会怎么考察我?
答:兴趣+悟性+基础
你是很阳光的小帅哥,团队协作啥的不会卡到你。

关于鉴别公司
问:如果重新找工作,我怎么识别公司,如何站住脚?
答:不管公司网站如何吹嘘自己,都无法隐藏其实力,魔鬼就在细节中。打个比方吧:如果简历上写着“精通Java,精通HTML”,这人一定很初级,因为一个技术高手绝不会将HTML写上。
关于站住脚,最重要是把自己定位好,而不决定于能力。比如HP上海,以你目前的能力,你就别选高级职位;另外,如果你的能力目前只适合做开发,那么就先别做平台研发,一步步来。

关于薪酬待遇
问:如果我换工作,我该怎么要价呢?
答:首先,毕业头几年,别关注薪酬,一般的公司即使你不提,也不会亏待你。
其次,找工作时,简历上明确标明薪水。一方面,不标明薪水,公司一般会认为这人特没自信;另一方面,公司会根据你的简历评估你的能力和薪资期望的匹配度,你的面试有效机会会更多。而且,面试成功几率更大,因为你要3k,面试官不会问你8k薪水的问题。

关于简历详细程度
问:我的简历是不是写得越详细越好?
答:如果人力资源一天要阅读200封简历,只分配了3小时,意味着一分钟看一封,那你觉得该怎么写?简明扼要,突出重点。


题后记:
这位rubys同学在成都出差,所以尽地主之仪嘛,请他品尝了一下成都特色美食。
如果在JavaEye上有朋友在成都,别忘了告诉我哦。我现在从事旅游电子商务,很喜欢大自然,对成都及周边很熟,东西南北各150公里,我几乎都开车玩过。

上面的对话,为了保持简洁,我省略了很多扩展,不免会有阅读障碍。如果有疑问,我会在回复里补充。





作者: zwchen 
声明: 本文系JavaEye网站发布的原创文章,未经作者书面许可,严禁任何网站转载本文,否则必将追究法律责任!

已有 50 人发表回复,猛击->>这里<<-参与讨论


JavaEye推荐



Thu 05 August, 2010

Channel Image23:48 借助HotSpot SA来一窥PermGen上的对象» JavaEye论坛最新精华讨论贴
(Disclaimer:如果需要转载请先与我联系;
作者:RednaxelaFX -> rednaxelafx.javaeye.com)

接着前天的昨天的帖,今天也来介绍一个HotSpotServiceability Agent(以下简称SA)的玩法例子。

昨天用SA把x86机器码反汇编到汇编代码,或许对多数Java程序员来说并不怎么有趣。那么今天就来点更接近Java,但又经常被误解的话题——HotSpot的GC堆的permanent generation。

要用SA里最底层的API来连接上一个Java进程并不困难,不过SA还提供了更方便的封装:只要继承 sun.jvm.hotspot.tools.Tool 并实现一个 run() 方法,在该方法内使用SA的API访问JVM即可。

这次我们就把一个跑在HotSpot上的Java进程的perm gen里所有对象的信息打到标准输出流上看看吧。
测试环境是32位Linux,x86,Sun JDK 6 update 2
(手边可用的JDK版本很多,随便拿了一个来用,呵呵 >_<)

代码如下:
import sun.jvm.hotspot.gc_implementation.parallelScavenge.PSPermGen;
import sun.jvm.hotspot.gc_implementation.parallelScavenge.ParallelScavengeHeap;
import sun.jvm.hotspot.gc_implementation.shared.MutableSpace;
import sun.jvm.hotspot.gc_interface.CollectedHeap;
import sun.jvm.hotspot.memory.Universe;
import sun.jvm.hotspot.oops.HeapPrinter;
import sun.jvm.hotspot.oops.HeapVisitor;
import sun.jvm.hotspot.oops.ObjectHeap;
import sun.jvm.hotspot.runtime.VM;
import sun.jvm.hotspot.tools.Tool;

/**
 * @author sajia
 * 
 */
public class TestPrintPSPermGen extends Tool {
    public static void main(String[] args) {
        TestPrintPSPermGen test = new TestPrintPSPermGen();
        test.start(args);
        test.stop();
    }

    @Override
    public void run() {
        VM vm = VM.getVM();
        Universe universe = vm.getUniverse();
        CollectedHeap heap = universe.heap();
        puts("GC heap name: " + heap.kind());
        if (heap instanceof ParallelScavengeHeap) {
            ParallelScavengeHeap psHeap = (ParallelScavengeHeap) heap;
            PSPermGen perm = psHeap.permGen();
            MutableSpace permObjSpace = perm.objectSpace();
            puts("Perm gen: [" + permObjSpace.bottom() + ", " + permObjSpace.end() + ")");
            long permSize = 0;
            for (VM.Flag f : VM.getVM().getCommandLineFlags()) {
                if ("PermSize".equals(f.getName())) {
                    permSize = Long.parseLong(f.getValue());
                    break;
                }
            }
            puts("PermSize: " + permSize);
        }
        puts();
        
        ObjectHeap objHeap = vm.getObjectHeap();
        HeapVisitor heapVisitor = new HeapPrinter(System.out);
        objHeap.iteratePerm(heapVisitor);
    }
    
    private static void puts() {
        System.out.println();
    }
    
    private static void puts(String s) {
        System.out.println(s);
    }
}


很简单,假定目标Java进程用的是Parallel Scavenge(PS)算法的GC堆,输出GC堆的名字,当前perm gen的起始和结束地址,VM参数中设置的PermSize(perm gen的初始大小);然后是perm gen中所有对象的信息,包括对象摘要、地址、每个成员域的名字、偏移量和值等。
对HotSpot的VM参数不熟悉的同学可以留意一下几个参数在HotSpot源码中的定义:
product(ccstrlist, OnOutOfMemoryError, "",
    "Run user-defined commands on first java.lang.OutOfMemoryError")
product(bool, UseParallelGC, false, "Use the Parallel Scavenge garbage collector")
product_pd(uintx, PermSize, "Initial size of permanent generation (in bytes)")
product_pd(uintx, MaxPermSize, "Maximum size of permanent generation (in bytes)")


要让SA连接到一个正在运行的Java进程最重要是提供进程ID。获取pid的方法有很多,今天演示的是利用OnOutOfMemoryError参数指定让HotSpot在遇到内存不足而抛出OutOfMemoryError时执行一段用户指定的命令;在这个命令中可以使用%p占位符表示pid,HotSpot在执行命令时会把真实pid填充进去。

然后来造一个引发OOM的导火索:
public class Foo {
    public static void main(String[] args) {
        Long[] array = new Long[256*1024*1024];
    }
}

对32位HotSpot来说,main()方法里的new Long[256*1024*1024]会试图创建一个大于1GB的数组对象,那么只要把-Xmx参数设到1GB或更小便足以引发OOM了。
如何知道这个数组对象会占用超过1GB的呢?Long[]是一个引用类型的数组,只要知道HotSpot中采用的对象布局:
            -----------------------
(+0)      |        _mark         |
            -----------------------
(+4)      |      _metadata       |
            -----------------------
(+8)      |    数组长度 length    |
            -----------------------
(+12+4*0) |     下标为0的元素      |
            -----------------------
(+12+4*1) |     下标为1的元素      |
            -----------------------
            |         ...          |
            -----------------------
(+12+4*n) |     下标为n的元素      |
            -----------------------
           |         ...          |
            -----------------------

就知道一大半了~


跑一下Foo程序。留意到依赖SA的代码要编译的话需要$JAVA_HOME/lib/sa-jdi.jar在classpath上,执行时同理。指定GC算法为Parallel Scavenge,并指定GC堆(不包括perm gen)的初始和最大值都为1GB:
[sajia@sajia ~]$ java -server -version
java version "1.6.0_02"
Java(TM) SE Runtime Environment (build 1.6.0_02-b05)
Java HotSpot(TM) Server VM (build 1.6.0_02-b05, mixed mode)
[sajia@sajia ~]$ javac Foo.java
[sajia@sajia ~]$ javac -classpath ".:$JAVA_HOME/lib/sa-jdi.jar" TestPrintPSPermGen.java
[sajia@sajia ~]$ java -server -XX:+UseParallelGC -XX:OnOutOfMemoryError='java -cp $JAVA_HOME/lib/sa-jdi.jar:. TestPrintPSPermGen %p > foo.txt' -Xms1g -Xmx1g Foo
#
# java.lang.OutOfMemoryError: Java heap space
# -XX:OnOutOfMemoryError="java -cp $JAVA_HOME/lib/sa-jdi.jar:. TestPrintPSPermGen %p > foo.txt"
#   Executing /bin/sh -c "java -cp $JAVA_HOME/lib/sa-jdi.jar:. TestPrintPSPermGen 23373 > foo.txt"...
Attaching to process ID 23373, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 1.6.0_02-b05
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
        at Foo.main(Foo.java:5)
[sajia@sajia ~]$ 


得到的foo.txt就是要演示的输出结果。把它压缩了放在附件里,有兴趣但懒得自己实验的同学也可以观摩一下~

在foo.txt的开头可以看到:
GC heap name: ParallelScavengeHeap
Perm gen: [0x70e60000, 0x71e60000)
PermSize: 16777216

这里显示了GC堆确实是Parallel Scavenge的,其中perm gen当前的起始地址为0x70e60000,结束地址为0x71e60000,中间连续的虚拟内存空间都分配给perm gen使用。简单计算一下可知perm gen大小为16MB,与下面打出的PermSize参数的值完全吻合。

通过阅读该日志文件,可以得知HotSpot在perm gen里存放的对象主要有:
- Klass系对象
- java.lang.Class对象
- 字符串常量
- 符号(Symbol/symbolOop)常量
- 常量池对象
- 方法对象
等等,以及它们所直接依赖的一些对象。具体这些都是什么留待以后有空再写。


接下来挑几个例子来简单讲解一下如何阅读这个日志文件里的对象描述。

首先看一个String对象。先看看JDK里java.lang.String对象的声明是什么样的:
package java.lang;

// imports ...

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence
{
    /** The value is used for character storage. */
    private final char value[];
    
    /** The offset is the first index of the storage that is used. */
    private final int offset;
    
    /** The count is the number of characters in the String. */
    private final int count;
    
    /** Cache the hash code for the string */
    private int hash; // Default to 0
    
    /** use serialVersionUID from JDK 1.0.2 for interoperability */
    private static final long serialVersionUID = -6849794470754667710L;
    
    private static final ObjectStreamField[] serialPersistentFields =
        new ObjectStreamField[0];
    
    public static final Comparator<String> CASE_INSENSITIVE_ORDER
                                         = new CaseInsensitiveComparator();
    private static class CaseInsensitiveComparator
                         implements Comparator<String>, java.io.Serializable {
        // ...
    }
}

留意到String对象有4个成员域,分别是:
名字 类型 引用类型还是值类型
value char[] 引用类型
offset int 值类型
count int 值类型
hash int 值类型

String类自身有三个静态变量,分别是:
名字 类型 引用类型还是值类型 备注
serialVersionUID long 值类型 常量
serialPersistentFields java.io.ObjectStreamField[] 引用类型 只读变量
CASE_INSENSITIVE_ORDER java.lang.String.CaseInsensitiveComparator 引用类型 只读变量

回到我们的foo.txt日志文件来看一个String的对象实例:
"main" @ 0x7100b140 (object size = 24)
 - _mark:	 {0} :1
 - _klass:	 {4} :InstanceKlass for java/lang/String @ 0x70e6c6a0
 - value:	 {8} :[C @ 0x7100b158
 - offset:	 {12} :0
 - count:	 {16} :4
 - hash:	 {20} :0

这是在HotSpot的字符串池里的一个字符串常量对象,"main"。
日志中的“"main"”是对象的摘要,String对象有特别处理显示为它的内容,其它多数类型的对象都是显示类型名之类的。
在@符号之后的就是对象的起始地址,十六进制表示。
紧接着后面是对象占用GC堆的大小。很明显这个String对象自身占用了24字节。这里强调是“占用”的大小是因为对象除了存储必要的数据需要空间外,为了满足数据对齐的要求可能会有一部分空间作为填充数据而空占着。

String在内存中的布局是:
       -----------------------
(+0) |        _mark        |
       -----------------------
(+4) |      _metadata      |
       -----------------------
(+8) |        value        |
       -----------------------
(+12)|        offset       |
       -----------------------
(+16)|        count        |
       -----------------------
(+20)|        hash         |
       -----------------------

32位HotSpot上要求64位/8字节对齐,String占用的24字节正好全部都是有效数据,不需要填充空数据。

上面的String实例在内存中的实际数据如下:
偏移量(字节) 数值(二进制表示) 数值(十六进制表示) 宽度(位/字节)
  +0 00000000000000000000000000000001 00000001 32位/4字节
  +4 01110000111001101100011010100000 70e6c6a0 32位/4字节
  +8 01110001000000001011000101011000 7100b158 32位/4字节
+12 00000000000000000000000000000000 00000000 32位/4字节
+16 00000000000000000000000000000100 00000004 32位/4字节
+20 00000000000000000000000000000000 00000000 32位/4字节


OK,那我们来每个成员域都过一遍,看看有何玄机。

第一个是_mark。在HotSpot的C++代码里它的类型是markOop,在SA里以sun.jvm.hotspot.oops.Mark来表现。
它属于对象头(object header)的一部分,是个多用途标记,可用于记录GC的标记(mark)状态、锁状态、偏向锁(bias-locking)状态、身份哈希值(identity hash)缓存等等。它的可能组合包括:
比特域(名字或常量值:位数) 标识(tag) 状态
身份哈希值:25, 年龄:4, 0:1 01 未锁
锁记录地址:30 00 被轻量级锁住
monitor对象地址:30 10 被重量级锁住
转向地址:30 11 被GC标记
线程ID:23, 纪元:2, 年龄:4, 1:1 01 被偏向锁住/可被偏向锁

例子中的"main"字符串的_mark值为1,也就是说它:
- 没有被锁住;
- 现在未被GC标记;
- 年龄为0(尚未经历过GC);
- 身份哈希值尚未被计算。

HotSpot的GC堆中许多创建没多久的对象的_mark值都会是1,属于正常现象。


接下来看SA输出的日志中写为_klass而在我的图示上写为_metadata的这个域。
在HotSpot的C++代码里,oopDesc是所有放在GC堆上的对象的顶层类,它的成员就构成了对象头。HotSpot在C++代码中用instanceOopDesc类来表示Java对象,而该类继承oopDesc,所以HotSpot中的Java对象也自然拥有oopDesc所声明的头部。
hotspot/src/share/vm/oops/oop.hpp:
class oopDesc {
 private:
  volatile markOop  _mark;
  union _metadata {
    wideKlassOop    _klass;
    narrowOop       _compressed_klass;
  } _metadata;
};

_metadata与前面提过的_mark一同构成了对象头。
_metadata是个union,为了能兼容32位、64位与开了压缩指针(CompressedOops)等几种情况。无论是这个union中的_klass还是_compressed_klass域,它们都是用于指向一个描述该对象的klass对象的指针。SA的API屏蔽了普通指针与压缩指针之间的差异,所以就直接把_metadata._klass称为了_klass。

对象头的格式是固定的,而对象自身内容的布局则由HotSpot根据一定规则来决定。Java类在被HotSpot加载时,其对象实例的布局与类自身的布局都会被计算出来。这个计算规则有机会以后再详细写。

现在来看看"main"这个String对象实例自身的域都是些什么。
value:指向真正保存字符串内容的对象的引用。留意Java里String并不把真正的字符内容直接存在自己里面,而是引用一个char[]对象来承载真正的存储。
从Java一侧看value域的类型是char[],而从HotSpot的C++代码来看它就是个普通的指针而已。它当前值是0x7100b158,指向一个char[]对象的起始位置。
offset:字符串的内容从value指向的char[]中的第几个字符开始算(0-based)。int型,32位带符号整数,这从Java和C++来看都差不多。当前值为0。
count:该字符串的长度,或者说包含的UTF-16字符的个数。类型同上。当前值为4,说明该字符串有4个UTF-16字符。
hash:缓存该String对象的哈希值的成员域。类型同上。当前值为0,说明该实例的String.hashCode()方法尚未被调用过,因而尚未缓存住该字符串的哈希值。

String对象的成员域都走过一遍了,来看看value所指向的对象状况。
[C @ 0x7100b158 (object size = 24)
 - _mark:	 {0} :1
 - _klass:	 {4} :TypeArrayKlass for [C @ 0x70e60440
 - _length:	 {8} :4
 - 0:	 {12} :m
 - 1:	 {14} :a
 - 2:	 {16} :i
 - 3:	 {18} :n

这就是"main"字符串的value所引用的char[]的日志。
[C 是char[]在JVM中的内部名称。
在@符号之后的0x7100b158是该对象的起始地址。
该对象占用GC堆的大小是24字节。留意了哦。

看看它的成员域。

_mark与_klass构成的对象头就不重复介绍了。可以留意的是元素类型为原始类型(boolean、char、short、int、long、float、double)的数组在HotSpot的C++代码里是用typeArrayOopDesc来表示的;这里的char[]也不例外。描述typeArrayOopDesc的klass对象是typeArrayKlass类型的,所以可以看到日志里_klass的值写着TypeArrayKlass for [C。

接下来是_length域。HotSpot中,数组对象比普通对象的头要多一个域,正是这个描述数组元素个数的_length。Java语言中数组的.length属性、JVM字节码中的arraylength要取的也正是这个值。
日志中的这个数组对象有4个字符,所以_length值为4。

再后面就是数组的内容了。于是该char[]在内存中的布局是:
        -----------------------
(+0)  |        _mark         |
        -----------------------
(+4)  |      _metadata       |
        -----------------------
(+8)  |    数组长度 length    |
        -----------------------
(+12) |  char[0] | char[1]   |
        -----------------------
(+16) |  char[2] | char[3]   |
        -----------------------
(+20) |         填充0         |
        -----------------------

Java的char是UTF-16字符,宽度是16位/2字节;4个字符需要8字节,加上对象头的4*3=12字节,总共需要20字节。但该char[]却占用了GC堆上的24字节,正是因为前面提到的数据对齐要求——HotSpot要求GC堆上的对象是8字节对齐的,20向上找最近的8的倍数就是24了。用于对齐的这部分会被填充为0。


"main"对象的value指向的char[]也介绍过了,回过头来看看它的_metadata._klass所指向的klass对象又是什么状况。

从HotSpot的角度来看,klass就是用于描述GC堆上的对象的对象;如果一个对象的大小、域的个数与类型等信息不固定的话,它就需要特定的klass对象来描述。
instanceOopDesc用于表示Java对象,instanceKlass用于描述它,但自身却又有些不固定的信息需要被描述,因而又有instanceKlassKlass;如此下去会没完没了,所以有个klassKlass作为这个描述链上的终结符。
klass的关系图:

(图片来源)

回到foo.txt日志文件上来,找到"main"对象的_klass域所引用的instanceKlass对象:
InstanceKlass for java/lang/String @ 0x70e6c6a0 (object size = 384)
 - _mark:	 {0} :1
 - _klass:	 {4} :InstanceKlassKlass @ 0x70e60168
 - _java_mirror:	 {60} :Oop for java/lang/Class @ 0x70e77760
 - _super:	 {64} :InstanceKlass for java/lang/Object @ 0x70e65af8
 - _size_helper:	 {12} :6
 - _name:	 {68} :#java/lang/String @ 0x70e613e8
 - _access_flags:	 {84} :134217777
 - _subklass:	 {72} :null
 - _next_sibling:	 {76} :InstanceKlass for java/lang/CharSequence @ 0x70e680e8
 - _alloc_count:	 {88} :0
 - _array_klasses:	 {112} :ObjArrayKlass for InstanceKlass for java/lang/String @ 0x70ef6298
 - _methods:	 {116} :ObjArray @ 0x70e682a0
 - _method_ordering:	 {120} :[I @ 0x70e61330
 - _local_interfaces:	 {124} :ObjArray @ 0x70e67998
 - _transitive_interfaces:	 {128} :ObjArray @ 0x70e67998
 - _nof_implementors:	 {268} :0
 - _implementors[0]:	 {164} :null
 - _implementors[0]:	 {168} :null
 - _fields:	 {132} :[S @ 0x70e68230
 - _constants:	 {136} :ConstantPool for java/lang/String @ 0x70e65c38
 - _class_loader:	 {140} :null
 - _protection_domain:	 {144} :null
 - _signers:	 {148} :null
 - _source_file_name:	 {152} :#String.java @ 0x70e67980
 - _inner_classes:	 {160} :[S @ 0x70e6c820
 - _nonstatic_field_size:	 {196} :4
 - _static_field_size:	 {200} :4
 - _static_oop_field_size:	 {204} :2
 - _nonstatic_oop_map_size:	 {208} :1
 - _is_marked_dependent:	 {212} :0
 - _init_state:	 {220} :5
 - _vtable_len:	 {228} :5
 - _itable_len:	 {232} :9
 - serialVersionUID:	 {368} :-6849794470754667710
 - serialPersistentFields:	 {360} :ObjArray @ 0x74e882c8
 - CASE_INSENSITIVE_ORDER:	 {364} :Oop for java/lang/String$CaseInsensitiveComparator @ 0x74e882c0

还记得上文提到过的String类的3个静态变量么?有没有觉得有什么眼熟的地方?
没错,在HotSpot中,Java类的静态变量就是作为该类对应的instanceKlass的实例变量出现的。上面的日志里最后三行描述了String的静态变量所在。
这是件非常自然的事:类用于描述对象,类自身也是对象,有用于描述自身的类;某个类的所谓“静态变量”就是该类对象的实例变量。很多对象系统都是这么设计的。HotSpot的这套oop体系(指“普通对象指针”,不是指“面向对象编程”)继承自Strongtalk,实际上反而比暴露给Java的对象模型显得更加面向对象一些。

HotSpot并不把instanceKlass暴露给Java,而会另外创建对应的java.lang.Class对象,并将后者称为前者的“Java镜像”,两者之间互相持有引用。日志中的_java_mirror便是该instanceKlass对Class对象的引用。
镜像机制被认为是良好的面向对象的反射与元编程设计的重要机制。Gilad Bracha与David Ungar还专门写了篇论文来阐述此观点,参考Mirrors: Design Principles for Meta-level Facilities of Object-Oriented Programming Languages

顺带把"main"对象的_klass链上余下的两个对象的日志也贴出来:
InstanceKlassKlass @ 0x70e60168 (object size = 120)
 - _mark:	 {0} :1
 - _klass:	 {4} :KlassKlass @ 0x70e60000
 - _java_mirror:	 {60} :Oop for java/lang/Class @ 0x70e76f20
 - _super:	 {64} :null
 - _size_helper:	 {12} :0
 - _name:	 {68} :null
 - _access_flags:	 {84} :0
 - _subklass:	 {72} :null
 - _next_sibling:	 {76} :null
 - _alloc_count:	 {88} :0

所有instanceKlass对象都是被这个instanceKlassKlass对象所描述的。

KlassKlass @ 0x70e60000 (object size = 120)
 - _mark:	 {0} :1
 - _klass:	 {4} :KlassKlass @ 0x70e60000
 - _java_mirror:	 {60} :Oop for java/lang/Class @ 0x70e76e00
 - _super:	 {64} :null
 - _size_helper:	 {12} :0
 - _name:	 {68} :null
 - _access_flags:	 {84} :0
 - _subklass:	 {72} :null
 - _next_sibling:	 {76} :null
 - _alloc_count:	 {88} :0

而所有*KlassKlass对象都是被这个klassKlass对象所描述的。

klass对象的更详细的介绍也留待以后再写吧~至少得找时间写写instanceKlass与vtable、itable的故事。

嘛,今天的废话到此结束 ^_^
希望这帖能解答先前关于java里面的全局变量的内存分配一帖中的疑问。也希望大家能多多支持高级语言虚拟机圈子,有什么HLL VM相关的话题想讨论的欢迎来转转~

    本文附件下载:
  • foo.zip (1.7 MB)


作者: RednaxelaFX 
声明: 本文系JavaEye网站发布的原创文章,未经作者书面许可,严禁任何网站转载本文,否则必将追究法律责任!

已有 14 人发表回复,猛击->>这里<<-参与讨论


JavaEye推荐



Channel Image17:41 探索并发编程» JavaEye论坛最新精华讨论贴

近来在项目过程中一直受并发问题所困,也是由于自己对并发的一些技术细节一知半解,因此最近一周潜心学习了并发编程的各个方面,知识来源主要是《操 作系统》和《Java并发编程实践》,另外也结合一些分布式项目里面的一些经验,总结了一个并发编程系列,由于篇幅较长,就把链接帖出来,希望对大家有所 帮助:

探索并发编程(一)------操作系统篇

探索并发编程(二)------写线程安全的Java代码

探索并发编程(三)------Java存储模型和共享对象

探索并发编程(四)------Java并发工具

探索并发编程(五)------Java多线程开发技巧

探索并发编程(六)------Java多线程性能优化

探索并发编程(七)------分布式环境中并发问题



作者: cutesource 
声明: 本文系JavaEye网站发布的原创文章,未经作者书面许可,严禁任何网站转载本文,否则必将追究法律责任!

已有 17 人发表回复,猛击->>这里<<-参与讨论


JavaEye推荐



Channel Image17:26 去跨国公司还是去创业公司?» robbin的自言自语
去跨国公司工作可能是大部分人的梦想,特别是《杜拉拉升职记》的流行,更加说明了这个现象。不过作为一个曾经尝过螃蟹的人来说,跨国公司也并非十全十美,并不是每个人都适合去跨国公司,特别是有志于钻研技术的程序员,跨国公司未必是合适的选择。

以下摘自JavaEye网站的讨论贴:

XXX 写道
小公司真他妈的操蛋
1 感觉干活就是在抢任务,无规划无文化
2 由于小公司这个平台,竞争力不行,所以利润就来源于你对员工的剥削程度
3 无任何安全感
4 积累不了任何经验和资历
还不如在大公司划水,所以在大公司划水的兄弟们,一路划好,千万不要轻易上创业公司的贼船!


以下是我的回答:

你可以去外企试试看,不过等你去了外企以后,也许你会发现,你还是怀念在小公司工作的日子,特别是对于喜欢钻研技术的人来说,去外企很可能不合适。毕竟外企的生存文化完全不一样,外企混得好,你必须懂潜规则,必须适应外企政治。小公司也不见得一昧得差,也要看运气,找到一个好老板对技术人员未尝不是很幸运的事情。

话说回来,利润微薄的小公司也不见得是老板无能老板非要剥削员工,你不明白在中国的经济环境下,创业公司面临的环境是九死一生的。小公司如果利润微薄的话,就等于是给员工打工,给国家创造就业机会而已。 你说的一些小公司的问题固然有,不过大公司也有大公司的问题,这个是中国经济环境和社会环境造成的,和公司规模关系不大。

我自己的职业轨迹就是“小公司 -> 外企 -> 上市网络公司 -> 独立咨询 -> 创业”这条路发展过来的,到现在我最怀念的还是一开始那家小公司,十几个人,虽然管理混乱,老板能力不足,我什么事情都要负责,公司业务不好,但是我们同事之间的感情很好,就像一家人一样没有隔阂。

我当初也像你现在一样,这抱怨那抱怨,非要跳槽去外企不可,但后来真去了外企以后发现自己真不适合外企的文化,那里就是搞人和搞政治的地方,不适合在技术上有真才实学的人待。

我在外企干的时候性格并不张扬,但是感觉被束缚的很厉害,浑身有力无处使,把你按在一个框框里面,每天把屁大点的事情当圣旨来干,还得装出一副我干了多么伟大事业,做出多么伟大贡献,team多么支持我,同事多么融洽的嘴脸,然后天天写report和email装点自己的业绩。 外企说白了就是这么回事,你要真会来事,擅长写report,那你肯定如鱼得水,Boss赏识你,升迁机会多多。但是干久了你就只剩下干这些事情的能力了,你的技术完全废掉了。为什么外企的人待久了就不愿意出来了?因为他们已经废了,出来干不了技术这活了,只能吃外企这口饭了。我自己当初也是不想这辈子就这么废掉,而且我也不喜欢搞那些事情,所以就出来了。

外企出来的人都有个特点:屁大点事情当成圣旨一样的事情去干,干完以后写一堆report邀功。从积极的一面去看,这叫做职业素养好,有高度的敬业精神;从消极的一面看,这些人做不了实打实的技术骨干,在外企,难的技术活要么老外做了,要么外包出去做了。

小公司出来的人正好相反:往往有技术能力,可以担当大任,但是职业素养差,做事情细节做不好,脾气大,挑剔,难伺候。

这两种人要我来看,都不能用,嘿嘿,一个是没真正的能力,一个是没正确的做事情的态度。真正两者都具备的人,那才是真正的“人才”,这种人才那是可遇而不可求。

小公司里面只要你自己积极主动有责任心,公司能提供的所有机会都是你的,自己再勤奋好学一些,技术能力很快就锻炼出来了。我在第一家小公司的时候,从开始什么都不会到后来所有技术全部大拿,就是靠自己的勤奋,公司所有技术方面的事情,无论是网络,系统管理,数据库还是设计、架构和开发,样样都是靠我。我的能力就是在这段时间锻炼出来的。

后来去了外企对外企很失望又离开外企,心想只能去重视技术的IT类上市公司了,但去了以后被排挤的很厉害,我那个时候的技术能力已经出类拔萃了,我的Boss和Boss的Boss技术都被我甩开很远,有我在他们很不舒服,下面的员工都还很仰慕我,所以我只能走。

这样一来,我的就业选择就很少了,只能去当人家公司的CTO才行,但当时一直没有遇到合适的机会。因为做JavaEye的缘故在行业也比较有名气了,索性就自己做独立咨询顾问了,再后来成立公司创业,就死心塌地自己干了。

我一直很怀念当初在小公司工作的岁月,小公司的第一任务就是赚钱保证公司能发工资不死掉,所以什么管理,什么福利确实很难跟上,这些问题都是要吃饱肚子才可能去解决的问题。当时并不理解这些问题,但现在回过头去看,都明白了。所以对小公司这些方面的问题没有必要太挑剔,关键是看公司员工的氛围好不好,看工作机会多不多,是不是能够做自己感兴趣的事情,并且能有很多挑战性的问题等着你去解决。 其实一旦小公司跨越了收入艰难的阶段,特别是迅速成长期的小公司,对技术人员来说是梦寐以求的工作环境。不要忘记了,仅仅几年以前,百度,盛大,阿里和腾讯可还都是小公司。

推荐看看前百度的曹政(他当时的老板是俞军)的几篇文章:

loser们的共同特征
心态决定成败2
心态决定成败

caoz 写道
caoz在之前的公司,经常给一些新员工作培训的时候,caoz的上司也在座的情况下,caoz会告诉新员工,你为谁打工?这是最重要的,而唯一正确的答案是,为自己。你不是为老板工作,不是为公司工作,也不是为你的上司工作,而是为你自己工作,那么这里正好有一个非常好的案例,就是百度离职副总裁俞军,俞军是为李彦宏工作吗?其实根本不是,李彦宏提供了一个很好的工作平台,俞军是为实现自己的理想,完成自己的志愿而工作,而这样的工作态度,工作方式,也使他能够真正有所成就。并不是每个人都明白这个道理,所以很少有人能成为俞军。 caoz经常讲,打工要有创业心态,为什么这么说,是不是打工者最后都要去创业,不是,但是你要想的是,公司提供了一个环境,一个可以让你实现理想的环境,有人说了,你所在的公司不能提供这样的环境,那就找一个那样的环境!有人说了,找不到这样的公司,其实不是没有,而是你忽视了,因为你在找工作的时候,优先排列的是工资,福利待遇,级别,工作地点,公司品牌等等,而没有任何自己梦想在里面,看看俞军当年的求职简历吧,一个成功者是怎么找工作的。




已有 33 人发表留言,猛击->>这里<<-参与讨论


JavaEye推荐



Channel Image15:39 姐姐在1987年写的日记《我的弟弟》» robbin的自言自语
因为母亲过世,我们都回到老家,在整理物品的时候看到了小时候写的日记,有一篇是姐姐的日记,写我的,现在来看,很有趣。这篇日记是姐姐在1987年6月写的,当时姐姐上高中一年纪,我上小学五年纪。


《我的弟弟》 (1987年6月)

我敢这么说,从我弟弟的举止言谈之中都透露出一股聪明劲儿来,我觉得在同龄的孩子中很少有几个像他那样思维敏捷,兴趣爱好又很广泛的,你不信么?

他的聪明首先在学习上体现出来,二年级时,他的口算心算非常的快,常常是班里面的其他的学生所不及的,有一次妈妈问他:“你是不是有什么好的方法?“弟弟一笑说:”有哇,我创造了一个小小的办法,比如做减法时,如果个位不够减需要借位,那你就用减数减去被减数的个位,再用被减数的十位减去差就行了,你看54-9,你用9-4=5,再用50-5=45,对不对?我觉得这个方法对我来说太适合了,爸总让我用他的办法,可我认为那种方法不如我的简单。”连大学生的妈妈一下子也搞不清楚这是什么原理,这对于二年纪的小学生来讲,是难能可贵的,把天资用在了学习上,许多的学生都没有做到。

前几天,他告诉我,他又发现了一个约分的好办法,我给了出了18/54,他马上就写出答案1/3,我问他怎么得到的,他轻描淡写的说:“把18除以2,54再除以商,你看得到了什么?你再出一个。”我又出了16/72,他马上写出了2/9,”用16除以2,用12除以8,不就得到了2/9吗?其实分子上的数你用几个数迅速的一试就行了,凭我的经验,这几个数字基本上是2,3,4,5。下面你用乘法口诀非常快的就可以算出来了。”“你怎么想到这些的呢?”那天我们张老师讲了求最小公倍数的方法,我灵机一动,倒过来试试,再经过考虑和实践,就得到了这个办法。“逆向思维是思维的一种方式,我很高兴看到弟弟身上,已初步的具有这种思维方式。

弟弟的聪明劲儿在生活中处处可以体现出来,一天在饭桌上,爸爸对他说:”范凯,你打算继续进取还是停滞不前?“弟弟说:”继续进取吧!”

“那你应该拿出实际行动来。”
“我问你,你是引导教育还是饭桌训子?”

爸爸故意板着的脸一下松弛了,忍不住笑了起来。

一次,弟弟不小心打翻了菜盘,而那天爸爸不在家,弟弟说:“坏了,我大意失荆州了。我的想好词回来对付爸爸,省得他嘲笑我。“

弟弟的思维活动得很快,也很调皮,星期天,他没写完周记,妈妈不让他看电视,他偷偷得打开电视,妈妈发现了让他关上,他马上说:”我看电视是为了找素材。“

弟弟在学习上从来没有过负担,他很少在家完成作业,他兴趣广泛地能使你大吃一惊,羽毛球,乒乓球,旱冰,围棋,象棋,电子琴,骑自行车,书法等等,他要求当学校的广播员,可老师把他安排在报道组,同时他又是红领巾检查站的成员,他在学校的工作多得很,他们班的老班主任病了,由新班主任执笔拟了一份慰问信,由弟弟用毛笔抄一遍。新年晚会的请柬全是由他用毛笔写,如此繁重的工作,如此广泛的兴趣,竟丝毫不影响他的学习成绩,这次期中考试,由于一道5分的英语题没看见,屈居第三名。但他连续几次都获得数学满分,大概是小孩所特有的吧,他每门考试都抢头卷交。

有的人兴趣多而不精,可弟弟兴趣多而精。他在围棋上进展很快,从初级班升到提高班,为提高班的第三名;他比我还矮一头,照样骑28的自行车;他的旱冰技术可以说是“炉火纯青”了。

弟弟的进步,与爸爸妈妈的关系极大,由于我的父母都是大学生,从来不允许他骂人,打架,所以说不定您哪天看见了他,给你的第一眼印象肯定是温文尔雅,又极聪明。妈妈从教育哥哥身上得到经验,对弟弟的要求极严格,弟弟很小的时候,妈妈就要求他背成语字典,唐诗宋词,所以现在他一出口就是形容词,很让人惊奇。因为他还只是五年纪的学生,他读的书很多:天文,地理,生活百科知识,连平时的大小病,他也能说上一二,一天到晚在家里像一只“小喇叭”一样,不停的广播。有一次他有些头疼,他就说自己得了“游走性偏头痛”,真是逗死人。

我觉得在弟弟身上有一种使不完的劲儿,支配着他。

弟弟也是有缺点的,比如说粗心。粗心是他最大的毛病。他每次考试总是有那么一两道题没看见,要么就是看错题。

弟弟也很喜欢搞恶作剧,爸爸让他睡午觉,他从不睡。一次爸爸刚闭眼,弟弟就逃了出去,弟弟说他设计了三个“现场”,布下了“迷魂阵”,使爸爸抓不住他。

我衷心希望弟弟能改掉自己的缺点,成为像全波那样的人,弟弟,请接受我的祝愿!

已有 22 人发表留言,猛击->>这里<<-参与讨论


JavaEye推荐



Wed 04 August, 2010

Channel Image11:53 日积月累-分享我的工具库» JavaEye论坛最新精华讨论贴

 

 

  • 批量替换 指定目录及其子目录中所有文件内的字符串

 

#!/usr/bin/env ruby

class File
        class << self

        def gsub!(s,t,dir=Dir.pwd)

                Dir.entries(dir).each do |f| 
                        puts f
                        next if(f == "." || f == "..")

                        if(File.file? f) then
                                ct = IO.readlines(f).join
                                if ct.index(s) then
                                        puts "replace file <#{f}>." 
                                        ct.gsub!(s,t)
                                        File.open(f,"w+"){|ff|ff.write ct} 
                                end 
                        elsif(File.directory? f) then
                                self.gsub!(s,t,File.join(dir,f))
                        end 
                end 

        end 

        #end of <<

        end 

end

 

使用的时候:

 

File.gsub!("reg","--reg--",Dir.pwd)

 

就可以了.

 

 

 

 

  • 批量删除指定目录及子目录下含有某个扩展名的文件

#!/usr/bin/env ruby

class File
	class << self
	
	def grm(path,ext)
	  
  	  Dir.entries(path).each do |f|
  		next if (File.basename(f) == '.' || File.basename(f) == '..')

  		if File.directory?(File.join(path,f))
  			grm(File.join(path,f),ext)
  		elsif f != File.basename(f).chomp(ext)
  			puts File.join(path,f)
  			FileUtils.rm_f(File.join(path,f))
  		end
  	  end
  	
        end

	#end of <<

	end

end
 
File.grm("C:\\scripts",'log')
可以删除scripts下面及该目录的子目录下面所有log文件.


  • 获得本机IP

class Socket
    class << self

    def ips
          Socket.getaddrinfo(Socket.gethostname, nil)
    end

    end
end
 Socket.ips可以获得本机的IP列表数组.


作者: CharlesCui 
声明: 本文系JavaEye网站发布的原创文章,未经作者书面许可,严禁任何网站转载本文,否则必将追究法律责任!

已有 35 人发表回复,猛击->>这里<<-参与讨论


JavaEye推荐



Channel Image03:45 关于Powerdesigner的感触» JavaEye论坛最新精华讨论贴

 这年头要是不懂UML啊,你都不好意思跟人家打招呼。

你如果还在一味手敲建表语句,你都不好意思说自己是搞面向对象的。
厌倦了写sql建表,索引,外键了吧;
苦恼频繁地更新数据字典吧;
痛苦转换数据库吧,1个还能忍受,那5个呢?
别愣着了,赶快找个UML工具武装一下吧,到处都是砖家,喷子,不拍死也淹死。
 
现在比较流行建模工具有Rose,Visio,Powerdesigner。
Rose玩不转,软件跟公司名字一样大,等高手来解答。
Visio画UML图还可以,要数据库建模的话就歇菜了。
顶Powerdesigner,还是国人开发的(不过不是在天朝)。在3个中,数据库建模应该是最强的。CDM->PDM->OOM...... 爽到死为止。
小的不才,从刚接触powerdesigner到现在也4年了,居然都没什么长进。不得不承认自己的急功近利,让我耐下心来看文档,那真是如坐针毡,哪怕捣鼓一段呕心的CSS,JS都比这个强。让我看了还要再写文档,那我只有内牛满面了。记得第一次去客户那里了解需求,只记下了一句话,真是言简意赅啊。
扯远了。
写这段文字的初衷是 不小心看到以前公司的代码狂人在博客上炫耀自己的系统设计,岂是一个屌字了得。
哥看不下去了,一狠心就花了2天好好研究了一番,还是有点用的。
谁都不喜欢重复的工作,就像一直吃同一道菜,久了就会厌了。有同事对羊肉土豆丝百吃不厌的,我表示鸭梨很大。
在这里我就简单说下我用powerdesigner的几个感受吧。
1.对各实体之间的逻辑关系更清楚了。
2.建好CDM以后,就不用考虑数据库的差异了,生成哪种PDM都可以。
3.可以生成测试数据,手动造数据真是个噩梦。
4.可以根据现有的数据库或sql反向生成PDM,转而生成CDM。不过反过来的总没正着舒服,某些重口味的除外。
5. ta ta ta对你面试也有用。。。
 
光看别人自己不实践的,再热血沸腾都有可能是ZX。特别是喜欢看日本动作片的童鞋们,要实践啊。
关于powerdesigner使用方面的,有什么意见问题我们可以一起探讨。
本性纯洁,请不要与孤讨论日本动作片,听不懂。 
powerdesigner15工具太大,自己下吧。慎用中文补丁,效果不是很理想。
 
附件:相关学习文档和我做的一个精美小例子。效果图奉上

角色和功能表的CDM示例

 生成的mysql的PDM示例





作者: zprill 
声明: 本文系JavaEye网站发布的原创文章,未经作者书面许可,严禁任何网站转载本文,否则必将追究法律责任!

已有 38 人发表回复,猛击->>这里<<-参与讨论


JavaEye推荐



Tue 03 August, 2010

Channel Image22:09 与工作有关的几则信息» DBA Notes

从大公司出来,投身创业团队。遇到的最大问题就是招聘问题。创业公司,薪水不可能像上市公司那样大方,公司福利也做不到国企那要啥有啥。好不容易瞄上个技术差不多的,仔细沟通下来,人家还未必愿意一起来参与创业,更想着找个钱多人傻、事少家近的地方,不能破坏人家美好生活不是。其它包括地域问题的、家庭原因的... 所以,招聘是个困难活儿,也是个持久战。创业团队,只能慢慢来,只找合适的,找对的。

下面是广告时间:

北京方面,豆瓣技术团队在寻求运维工程师、DBA、系统架构师等方面的技术人才,具体信息请移步访问豆瓣招聘页面。在北京的技术人,不妨考虑一下豆瓣的的职业机会。

杭州方面,我所负责的丁香园技术团队也在招聘,需要的人才包括:视觉设计师、Java开发工程师、PHP开发工程师等。如果你不愿意去北京,不妨考虑来杭州看看。

当然,如果你只想在上海发展,PHP 方面的牛人不妨联系一下我,也有很不错的公司求贤若渴呢。更多工作机会,请关注 JobsDigg.com

另外,我的Blog首页放了一则技术推广广告,Adobe Flash Builder 4 中文版的下载。请路过的朋友支持一下吧。

--EOF--
Channel Image18:37 No One Nos: Learning to Say No to Bad Ideas» A List Apart
You can't create what clients need when you're too busy saying yes to everything they want. As a user experience designer, it's your job to say no to bad ideas and pointless practices. But getting to no is never easy. Proven techniques that can turn vocal negatives into positive experiences for you, the client, and most importantly, the end-user include citing best practices and simple but powerful business cases; proving your point with numbers; shifting focus from what to who; using the "positive no"; and, when necessary, pricing yourself out.
Channel Image18:37 Kick Ass Kickoff Meetings» A List Apart
Too many kickoff meetings squander the busiest, most expensive people's time reiterating what everyone already knows. If every meeting is an opportunity, why waste your first one? By asking stakeholders tough questions before the kick-off, and using the meeting itself to explore ideas and build relationships, you can turn a room of mutually suspicious turf battlers into an energetic team with shared ownership of the end-product and the kind of bond that can sustain the group through the challenges ahead.

Mon 02 August, 2010

Channel Image10:31 致母亲的悼文» robbin的自言自语
母亲已经去世了,经历了长达一年多癌症病痛的折磨之后,在7月23日凌晨辞世。为了寄托我们的哀思,我和姐姐商量给母亲建立一个独立域名的博客,发布一些母亲生前的日记和照片,以及我们的回忆文章。下面是追悼会上我念的悼词,是我和姐姐给母亲灵堂守灵的夜晚,由我来执笔撰写,姐姐删改而成。

致母亲的悼文

尊敬的各位领导,各位长辈,各位亲朋好友:

今天我们全家怀着万分悲痛的心情,悼念亲爱的母亲,并向她的遗体做最后的告别。感谢大家不辞辛劳来到这里吊唁,在她患病期间,同事和亲友不间断来探视和关心她,在此,我代表全家向各位表达我们最衷心的感谢!

我还要代表我们三个子女感谢我们的父亲。他在母亲患病期间给予了她无微不至的悉心照顾,为去北京就医,他甚至冬天在凌晨三点排队去买票。这样的事情在母亲患病期间不胜枚举,感谢你,父亲!

母亲是一位性格十分坚强和吃苦耐劳的人,总是为他人着想。母亲的一生是无比坎坷的一生,也是吃苦拼搏,奉献自己的一生,她是我们心目中最伟大的母亲。

母亲1939年出生在东北一个贫困的平民家庭。童年时期经历了抗战和内战带来的兵荒马乱时代,家境极度贫寒;学生时期又经历了大饥荒,文革等多次浩劫;大学毕业时又下乡到军垦农场被改造了两年;从上海到二汽后又改行,从设计军舰、鱼雷导航系统改行搞机械夹具设计;在六七十年代的艰苦条件下,养育了我们三个子女;在退休后搬进新家又身患绝症。

但母亲面对这样的坎坷和困难,却从未退缩。母亲八岁就开始做家务,十一岁就给家人做衣服和鞋子;上高三时生病,家里没有钱读大学,但还是考取了天津大学,靠助学金完成学业;在军垦农场被改造,却评上了“五好学员”;在化油器厂工作的23年中,晋升了高工,多次被评为先进工作者和三八红旗手。

母亲在二汽建厂,条件艰苦的环境下,含辛茹苦的养育了我们成人。在我们1岁时就教我们看图识字;4岁就教我们写字和算术;并且从小培养和监督我们养成良好的习惯和素养,从各方面严格要求我们。俗话说:严父慈母,我们家则是严母慈父,但母亲的严格要求换来的是子女的出息。

母亲在得知自己的病情后十分平静,没有流过一滴眼泪。她从容的安排自己的身后事,积极配合治疗。尽管医生诊断她只有8-11个月生命,但她却和病魔顽强的斗争了16个月。

因为我们三个子女都在外地,母亲担心我们回来会影响工作,总是反复劝我们不要回来陪她,而每次我们回来陪她时,她又不停劝我们赶紧回去,不要影响工作;同事和朋友来探望时,她总是强打精神,显得十分高兴,根本不像身患重病的人;母亲直到临终的前一天都坚持生活自理,从来不麻烦别人。她就是这样一位总是为别人着想,从不计较自己得失的人。

此刻,母亲穿着她为自己早已准备好的衣装,安睡在花丛之中,安详,恬静,她和我们永别了,但她对亲人,同事和朋友无私的奉献精神,对人生困难无畏无惧的勇气和拼搏精神将永远铭刻在我们心中,代代传承下去!

安息吧!亲爱的母亲!

长子:范威
次子:范凯
女:   范奕

叩首!


挽联:

玉凤展翅万里长空仙乡去
凤远音容千年传颂神州留

注:母亲原名张玉凤,因为和毛的秘书重名,大学时代改名张凤远。


已有 74 人发表留言,猛击->>这里<<-参与讨论


JavaEye推荐



Channel Image05:48 Happy 8th Birthday to this blog!» Raible Designs
Eight years ago today, this blog was born in the wee hours of the morning. I was inspired to start it after reading Dave Johnson's article on Roller. I have to say, it's been a great ride and I remember the early days like they were yesterday. Many of the Java bloggers wrote daily and shared short tips, tricks and snippets on their blogs; much in the same way we do on Twitter today.

A lot has happened in my life since this blog was started: Abbie was born, Jack was born, I started AppFuse, wrote Spring Live, had some really cool gigs and gained a whole new perspective on my life.

For those long time readers, you might've noticed the vacation posts have picked up recently and the technology posts have subsided somewhat. The good news is this indicates I'm having a lot of fun; the bad news is I'm not learning as much as I'd like. Hopefully that'll change soon and I'll be writing about developing apps for the online video space in the near future. There's a good chance the posts about my life and how much fun I'm having will continue, especially as Abbie and Jack continue to grow into world-class skiers.

As usual, I have many ambitions for this fall, including helping Apache Roller, finishing AppFuse 2.1 and learning how to play the guitar. Along the way, I'll be helping build/release some kick-ass software for a major cable provider, building a sauna in my basement and enjoying the hell out of Devoxx 2010. You can sure I'll be blogging about these along the way, as well as many years into the future.

Thanks for reading all these years, it's been a fantastic experience. :)

Sun 01 August, 2010

Channel Image17:44 MT上“Name "Locale::Maketext::Lexicon" used only once:” 问题的解决: 改用Perl内置函数库» 车东[Blog^2]

最近从服务器日志中经常发现MT的错误日志:
Name "Locale::Maketext::Lexicon" used only once: possible typo at
.../extlib/Locale/Maketext.pm line 653.,....

解决方法:
删除 mt/extlib/目录下的I18N/ 和 Locale/ 目录即可;

原因:
服务器上已经升级到Perl 5.10.x了: 很多国际化字符集方面的支持直接使用Perl内置的支持包即可。
[chedong@titans ~/logs/chedong.com/http]$ perl -v

This is perl, v5.10.0 built for x86_64-linux-gnu-thread-multi

Copyright 1987-2007, Larry Wall

Perl may be copied only under the terms of either the Artistic License or the
GNU General Public License, which may be found in the Perl 5 source kit.

Complete documentation for Perl, including FAQ lists, should be found on
this system using "man perl" or "perldoc perl". If you have access to the
Internet, point your browser at http://www.perl.org/, the Perl Home Page.

Sat 31 July, 2010

Fri 30 July, 2010

Channel Image13:54 Jess and Lili's Legendary Wedding on The Lost Coast» Raible Designs
If you're a long-time reader of this blog, you'll know I've been to some great weddings in the last couple years. This past weekend, I had the pleasure of experiencing yet another fantastic celebration with two old and close friends, Clint and Jess. You might remember Clint from his wedding in Costa Rica or when we almost slept in a snow cave. I'm happy to report we didn't get in any trouble and everyone survived the weekend without a scratch.

My trip to Jess's wedding (on the Lost Coast of Northern California) started with a flight to Portland, Oregon. After arriving, I drove to Clint and Autumn's house in Eugene where we enjoyed some sweet Oregon micros and reminisced about Costa Rica. The next morning, we headed for the wedding; an 8-hour drive. Our road trip was awesome, especially when we started driving through the Redwood Groves on 101.

We stayed in a sweet beach house for the weekend. While it was foggy most of the time, the sun did come out on Saturday. We quickly became surrounded by beautiful views and headed to the beach to relax with Jess.

Whoo hoo! Sunshine! Taking it all in Fog Lifting Clint and Jess

The wedding was on Sunday, a mere block from where we were staying. The ceremony was one of the most heartfelt I've ever heard, especially since the Wedding Official was a friend of the bride's since she was born.

Jess and Kai Smiles all around Vows Aawwwwww

The reception afterwards was a truly spectacular party that lasted well into the evening. Clint and I vowed to go to bed early, but we ended up having so much fun we closed the place down. Jess and Lili were an instrumental part in creating a spectacular night, especially with their wedding dance and infectious happiness.

Lili and Jess

The next day, we woke up on time, embarked on the 10-hour road trip back to Oregon and enjoyed a quick detour through the Avenue of the Giants. I did end up missing my flight home, but it was worth it. Thanks to Lili and Jess (and their families) for showing us such a great time. It was truly spectacular.

For more pictures, see albums on Flickr, Facebook or the slideshow below.

Thu 29 July, 2010

Channel Image21:50 Say It Isn’t So: Marketing Resource Site Marketing Profs Seems To Be Cloaking Search Engines – Inadvertantly?» Antezeta Web Marketing
Years ago savvy webmasters realized they could achieve better search engine visibility by creating two copies of a web page. One, text rich and graphics poor, would be seen by search engine robots, such as Googlebot, Yahoo Slurp and Microsoft Bing’s msnbot/bingbot. Everyday web users, surfing with Internet Explorer, Firefox, Chrome or Safari, would see [...]

Mon 26 July, 2010

Channel Image21:43 在 Sedo 交易域名应谨慎» DBA Notes

最近在 Sedo 上尝试购买一个域名,折腾了一笔不算成功的交易,算是交了一次不菲的学费。

或许是因为技术因素的限制,Sedo 的交易过程没办法像国内域名交易商(比如 4.cn)那样很严格的确定交易中的每一步的状态,而是完全靠代理人(也就是客服人员)对交易状态进行驱动,用户容易被误导。让人不能理解的是,Sedo 会在卖家将域名 Push 到 Sedo 后就给卖家付款(这个过程居然是不通知买家的,或许国外的担保交易都这样?),这个时候如果卖家申请取消交易是不可能的事情,我就是在失误在了这里。因为信息的不透明,新手的确不知道发生了什么,比如我。

事后搜索了一下,用户对 Sedo 的抱怨还是挺多的。虽说 Sedo 已号称进军中国,但实际上只是汉化了几个页面而已,客户支持方面的力度弱也可想而知,据说 Sedo 负责亚太区的只有一个人,也就是张谦先生,尽管反复的沟通后被告知结果不可改变,但还是感谢他的耐心吧,沟通中还是了解了不少东西的。

教训或许有点惨重,但是自己疏忽在先。吃一堑长一智。以后购买域名的时候建议朋友们优先考虑国内的平台交易吧,毕竟沟通起来更方便一些。

--EOF--

更新:我在后续要求 Invoice from Seller ,Sedo 给我的明显不是卖家的信息。只是他们自己简单做的一个 Invoice。

Fri 23 July, 2010

Channel Image18:09 你缺的,可能不是人才» 刘润

- 首发于《福布斯》中文网:http://www.forbeschina.com/column/liurun/2622

虽然IT和互联网越来越被认为是两个行业,但身在IT公司的我,有很多朋友在互联网跌爬滚打,我也有时会帮做投资的朋友看一些互联网项目,给些意见。

对人才的“渴求”,可能是因为策略的缺失

我发现一件很有意思的事情,有些朋友会感叹:

“招人真难。我的项目马上就要上线了,现在最头疼的事情,就是找不到合适的人才,尤其是好的销售总监,能想到办法帮我把东西卖出去。”

我的第一反应是,不,你缺的可能不是人才,你缺的是策略。

情景通常是这样的。创业者A结合自己的线下资源,比如一个皮鞋品牌,觉得可以考虑在互联网上开一个专门卖鞋的B2C网站。于是他和那个品牌合股,雇人开发一个独立的B2C网站,开始网上卖鞋。生意开张了,A觉得是时候招聘一个销售总监,来完成大家的销售预期了。但是却发现,这样的人很难招到。于是A开始感慨:人才难找啊。

真的是这样吗?给大家讲两个我听来的故事。

周鸿祎早期是做3721,卖中文域名的。这玩意难卖,看不见、摸不着。开发出这玩意,招一个销售总监,给他定指标是最容易做的事情,这似乎就叫:管理。但是,有用吗?如果你自己都不知道这东西卖给谁、怎么卖,当初为什么要开发它呢?

周鸿祎制订了一个“农村包围城市”的策略。他找到一群销售人员,给他们几个简单的指引:1)到二线城市去,2)引:中文域名就是网上门牌号码,3)诱:你不买,别人可能就买了。据说就靠这样简单的销售策略,3721中文域名销售战绩颇丰。这样的销售策略,你需要每一个销售人员都是销售大师吗?不需要。

另一个故事有关史玉柱的《征途》。这是一款新游戏,怎么卖?当时网络游戏是盛大《传奇》的天下。标准“管理”的做法是:找一群销售,给销售定指标,和奖金挂钩,然后让销售各显其能。有用吗?

传闻史玉柱制订了一项“策反”的销售策略。他找到一批人,每天去玩《传奇》。他们的任务很简单:1)找到其他玩家,2)聊天,3)说:《征途》挺好玩的,去玩吧。这样的销售策略,你需要每一个销售人员都是销售大师吗?不需要。

这两个故事都未经验证。但听闻后,深感这两个“策略”的简单、高明。

作为一个创业者,你自己是最重要的“人才”。你自己对“人才”的“渴求”,可能是因为策略的缺失。你渴求的人才,也是希望给你带来“策略”的人才。你需要的,首先是策略,然后才是人才。

对人才的“不满”,可能是因为管理的薄弱

有一位从事互联网的朋友说“工程师一个必须的突破就是,化繁为简的能力。”

这句话是很有道理的。但或许应该这么说:“工程师如果有了化繁为简的能力,那就太好了。”这是一种期望。之所以叫做期望,是因为永远都只有少部分人能做到。这不是“必须的突破”。可为什么管理者喜欢这么提呢?

我认为,是因为管理的薄弱。

化繁为简的能力,是组织的能力,是管理者的能力,不是工程师的能力。怎样才是好的管理?我们来看看盖房子。你找20个工人来,说,这是图纸,这是钱。你们必须有化繁为简的能力,把房子在20天内盖好。这是不现实的。作为管理者,我们懂得如何化繁为简。我们要先把工种先分为木工、水电工、瓦工等等。再把工序分为一二三四五。然后,重要的部分来了,你要给每个工人下达一个“简单”的指令,比如把砖头直直的砌起来,3米高、2米宽。就这样。

工程师只应接受简单的指令。我们“期望”大家足够聪明,但如果把质量“依赖于”大家的聪明,就会把公司置于巨大的风险之上了。

对人才期望过高,对现有能力和主动性不满,可能是因为管理的薄弱。

不要期望雇人来做你做的事

最后,反过来说,找到有主观能动性、有策略、能够化繁为简的人才,当然是公司的重要资产。这些关乎文化。强调创始人的策略和管理能力时,不应忽略员工文化的建设。

但是,特别是在创业初期,不要期望雇人来做你做的事,不要把策略、管理的缺失,转嫁为对“人才”的依赖。这样,会过早的在公司建立复杂的架构,在革命尚未成功之时,已经患上大公司病。

Thu 22 July, 2010

Channel Image18:57 架构师应该知道的那些事儿» DBA Notes

在新的团队有点忙,刚好这本 《软件架构师应该知道的97件事》 适合断断续续的阅读,然后慢慢"琢磨"。每个人偏重的技术角度不同,所以有些事情读罢可能未必能引发什么进一步的想法,但读到有些以前没关注过的话题则可能触发进一步思考。

印象最深的一句话是"确保简单的问题有简单的解",这本书里面很多话题都提到了"简单"这个词,我更喜欢用"简朴",不把简单的事情复杂化,和我一直坚持的理念有点不谋而合。其实,有些资深开发者很难抗拒"炫技"的诱惑,时常想用最新最酷的技术来做他认为"最有挑战最有难度"的事情,殊不知用更小的时间、人力、技术成本解决问题也是真正有技术含量的事情。

究竟什么才是称职的架构师,我想很难界定,很多公司对架构师有不一样的期待,但有一条,作为技术人总要不断的思考,持续学习,不断进步才能迎接更大挑战,才会称为别人眼中称职的架构师。

我的一个疑惑是:不知道有多少架构师现在还在公司之外保持阅读技术书籍的习惯呢?

--EOF--

Tue 20 July, 2010

Channel Image19:35 SVG with a little help from Raphaël» A List Apart
Want to make fancy, interactive, scalable vector graphics (SVGs) that look beautiful at any resolution and degrade with grace? Brian Suda urges you to consider Raphaël for your SVG heavy lifting.
Channel Image19:35 JavaScript Minification Part II» A List Apart
Variable naming can be a source of coding angst for humans trying to understand code. Once you’re sure that a human doesn’t need to interpret your JavaScript code, variables simply become generic placeholders for values. Nicholas C. Zakas shows us how to further minify JavaScript by replacing local variable names with the YUI Compressor.

Sat 17 July, 2010

Channel Image19:41 Google removes Suggest result counts – even behind the scenes» Antezeta Web Marketing
Figure 1: Once upon a time: Google Suggest with results counts Google Suggest is one of those wonderful features that makes Google irresistible. Ever noticed that as you start to type a query, Google provides the top queries which start with what you’ve typed? Initially introduced as a Google Labs experiment in 2004, Google introduced [...]

Wed 14 July, 2010

Channel Image02:18 Scaling Flash Movies to match Browser Zoom Levels» Raible Designs
Recently I was tasked with figuring out how to scale the Flash assets in the web application I'm working on. In the app, there's two different Flash assets: a Spotlight (cycles through images) and a Video Player. Before I started working on the issue, our assets would stay the same fixed size no matter what the browser zoom level. You can see this issue in action by going to Hulu or Fancast and zooming in/out (Command +/- on Mac, Control +/- on Windows). The Flash assets don't scale with the browser's text.

I found a lot of references for how to trap and handle resizing in JavaScript, so that's the initial path I took. I ended up having issues trapping the resize event in IE, as well as persisting the appropriate zoom level on page reload. Because of this, I ended up using a pure ActionScript solution that works much better. This article shows how I implemented both solutions.

Regardless of implementation, the first change I had to make was to move the height and width from the Flash asset (object/embed/JS) to its surrounding tag (<section> in our app). Then I changed the height/width to 100% on the Flash asset.

JavaScript Implementation
To allow zooming in ActionScript, I modified our main class to expose a "zoom" method to JavaScript:

ExternalInterface.addCallback("zoom", _zoom);

...

private function _zoom(scale:Number):void {
    _view.scaleX = _view.scaleX * scale;
    _view.scaleY = _view.scaleY * scale;
}
In the code above, _view refers to the container that holds all the items in the player. To call this method in JavaScript, I added the following code:

var windowHeight;
var documentHeight;

$(document).ready(function() { 
    ...
    windowHeight = $(window).height();
    documentHeight = $(document).height();

    $(window).resize(resizeWindow);
}

// Resize Flash assets when page is zoomed
function resizeWindow() {
    var newWindowHeight = $(window).height();
    var newDocumentHeight = $(document).height();
    // if document height hasn't changed, it's a browser window resize
    // event instead of a text zoom - don't change anything
    if (newDocumentHeight === documentHeight) {
        return;
    } else {
        documentHeight = newDocumentHeight;
    }
    var scale = (windowHeight / newWindowHeight); 

    var player = getFlashMovie('playerId');
    if (player && player.zoom) {
        player.zoom(scale);
    }
    var spotlight = getFlashMovie('spotlightId');
    if (spotlight && spotlight.zoom) {
        spotlight.zoom(scale);
    }

    windowHeight = newWindowHeight;
}

This seemed to work well in Firefox, Safari and Opera, but not in IE. I found this explanation about why it might not work, but I was unsuccessful in getting IE to recognize a resize/zoom event.

To fix scaling in our Spotlight asset, I used a similar solution. However, since the Spotlight didn't have all its elements in a container (they were being added directly to the stage), I had to refactor the code to add a SpotlightView (extends Sprite) that contains the bulk of the code.

Browsers persist the zoom level you've set for a site. The problem with the solution used here is it only scales up and down properly if you start from scale = 1 and revert to scale = 1 before leaving the site. If you zoom in and close your browser, when you come back the flash movies will be scale = 1 while the rest of the site is zoomed in. To solve this problem, I attempted to save the scale value in a cookie. This worked, and I was able to read the cookie in the *.as files to scale the movie correctly. However, I experienced some issues with this approach and didn't like having to delete cookies when I wanted the Flash assets to scale correctly.

ActionScript Implementation
After discovering issues with the JavaScript implementation, I did some research to see if it was possible to listen for the browser resize event in ActionScript. The Flash Fluid Layouts and Stage Resize in AS3 tutorial clued me in that the stage could listen for a resize event.

stage.addEventListener(Event.RESIZE, resizeListener); 

After adding the above line in the initialization, I added a resizeListener function that scales based on the default dimensions. It also ensures no scaling happens in full screen mode.

private function resizeListener(e:Event):void {
    // don't scale if entering full screen mode
    if (stage.displayState == StageDisplayState.FULL_SCREEN)  {
        _view.scaleX = 1;
        _view.scaleY = 1;
    } else {
        _view.scaleX = stage.stageWidth / 964;
        _view.scaleY = stage.stageHeight / 586;
    }
}

For the Spotlight asset, there are a number of different layouts (home, featured and news). The main class has a resizeListener function that scales accordingly to which layout type is being used.

private function resizeListener(e:Event):void {
    var type:String = _view.getLayoutType();

    if (type == "featured") { 
        _view.scaleX = stage.stageWidth / 958;
       _view.scaleY = stage.stageHeight / 180;
   } else if (type == "home") { 
        _view.scaleX = stage.stageWidth / 964;
        _view.scaleY = stage.stageHeight / 428;
    } else if (type == "news") {
        _view.scaleX = stage.stageWidth / 964;
        _view.scaleY = stage.stageHeight / 189;
    }
}

Because the layout type isn't set until the XML is loaded, I listen for that event in my URLLoader.

xmlLoader.addEventListener(Event.COMPLETE, resizeListener);

With the pure ActionScript implementation, the zoom level is automatically persisted. The Event.RESIZE event is fired by the Flash plugin when the page first loads if the dimensions are not the default.

That's it! Special thanks to James Ward for clueing me into scaleX and scaleY. Hopefully Hulu and Comcast can use this tutorial to scale their video players too. ;-)

Tue 13 July, 2010

Channel Image22:12 My Summer Vacation in Montana» Raible Designs
My favorite time of year is summertime. My favorite place to spend it is in Montana, often called "The Last Best Place" by natives. This year was no different and I spent the last two weeks at my family's cabin celebrating the 4th of July. Shortly after returning from our Father's Day Camping Trip, my parents packed up Abbie and Jack and headed on a 3-day road trip through Wyoming and Montana, camping and sight-seeing along the way. I followed them a few days later and made the 950-mile drive in just over 14 hours. With scenes like the one below, the trip was very enjoyable, despite it being so long.

Big Sky Country

The first week I was there, I worked remotely. It's always fun to tell people The Cabin has no electricity or running water, but it does have DSL. To be fair, it does have electricity, but it's not "on the grid" electricity - it's my Dad's concoction of generators, batteries and inverters. While I worked most of the week, I did manage to get a nice mountain bike ride in along the Foothills Trail to Holland Lake.

My real vacation began on the 4th of July weekend and we did it up right with the Swan Valley Parade and lots of big fireworks I picked up in Wyoming. The kids dressed up as Woody and Jesse (from Toy Story) and walked in the parade all by themselves (first time w/o me). They were especially excited when their pictures appeared in the local paper the following week.

Ready for the Parade Tossing Candy in the Parade Woddy and Jesse in the 4th of July Parade

Last week was spent hiking to Glacier Lake in the rain, golfing in Seeley Lake and Columbia Falls and hanging out with my good friend Owen Conley and his family.

Made it to Glacier Lake Chris Auchenbach Meadow Lake Golf Course in Columbia Falls Sunset from The Conley's

The kids and I drove home last Sunday and it only took us 15 minutes longer than it did for me solo. I think they're quickly becoming road-tripping professionals. :-)

My favorite part of this year's trip to The Cabin was seeing it as a home again. My Mom retired in April and my parents moved back to Montana shortly after. Seeing how happy they are there is truly magical. I especially enjoy the thought of visiting them and all the wonderful folks in the Swan Valley many, many times in the future.

To see all the pictures I took on this trip, check out the slideshow below.

P.S. An interesting note about all the pictures I took - they're all from my iPhone 4. I forgot my camera's battery at home and it seemed like a good experiment.

Thu 08 July, 2010

Channel Image22:51 EMC 收购 Greenplum 这事儿» DBA Notes

自从 Oracle 收购 Sun 之后,似乎放慢了收购步伐。这次 EMC 收购 Greenplum 的消息传出来,倒像是 Oracle 被偷袭。因为 Greenplum 和 Sun 之间关系密切,据说 Sun 还是大股东 (?),Greenplumn 出来打市场,基本上是和 Sun 硬件捆绑着卖的。Oracle 收购 Sun 后,我一度以为早晚 Greenplum 也会被 Oracle 收入囊中呢。

在我看来,EMC 收购 Greenplumn 目的其实也挺明确--就是为了卖更多的存储出去(当然要打着云计算的噱头)。我不看好这个买卖,EMC 在高端存储的优势余威犹在,在低端上似乎快被甩下了,我不是说技术上的问题,而是现在整个计算环境的模式已经发生了很大变化,廉价存储方案比比皆是,买存储盒子的用户只怕会越来越少。针对大规模数据的处理,Greenplumn 类似的计算模式颠覆了传统的数据仓库技术环境,估计也是 Oracle 不愿意看到的。但这是趋势,谁也无力抗拒。随着 Oracle 也开始卖自己的 Exadata 存储,与很多合伙伙伴都逐渐开始了竞争关系,EMC 倒也不能不防。

个人觉得 Redhat 倒是挺适合收购 Greenplum 的。原因无他,数据库软件和操作系统捆在一起卖更容易讨用户喜欢,而随着硬件卖,用户会有被挟持了的感觉。当然,我也是信口扯淡,我这么一说,你这么一听也就算了。

这次收购也让人猜测麦克尼利大叔与拉里大爷是不是关系紧张了呢?

--EOF--

Tue 06 July, 2010

Channel Image22:16 大型互联网公司应避免与初创公司争利» DBA Notes

前几天关于卷豆网的"淘金链 LinkMiner"产品被抄袭的争论引起了不少业界朋友的关注。在这里我不想探讨抄袭与否或者谁是谁非,而是想说说大型互联网企业与初创公司争利这事儿。

我们常说硅谷有更多的创新,创新来自哪里?多数来自初创公司(Startup)。而大公司针对初创公司取得的创新产品,常常采用收购的策略,这样,不但拿到了产品,占领了潜在市场,重要的是,还收获了团队。但是国内大公司采取的做法呢?是抄袭,或者说的好听一点叫"模仿",创新很难,模仿很容易,只要人手够,肯砸钱就成。这样下来的结果是,大公司内负责拷贝的小团队(或者是总算有了活干的产品经理)得到了短期的业绩,但公司长此以往则失去了创新的能力,而创业团队得不到益处。

这种中外差异,建硕认为是因为彼岸的人才在大公司之外,我觉得还是因为大公司心态问题。很多喊着开放的公司,为了一点小利而让整个社区齿寒,什么产品都弄一份"自己的",试问如何构建所谓的"开放平台"?我想很多公司都梦想着把自己的拳头产品打造成行业平台吧,问题是你骨子里就不打算让第三方开发者真正的得到益处,或者说大家都是来陪着太子读书的,稻子成熟了都叫你圈地收割了,这样如何达成愿景呢?

所以我说,大型公司应避免与初创公司争利,相反,应该花点心思扶植一些这样的团队,毕竟这样,才对整个生态有利。长期来看,这才是划算的买卖。

--EOF--
Channel Image19:30 Supersize that Background, Please!» A List Apart
Background images that fill the screen thrill marketers but waste bandwidth in devices with small viewports, and suffer from cropping and alignment problems in high-res and widescreen monitors. Instead of using a single fixed background size, a better solution would be to scale the image to make it fit different window sizes. And with CSS3 backgrounds and CSS3 media queries, we can do just that. Bobby van der Sluis shows how.
Channel Image19:30 Prefix or Posthack» A List Apart
Vendor prefixes: Threat or menace? As browser support (including in IE9) encourages more of us to dive into CSS3, vendor prefixes such as -moz-border-radius and -webkit-animation may challenge our consciences, along with our patience. But while nobody particularly enjoys writing the same thing four or five times in a row, prefixes may actually accelerate the advancement and refinement of CSS. King of CSS Eric Meyer explains why.

Thu 01 July, 2010

Channel Image21:54 儿子 @ 20个月» 刘润

1、一早起床,上班之前,陪儿子玩几分钟。儿子:拍什么拍,谁让你拍的?你是哪家报社的,不准拍!

2、晚上,儿子迷上了艺术,毕加索附身般的,在磁性画板上挥毫作画,然后拉我们欣赏。端详了半天,还是不解,儿子只好一边指点,一边口齿不清的解释:爸爸、妈妈、宝宝、外公、外婆~~~

 

3、上海这几天好热,于是上淘宝上买了一台风扇。这个新奇的没有叶子的玩意,是儿子生命中见到的第一台风扇。看他玩的多开心,哈哈,我在想,以后他看到有叶子的风扇,会不会反而觉得是神器呢?

Wed 30 June, 2010

Channel Image19:25 相忘于江湖» DBA Notes

有些时间没写新的东西。关注我的朋友应该已经从Twitter或是业界新闻获知(refer),我已经告别阿里巴巴集团旗下的支付宝,加入丁香园(http://dxy.cn)创业团队。所以近期比较忙,写东西的频率一下子也降了下来。

很多人认为我转换了行业,其实没有,接下来要做的依然是互联网,而且有更多的机会可以把想法变成现实,要做的业务也不排除会更多涉及电子商务。我非常认同丁香园的愿景,可能是因为多少还有点生物技术知识背景的缘故吧,我相信我们要做的事情会让这个社会更加美好一些。

从2005年加入阿里巴巴到现在,我在支付宝工作已五年多的时间。现在支付宝是国内最大的第三方支付公司,已彻底改变了电子商务的环境。自己有幸目睹支付宝从最初的数百万用户到今天为数亿用户提供服务的发展历程,对一个技术人员来说,有机会面对这样难得的技术挑战,是一份非常难得的成长机遇。在这个过程中,有艰辛和痛苦,但真是获益良多,更值得自豪的是,有幸在这个过程中奉献了一点微薄之力。若干年后回首往事也足以感到欣慰了。

要做的已经做过,想得到的业已得到,接下来在新的公司要面对更大的挑战。临别之际,想到的不是一些大事,倒是一些小事更让人感慨。还记得,一起和同事们奋战过的日子,还有那些欢笑和眼泪...

"相濡以沫,不如相忘于江湖"
--《庄子·大宗师》

--EOF-- PS. 部分内容来自我的告别邮件. a337dc067b044c15babbffc1aaa396f4

Tue 29 June, 2010

Channel Image12:36 Get Architecture Done -《分布式Java应用:基础与实践》» DBA Notes

按:承蒙林昊( @Bluedavy )看得起,嘱托我为他的大作《分布式Java应用:基础与实践》写序,倍感荣幸之余也颇有压力。读完本书的绝大部分章节后,这相信这会是我今年要向朋友们推荐的关于架构的图书。毕竟我在阿里系工作有年,对几家子公司的技术还算有所了解,内容有没有料还是可以一目了然的分辨的出来的。下文是推荐序。


  

提起诸如"高性能"、"高可用"、"大规模并发"、"可扩展性"这些词汇,我相信多数技术人的心情都是激动而稍有点复杂的,当然,也或许是不屑一顾。毕竟不是谁都有机会面对这些富有挑战的技术场景,也不是每个架构师在面对这些挑战之前都能做好技术上的准备。那些意外故障总是不期而至,疲于奔命的解决问题的场景回顾起来对架构师来说犹如一场噩梦。

  

本书阐述当一个面向数以亿计用户的网站经过几年高速发展,技术团队不得不面临大规模、高并发、高扩展性等挑战带来的技术困境的时候,一个出色的架构师经过多年一线实践后累积的经过时间考验的解决方案以及宝贵的实战经验。在这本书里,你会看到作者在解决一些关乎Web应用问题的指导原则、实践方法、多重工具的综合运用以及作者本人的感悟。要强调的是,本书讲述的内容是一个Web应用从小到大过程中遇到的棘手问题的解决之道,并非宏观解析,亦非屠龙之技。无论您面对的站点是大是小,皆会有参考作用,毕竟大站点会越来越复杂,而小站点总有一天也将变大。

  

如今到计算机书店里走一下,会发现Java架构相关的技术图书业已不少,但仍有理由相信本书内容填补了在Java架构实战方面的空白。在互联网应用大行其道的今天,有些名义上主题为Java架构的图书,要么单从Java本身阐述,缺乏整体应用的大局观;要么是高屋建瓴,从编程思想的高度坐而论道,缺乏实践性;要么是闭门造车之作,缺乏验证性。本书作者林昊多年来致力于推动OSGi在国内的发展,不乏理论技术功底,而后加盟淘宝网 (Taobao.com)的几年间奋战在架构一线,爬摸滚打积累了丰富的实践心得。所以,本书是一本不折不扣的"理论结合实践"之作。

  

考虑国内的技术图书出版环境以及必须尽力迎合读者的预期,写书本身是一件费力不讨好的事情,但将知识传递给更多人无疑是让人快乐的。现在,经过作者近两年的梳理与总结,这本书即将出版,相信您在研读本书之后有所收获并运用到您所面对的Web应用上,也期待将来有更多朋友能够分享架构实践经验,不亦快哉!

--EOF--   

Sat 26 June, 2010

Channel Image20:53 Move over Basic Search Results, Google Rich Snippets are here» Antezeta Web Marketing
For years search engine search results were limited to 3 basic pieces of information: a title, a summary and a URL. Attentive observers of Google search engine results may have noticed the appearance of additional information, what Google calls rich snippets, in certain results over the past few months. Some results contain review ratings, such [...]
Channel Image06:22 Another Fun Father's Day at The Great Sand Dunes» Raible Designs
For this year's Father's Day Camping trip, my parents drove down from Montana and we headed to the Great Sand Dunes (like last year). My friend Jason and his Dad joined us, as well as my co-worker Noah and his family. The weather was beautiful, the sand was hot and we had a blast flying kites while admiring the Medano Fire.

Friday Breakfast Hot, hot, hot! Aaaahhhhhh!! Fire in Background, 100 foot tall flames

Nice Grill At the Top Dillon Colors

On Saturday, we spent several hours on "the beach" watching the kids play, flying kites and sipping on cold ones. A good time was had by all.

Inventing Stuff Where did Jack go? Push me again! Smile!

Ela and Jack Abbie and Mimi Sweet Sunset

To see all the pictures I took on this trip, see my Great Sand Dunes 2010 set on Flickr.

Fri 25 June, 2010

Channel Image19:02 How Custom Events Will Save Us All» Ajaxian » Front Page
I am a big fan of both Andrew Dupont, and custom events. In his presentation he goes through some very nice use cases. Some are cross cutting (e.g. the fact that you can unit test, or debug, or ... so much easier) and some are specific such as: Scripty2 animation heartbeat PLAIN TEXT JAVASCRIPT: // keep the heartbeat going setTimeout(function() {   [...]

Thu 17 June, 2010

Channel Image22:55 Yahoo Directories in Europe RIP. Did anybody notice?» Antezeta Web Marketing
Yahoo! has closed some of their European directories sometime this year – their Italian and German directory URLs, e.g. http://it.dir.yahoo.com/, are now redirecting to their search page. Others may be impacted but I haven’t checked. At least the Italian directory is still in Google and Yahoo search results but not those of Bing. Yahoo Site [...]

Wed 16 June, 2010

Channel Image02:48 探索Google App Engine背后的奥秘(6)- 总结» DBA Notes
按:此为客座博文系列。投稿人吴朱华曾在IBM中国研究院从事与云计算相关的研究,现在正致力于研究云计算技术。

本篇是本系列的最终章,将总结一下App Engine在使用方面的注意点,最佳实践和适用场景,最后会谈一下我对App Engine的一些期望。

注意点

  • 执行速度偏慢:由于其分布式的设计,所以在速度方面不是最优的,比如普通的Memcache能在几毫秒完成操作,而App Engine的Memcache则大概需要50(毫)秒才能完成操作。
  • 私有API:其API有很多都是私有,特别是在其服务方面,虽然Google提供了很不错的文档,但是在学习和移植等方面,成本都很高。
  • 执行会出现失败的情况:根据很多人的实际经验,App Engine会不定时出现执行失败的情况,特别是Datastore和URLFetch这两部分,虽然Google已经将Datastore方面出现错误的几率从原先的0.4降至现在的0.1,但是失败的情况是很难避免的。
  • 有时会停机:虽然总体而言,停机并不频繁,但是在今年初出现长达136分钟故障导致部分用户的应用无法正常运行,其发生原因来自于其备份数据中心出现了问题。
  • 无法选择合适的数据中心:比如,你应用所面对的用户主要在欧洲,但是你应用所属App Engine服务器却很有可能是被部署在一个美国的数据中心内,虽然你的应用很有可能在将来移动至欧洲某个数据中心,但是你却无法控制整个过程。
  • 有时会处理请求超时:虽然能平均在100至200ms之间完成海量的请求,但是有时会出现处理请求超时的情况。
  • 不支持裸域名:只支持类似CNAME的子域名。

最佳实践

  • 适应App Engine的数据模型:因为其数据模型,并不是传统的关系模式,而且在性能方面表现也和关系型数据库差别很大,所以如果想要用好非常关键的Datastore,那么理解和适应其数据模型是不可或缺的。
  • 对应用进行切分:由于App Engine对每个应用都有一定资源限制,而且为了让应用更SOA化和更模块化,可以对一个应用切分多个子应用,比如,可以分成一个用于前端的Web应用和多个用于REST服务的后台应用。
  • 极可能多地利用Memcache,这样不仅能减少昂贵的Datastore操作,而且能减轻Datastore的压力。
  • 在上面提到过,由于App Engine在执行某些操作时会出现失败的情况,比如Datastore方面,所以要在设计和实现这两方面做好相应的异常处理工作。
  • 由于Datastore不是关系型数据库,导致在执行常见的求总数操作时显的有点"捉襟见肘",所以最好使用Google推荐的Sharded Counters技术来计算总数。
  • 由于Blobstore还只是刚走出试验期而已,而且其他模块对静态文件(比如图片等)支持不佳,比如Datastore只支持1MB以内的对象,同时每个应用只能最多上传一千个文件,而且速度不是最优,所以推荐使用其他专业的云存储,比如Amazon的S3或者Google马上就要推出的Google Storage等。
  • 尽量使用批处理方式,不论是在使用Datastore还是发送邮件等。
  • 不要手动创建Index:因为App Engine会自动根据你在代码中查询来创建相关的Index。

适用场景

现在而言,App Engne主要适用于下面这三个场景:

  • Web Hosting:这是最常见的场景,在App Engine上已经部署了数以十万计的小型网站(其中有很多主要为了学习目的),而且还部署了一些突发流量很大的网站,其中最著名的例子就是美国白宫的"Open For Questions"这个站点,主要用于让美国人民给奥巴马总统提问的,这个站点在短短的几个小时内处理接近百万级别的流量。
  • REST服务:这也是在App Engine平台上很常见的场景,最出名的例子就是BuddyPoke,BuddyPoke的客户端就是一个Flash应用,在用户的浏览器上运行,而它的服务器端则是以REST服务的形式放置在App Engine上,每当Flash客户端需要读取和存储数据的时候,它都会发请求给后端的REST服务,来让其执行相关的Datastore操作。
  • 依赖Google服务的应用:比如应用能够通过App Engine的Email服务来发送大规模的电子邮件。

未来的期望

  • 更稳定的表现,更少的超时异常和更快的反应速度,特别是在Datastore和Memcached这两方面。
  • 支持对数据中心的选择,虽然现在App Engine会根据应用的用户群的所在地来调整应用所在的数据中心,但由于整个过程对开发者而言是不可控的,所以希望能在创建应用的时候,能让用户自己选择合适的数据中心。
  • SLA,如果App Engine能像S3那样设定一些SLA条款,这样将使用户更放心地在App Engine上部署应用。
  • 新的语言:比如PHP,但是如果在现有的App Engine架构上添加一门新的语言,整个工作量会非常大的,因为App Engine有接近一半的模块是语言特定的,比如应用服务器和开发环境等,所以短期内我认为不太可能支持新的语言。

总体而言,Google App Engine是Google大战略中一个不可分割的一部分,因为Google希望能通过App Engine来降低Web应用开发的难度,只要难度降低了,那么Web应用替代客户端应用的整体速度将会加快,如果出现这样的情况的话,那么将会对Google今后的发展非常有利。

本系列文章结束。

参考资料:

--EOF--
Channel Image02:47 探索Google App Engine背后的奥秘(5)- Datastore的设计» DBA Notes
按:此为客座博文系列。投稿人吴朱华曾在IBM中国研究院从事与云计算相关的研究,现在正致力于研究云计算技术。

本篇会首先会从程序员角度来介绍一下Datastore在使用方面的一些信息,之后会接着介绍Datastore是如何构建的。

使用方面

首先,在编程方面,Datastore是基于"Entity(实体)"这个概念,而且Entity和"对象"这个概念比较类似,同时Entity可以包括多个Property(属性),Property的类别有整数,浮点和字符串等,比如,可以设计一个名为"Person"的Entity,它包含名为"Name"的字符串Property和名为"Age"的整数Property。由于Datastore是"Schema-less"的,所以数据的Schema都由应用维护,而且能非常方便地对一个Entity所包含的属性进行增删和修改。在存储方面,一个Entity的实例可以被认为是一个普通的"Row(行)",而包含所有这种Entity的实例的Table被称为Kind,比如,所有通过"Person"这个Entity生成实例,比如小吴,小朱和小华等,它们都会存放在同一个名为"Person"的Kind中。在结构方面,虽然也能通过特定的方式在Datastore中实现关系型结构,但是Datastore在设计上是为层次(Hierarchical)性结构"度身定做"的,有Root Entity和Child Entity之分,比如,可以把"Person"作为Root Entity(父实体),"Address"作为"Person"的Child Entity,两者合在一起可以称为一个"Entity Group"。这样做的好处是能将这两个实体集中一个BigTable本地分区中,而且能对这两个实体进行本地事务。

接下来,将谈一下Datastore支持那些高级功能:其一是提供名为GQL(Google Query Language)的查询语言,GQL是SQL的一个非常小的子集,包括对">","<"和"="等操作符。其二是App Engine会根据代码中查询语句来自动生成相应Index,但不支持对Composite Index生成。其三是虽然由于Datastore分布式的设计,所以在速度方面和传统的关系型数据库相比一定的差距,但是Google的架构师保证大部分对Datastore的操作能在200ms之内完成,同时也得益于它的分布式设计,使得它在扩展性方面特别出色。其四是Datastore也支持在实体之间创建关系,比如在Python版App Engine中可以使用ReferenceProperty在实体间构建一对多和多对多的关系。

下表为Datastore和传统的关系型数据库之间的比较:

  Datastore 关系型数据库
SQL支持 只支持一些基本的查询 全部支持
主要结构 层次(Hierarchical) 关系
Index 部分可自动创建 手动创建
事务 只支持在一个Entity Group内执行 支持
平均执行速度(ms) 低于200 低于100
扩展型 非常好 很困难,而且需要进行大量的修改

表1. Datastore和关系型数据库之间的比较

最后,在接口方面,Python版提供一套私有的API和框架,在基本功能方面,比较容易学习,但在部分高级功能方面,比如关系和事务等方面,学习难度很高;Java版的API是基于JDO和JPA这两套官方的ORM标准,但是和现在事实的标准Hibernate有一定的差异。

实现方面

在实现方面,Datastore是在BigTable的基础上构建的,所以本段会首先重新介绍一下BigTable,之后会介绍Datastore的两个组成部分:Entities Table和Index,最后会讲一下它在事务和备份这两方面所采用的机制。

BigTable

在本系列的第一篇已经按照Google的Paper对BigTable技术做了一定的介绍,但其实BigTable本身其实没有之前介绍的那样复杂,其实就是一个非常巨大的Table,这也是是它之所以名为"BigTable"的原因,而且结构就像图1那样非常简单,就是一个个ROW,每个ROW都有一个Name和一组Cloumn,但是为了支持海量的数据,它将这个大的Table进行分片(Sharding)处理,每台服务器存储一个海量的Table的一小部分,并且为了查询效率,会对这个Table进行排序。就像App Engine的创始人之一Ryan Barrett所说的那样"BigTable is a sharded, sorted array "。

BigTable Simple.PNG

图1. BigTable简化版模型

在功能方面,首先,BigTable支持基本的CRUD操作,也就是增加(Create),查询(Read),更新(Update)和删除(Delete)。其次支持对Single-Row的事务与基于前缀和范围的扫描。

Entities Table

它是Datastore最核心的Table,是以BigTable的形式存在的,主要用于存储所有的Entity,而且是格式非常简单,每行都会有一个Row Name,也称为Entity Key(可认为它是一个Entity的Primary Key),而且只有唯一一个Column,主要用于存放被序列化的Entity。每个Entity的Key的生成是基于它的父Entity(如果有的话)和其父至上的Entity,直到其Root Entity。以下图为例,timmy的父Entity是jane,jane的父Entity兼Root Entity是Ethel,所以最后timmy的Entity Key是"/Grandparent:Ethel/Parent:Jane/Child:Timmy"。

entity keys.PNG

图2. Entity Key的例子

Index

Index主要是为方便和加速查询而生的,所以在切入Index之前,先介绍一下Datastore主要支持那些查询,主要有三类:其一是基于Kind的,其二是基于Property值的,其三是基于多个Property值的。

Index表也是以BigTable的形式存在,但是和上面的Entities Table是分离的,主要用来单独存放那些需要被Index的数据,而且由于怕Index表体积太大,所以不会有时将其放置在内存中以提升查询速度。

主要有下面这几种Index表:

  • Kind Index:用于加速那些用于获取所有属于某个Kind的Entity的查询,比如把所有属于Person这个Kind的Entity,包括小吴,小朱和小华等提取出来,Kind Index表每行有Kind和Entity Key这两个列,此Index会有系统自动生成。
  • Single-property Index:用于加速那些基于单一属性值的查询,比如要找出所有Age在20之下的Person,Age就是所谓的那个单一属性值,Single-property Index表每行除了Kind和Entity Key之外,还有属性名和属性值这两个列,此Index也会有系统自动生成,还会根据升降序的不同,生成两个表。
  • Composite Index:用于加速那些基于对多个属性值的查询,Composite Index表基本和上面的Single-property Index表非常类似,但是每行包括多个属性名和属性值,而且由于此Index消耗资源非常多,所有由开发人自己确定是不是需要这个Index,系统不自动生成。

事务

原则上所有对单一Entity的Write操作都是事务的,并基于上面提到的BigTable的Single-Row事务和Optimistic Concurrency Control这两个技术,下面是流程:首先,系统会读这个Entity的Committed Timestamp(提交时间戳),Write会以串行(Serialized)的形式写入到BigTable的日志中,之后,系统会将日志更新到BigTable的表中,如果成功的话,系统会更新这个Entity的Committed Timestamp,但如果系统发现在更新之前,Committed Timestamp发生了变化,也就是说另一个事务在这个事务执行过程中已经对这个Entity进行了操作,在这个时候,系统会重新执行这个事务。由于在整个事务过程采用Optimistic Concurrency Control,而不是Locking,所以在吞吐量方面表现不错。

如果要对多个Entity执行事务,那就需要将这几个Entity设为一个Entity Group,也就意味着将这几个Entity放在同一台物理机上。在执行的时候,会将以Root Entity的Committed Timestamp为准来对所有参与事务的Entity进行和上面差不多的事务操作。

备份

与BigTable基于Row级别的备份不同的是,Datastore是基于Enity Group级别,而且采用Paxos算法,所以Datastore的备份方法比BigTable的更安全。

总体而言,Datastore在设计理念上和传统的关系型数据库有很大的不同,所以其在反应速度和写数据方面不是最优的,但是现在Web应用以读为主,而且需要能通过简单的扩展就能支持其海量的数据,而这两点却是Datastore所擅长,所以Datastore非常适合支撑Web应用。

本篇结束,下篇是本系列的总结。

--EOF--
Channel Image02:16 探索Google App Engine背后的奥秘(4)- Google App Engine的架构» DBA Notes
按:此为客座博文系列。投稿人吴朱华曾在IBM中国研究院从事与云计算相关的研究,现在正致力于研究云计算技术。

本篇将首先介绍App Engine的一些设计理念,接着将对App Engine的组成部分等进行介绍。

设计理念

App Engine在设计理念方面,主要可以总结为下面这五条:

  • 重用现有的Google技术:大家都知道,重用是软件工程的核心理念之一,因为通过重用不仅能减低开发成本,而且能简化架构。在App Engine开发的过程中,重用的思想也得到了非常好的体现,比如Datastore是基于Google的bigtable技术,Images服务是基于Picasa的,用户认证服务是利用Google Account的,Email服务是基于Gmail的等。
  • 无状态:为了让更好地支持扩展,Google没有在应用服务器层存储任何重要的状态,而主要在datastore这层对数据进行持久化,这样当应用流量突然爆发时,可以通过为应用添加新的服务器来实现扩展。
  • 硬限制:App Engine对运行在其之上的应用代码设置了很多硬性限制,比如无法创建Socket和Thread等有限的系统资源,这样能保证不让一些恶性的应用影响到与其临近应用的正常运行,同时也能保证在应用之间能做到一定的隔离。
  • 利用Protocol Buffers技术来解决服务方面的异构性:应用服务器和很多服务相连,有可能会出现异构性的问题,比如应用服务器是用Java写的,而部分服务是用C++写的等。Google在这方面的解决方法是基于语言中立,平台中立和可扩展的Protocol Buffer,并且在App Engine平台上所有API的调用都需要在进行RPC(Remote Procedure Call,远程方面调用)之前被编译成Protocol Buffer的二进制格式。
  • 分布式数据库:因为App Engine将支撑海量的网络应用,所以独立数据库的设计肯定是不可取的,而且很有可能将面对起伏不定的流量,所以需要一个分布式的数据库来支撑海量的数据和海量的查询。

组成部分

GAE ARCH.jpg

图1. GAE的架构图(图源自参[6])

简单而言,其架构可以分为三个部分:前端,Datastore和服务群:

前端

共包括四个模块:

  • Front End:既可以认为它是Load Balancer,也可以认为它是Proxy,它主要负责负载均衡和将请求转发给App Server(应用服务器)或者Static Files等工作。
  • Static Files:在概念上,比较类似于CDN(Content Delivery Network,内容分发网络),用于存储和传送那些应用附带的静态文件,比如图片,CSS和JS脚本等。
  • App Server:用于处理用户发来的请求,并根据请求的内容来调用后面的Datastore和服务群。
  • App Master:是在应用服务器间调度应用,并将调度之后的情况通知Front End。

Datastore

它是基于BigTable技术的分布式数据库,虽然其也可以被理解成为一个服务,但是由于其是整个App Engine唯一存储持久化数据的地方,所以其是App Engine中一个非常核心的模块。其具体细节将在下篇和大家讨论。

服务群

整个服务群包括很多服务供App Server调用,比如Memcache,图形,用户,URL抓取和任务队列等。

Python版和Java版App Engine在实现方面的区别

因为大多数服务都可以被这两个版本共享,所以两者之间的区别主要集中在App Server端,Python版App Server应该是经过Google修改的Python Runtime,版本号应该是2.5.2,而Java版App Server是基于Jetty 6的,因为它的体积和最常用的Tomcat相比更娇小,这样能使得一台服务器支持更多的应用,而且其应该经过Google的一定的修改。

流程

在这里举一个普通的HTTP请求的处理流程为例:

  • 用户发送一个HTTP请求。
  • Front End接受这个请求,并将这个请求转发给一个空闲的App Server。
  • App Server会处理这个请求。
  • 检查用于处理这个请求的Handler是不是已经被初始化了,如果没有的话,需要对这个Handler进行初始化。
  • 调用服务群的用户认证服务来对用户进行认证,如果失败的话,需要终止整个请求的处理工作,并返回用户无法被认证的信息。
  • 查看这个请求所需的数据是否已经缓存在Memcahe中,如果没有的话,将对Datastore发出查询请求来得到数据。
  • 通过整合上步得到数据来生成相关的HTML,并返回给用户。
  • 由于HTML里面会包含对一些静态文件的引用,比如图片和CSS等,所以当用户收到HTML之后,还会通过Front End对Static Files里面存储的静态文件进行读取。

本篇结束,下篇将关注App Engine最核心的Datastore的设计。

--EOF--

Tue 15 June, 2010

Channel Image21:11 探索Google App Engine背后的奥秘(3)- Google App Engine的简介» DBA Notes
按:此为客座博文系列。投稿人吴朱华曾在IBM中国研究院从事与云计算相关的研究,现在正致力于研究云计算技术。

通过前面两篇介绍,大家应该对Google强大的基础设施有一定的了解。本篇开始介绍构筑在这强大基础设施之上的Google App Engine。

Google App Engine的介绍

由于发布S3和EC2这两个优秀的云服务,使得Amazon已经率先在云计算市场站稳了脚跟,而身为云计算这个浪潮的发起者之一的Google肯定不甘示弱,并在2008年四月份推出了Google App Engine这项PaaS服务,虽然现在无法称其为一个革命性的产品,但肯定是现在市面上最成熟,并且功能最全面的PaaS平台。

Google App Engine 提供一整套开发组件来让用户轻松地在本地构建和调试网络应用,之后能让用户在Google强大的基础设施上部署和运行网络应用程序,并自动根据应用所承受的负载来对应用进行扩展,并免去用户对应用和服务器等的维护工作。同时提供大量的免费额度和灵活的资费标准。在开发语言方面,现支持Java和Python这两种语言,并为这两种语言提供基本相同的功能和API。

功能

在功能上,主要有六个方面:

  • 动态网络服务,并提供对常用网络技术的支持,比如SSL等 。
  • 持久存储空间,并支持简单的查询和本地事务。
  • 能对应用进行自动扩展和负载平衡。
  • 一套功能完整的本地开发环境,可以让用户在本机上对App Engine进行开发和调试。
  • 支持包括Email和用户认证等多种服务。
  • 提供能在指定时间和定期触发事件的计划任务和能实现后台处理的任务队列。

使用流程

整个使用流程主要包括五个步骤:

  • 下载SDK和IDE,并在本地搭建开发环境。
  • 在本地对应用进行开发和调试。
  • 使用GAE自带上传工具来将应用部署到平台上。
  • 在管理界面中启动这个应用。
  • 利用管理界面来监控整个应用的运行状态和资费。

由于本系列是专注于GAE的实现和设计两方面,所以不会对GAE的使用有非常深入地介绍,如果希望大家对GAE的使用方面有更深的理解,具体可以参看一下GAE的官方文档

Google App Engine的主要组成部分

主要可分为五部分:

  • 应用服务器:主要是用于接收来自于外部的Web请求。
  • Datastore:主要用于对信息进行持久化,并基于Google著名的BigTable技术。
  • 服务:除了必备的应用服务器和Datastore之外,GAE还自带很多服务来帮助开发者,比如:Memcache,邮件,网页抓取,任务队列,XMPP等。
  • 管理界面:主要用于管理应用并监控应用的运行状态,比如,消耗了多少资源,发送了多少邮件和应用运行的日志等。
  • 本地开发环境:主要是帮助用户在本地开发和调试基于GAE的应用,包括用于安全调试的沙盒,SDK和IDE插件等工具。

应用服务器

应用服务器依据其支持语言的不同而有不同的实现。

Python的实现

Python版应用服务器的基础就是普通的Python 2.5.2版的Runtime,并考虑在在未来版本中添加对Python 3的支持,但是因为Python 3对Python而言,就好比Java2之于Java1,跨度非常大,所以引入Python3的难度很大。在Web技术方面,支持诸如Django,CherryPy,Pylons和Web2py等Python Web框架,并自带名为"WSGI"的CGI框架。虽然Python版应用服务器是基于标准的Python Runtime,但是为了安全并更好地适应App Engine的整体架构,对运行在应用服务器内的代码设置了很多方面的限制,比如不能加载用C编写Python模块和无法创建Socket等。

Java的实现

在实现方面,Java版应用服务器和Python版基本一致,也是基于标准的Java Web容器,而且选用了轻量级的Jetty技术,并跑在Java 6上。通过这个Web容器不仅能运行常见的Java Web 技术,包括Servlet,JSP,JSTL和GWT等,而且还能跑大多数常用的Java API(App Engine有一个The JRE Class White List来定义那些Java API能在App Engine的环境中被使用)和一些基于JVM的脚本语言,例如JavaScript,Ruby或Scala等,但同样无法创建Socket和Thread,或者对文件进行读写,也不支持一些比较高阶的API和框架,包括JDBC,JSF,Struts 2,RMI,JAX-RPC和Hibernate等。

Datastore

Datastore提供了一整套强大的分布式数据存储和查询服务,并能通过水平扩展来支撑海量的数据。但Datastore并不是传统的关系型数据库,它主要以"Entity"的形式存储数据,一个Entity包括一个Kind(在概念上和数据库的Table比较类似)和一系列属性。

Datastore提供强一致性和乐观(optimistic)同步控制,而在事务方面,则支持本地事务,也就是在只能同一个Entity Group内执行事务。

在接口方面,Python版提供了非常丰富的接口,而且还包括名为GQL的查询语言,而Java版则提供了标准的JDO和JPA这两套API。

而且Google已经在今年的Google I/O大会上宣布将在未来的App Engine for Business套件中包含标准的SQL数据库服务,但现在还不确定这个SQL数据库的实现方式,是基于开源的MySQL技术,还是基于其私有的实现,这是一个问题。

服务

Memcache

Memcache是大中型网站所备的服务,主要用来在内存中存储常用的数据,而App Engine也包含了这个服务。有趣的是App Engine的Memcache也是由Brad Fitzpatrick开发。

URL抓取(Fetch)

App Engine的应用可以通过URL抓取这个服务抓取网上的资源,并可以这个服务来与其他主机进行通信。这样避免了应用在Python和Java环境中无法使用Socket的尴尬。

Email

App Engine应用使用这个服务来利用Gmail的基础设施来发送电子邮件。

计划任务(Cron)

计划服务允许应用在指定时间或按指定间隔执行其设定的任务。这些任务通常称为Cron job。

图形

App Engine 提供了使用专用图像服务来操作图像数据的功能。图像服务可以调整图像大小,旋转、翻转和裁剪图像。它还能够使用预先定义的算法提升图片的质量。

用户认证

App Engine的应用可以依赖Google帐户系统来验证用户。App Engine还将支持OAuth。

XMPP

在App Engine上运行的程序能利用XMPP服务和其他兼容XMPP的IM服务(比如Google Talk)进行通信。

任务队列(Task Queue)

App Engine应用能通过在一个队列插入任务(以Web Hook的形式)来实现后台处理,而且App Engine会根据调度方面的设置来安排这个队列里面的任务执行。

Blobstore

因为Datastore最多支持存储1MB大小的数据对象,所以App Engine推出了Blobstore服务来存储和调用那些大于1MB但小于2G的二进制数据对象。

Mapper

Mapper可以认为就是"Map Reduce"中的Map,也就是能通过Mapper API对大规模的数据进行平行的处理,这些数据可以存储在Datastore或者Blobstore,但这个功能还处于内部开发阶段。

Channel

其实Channel就是我们常说的"Comet",通过Channel API能让应用将内容直接推至用户的浏览器,而不需常见的轮询。

除了Java版的Memcache,Email和URL抓取都是采用标准的API之外,其他服务无论是Java版还是Python版,其API都是私有的,但是提供了丰富和细致的文档来帮助用户使用。

管理界面

用了让用户更好地管理应用,Google提供了一整套完善的管理界面,地址是http://appengine.google.com/ ,而且只需用户的Google帐户就能登录和使用。下图为其截屏:

Dashboard.PNG 图1. 管理界面(点击看大图)

使用这个管理界面可执行许多操作,包括创建新的应用程序,为这个应用设置域名,查看与访问数据和错误相关的日志,观察主要资源的使用状况。

本地开发环境

为了安全起见,本地开发环境采用了沙箱(Sandbox)模式,基本上和上面提到的应用服务器的限制差不多,比如无法创建Socket和Thread,也无法对文件进行读写。Python版App Engine SDK是以普通的应用程序的形式发布,本地需要安装相应的Python Runtime,通过命令行方式启动Python版的Sandbox,同时也可以在安装有PyDev插件的Eclipse上启动。Java版App Engine SDK是以Eclispe Plugin形式发布,只要用户在他的Eclipse上安装这个Plugin,用户就能启动本地Java沙箱来开发和调试应用。

编程模型

因为App Engine主要为了支撑Web应用而存在,所以Web层编程模型对于App Engine也是最关键的。App Engine主要使用的Web模型是CGI,CGI全称为"Common Gateway Interface",它的意思非常简单,就是收到一个请求,起一个进程或者线程来处理这个请求,当处理结束后这个进程或者线程自动关闭,之后是不断地重复这个流程。由于CGI这种方式每次处理的时候,都要重新起一个新的进程或者线程,可以说在资源消耗方面还是很厉害的,虽然有线程池(Thread Pool)这样的优化技术。但是由于CGI在架构上的简单性使其成为GAE首选的编程模型,同时由于CGI支持无状态模式,所以也在伸缩性方面非常有优势。而且App Engine的两个语言版本都自带一个CGI框架:在Python平台为WSGI。在Java平台则为经典的Servlet。最近,由于App Engine引入了计划任务和任务队列这两个特性,所以App Engine已经支持计划任务和后台进程这两种编程模型。

限制和资费

首先,谈一下App Engine的使用限制,具体请看下表:

类别 限制
每个开发者所拥有的项目 10个
每个项目的文件数 1000个
每个项目代码的大小 150MB
每个请求最多执行时间 30秒
Blobstore(二进制存储)的大小 1GB
HTTP Response的大小 10MB
Datastore中每个对象的大小 1MB

表1. App Engine的使用限制

虽然这些限制对开发者是一种障碍,但对App Engine这样的多租户环境而且却是非常重要的,因为如果一个租户的应用消耗过多的资源的话,将会影响到在临近应用的正常使用,而App Engine上面这些限制就是为了是运行在其平台上面应用能安全地运行着想,避免了一个吞噬资源或恶性的应用影响到临近应用的情况。除了安全的方面考虑之后,还有伸缩的原因,也就是说,当一个应用的所占空间(footprint)处于比较低的状态,比如少于1000个文件和大小低于150MB等,那么能够非常方便地通过复制应用来实现伸缩。

接着,谈一下资费情况,App Engine的资费情况主要有两个特点:其一是免费额度高,现有免费的额度能支撑一个中型网站的运行,且不需付任何费用。其二是资费项目非常细粒度,普通IaaS服务资费,主要就是CPU,内存,硬盘和网络带宽这四项,而App Engine则除了常见的CPU和网络带宽这两项之外,还包括很多应用级别的项目,比如:Datastore API和邮件API的调用次数等。具体资费的机制是这样的:如果用户的应用每天消费的各种资源都低于这个额度,那们用户无需支付任何费用,但是当免费额度被超过的时候,用户就需要为超过的部分付费。因为App Engine整套资费标准比较复杂,所以在这里就主要介绍一下它的免费额度,具体请看下表:

类型 数量(每天)
邮件API调用 7000次
传出(outbound)带宽 10G
传入(inbound)带宽 10G
CPU时间 46个小时
HTTP请求 130万次
Datastore API 1000万次
存储的数据 1G
URL抓取的API 657千次

表2. App Engine的免费额度表

从上面免费额度来看,除了存储数据的容量外,其它都是非常强大的。

本篇结束,下篇将对App Engine的架构进行介绍。

--EOF--
Channel Image13:57 仰望星空,脚踏实地:市场不会跑偏» 刘润

- 首发于《福布斯》中文网:http://www.forbeschina.com/column/liurun/1790

关于我的系列微博“刘润的数字化家庭”连载,一位关注者提出很好的观点。限于微博字数,我答应写篇博文来说说我的看法。


关注者:
俺的好多想法都不知道向哪哭诉去. 比如数字家庭, 俺觉得现在市场有点跑偏了. 当务之急是制定和确立一套 家电控制协议. 技术上没有问题, 就是缺乏领导者. MS大概可以做到. 谁能掌握这个发言权, 谁就是数字家庭的盟主。

我:
不,市场不会跑偏,通常是我们自己跑偏了。

 

市场会跑偏吗?

这个问题,就象问:客户的需求会错吗。客户的需求可以愚昧、可以可笑、可以超前、可以无理,但是客户的需求不会“错”。市场可以不成熟,可以不统一、可以很小众、可以很滞后,但是市场不会“偏”。

最早,MSN(Microsoft Network)是随着Windows 95发布的网络内容服务,独立于互联网。互联网刚刚崛起,一些人认为,市场跑偏了,市场更需要的是集中的、经过整理的网络内容服务,而不是分布式的、没有统一标准的互联网。市场随即毫不犹豫的回敬了一记响亮的耳光:是你跑偏了,并以排山倒海之势袭来。善于快速学习、持续改进的微软,迅速在1996年调整了MSN的定位,回归互联网的一员。

1999年,微软提出“数字家庭”的梦想,并推出“维纳斯”计划。网络尚处窄带,家电远未智能,市场说:微软,这次你跑快了。轰轰烈烈之后,维纳斯计划无疾而终。11年间,微软取道Xbox逐步实现当年的“数字家庭”梦想,但是方法更加成熟、渐进。Windows Media Center,Windows Home Server都是渐进的尝试。

今天(2010年6月15日),微软在中国正式推出了Office 2010。Office 2010一个重大亮点,是基于云计算的“在线Office文件建立、编辑、保存”,让人振奋。其实,早在2000年,微软就投入据说有上千人的团队,研发一款代码为“NETDOC”的产品实现此梦想,但是由于早产,再次被市场打破。而今天,终于等到了恰逢其时。

把未来的梦想放在心中,让今天的市场指导行为,仰望星空,脚踏实地。梦想创造未来的模式,市场产生今天的营收。我们不是那只手,市场才是。他会把你从跑快的、跑慢的、跑偏的路上抓回来,抓不回来的,就放弃掉。

(注:本文只代表作者本人观点,不代表其服务的公司、机构。)

Channel Image13:42 My Incredible Trip to Ireland» Raible Designs
If you ever get a chance to travel to Ireland, take it! I don't know when I heard these words, or how they came into my head, but I remembered them clearly when I was first introduced to Barry Alistair by Jeff Genender. Soon after, I was able to negotiate my way into being a speaker at The 2010 Irish Software Show.

The show was last week and I had a blast traveling to Dublin to speak and explore. My sister came me on this trip, but missed a connection in Seattle and had to join me a day late. I left Denver at noon on Monday and arrived at Dublin Airport at 7 am. I was on the same flight as Josh Long and thoroughly enjoyed my iPad as a travel companion. When I got off the plane, my battery life was at 60% and I'd been watching movies and listening to music for 6 hours.

I took a cab through the misty, cool morning to my hotel. I grabbed a coffee, cleaned up, and walked a few blocks to Trinity College for the conference. I made it in time for the opening keynote by Chris Horn. It was an interesting talk, focusing on what needed to happen to make Ireland the Innovation Hub of Europe. After that, I attended Tim Berglund's session on Complexity Theory and Software Development. After lunch and a few more talks, I teamed up with Andres Almiray and Josh Long for a pint at the hotel bar.

That evening, we attended Jeff Genender's talk on Getting into Open Source. The free drinks loosened everyone up and Jeff did a great job with a humorous presentation on how to get Committer Status. After Jeff's talk, about 10 of us headed to a Moroccan restaurant for a late dinner. I was in bed around midnight.

Andres Almiray and Josh Long The Genenders Heading for Indian After Jeff's Talk Streets of Dublin in the early morning

Wednesday morning, my sister arrived in my hotel room at 8 and promptly fell into bed. I set my alarm to sleep an hour and closed the Vegas-style, no-light-allowed curtains. We awoke much later (12:30) than we'd planned (9:00). We quickly got up and headed for some sight-seeing in Dublin. First off, we hit Dublinia and Christ Church Cathedral. Both sites were spectacular and we both learned a lot about the history of Dublin. From there, we skipped across the bridge to The Old Jameson Distillery for a tour and a bit of whiskey.

Runes Exhibit in Dublinia Christ Church Cathedral and Dublinia Tasting Whiskey The 18 Year

The picture below was taken on the Ha'penny Bridge as we were heading back from Jameson. The expression of the girl on the left is priceless.

Kalin on the Half Penny

A couple hours later and I was delivering my talk on The Future of Web Frameworks. The crowd was lively; the Guinness I drank while talking was lovely. My session was followed by a Web Framework Experts Panel with Peter Ledbrook (Grails), Jamie van Dyke (Rails), Shay Friedman (ASP.NET MVC), Julian Fitzell (Seaside) and myself (Java Frameworks). The debate was good and there was much discussion about the right apps for each framework and how important statelessness is for scalable applications. After 3 hours of talking, my sister and I headed back to the hotel. I was particularly happy about the evening since it was the first time a family member of mine had seen me speak.

Correction from my Dad: This wasn't the first time a family member saw me speak. He attended my talk at ApacheCon EU 2007.

A block from the hotel, we spotted a nice looking pub (Doyles) and stopped in for a pint. As we bellied up to the end of the bar, we recognized Jamie (from the panel) and got introduced to his friend Rob. We quickly got lost in conversation, stories and laughter and were surprised when we discovered it was 2:30am. Since I had a talk first thing in the morning, we ducked out shortly after.

Web Framework Experts Panel Barry on Evangelist Night The Night we met Jamie and Rob

Thursday started with my talk Comparing Kick-Ass Web Frameworks. Then my sister and I did some more site-seeing, starting at the Guinness Storehouse. We met Josh and John Willis as they were leaving and they advised we go straight to The Gravity Bar at the top. We took there advise and were getting great views of Dublin and savoring sweet pints of Guinness moments later. The tour facility was freakin' awesome and I loved how it was shaped like a pint glass.

Straight to the top! Mmmmm, Guinness The Storehouse is shaped like a pint glass Brainwave

We grabbed some gear from the gift shopped and landed (by accident) at The Brazen Head (Ireland's Oldest Pub, Est. 1198) for a pint of cider and Guinness. Since my sister used to be in the cider business, she was particularly happy there was so much on tap in Ireland.

From the pub, we headed to John Willis's session on The Cambrian Cloud Explosion. Following John's session, we headed to the Speaker's Dinner for a very fun evening with the hosts and speakers of the conference.

John Willis and Barry Alistair Speaker's Dinner at Irish Software Show 2010 Speaker's Dinner at Irish Software Show 2010 Speaker's Dinner at Irish Software Show 2010

Speaker's Dinner at Irish Software Show 2010 Speaker's Dinner at Irish Software Show 2010 Speaker's Dinner at Irish Software Show 2010

On Friday, we woke up in the early afternoon and quickly decided the Book of Kells was our best chance of getting some site seeing in. After visiting the Book of Kells, my favorite quote of the conference happened in the courtyard.

Josh looked at Jamie (with his bad hangover) and exclaimed, "My God Man. Your skin is so white it's hurting my eyes!". You probably had to be there (or know Josh) to enjoy the humor, but I wanted to capture the memory in this post so I could laugh whenever I read this in the future. After that, Jamie, Josh, Kalin and I enjoyed a Starbuck's patio talking about living in the South of France for a couple hours. Then we walked 2 blocks to the Porterhouse Brewing Co. to watch the World Cup and enjoy more interesting conversations.

The Book of Kells Jamie with the Wenches Lovely Wenches Jamie and his Lady Drink

Jamie left the conference that evening and we joined a whole slew of other speakers for dinner at an excellent Lebanese restaurant near Temple Bar. Good times where had afterwards at a nearby Silent Disco.

Kalin and Craig Post Absinthe

Saturday, we woke up early to catch a tour bus out to Glendalough with Josh and John. The bus ride was not pleasant, but the destination was spectacular. We hung out there for several hours, exploring the buildings, walking to the lake and humoring each other.

Glendalough Beautiful Views at Glendalough Glendalough Lower Lake at Glendalough

Our last night in Dublin was an early, relaxing one. As you can tell, I really enjoyed this trip, particularly hanging out with my sister and all the cool people we met. I can easily say that this trip registers as one of my favorite conference experiences to date.

To see all the pictures I took on this trip, check out my Irish Software Show 2010 set on Flickr.

Sat 12 June, 2010

Channel Image23:05 微博连载《刘润的数字化家庭》 (最终)» 刘润

我心血来潮的想尝试一种有趣的形式分享一些关于“数字化家庭”的浅薄心得,并计划完成22页的PPT,努力做到每天发布一张(图片格式)在四大微博(新浪、腾讯、搜狐、网易)上。等完整发布后,再上载原始PPT文件到本博客。

新浪:http://t.sina.com.cn/runliu
腾讯:http://t.qq.com/runliu
搜狐:http://t.sohu.com/runliu
网易:http://t.163.com/runliu

1 of 22 (大图):
用数字化的技术,从娱乐的舒适,工作的高效,体验生活的美好。

2 of 22(大图):
你这里所看到的,都是已经部署在刘润家中的实际应用。或许浅薄,但望能够抛砖引玉,和诸位切磋学习。数字化是手段,不能忘记,提高生活质量是目的。每一样看似复杂的设计,都是为了让生活更简单,更美好。

3 of 22(大图):
追求近乎完美体验的次世代“高清家庭影院”,是整个家庭娱乐的中枢。我选择了“哈曼卡顿次世代AV功放 AVR-354”,作为化复杂为简单的中心设备。

4 of 22(大图):
在星状结构下,你可以添加尽量多的数码设备作为高清家庭影院的输入设备,并且不会因此而增添操控麻烦。当然,如果你喜欢打游戏,还可以考虑Xbox E3。如果你有在家演示PPT的习惯,还可以接根VGA线。

5 of 22大图):
看电视就用液晶,看电影就用灰幕。躺入柔软的沙发,在完全独享的空间里,对着92吋-120吋的灰幕,被震撼的音响包围,欣赏不管超级大片,还是世界杯,都是一个无法抵挡极致享受。

6 of 22大图):
你想不想在泡浴缸的时候,回荡着舒缓的音乐?或者在厨房做饭的时候,也能听着正在播放的电视剧?再或者,早上刷牙洗脸的时候,耳边播报早新闻?如果想,那么,你需要背景音乐系统。

7 of 22大图):

卧室想完全分享客厅的音视频资源,需要在装修设计的时候,留一组从次世代功放到卧室的色差+音频线。如果你幸运的拥有罗技Harmony 890遥控器+RF Wireless Extender,你就可以拿着这个遥控器,穿墙遥控客厅所有设备了。

8 of 22大图):
工作繁忙,时常觉得累,所以一直想养成锻炼身体的习惯。习惯本身不痛苦,养成习惯的过程很痛苦。我需要一种力量/乐趣,能够帮助我克服养成习惯的过程中的痛苦。Nike+的Sportband用“跑步社交”的办法帮我解决了这个问题。等我练好了,我要参加刘翔挑战赛。:-)

9 of 22大图):
高速而且智能的网络,是家庭工作以及娱乐的基础。而智能的前提,是高速。今后的工作越来越多的云计算,今后的娱乐越来越多的在线互动,都需要高速并且智能的网络支持。随时随地,与世界高速连通。

10 of 22大图):
下载大文件时上网速度明显变慢?想从外面访问家庭网络?你的路由器必须很智能。千兆交换机是网络心脏,智能路由器就是网络大脑。(终于第10张了!做PPT真累,呵呵,希望对大家有价值。)

11 of 22大图):
你旅游回酒店,把照片存回家,父母拿着遥控器分享你的风景。或者有小孩子,你发现有越来越多的照片、视频,要收藏也要常看,拍不完看不厌。又或者,你喜欢整理,数年的邮件、文档上百G,刻盘保存,但总是访问不便。那么一个家庭中央存储非常必要。

12 of 22大图):
送你一个免费的、忠诚的、很省电的机器人,就坐在你家4M互联网的入口,饥渴的等待你的指令,随时准备不眠不休、默默无闻的帮你每月从互联网上拉下1200G的文件,却只吃5元的电费,你觉得怎样?

13 of 22大图):
去什么地方查了地图想打印出来?或者定了餐厅,需要打折券?学校要求儿子的作业要打印交到学校?另外,你知道Windows 7接上电话线就是很棒的传真机吗……想把收到的传真打印出来?其实很简单,你只需要一个小小的价值100元“打印服务器”。

14 of 22大图):
每天有好几个设备要充电,插在不同的地方,线很乱,很不美观。能不能把所有的线都藏起来呢?在网上看到这样的充电站,很是精致,就请大理石工人照做了一个。细腻的、纯白的蒙特利石材做的充电站非常美观,很是亮点。

15 of 22大图):
这是数字心脏和设备之间的线路关系。很复杂,对吗?背后的复杂和汗水,是为了使用的简单。

16 of 22大图):
适当的运用感知设备会大幅提高生活质量。红外感知、光控、煤气/天然气监测。让设备感知环境,让环境控制设备,这比自己控制更加智能。

17 of 22大图):
家庭高清影院配置好了之后,来了新问题,看电影,要先放下幕布,换遥控器打开投影机,换遥控器打开功放,换遥控器打开高清播放机~~客厅的用户体验,现在很大很大程度上取决于遥控器的体验。

18 of 22大图):
用任何一台可以上网的电脑,甚至是一只手机,就可以在全世界的任何地方,控制大部分我可以在家控制的设备。获取文件、配置网络、录制节目、授权下载 …

19 of 22大图):
现在,把所有设备藏起来。把所有客厅设备放进柜子,用RF Wireless Extender遥控;把家庭影院幕布藏入吊顶;把数字心脏藏入客厅的柜子(留有百叶窗散热);把除了万能遥控器外的所有遥控器收起来。复杂的下一步:简单!

20 of 22大图):
还嫌复杂?你可以期待微软动作感应系统。微软动作感应系统(Kinect)可以感知你的每一个自然的动作。如果有合适的应用,我们可以用最自然的动作控制家中的一切设备。那么,你的客厅就连遥控器都不需要了。

21 of 22大图):
数字化的投入,增加投资:39,687 – 20,568 = 19,119,主要投资增加在高清家庭影院。

22 of 22大图):
开始体验数字化带来的美好家庭生活吧!

感谢1大图):
感谢徐强来前来参观指导,在他的强烈要求下,我才下决心花时间做了这个总结。观瞻一下,就是图中这个男子,单身!

感谢2大图):
还要感谢SHE。我整理PPT思路的时候,画脑图,用玻璃墙来打的草稿,是她帮我收拾的残局。只画不擦,真是非常好的体验,哈哈!

感谢3大图):
还要感谢一直在客厅奋战的9只遥控器。这代表从简单到复杂。虽然有了罗技Harmony 890万能遥控器,实现从复杂到简单,但是你们不会被忘记。由简到繁、再由繁到简,人生也是如此。

感谢4大图):
最后我要感谢的是Microsoft Office 2010,尤其是Powerpoint。我大量用到了其中图片的删除背景、增加阴影等功能。这让刘润的数字化家庭连载可以采用实物图片而不是图标代替。

下载自Office Live网站 ):
http://cid-7eba47c9e4d1ed8f.office.live.com/self.aspx/.Public/演讲文稿/刘润的数字化家庭(最终).pptx

Thu 10 June, 2010

Channel Image21:11 My Presentations from The Irish Software Show 2010» Raible Designs
This week I've been enjoying Dublin, Ireland thanks to the 2nd Annual Irish Software Show. On Wednesday night, I spoke about The Future of Web Frameworks and participated in a panel with Grails, Rails, ASP.NET MVC and Seaside developers. It was a fun night with lots of lively discussion. Below is my presentation from this event.

This morning, I delivered my Comparing Kick-Ass Web Frameworks talk. This presentation contains updated statistics for various metrics comparing Rails vs. Grails and Flex vs. GWT.

Thanks to all who attended my talks this week!

P.S. I believe audio was recorded on Wednesday night, but I'm unsure how it turned out. I'm pretty sure no recordings were done on this morning's session.

Tue 08 June, 2010

Channel Image01:54 A Nice Riding Weekend before heading to the Emerald Isle» Raible Designs
I'm writing this post while waiting to board a flight to the Irish Software Show in Dublin, Ireland. Before I go, I thought I'd let y'all know about the killer weekend I had tooling around on my bike.

Saturday was Big Head Todd and the Monsters at Red Rocks, so I had the pleasure of joining Bruce and The Professor for our annual Ride to Red Rocks. We were slow getting out there (as usual), but had a great time at the show. 17th row seats and plenty of excellent music. The ride home was dark and fast; arriving at my house at 1:30. After late night Jerusalems, I crawled in bed at 2:30.

Red Rocks in Site Rainbow at Red Rocks Sunset at Red Rocks Big Head Todd

Six hours later, I hopped out of bed, jumped in my car and drove down to Castle Rock for Elephant Rock. I did the 25-mile off-road ride on my mountain bike. Unfortunately, there was no singletrack, and I'm pretty sure I was the last one to start the race. The ride itself was nice and windy with plenty of sun. When I reached the highway on the backside of Castlewood Canyon, I caught a stellar tailwind and had a blast cruising to the finish line.

Elephant Rock 25 mile cruiser Approaching Castlewood Canyon State Park Leaving Castlewood Canyon Sweet Tailwind

If you'd like to see more pictures from my weekend biking adventures, checkout my Big Head Todd and Elephant Rock set on Flickr.

If you're going to be at the Irish Software Show this week, be sure to stop by and say hi. I'll be speaking about The Future of Web Frameworks on Wednesday at 7:30pm and Comparing Kick-Ass Web Frameworks early on Thursday morning.

Mon 07 June, 2010

Channel Image09:50 Running Selenium Tests on Sauce Labs» Raible Designs
Recently I embarked on a mission to configure my team's Selenium testing process to support multiple browsers. We use Hudson for our continuous integration server. Since our Hudson instance runs on Solaris, testing with Firefox on Solaris didn't seem like a good representation of our clients. Our browser support matrix currently looks as follows:

Platform Browser
Supported
Windows IE7.x and 8.x, Firefox 2.x and 3.x
Mac Safari 3.x, 4.x
Best Effort
Windows and Mac Chrome 4.x

At first, I attempted to use Windows VMs to run Selenium tests on IE. This was a solution that didn't work too well. The major reasons it didn't work:

  1. I had issues getting the Selenium Plugin for Hudson working. Upgrading the plugin to use Selenium RC 1.0.5 may solve this issue.
  2. We had some unit tests that failed on Windows. I tried using the Cygpath Plugin for Hudson (which allows you to emulate a Unix environment on Windows), but failed to get it to work.
  3. We quickly realized it might become a maintenance nightmare to keep all the different VMs up-to-date.

Frustrated by these issues, I turned to Sauce Labs. They have a cloud-based model that runs Selenium tests on VMs that point back to your application. They also support many different browser/OS combinations. We asked them about support for OS X and various Windows versions and they indicated that their experience shows browsers are the same across OSes.

I'm writing this article to show you how we've configured our build process to support 1) testing locally and 2) testing on Sauce Labs. In a future post, I hope to write about how to run Selenium tests concurrently for faster execution.

Running Selenium Tests Locally
We use Maven to build our project and run our Selenium tests. Our configuration is very similar to the poms referenced in Integrating Selenium with Maven 2. Basically, we have an "itest" profile that gets invoked when we pass in -Pitest. It downloads/starts Tomcat (using Cargo), deploys our WAR, starts Selenium RC (using the selenium-maven-plugin) and executes JUnit-based tests using the maven-surefire-plugin. All of this configuration is pretty standard and something I've used on many projects over the past several years.

Beyond that, we have a custom BlockJUnit4ClassRunner class that takes screenshots and captures the HTML source for tests that fail.

public class SeleniumJUnitRunner extends BlockJUnit4ClassRunner {
    public SeleniumJUnitRunner(Class<?> klass) throws InitializationError {
        super(klass);
    }

    protected Statement methodInvoker(FrameworkMethod method, Object test) {
        if (!(test instanceof AbstractSeleniumTestCase)) {
            throw new RuntimeException("Only works with AbstractSeleniumTestCase");
        }

        final AbstractSeleniumTestCase stc = ((AbstractSeleniumTestCase) test);
        stc.setDescription(describeChild(method));

        return new InvokeMethod(method, test) {
            @Override
            public void evaluate() throws Throwable {
                try {
                    super.evaluate();
                } catch (Throwable throwable) {
                    stc.takeScreenshot("FAILURE");
                    stc.captureHtmlSource("FAILURE");
                    throw throwable;
                }
            }
        };
    }
}

To use the functionality SeleniumJUnitRunner provides, we have a parent class for all our tests. This class uses the @RunWith annotation as follows:

@RunWith(SeleniumJUnitRunner.class)
public abstract class AbstractSeleniumTestCase {
    // convenience methods
}

This class looks up the Selenium RC Server, the app location and what browser to use based on system properties. If system properties are not set, it has defaults for running locally.

public static String SERVER = System.getProperty("selenium.server");
public static String APP = System.getProperty("selenium.application");
public static String BROWSER = System.getProperty("selenium.browser");

protected Selenium selenium;

@Before
public void setUp() throws Exception {
    if (SERVER == null) {
        SERVER = "localhost";
    }

    if (BROWSER == null) {
        BROWSER = "*firefox3";
    }

    if (APP == null) {
        APP = "http://localhost:9000";
    }

    selenium = new DefaultSelenium(SERVER, 4444, BROWSER, APP);
    selenium.start("captureNetworkTraffic=true");
    selenium.getEval("window.moveTo(1,1); window.resizeTo(1021,737);");
    selenium.setTimeout("60000");
}

The system properties are specified as part of the surefire-plugin's configuration. The reason we default them in the above code is so tests can be run from IDEA as well.

<artifactId>maven-surefire-plugin</artifactId>
<version>2.5</version>
<configuration>
    <systemPropertyVariables>
        <selenium.application>${selenium.application}</selenium.application>
        <selenium.browser>${selenium.browser}</selenium.browser>
        <selenium.server>${selenium.server}</selenium.server>
    </systemPropertyVariables>
</configuration>

Running Selenium Tests in the Cloud
To run tests in the cloud, you have to do a bit of setup first. If you're behind a firewall, you'll need to setup SSH tunneling so Sauce Labs can see your machine. You'll also need to setup SSH Tunneling on your Hudson server, but installing/configuring/running locally is usually a good first step. Below are the steps I used to configure Sauce Labs' SSH Tunneling on OS X.

1. Install the Python version in /opt/tools/saucelabs. If you get an error (No local packages or download links found for install) download the egg and run it with:

sudo sh setuptools-0.6c11-py2.6.egg

NOTE: If you get an error (unable to execute gcc-4.2: No such file or directory) when installing pycrypto on OS X, you'll need to install the OS X Developer Tools.

2. Create a /opt/tools/saucelabs/local.sh script with the following in it. You should change the last parameter to use your username (instead of mraible) since Sauce Labs uses unique tunnel names.

python tunnel.py {sauce.username} {sauce.key} localhost 9000:80 mraible.local

3. Start the tunnel by executing local.sh. You should see output similar to the following.

$ sh local.sh 
/System/../Python.framework/../2.6/../twisted/internet/_sslverify.py:5: DeprecationWarning: the md5 module is deprecated; use hashlib instead
 import itertools, md5
/System/../Python.framework/../2.6/../twisted/conch/ssh/keys.py:13: DeprecationWarning: the sha module is deprecated; use the hashlib module instead
 import sha, md5
Launching tunnel ... 
Status: new
Status: booting
Status: running
Tunnel host: ec2-75-101-216-8.compute-1.amazonaws.com
Tunnel ID: 70f15fb59d2e7ebde55a6274ddfa54dd
<sshtunnel.TunnelTransport instance at 0x10217ad88> created
requesting remote forwarding for tunnel 70f15fb59d2e7ebde55a6274ddfa54dd 80=>localhost:9000
accepted remote forwarding for tunnel 70f15fb59d2e7ebde55a6274ddfa54dd 80=>localhost:9000

After setting up the SSH Tunnel, I modified AbstractSeleniumTestCase's setUp() method to allow running tests on Sauce Labs.

@Before
public void setUp() throws Exception {
    if (SERVER == null) {
        SERVER = "localhost";
    }

    if (BROWSER == null) {
        BROWSER = "*firefox3";
    } else if (BROWSER.split(":").length == 3) {
        String[] platform = BROWSER.split(":");

        String os = platform[0];
        String browser = platform[1];

        // if Google Chrome, don't use a version #
        String version = (platform[1].equals("googlechrome") ? "" : platform[2]);
        String printableVersion = ((version.length() > 0) ? " " + platform[2].charAt(0) : "");

        String jobName = description.getMethodName() + " [" + browser + printableVersion + "]";

        BROWSER = "{\"username\":\"{your-username}\",\"access-key\":\"{your-access-key}\"," +
                "\"os\":\"" + platform[0] + "\",\"browser\": \"" + platform[1] + "\"," +
                "\"browser-version\":\"" + version + "\"," +
                "\"job-name\":\"" + jobName + "\"}";

        log.debug("Testing with " + browser + printableVersion + " on " + os);
    }

    if (APP == null) {
        APP = "http://localhost:9000";
    }

    selenium = new DefaultSelenium(SERVER, 4444, BROWSER, APP);
    selenium.start("captureNetworkTraffic=true");
    selenium.getEval("window.moveTo(1,1); window.resizeTo(1021,737);");
    selenium.setTimeout("60000");
}

After making this change, I was able to run Selenium tests from IDEA using the following steps:

  1. Start Jetty on port 9000 (since that's what the tunnel points to). In IDEA's Maven panel, create a run/debug configuration for jetty:run, click the "Runner" tab and enter "-Djetty.port=9000" in the VM Parameters box.
  2. Right-click on the test to run and create a run/debug configuration. Enter the following in the VM Parameters box. The last two parameters allow skipping the xvfb and Selenium RC startup process.
    -Dselenium.browser="Windows 2003:iexplore:8." -Dselenium.application=mraible.local -Dselenium.server=saucelabs.com -Dxvfb.skip=true -Dselenium.server.skip=true

These same parameters can be used if you want to run all tests from the command line:

mvn install -Pitest -Dselenium.browser="Windows 2003:iexplore:8." -Dselenium.application=mraible.local -Dselenium.server=saucelabs.com -Dxvfb.skip=true -Dselenium.server.skip=true -Dcargo.port=9000

To simplify things, we create profiles for the various browsers. For example, below are profiles for IE8 and Firefox 3.6.

<profile>
    <id>firefox-win</id>
    <properties>
        <cargo.port>9000</cargo.port>
        <selenium.application>http://${user.name}.local</selenium.application>
        <selenium.browser>Windows 2003:firefox:3.6.</selenium.browser>
        <selenium.server>saucelabs.com</selenium.server>
        <selenium.server.skip>true</selenium.server.skip>
        <xvfb.skip>true</xvfb.skip>
    </properties>
</profile>
<profile>
    <id>ie-win</id>
    <properties>
        <cargo.port>9000</cargo.port>
        <selenium.application>http://${user.name}.local</selenium.application>
        <selenium.browser>Windows 2003:iexplore:8.</selenium.browser>
        <selenium.server>saucelabs.com</selenium.server>
        <selenium.server.skip>true</selenium.server.skip>
        <xvfb.skip>true</xvfb.skip>
    </properties>
</profile>

Issues
Since we've started using Sauce Labs, we've run into a number of issues. Some of these are Selenium-related and some are simply things we learned since we started testing on multiple browsers.

  • SSH Tunnels Keep Restarting This happens on our Hudson server that runs the tunnels as a service. This seems to happen daily and screws up our Hudson results because builds fail.
  • XPath vs. CSS Selectors One of the first things we noticed was that our IE tests were 2-3 times slower than the same tests on Firefox. We discovered this is because Internet Explorer has a very slow XPath engine. To fix this issue, it's recommended that ids or CSS Selectors be used whenever trying to locate elements. For more information on CSS Selectors and Selenium, see CSS Selectors in Selenium Demystified. To test CSS Selectors, I found Firefinder to be a very useful Firefox plugin. Note that many pseudo elements won't work in IE.
  • IE7 fails to initialize on Sauce Labs There's no errors in our JUnit reports, so we're not sure what's causing this. It could very well be bugs in our code/configuration, but IE8 works fine.
  • The Job Names on Sauce Labs don't get set correctly and often results in duplicate job names. This could certainly be related to my code. Finding videos that show failed tests is difficult when the job names aren't set correctly.
  • It would be slick if you could download the video of a failed test, similar to what we do by taking screenshots.
  • Google Chrome works on Sauce Labs, but I'm unable to get it working locally (on Windows or OS X). This seems to be a Selenium issue.
  • Safari 4 works, but when it fails, the screenshot shows a Safari can't find the file error. Since there's no real error to debug, it's difficult to figure out why the test fails. Since Safari 4 is not listed on platforms supported by Selenium, I'm unsure how to fix this.

Overall, Sauce Labs seems to work pretty well. However, in the process of messing with Hudson, build agents and Selenium infrastructure, it's become readily apparent that we need a team member to devote their full-attention to it. Having a developer or two work on it every now-and-then is inefficient, especially when we're still in the process of ironing everything out and making it all stable.

If you have any tips on how you've solved issues with Sauce Labs (ssh tunnels, IE7) or Selenium (Safari 4, Google Chrome), I'd love to hear them. I'm also interested to hear from anyone with experience running Selenium tests concurrently (locally or in the cloud).

Update: I discovered a bug in my AbstractSeleniumTest's setUp() method where job names weren't being set correctly. I've since changed the code in this class to the following:

private static String browser, printableVersion;

@BeforeClass
public static void parseBrowser() {

    if (BROWSER == null) {
        BROWSER = "*firefox3";
    } else if (BROWSER.split(":").length == 3) {
        String[] platform = BROWSER.split(":");

        String os = platform[0];
        browser = platform[1];

        // if Google Chrome, don't use a version #
        String version = (platform[1].equals("googlechrome") ? "" : platform[2]);
        printableVersion = ((version.length() > 0) ? " " + platform[2].charAt(0) : "");

        BROWSER = "{\"username\":\"{your-username}\",\"access-key\":\"{your-access-key}\"," +
                "\"os\":\"" + os + "\",\"browser\": \"" + browser + "\"," +
                "\"browser-version\":\"" + version + "\", " +
                "\"job-name\": \"jobName\"}";
    }
}

@Before
public void setUp() throws Exception {
    if (SERVER == null) {
        SERVER = "localhost";
    }

    if (APP == null) {
        APP = "http://localhost:9000";
    }

    String seleniumBrowser = BROWSER;
    if (BROWSER.startsWith("{")) { // sauce labs
        String jobName = description.getMethodName() + " [" + browser + printableVersion + "]";
        log.debug("=> Running job: " + jobName);

        seleniumBrowser = BROWSER.replace("jobName", jobName);
    }

    selenium = new DefaultSelenium(SERVER, 4444, seleniumBrowser, APP);
    selenium.start("captureNetworkTraffic=true");
    selenium.getEval("window.moveTo(1,1); window.resizeTo(1021,737);");
    selenium.setTimeout("60000");
}

Fri 04 June, 2010

Channel Image23:27 Versioning Static Assets with UrlRewriteFilter» Raible Designs
A few weeks ago, a co-worker sent me interesting email after talking with the Zoompf CEO at JSConf.

One interesting tip mentioned was how we querystring the version on our scripts and css. Apparently this doesn't always cache the way we expected it would (some proxies will never cache an asset if it has a querystring). The recommendation is to rev the filename itself.

This article explains how we implemented a "cache busting" system in our application with Maven and the UrlRewriteFilter. We originally used querystring in our implementation, but switched to filenames after reading Souders' recommendation. That part was figured out by my esteemed colleague Noah Paci.

Our Requirements

  • Make the URL include a version number for each static asset URL (JS, CSS and SWF) that serves to expire a client's cache of the asset.
  • Insert the version number into the application so the version number can be included in the URL.
  • Use a random version number when in development mode (based on running without a packaged war) so that developers will not need to clear their browser cache when making changes to static resources. The random version number should match the production version number formats which is currently: x.y-SNAPSHOT-revisionNumber
  • When running in production, the version number/cachebust is computed once (when a Filter is initialized). In development, a new cachebust is computed on each request.

In our app, we're using Maven, Spring and JSP, but the latter two don't really matter for the purposes of this discussion.

Implementation Steps
1. First we added the buildnumber-maven-plugin to our project's pom.xml so the build number is calculated from SVN.

<plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>buildnumber-maven-plugin</artifactId>
    <version>1.0-beta-4</version>
    <executions>
        <execution>
            <phase>validate</phase>
            <goals>
                <goal>create</goal>
            </goals>
        </execution>
    </executions>
    <configuration>
        <doCheck>false</doCheck>
        <doUpdate>false</doUpdate>
        <providerImplementations>
            <svn>javasvn</svn>
        </providerImplementations>
    </configuration>
</plugin>

2. Next we used the maven-war-plugin to add these values to our WAR's MANIFEST.MF file.

<plugin>
    <artifactId>maven-war-plugin</artifactId>
    <version>2.0.2</version>
    <configuration>
        <archive>
            <manifest>
                <addDefaultImplementationEntries>true</addDefaultImplementationEntries>
            </manifest>
            <manifestEntries>
                <Implementation-Version>${project.version}</Implementation-Version>
                <Implementation-Build>${buildNumber}</Implementation-Build>
                <Implementation-Timestamp>${timestamp}</Implementation-Timestamp>
            </manifestEntries>
        </archive>
    </configuration>
</plugin>

3. Then we configured a Filter to read the values from this file on startup. If this file doesn't exist, a default version number of "1.0-SNAPSHOT-{random}" is used. Otherwise, the version is calculated as ${project.version}-${buildNumber}.

private String buildNumber = null;

...
@Override
public void initFilterBean() throws ServletException {
    try {
        InputStream is = 
            servletContext.getResourceAsStream("/META-INF/MANIFEST.MF");
        if (is == null) {
            log.warn("META-INF/MANIFEST.MF not found.");
        } else {
            Manifest mf = new Manifest();
            mf.read(is);
            Attributes atts = mf.getMainAttributes();
            buildNumber = atts.getValue("Implementation-Version") + "-" + atts.getValue("Implementation-Build");
            log.info("Application version set to: " + buildNumber);
        }
     } catch (IOException e) {
        log.error("I/O Exception reading manifest: " + e.getMessage());
     }
}

...

    // If there was a build number defined in the war, then use it for
    // the cache buster. Otherwise, assume we are in development mode 
    // and use a random cache buster so developers don't have to clear 
    // their browswer cache.
    requestVars.put("cachebust", buildNumber != null ? buildNumber : "1.0-SNAPSHOT-" + new Random().nextInt(100000));

4. We then used the "cachebust" variable and appended it to static asset URLs as indicated below.

<c:set var="version" scope="request" 
    value="${requestScope.requestConfig.cachebust}"/>
<c:set var="base" scope="request"
    value="${pageContext.request.contextPath}"/>

<link rel="stylesheet" type="text/css" 
    href="${base}/v/${version}/assets/css/style.css" media="all"/>

<script type="text/javascript" 
    src="${base}/v/${version}/compressed/jq.js"></script>

The injection of /v/[CACHEBUSTINGSTRING]/(assets|compressed) eventually has to map back to the actual asset (that does not include the two first elements of the URI). The application must remove these two elements to map back to the actual asset. To do this, we use the UrlRewriteFilter. The UrlRewriteFilter is used (instead of Apache's mod_rewrite) so when developers run locally (using mvn jetty:run) they don't have to configure Apache.

5. In our application, "/compressed/" is mapped to wro4j's WroFilter. In order to get UrlRewriteFilter and WroFilter to work with this setup, the WroFilter has to accept FORWARD and REQUEST dispatchers.

<filter-mapping>
    <filter-name>rewriteFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

<filter-mapping>
    <filter-name>WebResourceOptimizer</filter-name>
    <url-pattern>/compressed/*</url-pattern>
    <dispatcher>FORWARD</dispatcher>
    <dispatcher>REQUEST</dispatcher>
</filter-mapping>

Once this was configured, we added the following rules to our urlrewrite.xml to allow rewriting of any assets or compressed resource request back to its "correct" URL.

<rule match-type="regex">
    <from>^/v/[0-9A-Za-z_.\-]+/assets/(.*)$</from>
    <to>/assets/$1</to>
</rule>
<rule match-type="regex">
    <from>^/v/[0-9A-Za-z_.\-]+/compressed/(.*)$</from>
    <to>/compressed/$1</to>
</rule>
<rule>
    <from>/compressed/**</from>
    <to>/compressed/$1</to>
</rule>

Of course, you can also do this in Apache. This is what it might look like in your vhost.d file:

RewriteEngine    on
RewriteLogLevel  0!
RewriteLog       /srv/log/apache22/app_rewrite_log
RewriteRule      ^/v/[.A-Za-z0-9_-]+/assets/(.*) /assets/$1 [PT]
RewriteRule      ^/v/[.A-Za-z0-9_-]+/compressed/(.*) /compressed/$1 [PT]

Whether it's a good idea to implement this in Apache or using the UrlRewriteFilter is up for debate. If we're able to do this with the UrlRewriteFilter, the benefit of doing this at all in Apache is questionable, especially since it creates a duplicate of code.

Thu 03 June, 2010

Channel Image22:06 Life without TV» Raible Designs
As a Denver sports enthusiast, April started as a great month. The Nuggets and the Avs both made the playoffs and both appeared like they would do fairly well. Of course, neither of them did and by April 30th, both teams' seasons where over. I watched the final Nuggets game of the season in Seattle and was so disgusted I decided to turn off my TV for a month.

When I first told my kids (who spend 50% of their time at my house), Jack's lower lip started to tremble (mostly because it meant no Wii). Abbie quickly asked "What about the iPad?" I said that was OK and both kids quickly cheered up. I don't generally watch a lot of TV (~10 hours/week), and I grew up without electricity, so this wasn't a huge change for me. However, I do have some shows that I've been following this year. Namely, 24, FlashForward, The Office and American Idol.

For the last 6 months, I've been developing an online video site, so it wasn't long before my brash "no TV" decision turned into a nice opportunity to research other sites offering online video. Here are some observations from my month without TV.

  • Almost no online video sites work on the iPad because of Flash. I get the feeling that most online video sites aren't doing HTML5 <video> because of DRM and progressive download vs. streaming.
  • Netflix is a cool app for the iPad, but most of the streaming content is crap. My kids found plenty to watch, but I never found anything.
  • Hulu is the bomb if they have shows you like to watch.
  • The ABC app for iPad is great if they have shows you like to watch.
  • I watched a lot less movies because I didn't have onDemand and didn't feel like renting/ordering DVDs.
  • I found my laptop offered a better viewing experience than the iPad.
  • My kids found the iPad offered a better viewing experience than my laptop (easier to hold/share).
  • The iPad isn't loud or comfortable enough to replace the modern TV.
  • My TV (and surround sound) offers a much better viewing experience than a computer.
  • Ads on Hulu are short and sweet (15 seconds) and seem to inspire higher engagement because you're willing to wait for the show to resume.
  • My team has developed both a webapp and a native app that work on the iPad, but I was never inspired to use either due to lack of shows I wanted to watch.
  • American Idol was difficult to find online. When I did find it, it was very poor quality.
  • I did not use BitTorrent because I forgot what a good resource it is.
  • I found myself going to bed a lot earlier.

Overall, it was a great experience and I recommend others try it. However, with the Stanley Cup Finals, NBA Finals and World Cup this month, I'm glad I turned my TV back on.