Webwork是一款优秀的WEB应用框架,在其基础之上发展而来的Struts2已经开始替代Struts作为MVC模式下的WEB框架。熟悉 Webwork的程序员很容易过渡到Struts2。本文来模拟一下Webwork的URL解析,应用反射机制实现,只作为说明,当然没有webwork 本身实现的完美。
Webwork默认解析的服务请求名是.action,这个过程是Servlet容器完成的,而不是框架本身,在web.xml配置<servlet-mapping>和<filer-mapping>时设置<url-pattern>即可实现,这里我们不做过多说明,仅用Servlet来模拟,配置文件使用属性配置文件properties。
首先还是回顾一下Webwork解析服务请求的方式吧。我们提交的请求以xx.action发出时,在<xwork>中配置的<action>元素中若没有method属性时,则执行的是class类中的execute()方法,若有method属性时,则执行 method中规定的方法。当请求以xx!yy.action形式发出时,在<action>元素中找到class属性的指向类,在该类中执行yy()方法来响应请求。因为Webwork的Action可以是一个POJO,而且方法返回值都默认为String,则在<action>中的<result>元素中的name值和方法返回值匹配后,就转向到<result>标识的目的地址中了,这个地址当然可以是目标页面也可以是另外一个请求地址。
创建一个WEB项目,起名就叫MVC,配置如下内容:
在web.xml中配置上一个核心控制器和字符过滤器,很简单,如下进行即可。
- < filter >
- < filter-name > characterEncoding </ filter-name >
- < filter-class > mvc.filters.CharacterEncodingFilter </ filter-class >
- < init-param >
- < param-name > encoding </ param-name >
- < param-value > UTF-8 </ param-value >
- </ init-param >
- </ filter >
- < filter-mapping >
- < filter-name > characterEncoding </ filter-name >
- < servlet-name > FrontController </ servlet-name >
- </ filter-mapping >
- < servlet >
- < servlet-name > FrontController </ servlet-name >
- < servlet-class > mvc.ctl.FrontController </ servlet-class >
- </ servlet >
- < servlet-mapping >
- < servlet-name > FrontController </ servlet-name >
- < url-pattern > *.action </ url-pattern >
- </ servlet-mapping >
这样所有已action为服务请求名的请求都被核心控制器FrontController处理了,那么只要设计好这个核心控制器就行了,那么我们就用这个核心控制器来实现MVC模式。
- package mvc.ctl;
- import java.io.*;
- import java.util.*;
- import java.lang.reflect.*;
- import javax.servlet.*;
- import javax.servlet.http.*;
- public class FrontController extends HttpServlet {
- private Map actions = new HashMap(); // 装资源文件中配置的action
- public Map urls = new HashMap(); // 装资源文件配置的url
- @Override
- public void init() throws ServletException {
- // 读取action配置文件
- ResourceBundle rb = ResourceBundle.getBundle("actions" );
- Enumeration keys = rb.getKeys();
- while (keys.hasMoreElements()) {
- String key = (String) keys.nextElement();
- String value = rb.getString(key);
- try {
- // 根据资源文件的value反射获取action对象并装入HashMap
- Object o = Class.forName(value).newInstance();
- actions.put(key, o);
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- // 读取目的地址配置文件
- ResourceBundle url = ResourceBundle.getBundle("urls" );
- keys = url.getKeys();
- while (keys.hasMoreElements()) {
- String key = (String) keys.nextElement();
- String value = url.getString(key);
- urls.put(key, value);
- }
- }
- public void doGet(HttpServletRequest request, HttpServletResponse response)
- throws ServletException, IOException {
- processRequest(request, response);
- }
- public void doPost(HttpServletRequest request, HttpServletResponse response)
- throws ServletException, IOException {
- processRequest(request, response);
- }
- public void processRequest(HttpServletRequest request,
- HttpServletResponse response) throws ServletException, IOException {
- // 从请求中获取URI
- String requestUri = request.getRequestURI();
- // 根据最后一个/截取请求地址(包含.action)
- String actionurl = requestUri.substring(
- requestUri.lastIndexOf('/' ) + 1 , requestUri.length());
- // 截取.action前的有效数据
- actionurl = actionurl.substring(0 , actionurl.indexOf( ".action" ));
- // 创建保存处理类和方法的变量
- String action = "" ;
- String method = "" ;
- if (actionurl.indexOf( "!" ) >= 0 ) { // 有!号的请求这样处理
- action = actionurl.substring(0 , actionurl.indexOf( "!" ));
- method = actionurl.substring(actionurl.indexOf("!" ) + 1 );
- } else { // 没有!号时则默认执行execute()方法
- action = actionurl;
- method = "execute" ;
- }
- // 根据截取的action名获得存放在HashMap中的action对象
- Object handler = null ;
- handler = actions.get(action);
- if (handler == null ) { // 没有找到时默认执行default配置的action
- handler = actions.get("default" );
- method = "execute" ;
- }
- // 存在时,获取Class实例
- Class handlerClass = handler.getClass();
- Method executor = null ;
- // 设置请求处理结束的派发地址
- String toJump = "index" ;
- try {
- // 反射获取执行方法,由于是Servlet,所以参数是HttpServletRequest和 HttpServletResponse
- executor = handlerClass.getMethod(method, new Class[] {
- HttpServletRequest.class , HttpServletResponse. class });
- } catch (Exception e) {
- e.printStackTrace();
- toJump = "frameerror" ;
- }
- try {
- // 利用反射机制执行方法,方法调用结束,返回值都是String类型的
- toJump = (String) executor.invoke(handler, new Object[] { request,
- response });
- } catch (Exception e) {
- e.printStackTrace();
- toJump = "frameerror" ;
- }
- // 转发处理结束以后的地址
- request.getRequestDispatcher(getURL(toJump)).forward(request, response);
- }
- /**
- * 从 HashMap中获取URL的方法
- */
- public final String getURL(String url) {
- return (String) urls.get(url);
- }
- }
代码中有详细的注释,我们说一下简单的思路。这是一个Servlet,那是肯定的,因为我们已经在web.xml中声明了。那么就要覆盖父类中的 doGet()和doPost()两个基本方法实现对Http请求的处理。但是这里我们也覆盖了init()方法用来初始化一些东西。可以看出是从 action.properties中加载配置信息,那么这个文件中有什么呢?很简单,就是action的名和类全名,如下:
- base=mvc.action.BaseAction
- default =mvc.action.DefaultAction
读取出这两个信息后,使用反射生成Action的实例并保存到一个HashMap中。这样就是以名/值对方式存在的了。下面就是获取 urls.properties中信息了,这里面记录了跳转的地址信息,是V层的实现。如下:
- index=index.jsp
- frameerror=frameerror.jsp
init()方法解释完后,我们看看具体的servlet处理方法processRequest()。首先获取到请求的URI,然后解析这个URI看看其具体格式。分为有!号的请求和没有!号的请求,这里就是模拟webwork的实现。创建两个String遍历存放handler和method,然后匹配 URI解析到的内容。进而使用反射机制获取具体的Action实例和方法。最后判断跳转到路径,然后使用 request.getRequestDispatcher().forward()方法进行跳转,那么流程处理就完成了。至此控制器C的原理就说完了。
模型M就是配置的Action类,我们看一看默认Action的写法:
- package mvc.action;
- import javax.servlet.http.HttpServletRequest;
- import javax.servlet.http.HttpServletResponse;
- public class DefaultAction {
- public String execute(HttpServletRequest request,
- HttpServletResponse response) {
- request.setAttribute("msg" , "未定义操作" );
- return "error" ;
- }
- }
是不是和WebWork的Action类很相似,这里还可以操作原始的request,response对象,执行效率也很高。
其实,这也是一个小框架的简单实现,功能简单但是原理清晰,对于理解MVC模式非常有帮助。
一家之言,仅供参考,欢迎交流。