標籤:padding getattr ref ota entry arch com art note
servlet作為一個web規範。其本身就算做一個web開發架構,可是其web action (響應某個URI的實現)的實現都是基於類的,不是非常方便,而且3.0之前的版本號碼還必須通過web.xml配置來添加新的action。
servlet中有一個filter的功能,能夠配置全部URI的功能都經過filter。我們能夠基於filter的功能來實現一個簡單的web架構。在這個架構中,主要改進URI action的映射,就像play framework中route的配置:
GET /hello com.codemacro.webdemo.test.TestController.helloGET /route com.codemacro.webdemo.test.TestController.routePOST /hello com.codemacro.webdemo.test.TestController.sayHello
即把某個URI映射到類介面層級。基於servlet實現web架構的優點不僅實現簡單,還能執行在全部支援servlet容器規範的web server上,比如Tomcat、Jetty。
本文提到的web framework demo能夠從我的github 上取得:servlet-web-framework-demo
功能
這個web framework URI action部分(或者說URI routing)如同前面描寫敘述,action的編寫如:
public class TestController extends BaseController { // 返回字串 public Result index() { return ok("hello world"); } // HTTP 404 public Result code404() { return status(404, "not found"); } // 使用JSP模板渲染 public Result template() { String[] langs = new String[] {"c++", "java", "python"}; return ok(jsp("index.jsp") .put("name", "kevin") .put("langs", langs) ); }}
有了action之後。配置route檔案對應URI就可以:
GET /index com.codemacro.webdemo.test.TestController.indexGET /404 com.codemacro.webdemo.test.TestController.code404GET /index.jsp com.codemacro.webdemo.test.TestController.template
然後配置web.xml。添加一個filter:
<filter> <filter-name>MyWebFilter</filter-name> <filter-class>com.codemacro.webdemo.MyServletFilter</filter-class></filter><filter-mapping> <filter-name>MyWebFilter</filter-name> <url-pattern>/*</url-pattern></filter-mapping>
最後以war的形式部署到Jetty webapps下就可以執行。想想下次要再找個什麼lightweight Java web framework。直接用這個demo就夠了。接下來講講一些關鍵區段的實現。
servlet basic
基於servlet開發的話。引入servlet api是必須的:
<dependency> <groupId>javax.servlet</groupId> <artifactId>servlet-api</artifactId> <version>2.5</version> <type>jar</type> <scope>compile</scope></dependency>
servlet filter的介面包括:
public class MyServletFilter implements Filter { // web app啟動時調用一次。可用於web架構初始化 public void init(FilterConfig conf) throws ServletException { } // 滿足filter url-pattern時就會調用;req/res分別相應HTTP請求和回應 public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { } public void destroy() { }}
init介面可用於啟動時載入routes設定檔,並建立URI到action的映射。
action manager
ActionManager負責啟動時載入routes配置。建立URI到action的映射。一個URI包括了HTTP method和URI String,比如GET /index。
action既然映射到了類介面上,那麼能夠在啟動時就同過Java反射找到相應的類及介面。
簡單起見。每次收到URI的請求時,就建立這個類相應的對象,然後調用映射的介面就可以。
// 比如:registerAction("com.codemacro.webdemo.test.TestController", "index", "/index", "GET");public void registerAction(String clazName, String methodName, String uri, String method) { try { uri = "/" + appName + uri; // 載入相應的class Class<? extends BaseController> clazz = (Class<? extends BaseController>) loadClass(clazName); // 取得相應的介面 Method m = clazz.getMethod(methodName, (Class<?>[])null); // 介面要求必須返回Result if (m.getReturnType() != Result.class) { throw new RuntimeException("action method return type mismatch: " + uri); } ActionKey k = new ActionKey(uri, getMethod(method)); ActionValue v = new ActionValue(clazz, m); logger.debug("register action {} {} {} {}", clazName, methodName, uri, method); // 建立映射 actions.put(k, v); } catch (Exception e) { throw new RuntimeException("registerAction failed: " + uri, e); }}
controller都要求派生於BaseController。這樣才幹夠利用BaseController更方便地擷取請求資料之類,比如query string/cookie 等。
收到請求時,就須要依據請求的HTTP Method和URI string取得之前建立的映射。並調用之:
public boolean invoke(HttpServletRequest req, HttpServletResponse resp) throws IOException { String uri = req.getRequestURI(); String method = req.getMethod().toUpperCase(); try { // 取得之前建立的映射,Map尋找 ActionValue v = getAction(uri, method); // 建立新的controller對象 BaseController ctl = (BaseController) v.clazz.newInstance(); ctl.init(req, resp, this); logger.debug("invoke action {}", uri); // 調用綁定的介面 Result result = (Result) v.method.invoke(ctl, (Object[]) null); // 渲染結果 result.render(); } catch (Exception e) { ... }}結果渲染
結果渲染無非就是把架構使用者返回的結果渲染為字串,寫進HttpServletResponse。
這個渲染過程能夠是直接的Object.toString,或者經過模板引擎渲染。或者格式化為JSON。
通過實現詳細的Result類,能夠擴充不同的渲染方式,比如最基礎的Result就是調用返回對象的toString:
public class Result { public void render() throws IOException, ServletException { PrintWriter writer = response.getWriter(); // result是controller action裡返回的 writer.append(result.toString()); writer.close(); }}
為了簡單,不引入第三方庫,能夠直接通過JSP來完畢。JSP本身在servlet容器中就會被編譯成一個servlet對象。
public class JSPResult extends Result { ... @Override public void render() throws IOException, ServletException { // 傳入一些對象到模板中 for (Map.Entry<String, Object> entry : content.entrySet()) { request.setAttribute(entry.getKey(), entry.getValue()); } // 託付給JSP來完畢渲染 request.getRequestDispatcher(file).forward(request, response); }}
JSP中能夠使用傳統的scriptlets運算式,也能夠使用新的EL方式。比如:
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%><h4>By EL</h4><c:forEach var="lang" items="${langs}"> <span>${lang}</span>|</c:forEach><% String[] langs = (String[]) request.getAttribute("langs"); %><% if (langs != null) { %><% for (String lang : langs) { %> <span><%= lang %></span>|<% } } %>
使用EL的話須要引入<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
BaseController
BaseController是一種template pattern實現,其封裝了一些方便的介面給詳細的controller使用,比如:
public class BaseController { // 取得/index?name=kevin中的name參數值 protected String getQueryString(String key) { return request.getParameter(key); } protected Result status(int code, String text) { response.setStatus(code); return new Result(response, text); } // 預設是HTTP 200 protected Result ok(Object obj) { return new Result(response, obj); } protected Result ok(Result result) { return result; } protected JSPResult jsp(String file) { return new JSPResult(request, response, file, actionMgr); }}Reverse routing
Reverse routing指的是在開發web過程中。要引入某個URL時,我們不是直接寫這個URL字串。而是寫其映射的介面,以使代碼更易維護(由於URL可能會隨著項目進展而改變)。而且,servlet app部署後URL會帶上這個app的名字首碼,比如/web-demo/index中的/web-demo。在模板檔案裡。比如要連結到其它URI,更好的方式當然是直接寫/index。
這裡的實現比較醜陋,還是基於字串的形式。比如:
<a href=‘<route:reverse action="com.codemacro.webdemo.test.TestController.hello" name="kevin"/>‘>index</a>
通過自己定義一個EL function reverse來實現。
這裡須要引入一個JSP的庫:
<dependency> <groupId>javax.servlet</groupId> <artifactId>jsp-api</artifactId> <version>2.0</version> <optional>true</optional></dependency>
首先實現一個SimpleTagSupport。為了支援?
name=kevin這樣的動態參數。還須要implements DynamicAttributes:
public class JSPRouteTag extends SimpleTagSupport implements DynamicAttributes { @Override // 輸出終於的URL public void doTag() throws IOException { JspContext context = getJspContext(); ActionManager actionMgr = (ActionManager) context.findAttribute(ACTION_MGR); JspWriter out = context.getOut(); String uri = actionMgr.getReverseAction(action, attrMap); out.println(uri); } @Override // name="kevin" 時調用 public void setDynamicAttribute(String uri, String name, Object value) throws JspException { attrMap.put(name, value); } // `action="xxx"` 時會調用`setAction` public void setAction(String action) { this.action = action; }}
為了訪問到ActionManager。這裡是通過寫到Request context中實現的,相當hack。
public JSPResult(HttpServletRequest req, HttpServletResponse resp, String file, ActionManager actionMgr) { super(resp, null); .. put(JSPRouteTag.ACTION_MGR, actionMgr);}
第二步添加一個描寫敘述這個新tag的檔案 WEB-INF/route_tag.tld:
<taglib> <tlibversion>1.0</tlibversion> <jspversion>1.1</jspversion> <shortname>URLRouteTags</shortname> <uri>/myweb-router</uri> <info></info> <tag> <name>reverse</name> <tagclass>com.codemacro.webdemo.result.JSPRouteTag</tagclass> <bodycontent></bodycontent> <info></info> <attribute> <name>action</name> <required>true</required> </attribute> <dynamic-attributes>true</dynamic-attributes> </tag></taglib>
最後在須要使用的JSP中引入這個自己定義tag:
<%@ taglib prefix="route" uri="/myweb-router" %>
參考資料
- Servlet生命週期與工作原理
- JSP/Servlet工作原理
- EL運算式
- 使用Servlet、JSP開發Web程式
- Java Web筆記 – Servlet中的Filter過濾器的介紹和使用 編寫過濾器
- 實現一個簡單的Servlet容器
原文地址: http://codemacro.com/2015/06/07/servlet-web-framework/
written by Kevin Lynx posted athttp://codemacro.com
基於servlet實現一個web架構