技术开发 频道

自行开发JSP HTTP服务器的绝对秘籍(三)

【IT168 专稿】摘要:第三部分(共5部分)介绍了本JSP HTTP服务器实现的以下特性:
对JSP的支持,包括自定义JSP语法,JSP源文件解析等。本部分为全文之核心
对Java Class Loader技术的使用技巧

相关文章:自行开发JSP HTTP服务器的绝对秘籍(一)
                     自行开发JSP HTTP服务器的绝对秘籍(二)
                     自行开发JSP HTTP服务器的绝对秘籍(三)
                     自行开发JSP HTTP服务器的绝对秘籍(四)
                     自行开发JSP HTTP服务器的绝对秘籍(五)

4.对JSP的支持

4.1 设计思路

通过HTTP服务器对本地的JSP源文件进行解释,生成中间java源文件。继而调用javac编译工具将java源文件编译成为class文件。HTTP服务器对class文件中定义的类进行载入并运行。

4.2 设计要点

(1)对JSP文件语法的定义
(2)将JSP文件转换为java源文件
(3)编译java源文件
(4)载入java class并执行类对象的实例对象

4.3 设计实施

(1)制定JSP文件语法,并根据JSP语法形式将代码解释为java源文件
考虑到JSP文件解释工作的简化,设计中主要考虑三种JSP语法形式:
A)JSP编译语句块 语法如下: 形式一:语句块开始标志(<%@)和结束标志(%>)在同一行。 <%@ 语句 %> 例如:<%@ import java.io.*; %> 形式二:语句块开始标志(<%@)和结束标志(%>)不再同一行,且开始标志和结束标志都独占一行。 <%@ 语句块 %> 例如: <%@ import java.io.*; import java.lang.*; %>
本设计中,对JSP文件中编译语句块的解释处理代码如下:
//Current line contain JSP compile block start flag if(line.indexOf("<%@") >= 0) { //file .jsp contains compiled header is_has_compiled_header = true; //Those statements in same line. if(line.indexOf("%>") >= 0) //JSP cimpile block end flag { int start = line.indexOf("<%@"); int end = line.indexOf("%>"); fout.println(line.substring(start + 3, end) ); } else //Those statements no in same line. { while(true) { line = fin.readLine(); if(line == null) //The compiled header is broken { return (false); } if(line.equals("%>") == true) //JSP compiled block end flag { break; } else { fout.println(line); //Output the middle statements of compiled header } }//while(true) } }//if(line.indexOf("<%@") >= 0)
以上处理代码是将JSP文件中编译语句块解释成java源文件。
如果编译语句存在于一行之中,则将语句开始标志和结束标志中间的编译语句取出形成java代码。
如果编译语句块存在于多行之中,则将语句开始标志行和结束标志行之间的编译语句行取出形成java代码。

B)JSP表达式

语法为:语句块开始标志(<%=)和结束标志(%>)在同一行。且同一行中只限制存在一个表达式。 <%= 表达式 %> 例如:<%= Request.getParameter(“UserID”) %>
本设计中,对JSP表达式语句解释处理代码如下:
//Current line contain JSP expression (mark "<%=" and "%>" must be in same line) if(line.indexOf("<%=") >= 0) { //Get start flag position int start = line.indexOf("<%="); //Get end flag position int end = line.indexOf("%>"); //Get frontal part of JSP expression block String pre = line.substring(0, start); //Get expression from line String exp = line.substring(start + 3, end); //Get back part of JSP expression block String back = line.substring(end + 2); //Replace character " of frontal part and back part with character ' pre = pre.replace('\"', '\''); back = back.replace('\"', '\''); //Output the new line (java statement) fout.println("outln(\"" + pre + "\" + " + exp + " + \"" + back + "\");"); }
以上代码中,首先是将JSP表达式所在的行按照表达式前部分,表达式和表达式后部分提取出来,经过符号替换之后再合成java语句。

C)JSP语句块约定
语法为:语句块开始标志(<%)和结束标志(%>)不在同一行,且开始标志和结束标志都独占一行。 <% 语句块 %> 例如: <% int i = 0; for(; page_info[i] != null; ++i) { outln(page_info[i]); } %>
本设计中,对JSP语句块解析处理如下:
//As to JSP statements block, mark "<%" and "%>" must be in sigle line while(true) { //Read next line line = fin.readLine(); if(line == null || line.equals("%>") == true) //JSP statements block end flag is "%>" { break; } fout.println(line); }
以上代码中,将语句块开始标志行(<%)和结束标志行(%>)之间的编译语句行取出形成java代码。(2)编译java源文件

调用java源文件编译器javac对java源文件进行编译。如果编译过程中提示编译错误,则将错误信息输入给客户端。如果编译成功,则生成class文件。以下是编译java源文件的处理代码:
//Generate the command line for javac String command = JAVA_BIN_PATH + "javac -classpath " + baseClassesDir + ITEM_SEPARATOR + extClassesDir + ITEM_SEPARATOR + " -d " + path + " " + javaFName; //Execute javac process and build the java source file Process pJavac = m_rt.exec(command); //Create javac process error output stream BufferedReader pin = new BufferedReader(new InputStreamReader(pJavac.getErrorStream() ) ); String line; boolean isError = false; //Get the error output from javac while( (line = pin.readLine() ) != null) { if(!isError) { m_sout.println("<pre align='center' style='font-family:Arial;font-size:12pt;color:#FF0000'>"); System.out.println("Error command: " + command); } //Output the error information to client socket m_sout.println(line); isError = true; } //Wait for javac process finish pJavac.waitFor(); if(isError == true) { m_sout.println("</pre>"); return; }
以上代码中,通过调用javac进程来编译java源文件以生成class文件。并通过读取javac进程的错误输出流来达到将java源文件在编译过程中的错误信息输出到客户端。

需要补充的是,并不是对于每个jsp源文件都必须再解释成java源文件并编译成class文件。其间应该遵循的规则有:

A)如果jsp源文件对应的class文件不存在,或者jsp源文件新于对应的class文件,则需要进行对jsp源文件进行重新解释和编译。
B)反之,如果jsp源文件对应的class文件存在,并且新于jsp文件(class文件由jsp文件解释和编译得到,所以如果jsp源文件不更新则class文件总会新于jsp源文件),则不需要重新编译jsp文件。

遵循以上规则的目的就是为了减少不必要的重复编译,提高系统的效率。(3)载入class对象并执行类对象的实例对象

上述过程中,虽然通过编译java源文件生成class文件,但是class仅仅只是一个定义声明。无法以实体的形式载入内存中进行执行相应的方法。

但是通过Java Class类提供的newInstance接口,可以通过类名来创建类实例(Instance)对象。这样就可以占用内存执行该对象的相应方法。以下将给出载入class对象和执行类对象的实例对象的代码。
//Create URL list by path. URL [] URLs = {new URL("file:///" + path + "/"), null}; //Create Java URLClassLoader by URLs URLClassLoader URLCl = new URLClassLoader(URLs); //Load class by 类名 Class cs = URLCl.loadClass(className); if(cs != null) { //Put 类名 (as key) and class instance object (as value) into container m_ht.put(className, cs.newInstance() ); System.out.println(className + " loaded ok."); return true; } else { System.out.println(className + " loaded faild."); return false; }
以上代码中,载入类实际上就是根据类名生成类对象的实例对象,再将类实例(Instance)对象载入到内存中。

对于类的执行,首先我们要清楚的是,我们所生成的类的用途是什么。不同于普通的Java Application,我们所通过jsp源文件生成的类都是服务页(Server Page)类,主要的用途就是从HTTP服务器接受到的参数中获取信息,并将信息的处理结果输出给客户端。所以,这些服务页类的框架应该是一致的,即具备参数解析和客户端应答功能。我们说执行这些服务页类,实际上就是执行定义在类对象的实例对象的相关函数,进行参数解析和客户端应答。以下是执行服务页类的实例代码:
//Get the class instance object by class name, //And convert the object to be ServerPage object obj = (ServerPage)(m_ht.get(className)); if(obj != null) { try { //Call the function Init for ServerPage obj.Init(params, out); } catch (Exception execute) { System.out.println("Initialize server page failure."); return; } }
以上代码中,我们根据类名来获取对应的类实例(Instance)对象。并将该类对象的实例对象转换为ServerPage对象(实际上,我们设计的思路就是,所有的网页类都继承于一个叫ServerPage的类),并调用ServerPage 类的Init方法。而Init方法就是所有服务页(Server Page)类的关键函数。Init函数的源代码如下:
//The member which be used for parse the params_list public static HTTPRequest Request = null; //The member which be used for response the result public static HTTPResponse Response = null;…… //---------------------------------------------------------------- //Initialize the member variants public void Init(final String params, final java.io.PrintWriter out) { try { //Create Request member for parse the parameters list Request = new HTTPRequest(params); //Create Respond member for output to client socket Response = new HTTPResponse(out); } catch (java.io.IOException init) { System.out.print("Initialize the params_list and response failure."); return; } }
在ServerPage类的Init方法中,正是使用客户端套接字的输入类对象和输出类对象创建了一个用于解析请求参数的对象和一个用于向客户端输出的对象。

这里需要补充的是,在本HTTP服务器设计中,设计了三个基本类,分别是:
(A)ServerPage(服务页)类,所有我们jsp源文件生成的类都是继承于ServerPage类。所有由jsp源文件生成的类的java源文件的框架的实例代码为:
//Generate framework of server page fout.println("//Java Server Page intermedia file. All rights reserved by Foolstudio."); fout.println("public class " + className + " extends ServerPage {"); //Overload the function init of class ServerPage fout.println("public void Init(final String __params, final java.io.PrintWriter __out) {"); fout.println("super.Init(__params, __out);"); fout.println("output();"); fout.println("}"); //All of output in the function output fout.println("private void output() {");
以上代码可以看出,HTTP服务器中所有由jsp源文件生成的类,都是继承于ServerPage类,并通过重载ServerPage的Init函数来创建和初始化该类的解析请求参数的对象和向客户端输出的对象。服务页所有输出语句都写在output函数中。

ServerPage类具有两个重要的成员。一个是Request类,顾名思义就是对客户端请求进行处理;一个是Respond类,就是对客户端进行回应。

(B)Request类,该类主要用于解析HTTP服务器传递过来的请求的参数,例如:“/login.jsp?name=paul&sex=male”中“?”以后的参数列表。并提供参数制的获取。
以下是Request类的主要方法:
//The member be used for contains the pairs of key and value. private Hashtable m_ht; //---------------------------------------------------------------- //Constructor //Parse the request public HTTPRequest(String params) throws java.io.IOException { //Create key-value container m_ht = new Hashtable(128); if(params.equals("") == true) { return; } //Decode the parameters list (can select GBK and GB2312) params = URLDecoder.decode(params, "GBK"); //Split the key-value list splitRequest(params); } …… //---------------------------------------------------------------- //Get the value by key public String getParameter(final String key)

(C)Respond类,主要用于向客户端进行输出。只需要具备输出函数即可。

5.活用Class Loader技术

5.1 设计思路


利用Java的URLClassLoader技术根据URL(class文件)来载入类,并依据类对象创建类对象的实例对象。首次创建时将类对象的实例对象存于Hash表中,再次执行该类时,就无需再通过class文件载入类并创建类对象实例对象了,而是直接从内存调用该类对象的实例对象。这样遵照“一次载入,多次重用”的原则,可以大大提高服务器的执行效率。

5.2 设计要点

(1)按照class文件载入类,并创建类对象的实例对象
(2)对类对象的实例对象的管理和调用

5.3 设计实施


(1)对于类对象的载入和创建类对象的实例对象,已经在上述段落3中进行了详细说明,在此不再重复。

(2)对于类对象的实例对象的管理和调用

从代码中可以看出,本设计中采用Hashtable对类实例代码进行管理。以类名为key,以类对象的实例对象(指针)为value。类对象的实例对象并不总是只创建一次,当jsp源文件存在更新时,也要将内存中对应的类对象的实例对象进行更新。类对象的实例对象的创建规则有:
(A)当class文件新于jsp源文件,且以该类名为key的实体已经存在于Hashtable(由客户端处理线程管理)中(例如:该页面已经被访问过,且jsp源文件一直都没有更新过),则不需要重新创建实例对象,而是以类名为key从Hashtable中取出对应的类对象实例,并调用Init方法即可。
(B)当class文件新于jsp源文件,但以该类名为key的实体不存在于Hashtable中(例如:该页面已经被访问过,但是重新启动HTTP服务器),则需要创建该类的实例对象,并以类名为key,新的类对象的实例对象为value插入到Hashtable中。
(C)当class文件旧于jsp源文件,但以该类名为key的实体已经存在于Hashtable中(例如:修改jsp源文件,且该旧的页面已经被访问过),则需要先从Hashtable中删除旧的记录,再重新编译jsp源文件生成class文件,并重新生成该类的实例对象,并以类名为key,新的类对象的实例对象为value插入到Hashtable中。
(D)当class文件旧于jsp源文件,且以该类名为key的实体并不存在于Hashtable中(例如:修改了jsp源文件,并重新启动HTTP服务器),则需要重新编译jsp源文件生成class文件,并重新生成该类的实例对象,并以类名为key,新的类对象的实例对象为value插入到Hashtable中。

0
相关文章