Jslfl【软件开发技术笔记】

自己写框架-MVC(四)

技术要点:
xml解析
监听器
java多态特性
反射

改造计划:
模仿struts实现通过xml文件来配置请求对应的action处理器、请求表单、跳转路径,整个过程由框架自动完成(创建分发action处理器、封装formbean、请求跳转)。

代码结构:
img1-1

1、创建mvc配置文件mvc-config
resource/mvc-config.xml代码清单:

1
2
3
4
5
6
7
8
9
10
11
12
<?xml version="1.0" encoding="UTF-8"?>
<mvc-config>
    <action name="login" class="cn.jslfl.mvc.demo.action.LoginAction" form="cn.jslfl.mvc.demo.form.LoginForm">
        <result name="success">/web/userInfo.jsp</result>
        <result name="error">/web/login.jsp</result>
    </action>

    <action name="reg" class="cn.jslfl.mvc.demo.action.RegAction" form="cn.jslfl.mvc.demo.form.RegForm">
        <result name="success">/web/login.jsp</result>
        <result name="error">/web/reg.jsp</result>
    </action>
</mvc-config>

2、mvc-config.xml解析处理
2.1、创建MvcConfig.java,定义配置文件结构名称常量
MvcConfig.java代码清单:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package cn.jslfl.mvc.core.config;

/**
 * mvc配置文件结构名称定义
 * @author jslfl
 *
 */

public class MvcConfig {
    public static final String ACTION_ATTR = "attr";
    public static final String ACTION_RESULT = "result";
    public static final String ACTION_NAME = "name";
    public static final String ACTION_CLASS = "class";
    public static final String ACTION_FORM = "form";
    public static final String RESULT_NAME = "name";
    public static final String MVC_CONFIG = "mvc_config";
}

2.2、创建ParseMvcConfig.java来做mvc-config.xml解析处理
ParseMvcConfig代码清单:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
package cn.jslfl.mvc.core.config;

import java.io.File;
import java.util.HashMap;
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;

/**
 * mvc配置文件解析操作
 * @author jslfl
 *
 */

public class ParseMvcConfig {
    /**
     * 解析完成后返回数据结构示例
     * {
     *  login:
     *      {
     *          attr:{name:'login', class:'cn.action.Login', form:'cn.form.LoginForm'},
     *          result:{success:'login.jsp', error:'error.jsp'}
     *      },
     *  reg:
     *      {
     *          attr:{name:'reg', class:'cn.action.Reg', form:'cn.form.RegForm'},
     *          result:{success:'login.jsp', error:'error.jsp'}
     *      }
     * }
     */

    @SuppressWarnings("unchecked")
    public static Map<String, Map<String,Map<String,String>>> parse(String xmlPath){
        Map<String, Map<String,Map<String,String>>> actionMap = new HashMap<String, Map<String,Map<String,String>>>();
       
        SAXReader reader = new SAXReader();
        try {
            Document doc = reader.read(new File(xmlPath));
            Element root = doc.getRootElement();
            List<Element> actionElementList = root.elements();
            for(Element actionElement : actionElementList){
                Map<String, Map<String,String>> action = new HashMap<String, Map<String,String>>();
               
                //action节点属性
                String actionName = actionElement.attribute(MvcConfig.ACTION_NAME).getStringValue();
                Map<String,String> attr = new HashMap<String,String>();
                attr.put(MvcConfig.ACTION_NAME, actionName);
                attr.put(MvcConfig.ACTION_CLASS, actionElement.attribute(MvcConfig.ACTION_CLASS).getStringValue());
                attr.put(MvcConfig.ACTION_FORM, actionElement.attribute(MvcConfig.ACTION_FORM).getStringValue());
               
                //action节点下result子节点
                List<Element> resultElementList = actionElement.elements();
                Map<String,String> result = new HashMap<String,String>();
                for(Element resultElement : resultElementList){
                    result.put(resultElement.attributeValue(MvcConfig.RESULT_NAME), resultElement.getText());
                }
               
                action.put(MvcConfig.ACTION_ATTR, attr);
                action.put(MvcConfig.ACTION_RESULT, result);
                actionMap.put(actionName, action);
            }
        } catch (DocumentException e) {
            e.printStackTrace();
        }
        return actionMap;
    }
}

本例中使用dom4j来解析xml,所以需要引入dom4j的jar包,并且解析结果使用Map来存储,最好方式是定义相应pojo类来存储。

3、创建监听器ActionServletListener.java,ActionServlet初始化时调用解析mvc-config.xml,结果存入servlet上下文
ActionServletListener.java代码清单:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package cn.jslfl.mvc.core.listener;

import java.util.Map;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import cn.jslfl.mvc.core.config.MvcConfig;
import cn.jslfl.mvc.core.config.ParseMvcConfig;
/**
 * ActionServlet初始化时解析mvc-config.xml
 * @author jslfl
 */

public class ActionServletListener implements ServletContextListener {
    @Override
    public void contextDestroyed(ServletContextEvent arg0) {
        System.out.println("mvc-framework: 系统退出!");
    }

    @Override
    public void contextInitialized(ServletContextEvent arg0) {
        String xmlPath = arg0.getServletContext().getInitParameter("mvc-config");
        System.out.println("mvc-framework: 取得参数mvc配置文件路径: " + xmlPath);//print================
       
        String mvcXmlPath = this.getClass().getClassLoader().getResource(xmlPath).getPath();//类路径下的配置文件
        System.out.println("mvc-framework: 取得参数mvc配置文件路径: " + mvcXmlPath);//print================
       
        //解析mvc配置文件
        Map<String, Map<String,Map<String,String>>> actionMap = ParseMvcConfig.parse(mvcXmlPath);
        System.out.println("mvc-framework: mvc配置加载成功!");//print================
        System.out.println("mvc-framework: mvc配置信息:");//print================
        System.out.println(actionMap);//print================
       
        arg0.getServletContext().setAttribute(MvcConfig.MVC_CONFIG, actionMap);
    }
}

4、改造formbean
4.1、定义formbean的基类
ActionFormBean.java代码清单:

1
2
3
4
5
6
7
8
package cn.jslfl.mvc.core.form;
/**
 * mvc 中from表单的父类
 * @author jslfl
 */

public class ActionFormBean {

}

4.2、FormBeanUtil.java改名为ActionFormBeanUtil.java,并返回ActionFormBean对象
FormBeanUtil.java代码清单:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package cn.jslfl.mvc.core.form;

import java.lang.reflect.Field;
import javax.servlet.http.HttpServletRequest;

/**
 * 自动解析封装FormBean对象
 * @author jslfl
 *
 */

public class ActionFormBeanUtil {
    @SuppressWarnings("rawtypes")
    public static ActionFormBean packFormBean(HttpServletRequest req, String formBeanName){
        ActionFormBean form = null;
        try {
            Class c = Class.forName(formBeanName);
            form = (ActionFormBean)c.newInstance();
           
            Field[] fieldArr = c.getDeclaredFields();
            for(Field f : fieldArr){
                f.setAccessible(true);
                f.set(form, req.getParameter(f.getName()));
                f.setAccessible(false);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return form;
    }
}

4.3、重构LoginForm、RegForm,让它们继承ActionFormBean类
LoginForm.java代码清单:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package cn.jslfl.mvc.demo.form;

import cn.jslfl.mvc.core.form.ActionFormBean;

public class LoginForm extends ActionFormBean{
    private String userName;
    private String passWord;
   
    public LoginForm(){
    }
    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;
    }
}

RegForm.java代码清单:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package cn.jslfl.mvc.demo.form;

import cn.jslfl.mvc.core.form.ActionFormBean;

public class RegForm extends ActionFormBean {
    private String userName;
    private String passWord;
    private String passWord2;
   
    public RegForm(){
    }
    public String getPassWord2() {
        return passWord2;
    }
    public void setPassWord2(String passWord2) {
        this.passWord2 = passWord2;
    }
    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;
    }
}

5、改造action
5.1、创建action接口MvcAction.java
MvcAction.java代码清单:

1
2
3
4
5
6
7
8
9
10
11
12
13
package cn.jslfl.mvc.core.action;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import cn.jslfl.mvc.core.form.ActionFormBean;

/**
 * Action接口
 * @author jslfl
 */

public interface MvcAction {
    public String doService(ActionFormBean formBean, HttpServletRequest req, HttpServletResponse resp);
}

5.2、重构LoginAction、RegAction,实现MvcAction接口,doService调用参数改为ActionFormBean
LoginAction代码清单:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package cn.jslfl.mvc.demo.action;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import cn.jslfl.mvc.core.action.MvcAction;
import cn.jslfl.mvc.core.form.ActionFormBean;
import cn.jslfl.mvc.demo.form.LoginForm;

public class LoginAction implements MvcAction {
    public LoginAction(){
    }
   
    public String doService(ActionFormBean actionFormBean, HttpServletRequest req, HttpServletResponse resp){
        LoginForm formBean = (LoginForm)actionFormBean;
        //业务处理开始...
        boolean flag = false;
        if("aa".equals(formBean.getUserName()) && "bb".equals(formBean.getPassWord())){
            flag = true;
        }
        //业务处理结束...
       
        String info = "<h1>登录" + (flag ? "成功" : "失败") + "!userName: " + formBean.getUserName()+ ", " + "password: " + formBean.getPassWord() + "</h1>";
        req.setAttribute("info", info);
        if(flag)
            return "success";
        return "error";
    }
}

RegAction代码清单:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package cn.jslfl.mvc.demo.action;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import cn.jslfl.mvc.core.action.MvcAction;
import cn.jslfl.mvc.core.form.ActionFormBean;
import cn.jslfl.mvc.demo.form.RegForm;

public class RegAction implements MvcAction {
    public RegAction(){
    }
   
    public String doService(ActionFormBean actionFormBean, HttpServletRequest req, HttpServletResponse resp){
        RegForm regBean = (RegForm)actionFormBean;
        //业务处理开始...
        String msg = "";
        if(regBean.getUserName() == null || "".equals(regBean.getUserName().trim())
                ||regBean.getPassWord() == null || "".equals(regBean.getPassWord().trim())
                || regBean.getPassWord2() == null || "".equals(regBean.getPassWord2().trim())){
            msg = "数据不完整";
        }else if(!regBean.getPassWord().equals(regBean.getPassWord2())){
            msg = "两次密码不匹配";
        }else{
            msg = "成功";
        }
       
        //业务处理结束...
       
        String info = "<h1>注册" + msg + "!userName: " + regBean.getUserName() + ", " + "password: " + regBean.getPassWord() + ", " + "password2: " + regBean.getPassWord2() +"</h1>";
        req.setAttribute("info", info);
        if("成功".equals(msg))
            return "success";
        return "error";
    }
}

6、改造ActionServlet
从ServletContext中取出mvc-config.xml的配置信息,通过actionName取得配置中请求映射action的信息,再创建FormBean、创建Action,调用action.doService,取得返回路径进行跳转

ActionServlet.java代码清单:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
package cn.jslfl.mvc.core.servlet;

import java.io.IOException;
import java.util.Map;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import cn.jslfl.mvc.core.action.MvcAction;
import cn.jslfl.mvc.core.config.MvcConfig;
import cn.jslfl.mvc.core.form.ActionFormBean;
import cn.jslfl.mvc.core.form.ActionFormBeanUtil;

public class ActionServlet extends HttpServlet{
    private static final long serialVersionUID = 1L;

    @SuppressWarnings({ "unchecked", "rawtypes" })
    public void doPost(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        resp.setCharacterEncoding("GBK");
       
        //取得请求名称,对应action名称
        String reqPath = req.getServletPath();
        String actionName = reqPath.substring(1, reqPath.lastIndexOf(".do"));//截取 "/login.do" -> "login"
        System.out.println("mvc-framework: 请求路径: " + reqPath + "  actionName: " + actionName );//print================
       
        //得到action配置信息
        Map<String, Map<String,Map<String,String>>> actionMap =
            (Map<String, Map<String,Map<String,String>>>)req.getServletContext().getAttribute(MvcConfig.MVC_CONFIG);
        Map<String,Map<String,String>> actionCfg = actionMap.get(actionName);
        System.out.println("mvc-framework: action信息: " + actionCfg);//print================
       
        //自动封装FormBean
        ActionFormBean formBean = ActionFormBeanUtil.packFormBean(req, actionCfg.get(MvcConfig.ACTION_ATTR).get(MvcConfig.ACTION_FORM));
       
        //调用业务action
        String result = "";
        try {
            Class actionClass = Class.forName(actionCfg.get(MvcConfig.ACTION_ATTR).get(MvcConfig.ACTION_CLASS));
            MvcAction action = (MvcAction)actionClass.newInstance();
            result = action.doService(formBean, req, resp);
        } catch (Exception e) {
            e.printStackTrace();
        }
        req.getRequestDispatcher(actionCfg.get(MvcConfig.ACTION_RESULT).get(result)).forward(req, resp);
    }
   
    public void doGet(HttpServletRequest req, HttpServletResponse resp){
        try {
            doPost(req, resp);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

7、修改web.xml配置
web.xml代码清单:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
    id="WebApp_ID" version="3.0">
    <display-name>mvc_framework</display-name>

    <!-- mvc framework配置 开始-->
    <context-param>
        <param-name>mvc-config</param-name>
        <param-value>/mvc-config.xml</param-value>
    </context-param>
    <listener >
        <listener-class>cn.jslfl.mvc.core.listener.ActionServletListener</listener-class>
    </listener>
    <servlet>
        <servlet-name>mvc</servlet-name>
        <servlet-class>cn.jslfl.mvc.core.servlet.ActionServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>mvc</servlet-name>
        <url-pattern>*.do</url-pattern>
    </servlet-mapping>
    <!-- mvc framework配置  结束-->
</web-app>

8、发布测试
img8-0
img8-1
img8-2
img8-3
img8-4
img8-5
img8-6
img8-7
img8-8

问题:action 中ActionFormBean应该直接用子类

本例只是对MVC基础原理做演示,故不做数据库操作和深入严谨的业务处理.
LoginForm、RegForm中一般需要实现Serializable接口,并提供无参构造方法,以支持序列化和保证反射创建对象时(在重载了构造方法时)不出错。

总结:

Comments are currently closed.