转载

Without SSH/JSP/Servlet,不走寻常路,Java可以更酷

标题的构思来源于Rod Johnson的那本"Without EJB"以及CCTV5中一句耳熟能详的广告词,

不过此文并不是用来批判SSH(Struts、Spring、Hibernate)/JSP/Servlet的,

也不是为某品牌做广告,而是用来分享这将近一年来的研究心得。

去年圣诞节时曾在JavaEye发过一两篇文章,不过现在找不到了,

文章内容提到要在3个月左右的时间内设计出一个有别于SSH的新型MVC框架,

设计的起点最初是以JSP/Servlet为基础的,虽然在两个多月后有了个雏形,

但是跟Rails这样的框架相比还是没有明显的优势,

比如在不使用反射的情况下,

很难将不同的uri对应到Servlet类中的public方法。

(Servlet类指的是继承自javax.servlet.http.HttpServlet的类)

每次修改Servlet类的源代码时总得经过烦人的手工编译步骤(有时还不得不重启Tomcat),

还有与数据库打交道的模型层也得人工干预,一堆烦人的映射配置。

那三个月内时常有沮丧感,似乎已走近了死胡同!

后来心一狠,决心甩开JSP/Servlet那一堆条条框框,把设计的起点再往下深一个层次。

因为2007年曾详细研究过Java语言的编译器(javac)实现细节,所以从编译器着手,

但是编译器的强项在于分析Java源代码,无法处理Http请求,

接着在网上把Tomcat6的源代码下下来研究了三个月,

期间顺便研究了Sun公司的超轻量级Http服务器"com.sun.net.httpserver"的源代码,

同时详细学习HTTP/1.0(RFC1945)与HTTP/1.1(RFC2616)协议。

但是Tomcat6过于臃肿了,包含的Java文件超过了1300个,

光是解析server.xml与web.xml的代码看完后就让人有烦躁感。

(如org/apache/tomcat/util/digester与org/apache/catalina/startup包中的很多类)

另外最重要一点,Tomcat6采用的是Eclipse JDT编译器,仅仅是用来编译JSP文件,

编译器在控制层没有发挥一点作用。

而Sun公司的超轻量级Http服务器又过于简单了,连HTTP/1.1的大多数功能都没实现,

除了参考一下它的SSL实现外基本上毫无价值。

本想在现有的JSP/Servlet容器上做一下简单扩展就得了,

哪知也是四处碰壁(还下过Jetty的源代码下来看了一会,结果发现比Tomcat6还糟),

后来决定对Tomcat6与Sun的Http服务器进行大刀阔斧的改造,

完成了一个精简版的改良后的基于NIO的Http服务器(目前的版本只有60个左右的Java源文件),

并且能跟Javac编译器完美结合,能直接运行Java源文件。

在模型层这一块,最初是从书上和网络上对Hibernate进行应用层次的研究,

但是并不想深入源代码,因为代码量也实在是太多了,倒是对Ibatis2.0深入研究了一下,

Ibatis2.0代码量比较少,也简单,看了不到一星期就基本上看完了,不过现在并没留下深刻映象,

因为并没发现什么特别出彩的地方,Ibatis2.0还是离不开xml,而我想要完全抛弃xml。

当然,不管Hibernate也好,Ibatis2.0也好,相比Rails的ActiveRecord还是逊色了点,

不过我的目标并不是要造一个Hibernate、Ibatis2.0或ActiveRecord这样的轮子,

我的要求更高,我在想如何才能写更少的代码,如何才能实现自动化?

可不可以在服务器启动时或运行时动态解析数据库的元数据,

让编译器跟据这些元数据动态生成类呢?

接着我转去研究JDBC-1.2/JDBC-2.1/JDBC-3.0/JDBC-4.0规范,研究数据库驱动的开发手册。

我得从零开始,我目前的实现是这样做的:你可以在你自己的Java源文件中直接引用动态生成的类,

就像这些类是你自己写的一样,ORM已基本上实现自动化了, 2.9 节专门讲Douyu的ORM。

最后一点值得一提的是,我在Java语言层次引入了权限管理模型,

不过你别担心,我并没有引入新的Java语言语法,

只是借助Annotation扩充了某些特殊的语义。

目前这个权限管理模型的粒度只是划分为功能、字段权限两个等级,

并没有实现与具体业务相关的数据权限,不过在未来的路线图中有打算引入工作流模型,

到时会努力尝试各种实现数据权限的方案。

与权限相关的细节请看 2.8节 Douyu的权限模型

折腾了半年后,发现已不再是个MVC框架了,我想称为平台更合适,

一种运行在JVM之上的新型平台,我给她起了个名字: Douyu

(呵呵,名字的由来暂时保密,也许你能猜出来。。。)

虽然孤军奋战将近一年,自我感觉小有成就,但是还有很多不怎么满意的地方,

各位大牛们也许更牛,看见不爽砸砖头便是。

Ok,上干货。

1. 安装配置

(这里只针对Windows平台,特别是XP操作系统,因为我没其他试验环境)

1.1 安装JDK

Douyu是在JDK1.6下开发的,不支持也不打算支持JDK1.4及更早的版本,JDK1.5我没有测试过,

所以我只能 推荐你安装JDK1.6了 ,安装细节我想你都会,

唯一要注意的一点是:最好是建个JAVA_HOME环境变量,然后把%JAVA_HOME%/bin加入到Path中,

因为在Douyu服务器的启动脚本中并没有进行过多的环境检测,

而是直接使用了%JAVA_HOME%/bin目录下的java命令来启动Java HotSpot VM。

1.2 安装Douyu服务器

Douyu项目主页目前放在:

http://code.google.com/p/douyu/

请先下载二进制版的压缩文件:

http://douyu.googlecode.com/files/Douyu_0_1_0.rar

目前的版本是:0.1.0,版本号很小,但大多数功能都包含了,

我并不推荐你用于工业级别的产品开发,

因为还不稳定,目前只适合分享、交流、尝鲜目的。

下下来后直接解压到一个你选定的目录(假定你解压到了D:/Douyu目录)

D:/Douyu目录里头有下面7个目录(跟Tomcat6差不多):

apps  //应用程序的源代码放在这里,里头有一些java源文件是下面的演示中用到的,当然你可以全都删了。 bin   //服务器的启动脚本和运行时类库都在这里 conf  //服务器的配置文件放在这里 lib   //应用程序使用到的第三方类库(比如数据库驱动)都放在这里,初始情况下是个空目录 logs  //存放服务器运行期间的日志(目前日志只是输出到控制台),初始情况下是个空目录 temp  //服务器运行期间用到的临时文件夹(比如上传文件时可能会用到),初始情况下是个空目录 work  //服务器运行期间的工作目录,初始情况下是个空目录

了解了这些就足够了,目前你不需要做任何配置。

2. 体验Douyu

2.1 如何运行Douyu服务器?

点"开始->运行",输入cmd,打开一个控制台,切换到D:/Douyu/bin目录,

然后输入 douyu  启动Douyu服务器 (要关闭Douyu服务器连按两次Ctrl+C既可)

见下图:

Without SSH/JSP/Servlet,不走寻常路,Java可以更酷

如果你是第一次打开操作系统第一次启动JVM运行Java程序

或是隔了一个小时左右重新启动JVM运行Java程序,这时可能要等待几秒钟(5--10秒),

出现这种情况并不是Douyu服务器的问题,而是JVM本身或操作系统的问题,

通常启动Douyu服务器如果不加载数据库的话,一般在一秒钟内就能启动完成了。

Douyu服务器默认情况下监听的主机名是: localhost,端口: 8000

如果你不喜欢这样的默认配置,

或者最常见的情况是端口8000被占用了

(一般抛出异常: java.net.BindException: Address already in use)

你可以打开conf/server.java这个服务器配置文件,

配置文件本身就是一个java源文件,参数的配置使用Java语言的Annotation语法,

所有与服务器配置有关的都是Annotation或是Enum,全都在com.douyu.config包中定义。

import com.douyu.config.*;  @Server(  port=8000,  .................

要修改默认主机名和端口,请修改hostName和port的值,

hostName是一个字符串,可以用IP地址来表示,port是一个整型(int)值。

其他很多参数先不罗列了,使用到时再详细说明。

当你修改了conf/server.java后,你也不需要自己去手工编译它,

启动Douyu服务器时,Douyu会自行决定是否要编译它。

如果conf/server.java存在语法错误,那么编译失败,

Douyu服务器的启动也会失败,同时向你显示编译错误信息。

下文中假定Douyu服务器已启动,监听的主机名是: localhost,端口是: 8000

以下所有例子都经过严格测试了,

我的JRE版本:

D:/Douyu/bin>java -version

java version "1.6.0_16"

Java(TM) SE Runtime Environment (build 1.6.0_16-b01)

Java HotSpot(TM) Client VM (build 14.2-b01, mixed mode, sharing)

测试浏览器用了两个:

傲游浏览器(IE6.0),

谷歌浏览器(Chrome 3.0.195.27)

2.2 Hello World!

2.2.1 程序代码

//对应apps/HelloWorld.java文件  import java.io.PrintWriter; import com.douyu.main.Controller;  @Controller public class HelloWorld {  public void index(PrintWriter out) {   out.println("Hello World!");  } }

2.2.2 手工编译已经Out了,你再也不需要这一步了。

2.2.3 运行HelloWorld

打开你心爱的浏览器,输入 http://localhost:8000/HelloWorld

如果你能看到下图中所示内容,恭喜你,你己经进入了Douyu的精彩世界。

Without SSH/JSP/Servlet,不走寻常路,Java可以更酷

(注:这是你第一次直接运行Java源文件,可能会等几秒钟(2--4秒),因为Douyu得初始化编译器)

2.2.4 程序代码说明

com.douyu.main包中的类大多数是Annotation,还包含一些重要的接口和类,

相当于java.lang,是你用Douyu开发程序时最常用到的,也是通往其他模块的快速入口,

本想让com.douyu.main包中的类像java.lang一样让编译器自动导入的,

但是考虑到很多开发人员更偏爱使用IDE,不同IDE内置的编译器不一样,

从而会引起找不到com.douyu.main包中的类的问题,所以最后决定放弃这样的设计了。

@Controller 这个Annotation是用来告诉Douyu这是一个控制器,

当你在浏览器的地址栏中输入http://localhost:8000/HelloWorld 这样的uri时,

浏览器内部通常会生成一个HTTP GET请求消息,消息内容类似这样:

GET /HelloWorld HTTP/1.1 Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg,...... Accept-Language: zh-cn Accept-Encoding: gzip, deflate User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; Maxthon) Host: localhost:8000 Connection: Keep-Alive

不过这里并不打算介绍HTTP协议,如果你有兴趣,可以把RFC2616下下来研究。

Douyu服务器收到浏览器发来的请求消息后,

特别留意 "GET /HelloWorld HTTP/1.1" 这一行消息,

其中的"/HelloWorld"表示想要获取Douyu服务器上的哪些资源,

资源有静态的(如html、jpg等文件),也有动态的,在Douyu服务器中动态资源只有一种,

凡是带有@Controller这个Annotation的Java源文件都是可以直接通过uri访问的动态资源。

不过Douyu服务器不能根据uri的表面特征一眼就看出它是动态的还是静态资源,

服务器内部有一个专用的资源装载器,装载器的搜索根目录是从apps这个地方开始的,

资源装载器会尝试将apps目录与uri组合成一个java.io.File对象,

如果File对象存在,那么它就是一个静态资源,

然后由Douyu服务器内部的静态资源处理器给浏览器发送包含有文件内容的响应消息;

如果File对象不存在,资源装载器把请求的uri当成一个类名,

然后尝试采用类装载器的方式装载类,如果找不到那么就直接返回未找到(404)消息;

如果找到了,并且uri是第一次请求的,资源装载器会返回java源文件,

然后把java源文件交给Douyu服务器内置的编译器处理,编译器的处理过程很复杂,

这里就不深入说明了,总之它会为你动态生成HelloWorld的实例,

然后调用它的index这个缺省的public方法,

之后调用out.println()方法把"Hello World!"发送给浏览器。

正文到此结束
Loading...