拿起筆來做刀槍 · 之三 再造一個jsp(java sign pages)

JSP的全稱叫做: java server pages。

我們這裏也打造一個jsp, 名稱叫做 java sign pages。

區別在於 沒有 server 這個字樣。 我會嘗試打造一個 類似於 freemarker的模板標記語言, 從而,讓 我們所寫的 viewer 和 server 分離。


雖然我們現在還毫無頭緒,但是,可以回想一下jsp 和 servlet的關係:

事實上,jsp就是servlet未編譯版本,任何jsp最終都被tomcat容器編譯到work目錄並以java類的形態執行。

你可以在 你的tomcat運行目錄中看到端倪:

\work\Catalina\localhost\_\org\apache\jsp

你的webroot下的jsp頁面的最終形態是:

index_jsp.class

當然tomcat還提供一個編譯前版本:

index_jsp.java
讓我們看看他的內容:

package org.apache.jsp;

import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.jsp.*;

public final class index_jsp extends org.apache.jasper.runtime.HttpJspBase
    implements org.apache.jasper.runtime.JspSourceDependent {

  private static java.util.Vector _jspx_dependants;

  public java.util.List getDependants() {
    return _jspx_dependants;
  }

  public void _jspService(HttpServletRequest request, HttpServletResponse response)
        throws java.io.IOException, ServletException {

    JspFactory _jspxFactory = null;
    PageContext pageContext = null;
    HttpSession session = null;
    ServletContext application = null;
    ServletConfig config = null;
    JspWriter out = null;
    Object page = this;
    JspWriter _jspx_out = null;
    PageContext _jspx_page_context = null;


    try {
      _jspxFactory = JspFactory.getDefaultFactory();
      response.setContentType("text/html; charset=UTF-8");
      pageContext = _jspxFactory.getPageContext(this, request, response,
      			null, true, 8192, true);
      _jspx_page_context = pageContext;
      application = pageContext.getServletContext();
      config = pageContext.getServletConfig();
      session = pageContext.getSession();
      out = pageContext.getOut();
      _jspx_out = out;

      out.write("\n");
      out.write("<html>\n");
      out.write("\t<head>\n");
      out.write("    <meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\">\n");
      out.write("    <title>multi-media searcher demo</title>\n");
      out.write("\t\t<style type=\"text/css\">\n");
      out.write("\t\t\tbody { background-color: #fff; padding: 0 20px; color:#000; font: 13px/18px Arial, sans-serif; }\n");
      out.write("\t\t\ta { color: #360; }\n");
      out.write("\t\t\th3 { padding-top: 20px; }\n");
      out.write("\t\t\tol { margin:5px 0 15px 16px; padding:0; list-style-type:square; }\n");
      out.write("\t\t</style>    \n");
      out.write("\t</head>\n");
      out.write("\t\n");
      out.write("\t<body>\n");
      out.write("\t\t\n");
      out.write("\t\t<h1>This is my first jsp page!</h1>\n");
      out.write("\t\t\n");
      out.write("\t</body>\n");
      out.write("\t\n");
      out.write("</html>");
    } catch (Throwable t) {
      if (!(t instanceof SkipPageException)){
        out = _jspx_out;
        if (out != null && out.getBufferSize() != 0)
          out.clearBuffer();
        if (_jspx_page_context != null) _jspx_page_context.handlePageException(t);
      }
    } finally {
      if (_jspxFactory != null) _jspxFactory.releasePageContext(_jspx_page_context);
    }
  }
}

如果我們也這麼照葫蘆畫瓢的來一份不就OK了?

你可以參考一下我的博文:

從String中動態(內存中)編譯和加載java類

我們採取這篇文章中提到的最簡單的方式,

生成一個java類,調用

import javax.tools.JavaCompiler;
import javax.tools.ToolProvider;

工具進行編譯,然後用

URLClassLoader

進行加載,並使用。


先看一個簡單的例子,試驗一下我們的想法是否可行:

創建一個接口:

package net.csdn.blog.deltatang;

public interface Cat {
	public void say();
}
動態定義實現類,並初始化,調用接口:

package net.csdn.blog.deltatang;
import java.io.File;
import java.io.FileWriter;
import java.net.URL;
import java.net.URLClassLoader;

import javax.tools.JavaCompiler;
import javax.tools.ToolProvider;

public class DynaCompTest {
	
	public static void main(String[] args) throws Exception {
		
		StringBuffer srcbuf = new StringBuffer();
		srcbuf.append("package test;");
		srcbuf.append("import net.csdn.blog.deltatang.Cat;"); 
		srcbuf.append("");
		srcbuf.append("public class CatImpl implements Cat {");
		srcbuf.append("");
		srcbuf.append("    static {");
		srcbuf.append("        System.out.print(\"hello, \");");
		srcbuf.append("    }");
		srcbuf.append("");
		srcbuf.append("    public CatImpl() {");
		srcbuf.append("        System.out.println(\"world\");");
		srcbuf.append("    }");
		srcbuf.append("");
		srcbuf.append("    public void say() {");
		srcbuf.append("        System.out.println(\"I'm a cat!\");");
		srcbuf.append("    }");
		srcbuf.append("");
		srcbuf.append("}");
		
		String source = srcbuf.toString();

		// Save source in .java file.
		File root = new File("/java"); 
		File sourceFile = new File(root, "test/CatImpl.java");
		sourceFile.getParentFile().mkdirs();
		new FileWriter(sourceFile).append(source).close();

		// Compile source file.
		JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
		compiler.run(null, null, null, sourceFile.getPath());

		// Load and instantiate compiled class.
		URLClassLoader classLoader = URLClassLoader
				.newInstance(new URL[] { root.toURI().toURL() });
		Class<?> cls = Class.forName("test.CatImpl", true, classLoader); // Should
																		// print
																		// "hello".
		Object instance = cls.newInstance(); // Should print "world".		
		System.out.println(instance); // Should print "test.Test@hashcode".
		
		Cat cat = (Cat)instance;
		cat.say();		
	}
}
輸出爲:

source code : -------------------------------
package test;
import net.csdn.blog.deltatang.Cat;

public class CatImpl implements Cat {

    static {
        System.out.print("hello, ");
    }
    public CatImpl() {
        System.out.println("world");
    }

    public void say() {
        System.out.println("I'm a cat!");
    }

}

---------------------------------------------
hello, world
test.CatImpl@578fd6
I'm a cat!

試驗成功!

讓我們整理下代碼:

package net.csdn.blog.deltatang.jsp4me;

import java.io.File;
import java.io.FileWriter;
import java.net.URL;
import java.net.URLClassLoader;

import javax.tools.JavaCompiler;
import javax.tools.ToolProvider;


public class StringToObjectBuilder {
	
	public String workdir = "/_web_workdir_tmp";
	
	public StringToObjectBuilder(String workdir) {
		super();
		this.workdir = workdir;
	}

	public StringToObjectBuilder() {
		super();
	}

	public Object build(String source, String classPath) {
		
		String javaFilePath = classPath.replaceAll("\\.", "/") + ".java";
		
		try {
			
			File root = new File(workdir); 
			File sourceFile = new File(root, javaFilePath);
			sourceFile.getParentFile().mkdirs();
			new FileWriter(sourceFile).append(source).close();

			JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
			compiler.run(null, null, null, sourceFile.getPath());
			
			URL[] urls = new URL[]{root.toURI().toURL()};
			URLClassLoader classLoader = URLClassLoader.newInstance(urls);
			Class<?> cls = Class.forName(classPath, true, classLoader); 
			Object instance = cls.newInstance(); 
			
			return instance;
			
		} catch (Exception e) {
			e.printStackTrace();
		}
		
		return null;
	}

	public static void main(String[] args) {

		StringBuffer srcbuf = new StringBuffer();
		srcbuf.append("package net.csdn.blog.deltatang.jsp4me;").append("\n");
		srcbuf.append("import net.csdn.blog.deltatang.jsp4me.Cat;").append("\n"); 
		srcbuf.append("").append("\n");
		srcbuf.append("public class CatImpl implements Cat {").append("\n");
		srcbuf.append("    public void say() {").append("\n");
		srcbuf.append("        System.out.println(\"I'm a cat!\");").append("\n");
		srcbuf.append("    }").append("\n");
		srcbuf.append("}").append("\n");
		
		String source = srcbuf.toString();
		
		System.out.println("source code : -------------------------------");
		System.out.println(source);
		System.out.println("---------------------------------------------");
		
		String classPath = "net.csdn.blog.deltatang.jsp4me.CatImpl";
		StringToObjectBuilder builder = new StringToObjectBuilder();
		Cat cat = (Cat)builder.build(source, classPath);
		cat.say();
	}

}

運行:
source code : -------------------------------
package net.csdn.blog.deltatang.jsp4me;
import net.csdn.blog.deltatang.jsp4me.Cat;

public class CatImpl implements Cat {
    public void say() {
        System.out.println("I'm a cat!");
    }
}

---------------------------------------------
I'm a cat!

同時,可以看到,

我們在效果上實現了 tomcat 對jsp說乾的事情:)


現在,讓我們來進行第二步工作,定義我們的標記語言格式。

同樣,我們參考jsp已經有的標記做一個子集,如下所示:

<%@ page import="java.util.*"%>
<%
List datalist = new ArrayList();
datalist.add("row1");
datalist.add("row2");
datalist.add("row3");
%>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
  <head>
    
    <title>My JSP - NOT java SERVER pages but java SIGN pages</title>
  </head>
  
  <body>
    <h1>This is my JSP page. </h1>
    <h1>My JSP - NOT java SERVER pages but java SIGN pages</h1>
    <h2>out.println()</h2>
    <%
    for(int i = 0; i < datalist.size(); i++) {
    	out.println(datalist.get(i) + "<br/>");
    }
    %>
    <h2>special marker</h2>
    <%
    for(int i = 0; i < datalist.size(); i++) {
    %>
    <%= datalist.get(i) %><br/>
    <%
    }
    %>    
  </body>
</html>

我們將支持:

<%@ page import="java.util.*"%>
<%
代碼片段
%>
<%= datalist.get(i) %>

out.print("testline");


這樣的約束條件足夠我們說明問題,並可以很容易的進行擴展:)

讓我們先創建 一個接口:

package net.csdn.blog.deltatang.jsp4me;

import java.util.Map;

public interface JspHandler {
	
	public String buildContent(Map<String, Object> context);

}

然後編譯jsp文件,動態的實現這個接口,

注意,假設我們在方法內定義了一個返回結果result,那麼我們要將

<%@ page import="java.util.*"%>
替換成java代碼的: result += "import java.util.*;";

<%= datalist.get(i) %>
替換成: result += datalist.get(i);

out.print("testline");
替換成:result += "testline";


由此,我們獲得了一下代碼實現:

package net.csdn.blog.deltatang.jsp4me;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;

public class JspToJavaLangBuilder {
	
	public String convert(String jspPath, String className) {
		
		StringBuilder source = new StringBuilder();	
		
		List<String> imports = new ArrayList<String>();
		StringBuilder codes = new StringBuilder();
		
		String content = getJspContent(jspPath);
		int clen = content.length();
		
		int fromIndex = 0;
		int toIndex = -1;
		
		while(fromIndex < clen) {
			
			fromIndex = content.indexOf("<%", fromIndex);
			
			String code = "";
			if(toIndex > 0) {				
				toIndex += 2;
				
				if(fromIndex < 0) {					
					code = content.substring(toIndex);
				} else {
					code = content.substring(toIndex, fromIndex);
				}				 
			} else {
				if(fromIndex < 0) {					
					code = content;
				} else {
					code = content.substring(0, fromIndex);
				}				 				
			}			
			code = code.replaceAll("\\\"", "\\\\\"");
			String[] lines = code.split("\n");
			for(String line : lines) {
				line = line.trim();
				if(line.equals("")) {
					continue;
				}
				codes.append("source.append(\"").append(line).append("\");").append("\n");
			}			
			
			if(fromIndex < 0) break;			
			
			fromIndex += 2;
			
			toIndex = content.indexOf("%>", fromIndex);					
			
			String snippet = content.substring(fromIndex, toIndex);
						
			fromIndex = toIndex + 2;
					
			if(snippet.startsWith("@")) {
				imports.add(snippet);
				continue;
			}
			
//			snippet = snippet.replaceAll("\\\"", "\\\\\"");
			
			if(snippet.startsWith("=")) {
				code = snippet.substring(1);
				codes.append("source.append(").append(code).append(");").append("\n");
				continue;
			}
						
			snippet = snippet.replaceAll("out.println", "source.append");
			codes.append(snippet);
			
		}		
		
		//class start
		source.append("package net.csdn.blog.deltatang.jsp4me;").append("\n");
		
		//imports
		source.append("import net.csdn.blog.deltatang.jsp4me.JspHandler;").append("\n");
		for(String s : imports) {
			int start = s.indexOf('"') + 1;
			int end = s.indexOf('"', start);
			String ipt = s.substring(start, end);
			source.append("import ").append(ipt).append(";");
		}	
		
		source.append("public class ").append(className).append(" implements JspHandler {").append("\n");
		source.append("    public String buildContent(Map context) {").append("\n");
		
		source.append("        StringBuilder source = new StringBuilder();").append("\n");
		
		source.append(codes);
		
		source.append("        return source.toString();").append("\n");			
		source.append("    }").append("\n");
		//class end
		source.append("}").append("\n");
		
		return source.toString();
	}
	
	private String getJspContent(String jspPath) {
		
		StringBuilder sb = new StringBuilder();
		InputStream is = null;
		
		try {
			is = this.getClass().getResourceAsStream(jspPath);
			byte buf[] = new byte[1024];
			int len = 0;
			while((len = is.read(buf)) > 0) {
				sb.append(new String(buf, 0, len));
			}			
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			if(is != null) {
				try {
					is.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
		
		return sb.toString();
	}

	public static void main(String[] args) {
		
		String jspPath = "./test.jsp";
		String java = new JspToJavaLangBuilder().convert(jspPath, "Test");
		System.out.println(java);
		
	}

}

最後,讓我們整合以上的代碼,完成我們的工作:

package net.csdn.blog.deltatang.jsp4me;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class JspCompiler {
	
	public static JspHandler compile(String jspPath, String classPath) {
		
		String className = classPath.substring(classPath.lastIndexOf('.') + 1);
		String source = new JspToJavaLangBuilder().convert(jspPath, className);
		System.out.println(source);
		
		StringToObjectBuilder builder = new StringToObjectBuilder();
		JspHandler handler = (JspHandler)builder.build(source, classPath);
		
		return handler;
	}

	public static void main(String[] args) {		
		
		String jspPath = "./test.jsp";
		String classPath = "net.csdn.blog.deltatang.jsp4me.TestJsp";
		Map<String, Object> context = new HashMap<String, Object>();
		
		List datalist = new ArrayList();
		datalist.add("data----------1");
		datalist.add("data----------2");
		datalist.add("data----------3");
		
		context.put("datalist", datalist);
		
		
		JspHandler handler = JspCompiler.compile(jspPath, classPath);
		
		System.out.println(handler.buildContent(context));
	}

}

run的結果爲:

jsp模板內容爲:

<%@ page import="java.util.*"%>
<%
List datalist = (List)context.get("datalist");
%>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
  <head>
    
    <title>My JSP - NOT java SERVER pages but java SIGN pages</title>
  </head>
  
  <body>
    <h1>This is my JSP page. </h1>
    <h1>My JSP - NOT java SERVER pages but java SIGN pages</h1>
    <h2>out.println()</h2>
    <%
    for(int i = 0; i < datalist.size(); i++) {
    	out.println(datalist.get(i) + "<br/>");
    }
    %>
    <h2>special marker</h2>
    <%
    for(int i = 0; i < datalist.size(); i++) {
    %>
    <%= datalist.get(i) %><br/>
    <%
    }
    %>    
  </body>
</html>

請注意模板中的context對象,顯而易見,我用它取代了jsp中的上下文:page、session、request、application

同時,我們保留了servlet中的 Printer類型實例: out


動態生成的java文件:

package net.csdn.blog.deltatang.jsp4me;
import net.csdn.blog.deltatang.jsp4me.JspHandler;
import java.util.*;public class TestJsp implements JspHandler {
    public String buildContent(Map context) {
        StringBuilder source = new StringBuilder();

List datalist = (List)context.get("datalist");
datalist.add("row1");
datalist.add("row2");
datalist.add("row3");
source.append("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">");
source.append("<html>");
source.append("<head>");
source.append("<title>My JSP - NOT java SERVER pages but java SIGN pages</title>");
source.append("</head>");
source.append("<body>");
source.append("<h1>This is my JSP page. </h1>");
source.append("<h1>My JSP - NOT java SERVER pages but java SIGN pages</h1>");
source.append("<h2>out.println()</h2>");

    for(int i = 0; i < datalist.size(); i++) {
    	source.append(datalist.get(i) + "<br/>");
    }
    source.append("<h2>special marker</h2>");

    for(int i = 0; i < datalist.size(); i++) {
    source.append( datalist.get(i) );
source.append("<br/>");

    }
    source.append("</body>");
source.append("</html>");
        return source.toString();
    }
}

main方法輸出結果:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
	<html>
		<head>
			<title>My JSP - NOT java SERVER pages but java SIGN pages</title>
		</head>
		<body>
			<h1>This is my JSP page. </h1>
			<h1>My JSP - NOT java SERVER pages but java SIGN pages</h1>
			<h2>out.println()</h2>
				data----------1<br/>
				data----------2<br/>
				data----------3<br/>
				<h2>special marker</h2>
				data----------1<br/>
				data----------2<br/>
				data----------3<br/>
		</body>
	</html>

結果符合預期,我們造出了一個新的 模板工具~~!

發佈了94 篇原創文章 · 獲贊 6 · 訪問量 21萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章