• 自己手写Struts2


    看过请留个言,转载请注明出处,尊重作者劳动成果,谢谢!

     

    记得以前教Struts的时候,自己动手写过一个类似StrutsMVC框架。在培训的过程中,我发现这种过渡非常平滑:学习MVC设计模式时,自己动手写一个Struts框架,再使用自己写的小框架完成一个项目。这样既让学员深刻地理解了MVC的重要思想,又为后面学习Struts框架做好了铺垫,以至于后面真正培训Struts只需要短短5天时间。所以要想灵活掌握一个框架,最好的方式还是自己模仿着去实现它,然后不停地发现问题,解决问题,参考原框架的源代码,从而深刻体会框架设计的精妙之处。

     

    现在Struts已经从1.x升级为2.x,它的架构体系也发生了很大变化。实际上稍有了解的人都知道,Struts2就是StrutsWebWork的强强联姻。至于这些框架的恩恩怨怨,大家可以参考我前面Struts2系列的文章,这里我就不赘述了。我们今天的主题是:模仿Struts2,手写自己的MVC框架。

     

           手写自己的框架?提到这里,有人犯嘀咕了。这有点难度也。我们知道,大凡比较著名的框架,都有着精巧的设计:OOP的设计思想,随处可见的设计模式,JAVA反射机制的灵活应用,XML配置文件的解析,元数据注解甚至IOCAOP的强大渗透。这里面没一个是好惹的主!但大家要注意,我们模仿着手写框架的目的,只是为了更好的学习和理解原框架,并不是重复发明轮子,所以我们只需要关注原框架的核心流程和重点实现,并不需要面面俱到。先来看一下Struts2的架构流程图,感受一下它的魅力。

     

           呵呵,眼看百遍还不如手写一遍。好啦,洪哥,我们动手吧!

     

           首先我们看一下我们小框架(暂时取名为mvc1)的核心配置文件mvc1.xml

    <?xml version="1.0" encoding="UTF-8"?>

    <mvc1> 

        <package> 

            <action name="login" method="logon" 

                class="org.leno.mvc1.app.action.LoginAction"> 

                <result name="success">/index.jsp</result>

                <result name="login">/login.jsp</result> 

            </action> 

        </package> 

    </mvc1> 

     

        将它放在/src底下,是不是很熟悉?这就是struts.xml的简化版。呵呵,我们这里先做一个初级版本,弄清楚核心原理后再慢慢升级。

           框架代码的核心部分是一个Filter,因为Struts2所有工作都是通过一个Filter来完成的(struts1.*是通过一个Servlet实现的)。我们先来看代码:

    package org.leno.mvc1.core;

     

    import java.io.IOException;

    import java.lang.reflect.Method;

    import java.util.HashMap;

    import java.util.Map;

     

    import javax.servlet.Filter;

    import javax.servlet.FilterChain;

    import javax.servlet.FilterConfig;

    import javax.servlet.ServletException;

    import javax.servlet.ServletRequest;

    import javax.servlet.ServletResponse;

    import javax.servlet.http.HttpServletRequest;

    import javax.servlet.http.HttpServletResponse;

     

    public class FilterDispatcher implements Filter {

        private Map<String, ActionConfig> actionMap = new HashMap<String, ActionConfig>();

     

        public void init(FilterConfig filterConfig) throws ServletException {

           // 得到框架核心配置文件mvc1.xmlweb服务器上的物理路径,然后解析XML

           String webPath = getClass().getClassLoader().getResource("mvc1.xml")

                  .getPath();

           ConfigUtil.parseConfigFile(webPath, actionMap);

        }

     

        @SuppressWarnings("static-access")

        public void doFilter(ServletRequest req, ServletResponse res,

               FilterChain chain) throws IOException, ServletException {

           // 针对HTTP协议,将请求和响应对象还原为HTTP类型

           HttpServletRequest request = (HttpServletRequest) req;

           HttpServletResponse response = (HttpServletResponse) res;

           // 设置请求和响应的编码方式,避免乱码

           request.setCharacterEncoding("utf-8");

           response.setCharacterEncoding("utf-8");

           // 得到request的请求路径

           String uri = request.getServletPath();

           // 如果后缀不为.action,不需要该核心Filter控制,直接过关

           if (!uri.endsWith(".action")) {

               chain.doFilter(request, response);

               return;

           }

           // 解析Request的路径

           int start = uri.indexOf("/");

           int end = uri.lastIndexOf(".");

           String path = uri.substring(start + 1, end);

           //path找到相对应的ActionConfig类,它里面包含了所有Action的相关信息

           ActionConfig config = actionMap.get(path);

           // 匹配不成功就返回找不到页面错误信息

           if (config == null) {

               response.setStatus(response.SC_NOT_FOUND);

               return;

           }

           //ActionConfig得到Action类的完整名字

           String clzActionName = config.getClzName();

           // 实例化Action对象, 不存在给出错误提示

           Object action = getAction(clzActionName);

           if (action == null) {

               response.setStatus(response.SC_NOT_FOUND);

               return;

           }

           // 前置拦截,取得request里面的参数,调用Actionset方法给属性设置值

           BeanUtil.requestToAction(request, action);

           // 执行Action的无参方法,例如execute()

           String result = executeAction(config, action);

           // 后置拦截,调用Actionget方法,将所有Action的属性放置到request区域

           BeanUtil.actionToRequest(request, action);

           // 返回相应的JSP页面

           if (result == null) {

               response.setStatus(response.SC_NOT_FOUND);

               return;

           }

           request.getRequestDispatcher(result).forward(request, response);

     

        }

     

        private String executeAction(ActionConfig config, Object action) {

           String method = config.getMethod();

           String result = null;

           try {

               Method callMethod = action.getClass().getMethod(method,

                      new Class[] {});

               result = (String) callMethod.invoke(action, new Object[] {});

           } catch (Exception e) {

               // TODO Auto-generated catch block

               e.printStackTrace();

           }

           return config.getResultMap().get(result);

        }

     

        private Object getAction(String clzActionName) {

           Object action = null;

           try {

               action = Class.forName(clzActionName).newInstance();

           } catch (Exception e) {

               // TODO Auto-generated catch block

               e.printStackTrace();

           }

           return action;

        }

     

        public void destroy() {

           // TODO Auto-generated method stub

     

        }

     

    }

     

    里面都做好了注释,好好把这个类品一下。FilterDispatcher过滤器里有两个重要的方法,一个init(),一个doFilter().我们分别来看看吧!

    init()方法就是做初始化的工作,我们在里面主要是解析配置文件mvc1.xml,然后将配置信息存放到actionMap属性里。我这里使用dom4j解析XML配置文件,里面涉及到一个很简单的帮助类,贴出来供大家参考:

    package org.leno.mvc1.core;

     

    import java.io.File;

    import java.util.Iterator;

    import java.util.List;

    import java.util.Map;

     

    import org.dom4j.Document;

    import org.dom4j.DocumentException;

    import org.dom4j.Element;

    import org.dom4j.io.SAXReader;

     

     

    public class ConfigUtil {

     

        @SuppressWarnings("unchecked")

        public static void parseConfigFile(String fileName, Map<String, ActionConfig> map) {

           SAXReader reader = new SAXReader() ;

           try {

               Document doc = reader.read(new File(fileName));

               Element root = doc.getRootElement();

               List<Element> list = root.selectNodes("package/action");

               System.out.println(list.size());

               for(Element actionNode : list)

               {

                  //封装成ActionConfig对象,保存到map中。

                  ActionConfig config = new ActionConfig();

                  //action节点的path, type, name属性值获取到。

                  String name = actionNode.attributeValue("name");

                  String clzName = actionNode.attributeValue("class");

                  String method = actionNode.attributeValue("method");

                  config.setName(name);

                  config.setClzName(clzName);

                  if(method == null || "".equals(method)){

                      method="execute";

                  }

                  config.setMethod(method);

                 

                  //遍历action节点下的forward子节点

                  Iterator itrNodes = actionNode.selectNodes("result").iterator();

                  while(itrNodes.hasNext()){

                      Element forwardNode = (Element)itrNodes.next();

                      String forwardName = forwardNode.attributeValue("name");

                      String forwardPath = forwardNode.getTextTrim(); 

                     

                      if(forwardName == null || "".equals(forwardName)){

                         forwardName="success";

                      }

                      config.getResultMap().put(forwardName, forwardPath);

                  }  

                  map.put(name, config);

               }

           } catch (DocumentException e) {

               // TODO Auto-generated catch block

               e.printStackTrace();

           }

        }

     

    }

     

           根据XML的配置,我们将action的节点抽象成一个类,它具有name,clzName,method等属性,是一个普通的Javabean,代码如下:

    package org.leno.mvc1.core;

     

    import java.util.HashMap;

    import java.util.Map;

     

    public class ActionConfig {

        private String name;

        private String clzName;

        private String method;

        private Map<String, String> resultMap = new HashMap<String, String>();

       

        public ActionConfig() {

           super();

        }

     

        public ActionConfig(String name, String clzName, String method,

               Map<String, String> resultMap) {

           super();

           this.name = name;

           this.clzName = clzName;

           this.method = method;

           this.resultMap = resultMap;

        }

     

        public String getName() {

           return name;

        }

     

        public void setName(String name) {

           this.name = name;

        }

     

        public String getClzName() {

           return clzName;

        }

     

        public void setClzName(String clzName) {

           this.clzName = clzName;

        }

     

        public String getMethod() {

           return method;

        }

     

        public void setMethod(String method) {

           this.method = method;

        }

     

        public Map<String, String> getResultMap() {

           return resultMap;

        }

     

        public void setResultMap(Map<String, String> resultMap) {

           this.resultMap = resultMap;

        }

     

    }

     

    doFilter方法是整个过滤器的核心,它的大概流程是:

    1. 针对HTTP协议,将请求和响应对象还原为HTTP类型,譬如ServletRequest要转化为HttpServletRequest。并设置请求和响应的编码方式为UTF-8,避免中文乱码。

    2. 解析客户端请求的URL路径,从而由actionMap得到相应的在mvc1.xml配置文件中所配置的Action完整类名以及执行方法名。如果不是合法的.action后缀,直接chain.doFilter(request, response)放行。

    3. request里面的参数取出,在调用Action执行方法之前,给具有set器的Action的属性赋值。这里利用了JAVA的反射机制。

    4. 继续利用JAVA的反射机制,动态执行Action的方法。

    5. 然后将具有get器的Action的属性存放在request区域中,供页面使用。大家记得在每次执行Action的方法时都会返回一个String字符串,比如”success”,”login”,”input”等,我们可以将这个值与mvc1.xml<result name="success" ....>/index.jsp</result> 中的name属性值进行匹配,取出需要返回的页面资源/index.jsp

    6.最后,调用request.getRequestDispatcher().forward()方法一次跳转到相应的页面上去。

          

           这里大家要对JAVA的反射机制比较熟悉,里面涉及到的比较晦涩的代码都跟反射有关,BeanUtil.requestToAction(request, action);

    BeanUtil.actionToRequest(request, action);

    前者将request里面的参数设置到action属性中去,后者将action属性的值保存在request区域中。我们来看看BeanUtil类的代码:

    package org.leno.mvc1.core;

     

    import java.lang.reflect.Field;

    import java.lang.reflect.Method;

    import java.util.Enumeration;

     

    import javax.servlet.http.HttpServletRequest;

     

    public class BeanUtil {

     

        @SuppressWarnings("unchecked")

        public static void requestToAction(HttpServletRequest request, Object action) {

           Class clzAction = action.getClass();

           Enumeration<String> names = request.getParameterNames();

           while (names.hasMoreElements()) {        

               String name =  names.nextElement();        //获得每一个参数名      eg:username

               String[] value = request.getParameterValues(name);   //获取这个参数对应的值  eg:leno

               if (value != null) {

                  try {

                      // Action里是否有name这个属性,并获取在Action里的数据类型

                      Class fieldType = clzAction.getDeclaredField(name).getType(); 

                      String setName = "set"

                             + name.substring(0, 1).toUpperCase()

                             + name.substring(1);

                      Method method = clzAction.getMethod(setName,

                             new Class[] { fieldType });

                     

                      Object[] o = transfer(fieldType, value);

                      //判断是否为数组属性

                      if (fieldType.isArray()) {

                         method.invoke(action, new Object[]{o});

                      } else {

                         method.invoke(action, new Object[]{o[0]} );

                      }

                  } catch (NoSuchFieldException e) {

                      //e.printStackTrace();不用处理,因为有些表单字段在Action中没有对应的属性。

                  } catch (Exception e) {

                      e.printStackTrace();

                  }

               }

           }

        }

       

       

     

        public static void actionToRequest(HttpServletRequest request, Object action) {

           Field[] fields = action.getClass().getDeclaredFields();

           for (int i = 0; i < fields.length; i++) {

               String fieldName = fields[i].getName();

               String getMethodName = "get"

                  + fieldName.substring(0, 1).toUpperCase()

                  + fieldName.substring(1);

               try {

                  Method getMethod = action.getClass().getMethod(getMethodName, new Class[]{});

                  Object value = getMethod.invoke(action, new Object[]{});

                  if(value!=null){

                      request.setAttribute(fieldName, value);

                  }

               } catch (Exception e) {

                  // TODO Auto-generated catch block

                  e.printStackTrace();

               }

              

          

        }

          

        }

       

        @SuppressWarnings("unchecked")

        private static Object[] transfer(Class fieldType, String[] value){

           Object[] os = null;

     

           String typeString = fieldType.getSimpleName().replace("[]", "");

           if("String".equalsIgnoreCase(typeString)){

               os = value;

           }

           else if("int".equals(typeString) || "Integer".equals(typeString)){

               os = new Integer[value.length];

               for(int i = 0; i < os.length; i++){

                  os[i] = Integer.parseInt(value[i]);

               }

           }

           else if("float".equals(typeString) || "Float".equals(typeString)){

               os = new Float[value.length];

               for(int i = 0; i < os.length; i++){

                  os[i] = Float.parseFloat(value[i]);

               }

           }

           else if("double".equals(typeString) || "Double".equals(typeString)){

               os = new Double[value.length];

               for(int i = 0; i < os.length; i++){

                  os[i] = Double.parseDouble(value[i]);

               }

           }

          

           return os;

        }

     

    }

        打完收工,框架编码工作暂时告一段落!

     

    别忘记了在web.xml中配置这个Filter

    <?xml version="1.0" encoding="UTF-8"?>

    <web-app version="2.5"

        xmlns="http://java.sun.com/xml/ns/javaee"

        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

        xsi:schemaLocation="http://java.sun.com/xml/ns/javaee

        http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">

    <filter>

           <filter-name>coreFilter</filter-name>

           <filter-class>org.leno.mvc1.core.FilterDispatcher</filter-class>

        </filter>

        <filter-mapping>

           <filter-name>coreFilter</filter-name>

           <url-pattern>/*</url-pattern>

        </filter-mapping>

      <welcome-file-list>

        <welcome-file>index.jsp</welcome-file>

      </welcome-file-list>

    </web-app>

     

    最后我们做个登陆的demo来测试一下吧,就按照本文最开始的那个mvc1.xml配置编写LoginAction.java(是不是觉得这个ActionStruts1的要简单的多?),这里我们不需要实现特殊的接口,也不用掺杂很多ServletAPI,是一个很容易测试的Action

    package org.leno.mvc1.app.action;

     

     

    public class LoginAction {

     

        private String username;

        private String password;

     

        public String getUsername() {

           return username;

        }

     

        public void setUsername(String username) {

           this.username = username;

        }

     

        public String getPassword() {

           return password;

        }

     

        public void setPassword(String password) {

           this.password = password;

        }

     

        public String logon() throws Exception {

           System.out.println("Login:username:"+username+" password:"+password);

           if("leno".equals(username)){

               return "success";

           }else{

               return "login";

           }

        }

     

    }

     

    下面是我们测试用到的页面login.jsp:

    <%@ page language="java" contentType="text/html; charset=UTF-8"

        pageEncoding="UTF-8"%>

    <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">

    <html>

        <head>

           <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">

           <title>Insert title here</title>

        </head>

        <body>

           <form method="post" action="login.action" name="loginForm">

               <table width="422" border="1" bgcolor="#0080c0" height="184">

                  <caption>

                      <h1>

                         用户登陆

                      </h1>

                  </caption>

                  <tbody>

                      <tr>

                         <td>

                             &nbsp;姓名:

                         </td>

                         <td>

                             &nbsp;

                             <input type="text" name="username">

                         </td>

                      </tr>

                      <tr>

                         <td>

                             &nbsp;密码:

                         </td>

                         <td>

                             &nbsp;

                             <input type="password" name="password">

                         </td>

                      </tr>

                      <tr align="center">

                         <td colspan="2">

                             &nbsp;

                             <input type="submit" value="登陆" name="submit">

                             <input type="reset" value="重置" name="reset">

                         </td>

                      </tr>

                  </tbody>

               </table>

           </form>

        </body>

    </html>

     

        这样我们自己的简易版Struts2就算完成了。怎么样,写框架也不是想象中那么困难吧。虽然我们这里完成的功能还非常简单,远远够不上一个真正框架的级别,但核心部分已经完成了。所以只要我们有一颗不断学习和创新的心,加上持续不断的努力,何愁写不出好的框架来呢?好啦,以后我们有时间再慢慢丰富我们的功能吧。今天就写到这里,大家有什么问题或者不明白的地方可以给我留言,祝愿大家学习愉快。哦,对了,别忘了导入dom4j.jarjaxen.jar包啊。呵呵,常识常识!

  • 相关阅读:
    CentOS6.5 mini安装到VirtualBox虚拟机中
    docker配置redis6.0.5集群
    docker搭建数据库高可用方案PXC
    我通过调试ConcurrentLinkedQueue发现一个IDEA的小虫子(bug), vscode复现, eclipse毫无问题
    ThreadLocal底层原理学习
    第九章
    多线程-java并发编程实战笔记
    Spring-IOC源码解读3-依赖注入
    Spring-IOC源码解读2.3-BeanDefinition的注册
    Spring-IOC源码解读2.2-BeanDefinition的载入和解析过程
  • 原文地址:https://www.cnblogs.com/CharmingDang/p/9663724.html
Copyright © 2020-2023  润新知