恕我斗胆直言,对开源的 WEB 框架了解多少,有没有尝试写过框架呢?XML 的解析方式有哪些?能答出来吗?! 心中没有答案也没关系,因为通过今天的分享,能让你轻松 get 如下几点,绝对收获满满。 a)XML 解析的方式; b)digester 的用法; c) Java WEB 框架的实现思路; d)从 0 到 1 徒手实现一个迷你 WEB 框架。 1. XML 解析方式在 Java 项目研发过程中,不论项目大小,几乎都能见到 XML 配置文件的踪影。使用 XML 可以进行项目配置;也可以作为对接三方 API 时数据封装、报文传输转换,等等很多使用场景。 而 XML 文件该如何解析?则是一个老生常谈的问题,也是研发中选型经常面临的一个问题。通过思维导图梳理,把问题都扼杀在摇篮里。
如导图所示,DOM 和 SAX 是 XML 常见的两大核心解析方式,两者的主要区别在于它们解析 XML 文件的方式不同。使用 DOM 解析,XML 文件以 DOM 树形结构加载入内存,而 SAX 采用的是事件模型。 基于这两大解析方式,衍生了一系列的 API,也就是造出了一大批轮子,到底用哪款轮子呢?下面就叨咕叨咕。
上面罗列的这些,你都知道或者用过吗?为了便于你记忆,咱们就聊聊发展历史吧。 首先 JAXP 的出现是为了弥补 JAVA 在 XML 标准制定上的空白,而制定的一套 JAVA XML 标准 API,是对底层 DOM、SAX 的 API 简单封装;而原始 DOM 对于 Java 开发者而言较为难用,于是一批 Java 爱好者为了能让解析 XML 得心应手,码出了 jdom;另一批人在 jdom 的基础上另起炉灶,码出了 dom4j,由于 jdom 性能不抵 dom4j,dom4j 则独占鳌头,很多开源框架都用 dom4j 来解析配置文件。 XStream 本不应该出现在这里,但是鉴于是经验分享,索性也列了出来,在以往项目中报文转换时用的稍微多些,尤其是支付 API 对接时用的超级多,使用它可以很容易的实现 Java 对象和 XML 文档的互转(感兴趣的可以自行填补一下)。 digester 是采用 SAX 来解析 XML 文件,在 Tomcat 中就用 Digester 来解析配置,在 Struts 等很多开源项目,也都用到了 digester 来解析配置文件,在实际项目研发中,也会用它来做协议解析转换,所以这块有必要深入去说一下,对你看源码应该会有帮助。 2. digester 的用法弱弱问一句:有没有听过 digester,若没有听过,那势必要好好读本文啦。 假如要对本地的 miniframework-config.xml 文件,采用 digester 的方式进行解析,应该怎么做?(配置文件的内容有似曾相识的感觉没?文末解谜) 复制代码 <?xml version="1.0" encoding="UTF-8"?> <action-mappings> <action path="/doOne" type="org.yyxj.miniframework.action.OneAction"> <forward name="one" path="/one.jsp" redirect="false"/> </action> <action path="/doTwo" type="org.yyxj.miniframework.action.TwoAction"> <forward name="two" path="/two.jsp" redirect="true"/> </action> </action-mappings> 复制代码 2.1. 定义解析规则文件 rule.xml digester 进行解析 xml,需要依赖解析规则(就是告诉 digester 怎么个解析法)。可以使用 Java 硬编码的方式指定解析规则;也可以采用零配置思想,使用注解的方式来指定解析规则;还可以使用 xml 方式配置解析规则。
为了清晰起见,本次就采用 xml 方式进行配置解析规则,解析规则 rule.xml 内容如下。
复制代码 <?xml version='1.0' encoding='UTF-8'?> <digester-rules> <pattern value="action-mappings"> <!-- value是匹配的xml标签的名字,匹配<action>标签 --> <pattern value="action"> <!--每碰到一个action标签,就创建指定类的对象--> <object-create-rule classname="org.yyxj.miniframework.config.ActionMapping"/> <!-- 对象创建后,调用ActionMappings的addActionMapping()方法, 将其加入它上一级元素所对应的对象ActionMappings中 --> <set-next-rule methodname="addActionMapping"/> <!-- 将action元素的各个属性按照相同的名称 赋值给刚刚创建的ActionMapping对象 --> <set-properties-rule/> <!-- 匹配<forward>标签 --> <pattern value="forward"> <!--每碰到一个forward标签,就创建指定类的对象--> <object-create-rule classname="org.yyxj.miniframework.config.ForwardBean"/> <!-- 对象创建后,调用ActionMapping的addForwardBean()方法, 将其加入它上一级元素所对应的对象ActionMapping中 --> <set-next-rule methodname="addForwardBean"/> <!-- 将forward元素的各个属性按照相同的名称 赋值给刚刚创建的ForwardBean对象 --> <set-properties-rule/> </pattern> </pattern> </pattern> </digester-rules> 复制代码 2.2. 创建规则解析依赖的 Java 类 首先是 ActionMappings 类,要提供 addActionMapping 方法以便添加 ActionMapping 对象,考虑到后面会依据请求路径找 ActionMapping,索性也定义一个 findActionMapping 的方法,代码如下。
复制代码 package org.yyxj.miniframework.config;
import java.util.HashMap; import java.util.Map;
/** * @author 一猿小讲 */ public class ActionMappings {
private Map<String, ActionMapping> mappings = new HashMap<String, ActionMapping>();
public void addActionMapping(ActionMapping mapping) { this.mappings.put(mapping.getPath(), mapping); }
public ActionMapping findActionMapping(String path) { return this.mappings.get(path); }
@Override public String toString() { return mappings.toString(); } } 复制代码 依据解析规则文件,接下来会匹配到 miniframework-config.xml 文件的 action 标签,要定义对应的 ActionMapping 类,包含请求路径及让谁处理的类路径,当然也要提供 addForwardBean 方法用于添加 ForwardBean 对象,代码定义如下。
复制代码 package org.yyxj.miniframework.config;
import java.util.HashMap; import java.util.Map;
/** * @author 一猿小讲 */ public class ActionMapping {
private String path;
private String type;
private Map<String, ForwardBean> forwards = new HashMap<String, ForwardBean>();
public void addForwardBean(ForwardBean bean) { forwards.put(bean.getName(), bean); }
public ForwardBean findForwardBean(String name) { return forwards.get(name); }
public String getPath() { return path; }
public void setPath(String path) { this.path = path; }
public String getType() { return type; }
public void setType(String type) { this.type = type; }
@Override public String toString() { return path + "==" + type + "==" + this.forwards.toString(); } } 复制代码 依据解析规则文件,接下来会匹配到 miniframework-config.xml 文件的 forward 标签,那么就要创建与之对应的 ForwardBean 类,并且拥有 name、path、redirect 三个属性,代码定义如下。
复制代码 package org.yyxj.miniframework.config;
/** * @author 一猿小讲 */ public class ForwardBean {
private String name;
private String path;
private boolean redirect;
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getPath() { return path; }
public void setPath(String path) { this.path = path; }
public boolean isRedirect() { return redirect; }
public void setRedirect(boolean redirect) { this.redirect = redirect; }
@Override public String toString() { return name + "==" + path + "==" + redirect; } } 复制代码 2.3. 引入依赖包,编写测试类 <dependency> <groupId>commons-digester</groupId> <artifactId>commons-digester</artifactId> <version>2.1</version> </dependency> 编写测试类。
复制代码 package org.yyxj.miniframework.config;
import org.apache.commons.digester.Digester; import org.apache.commons.digester.xmlrules.DigesterLoader; import org.xml.sax.SAXException;
import java.io.IOException;
/** * Digester用法测试类 * * @author 一猿小讲 */ public class Test {
public static void main(String[] args) throws IOException, SAXException { String rlueFile = "org/yyxj/miniframework/config/rule.xml"; String configFile = "miniframework-config.xml"; Digester digester = DigesterLoader.createDigester( Test.class.getClassLoader().getResource(rlueFile)); ActionMappings mappings = new ActionMappings(); digester.push(mappings); digester.parse(Test.class.getClassLoader().getResource(configFile)); System.out.println(mappings); } } 复制代码 2.4. 跑起来,看看解析是否 OK? 程序输出如下: {/doOne=/doOne==org.yyxj.miniframework.action.OneAction=={one=one==/one.jsp==false}, /doTwo=/doTwo==org.yyxj.miniframework.action.TwoAction=={two=two==/two.jsp==true}} 到这儿 digester 解析 xml 就算达到了预期效果,digester 解析其实起来很简单,照猫画虎撸两遍,就自然而然掌握,所以不要被乌央乌央的代码给吓退缩(代码只是方便你施展 CV 大法)。
不过,会用 digester 解析 xml 还不算完事,还想扩展一下思路,站在上面代码的基础之上,去尝试实现一个迷你版的 WEB 框架。
3. WEB 框架的实现思路 此时请忘记 digester 解析的事情,脑海里只需保留开篇提到的 miniframework-config.xml 文件,怕你忘记,就再贴一遍。
图中红色圈住部分,其实可以这么理解,当用户请求的 path 为 /doOne 时,会交给 OneAction 去处理,处理完之后的返回结果若是 one,则跳转到 one.jsp,给前端响应。 为了说的更清晰,说清楚思路,还是画一张图吧。
ActionServlet 主要是接收用户请求,然后根据请求的 path 去 AcitonMappings中寻找对应的 ActionMapping,然后依据 ActionMapping 找到对应的 Action,并调用 Action 完成业务处理,然后把响应视图返回给用户,多少都透漏着 MVC 设计模式中 C 的角色。 Action 主要是业务控制器,其实很简单,只需提供抽象的 execute 方法即可,具体怎么执行交给具体的业务实现类去实现吧。 4. 徒手实现迷你版的 WEB 框架鉴于 ActionMappings、ActionMapping、ForwardBean 已是可复用代码,主要是完成 miniframework-config.xml 文件的解析,那接下来只需把图中缺失的类定义一下就 Ok 啦。 4.1. 中央控制器 ActionServlet复制代码
package org.yyxj.miniframework.controller;
import org.apache.commons.digester.Digester;
import org.apache.commons.digester.xmlrules.DigesterLoader;
import org.yyxj.miniframework.config.ActionMapping;
import org.yyxj.miniframework.config.ActionMappings;
import org.yyxj.miniframework.config.ForwardBean;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* 中央控制器
* 1. 接收客户端的请求;
* 2. 根据请求的 path 找到对应的 Action 来处理业务。
*
* @author 一猿小讲
*/
public class ActionServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
private ActionMappings mappings = new ActionMappings();
public static final String RULE_FILE = "org/yyxj/miniframework/config/rule.xml";
public static final String EASY_STRUTS_CONFIG_FILE = "miniframework-config.xml";
@Override
public void init() {
Digester digester = DigesterLoader.createDigester(ActionServlet.class.getClassLoader().getResource(RULE_FILE));
digester.push(mappings);
try {
digester.parse(ActionServlet.class.getClassLoader().getResource(EASY_STRUTS_CONFIG_FILE));
} catch (Exception e) {
// LOG
}
}
@Override
public void service(HttpServletRequest request, HttpServletResponse response) throws IOException {
request.setCharacterEncoding("UTF-8");
response.setContentType("text/html;charset=utf-8");
// 获取请求路径
String uri = request.getRequestURI();
String path = uri.substring(uri.lastIndexOf("/"), uri.lastIndexOf("."));
// 1:根据请求路径获取对应的 Action 来处理具体的业务
ActionMapping mapping = mappings.findActionMapping(path);
try {
Action action = (Action) Class.forName(mapping.getType()).newInstance();
// 2:进行业务处理,并返回执行的结果
String result = action.execute(request, response);
// 3:依据执行结果找到对应的 ForwardBean
ForwardBean forward = mapping.findForwardBean(result);
// 4:响应
if (forward.isRedirect()) {
response.sendRedirect(request.getContextPath() + forward.getPath());
} else {
request.getRequestDispatcher(forward.getPath()).forward(request, response);
}
} catch (Exception e) {
// LOG
System.err.println(String.format("service ex [%s]", e.getMessage()));
}
}
}
复制代码
4.2. 业务控制器 Action 及业务实现
复制代码
package org.yyxj.miniframework.controller;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* 业务控制器
* @author 一猿小讲
*/
public abstract class Action {
public abstract String execute(HttpServletRequest request, HttpServletResponse response) throws Exception;
}
复制代码
紧接着就定义具体的业务实现呗。
复制代码
package org.yyxj.miniframework.action;
import org.yyxj.miniframework.controller.Action;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* 业务实现
*
* @author 一猿小讲
*/
public class OneAction extends Action {
@Override
public String execute(HttpServletRequest request,
HttpServletResponse response) throws Exception {
return "one";
}
}
复制代码
TwoAction 与 OneAction 一样都是继承了 Action,实现 execute 方法。
复制代码
package org.yyxj.miniframework.action;
import org.yyxj.miniframework.controller.Action;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* 业务实现
*
* @author 一猿小讲
*/
public class TwoAction extends Action {
@Override
public String execute(HttpServletRequest request,
HttpServletResponse response) throws Exception {
return "two";
}
}
复制代码
4.3. 配置 web.xml,配置服务启动入口
复制代码
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.4"
xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
<servlet>
<servlet-name>ActionServlet</servlet-name>
<servlet-class>org.yyxj.miniframework.controller.ActionServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>ActionServlet</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
</web-app>
复制代码
4.4. 画三个 JSP 页面出来,便于验证
index.jsp 内容如下。
复制代码
<%@ page pageEncoding="UTF-8"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>My JSP 'index.jsp' starting page</title>
</head>
<body>
<a href="doOne.do">DoOneAction</a><br/>
<a href="doTwo.do">DoTwoAction</a><br/>
</body>
</html>
复制代码
one.jsp 内容如下。
<%@ page pageEncoding="UTF-8"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
一猿小讲 say one .... ...
two.jsp 内容如下。
<%@ page pageEncoding="UTF-8"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
一猿小讲 say two .... ...
4.5. 部署、启动 WEB 服务
到这一个迷你版的 WEB 框架就完事啦,把项目打成 war 包,放到 tomcat 里跑起来,验证一下。
4.6. 项目结构一览
蓝色圈住部分可以打成 miniframework.jar 包,当做可复用类库,在其它项目中直接引入,只需编写红色圈住部分的业务 Action 以及页面就好啦。
以上内容转载自网络
|