基于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" %>
參考资料
- 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 at
http://codemacro.com
基于servlet实现一个web框架的更多相关文章
- 2、基于wsgiref模块DIY一个web框架
一 web框架 Web框架(Web framework)是一种开发框架,用来支持动态网站.网络应用和网络服务的开发.这大多数的web框架提供了一套开发和部署网站的方式,也为web行为提供了一套通用的方 ...
- Servlet规范简介——web框架是如何注入到Servlet中的
Servlet规范简介--web框架是如何注入到Servlet中的 引言 Web框架一般是通过一个Servlet提供统一的请求入口,将指定的资源映射到这个servlet,在这个servlet中进行框架 ...
- 第一个web框架tornado
简介 tornado,是我学到的第一个web框架是 FriendFeed 使用的可扩展的非阻塞式 web 服务器及其相关工具的开源版本.这个 Web 框架看起来有些像web.py 或者 Google ...
- koa : Express出品的下一代基于Node.js的web框架
https://www.liaoxuefeng.com/wiki/001434446689867b27157e896e74d51a89c25cc8b43bdb3000/001434501579966a ...
- 09 基于模块wsgiref版web框架
09 基于模块wsgiref版web框架 模块引入 真实开发中的python web程序,一般会分为两部分: 服务器程序:负责对socket服务器进行封装,并在请求到来时,对请求的各种数据 ...
- servlet的一个web容器中有且只有一个servlet实例或有多个实例的理解1
servlet的一个web容器中有且只有一个servlet实例或有多个实例的理解 (2013-06-19 19:30:40) 转载▼ servlet的非线程安全,action的线程安全 对提交 ...
- 基于Servlet+jsp的web计算器
基于Servlet+jsp的web计算器 这次老大为了让我们自己复习web中页面的跳转给布置得任务 天下代码一大抄,关键看你怎么抄 首先我想到的计算算法不是什么堆栈,是简单的(其实很复杂,但是我就只需 ...
- Go语言笔记[实现一个Web框架实战]——EzWeb框架(一)
Go语言笔记[实现一个Web框架实战]--EzWeb框架(一) 一.Golang中的net/http标准库如何处理一个请求 func main() { http.HandleFunc("/& ...
- 手把手和你一起实现一个Web框架实战——EzWeb框架(二)[Go语言笔记]Go项目实战
手把手和你一起实现一个Web框架实战--EzWeb框架(二)[Go语言笔记]Go项目实战 代码仓库: github gitee 中文注释,非常详尽,可以配合食用 上一篇文章我们实现了框架的雏形,基本地 ...
随机推荐
- Reorder List 最典型的linkedlist题目
https://oj.leetcode.com/problems/reorder-list/ Given a singly linked list L: L0→L1→…→Ln-1→Ln,reorder ...
- solaris 10 关闭ftp、telnet
安装solaris10,启动后发现找不到ftp.telnet的关闭方法, 管理命令 svcadm(服务状态管理,启动.停止等) # svcs 查看当前所有的服务状态,可以使用|管道符重定向作更个性化的 ...
- RabbitMQ消息队列服务
MQ 全称为 Message Queue, 消息队列( MQ ) 是一种应用程序对应用程序的通信方法.应用程序通过读写出入队列的消息(针对应用程序的数据)来通信,而无需专用连接来链接它们. 一个软件它 ...
- MongoDB: The Definitive Guide
第一章 简介 MongoDB是面向文档的数据库,不是关系型数据库.内置对MapReduce的支持,以及对地理空间索引的支持. 丰富的数据模型 容易扩展,它所采用的面向文档的数据模型可以使其在多台服务器 ...
- error:informix Unable to load translation shared library 解决方案
错误:设置informix ODBC时“error:informix Unable to load translation shared library ” 原因 INFORMIXDIR环境变量在操作 ...
- jQuery中样式和属性模块简单分析
1.行内样式操作 目标:扩展框架实现行内样式的增删改查 1.1 创建 css 方法 目标:实现单个样式或者多个样式的操作 1.1.1 css方法 -获取样式 注意:使用 style 属性只能获取行内样 ...
- JavaEE学习记录
软件152谭智馗 一.JavaEE开发环境配置 1.准备以下压缩包 (1)JDK1.7:jdk1.7.rar (2)开发工具:eclipse-jee-mars-2 (3)项目管理工具:maven-3. ...
- Dependency Injection in ASP.NET MVC
原文引自http://www.dotnetcurry.com/ShowArticle.aspx?ID=786 1.传统三层结构,相邻两层之间交互: 2.如果使用EntityFramework则View ...
- DevExpress 如何读取当前目录下文件,加载至grid
DBFileName=DevExpress.Utils.FileHelper.FindingFileName(Appliaction.StartupPath,"Data\\Product&g ...
- JQuery学习笔记系列(一)----选择器详解
笔者好长时间没有更新过博客园的笔记了,一部分原因是去年刚刚开始工作一段时间忙碌的加班,体会了一种每天加班到凌晨的充实感,之后闲暇时间了也因为自己懒惰没有坚持记笔记的习惯,现在重新拾起来. 借用古人的一 ...