• 基于servlet实现一个web框架


    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.hello
    GET     /route      com.codemacro.webdemo.test.TestController.route
    POST    /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.index
    GET /404    com.codemacro.webdemo.test.TestController.code404
    GET /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" %>
    

    參考资料

  • 相关阅读:
    004 使用文本编辑器
    003 第一个Python程序
    002 Python解释器
    001 安装Python
    000 Python教程
    001 Java环境变量配置
    002 基础语法1
    003 基础语法2
    dede首页调用会员积分和头像代码
    DEDE 会员调用方法
  • 原文地址:https://www.cnblogs.com/jhcelue/p/6925830.html
Copyright © 2020-2023  润新知