基於servlet實現一個web架構

來源:互聯網
上載者:User

標籤: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架構

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.