smartMVC(模仿mvc架构)

最近在学习mvc模式,试着开发一个简单的mvc框架。

架构图:

这里写图片描述

开发步骤:

一、准备阶段:

1.开发注解,该注解用于给处理器的方法提供对应的uri

package base.annotation;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.RUNTIME)
//上面的注解可以让我们开发的注解存活到被扫描到为止,如果不加默认存活到class文件
public @interface RequestMapping {
//这是注解的属性,可以把数据(比如uri)存在属性中,
//需要的时候可以通过反射技术读取出来
    public String value();
}

2.准备配置文件,此配置文件中保存处理器的类名,供HandlerMapping使用

<?xml version="1.0" encoding="UTF-8"?>
<beans>
    <!-- 配置处理器-->
    <bean class="demo.controller.LoginController"/>
</beans>
二、正式开发:

1.首先,我们要将控制器和其对应的uri读取出来。我们在前端控制器初始化时从smartmvc.xml中读取已配置的控制器(如LoginController),然后交给映射处理器处理

    public void init() throws ServletException {
        /*
         * 使用dom4j读取配置文件的内容
         */
        SAXReader reader = new SAXReader();
        InputStream ins = getClass().getClassLoader().getResourceAsStream("smartmvc.xml");
        try {
            // 解析配置文件
            Document doc = reader.read(ins);
            // 找到根节点
            Element root = doc.getRootElement();
            // 找到根节点下面的所有的子节点
            List<Element> eles = root.elements();
            // 遍历所有子节点
            List beans = new ArrayList();
            for (Element ele : eles) {
                // 读取class属性值
                String className = ele.attributeValue("class");
                System.out.println("className: " + className);
                // 将处理器实例化
                Object bean = Class.forName(className).newInstance();
                // 将处理器实例添加到集合里面。
                beans.add(bean);
            }
            System.out.println("beans:" + beans);

            // 将包含有处理器实例的集合给
            // HandlerMapping来处理。
            handlerMapping = new HandlerMapping();
            //HandlerMapping会建立请求路径与处理器的对应关系,存在map里
            handlerMapping.process(beans);

        } catch (Exception e) {
            e.printStackTrace();
            throw new ServletException(e);
        }
    }

2.上面写了 HandlerMapping 类 的 process 方法,但还没有实现。下面我们来实现它,该方法用于建立请求路径与处理器的对应关系

package base.common;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import base.annotation.RequestMapping;

/**
 * 映射处理器: 负责提供请求路径与处理器的对应关系
 */
public class HandlerMapping {

    // 存放请求路径与处理器的对应关系。
    private Map<String, Handler> handlerMap = new HashMap<String, Handler>();
    /**
     * 依据请求路径,返回Handler对象。 
     * 注: Handler对象包含了Method对象和处理器实例。
     */
    public Handler getHandler(String path) {
        return handlerMap.get(path);
    }
    /**
     * process方法遍历整个集合,将处理器实例取出来,
     * 通过java反射技术读取@RequestMapping
     * 注解中的路径信息,建立请求路径与处理器的对应关系。
     */
    public void process(List beans) {
        for (Object bean : beans) {
            // 找到该实例对应的class对象。
            Class clazz = bean.getClass();
            // 找出所有方法
            Method[] methods = clazz.getDeclaredMethods();
            // 遍历所有方法
            for (Method mh : methods) {
                // 获得方法前的@RequestMapping注解
                RequestMapping rm = mh.getDeclaredAnnotation(RequestMapping.class);
                // 获得路径信息
                String path = rm.value();
                System.out.println("path:" + path);
                // 建立请求路径与处理器的对应关系(这里需要解释一下,请看步骤3)
                handlerMap.put(path, new Handler(mh, bean));
            }
        }
        System.out.println("handlerMap: " + handlerMap);
    }
}

3.因为通过反射调用invoke方法时需要两个两个参数,是该类实例和要调用的方法实例,所以我们维护一个map集合,key是方法上@RequestMapping注解的value值,map集合的value里需要放入类实例和要调用的方法实例,所以我们要创建一个Handler类封装要存入集合的类实例和要调用的方法实例。

package base.common;

import java.lang.reflect.Method;
/**
 * 为了方便利用java反射进行方法的调用,将处理器实例和方法对象进行了封装。
 */
public class Handler {
    private Method mh;
    private Object obj;

    public Handler(Method mh, Object obj) {
        this.mh = mh;
        this.obj = obj;
    }
    public Method getMh() {
        return mh;
    }
    public void setMh(Method mh) {
        this.mh = mh;
    }
    public Object getObj() {
        return obj;
    }
    public void setObj(Object obj) {
        this.obj = obj;
    }
}

4.现在前端控制器的初始化已经完成,所有的处理器和其对应的uri都已经储存在HandlerMapping的一个map集合里,并对外提供方法,可以通过uri查询对应的处理器实例和方法实例。下面我们可以编写前端控制器的请求转发了,转发请求时需要利用反射技术,调用处理器的方法来处理业务(此版本还可以根据方法需要的参数类型自动传入参数,但目前只支持HttpServletRequest和HttpServletResponse类型参数的传入),处理器会返回视图名(即jsp的名称),然后前端控制器会将请求转发|重定向给对应的jsp

    protected void service(HttpServletRequest request, HttpServletResponse response)
    throws ServletException, IOException {
        // 获得请求资源路径
        String uri = request.getRequestURI();
        // 获得应用名
        String contextPath = request.getContextPath();
        // 截取请求资源路径的一部分
        String path = uri.substring(contextPath.length());
        System.out.println("path:" + path);

        // 依据请求路径(path)找到对应的处理器来处理
        Handler handler = handlerMapping.getHandler(path);
        System.out.println("handler:" + handler);

        // 获得要调用的Method对象
        Method mh = handler.getMh();
        // 获得处理器实例
        Object bean = handler.getObj();
        // returnVal是方法的返回值
        Object returnVal = null;
        try {
            /*
             * 调用处理器的方法: 需要查看处理器方法带不带参数,
             * 如果带有参数,需要给参数赋值,然后才能调用。
             */
            Class[] types = mh.getParameterTypes();
            if (types.length > 0) {
                // 带有参数的方法
                Object[] params = new Object[types.length];
                for (int i = 0; i < types.length; i++) {
                    if (types[i] == HttpServletRequest.class) {
                        params[i] = request;
                    }
                    if (types[i] == HttpServletResponse.class) {
                        params[i] = response;
                    }
                }
                // 调用带参数的方法
                returnVal = mh.invoke(bean, params);
            } else {
                // 调用不带参的方法
                returnVal = mh.invoke(bean);
            }
            System.out.println("returnVal:" + returnVal);
            /*
             * 看视图名是否以"redirect:"开头, 如果是,则重定向;否则转发。
             */
            String viewName = returnVal.toString();
            if (viewName.startsWith("redirect:")) {
                // 重定向
                // 生成重定向地址
                String redirectPath = contextPath + "/" + viewName.substring("redirect:".length());
                response.sendRedirect(redirectPath);
            } else {
                // 转发
                // 将视图名转换成对应的jsp
                String jspPath = "/WEB-INF/" + viewName + ".jsp";
                request.getRequestDispatcher(jspPath).forward(request, response);
            }
        } catch (Exception e) {
            e.printStackTrace();
            throw new ServletException(e);
        }
    }

5.现在,前端处理器已经可以将请求转发/重定向到视图层(jsp)了,下面我们写一个处理器和一个jsp来测试一下。
处理器:

package demo.controller;

import javax.servlet.http.HttpServletRequest;

import base.annotation.RequestMapping;

public class LoginController {

    @RequestMapping("/toLogin.do")
    public String toLogin() {
        System.out.println("LoginController的toLogin方法");
        return "login";
    }

    @RequestMapping("/login.do")
    public String login(HttpServletRequest request) {
        System.out.println("处理登录请求");

        String username = request.getParameter("username");
        String pwd = request.getParameter("pwd");
        System.out.println("username:" + username + " pwd:" + pwd);
        //模拟数据库查询结果
        if ("Tom".equals(username) && "test".equals(pwd)) {
            // 登录成功,重定向到用户列表
            // 视图名前面有"redirect:",表示
            // 重定向到某个地址。
            return "redirect:toList.do";
        } else {
            // 登录失败,转发到登录页面,并提示用户
            request.setAttribute("login_failed", "用户名或密码错误");
            return "login";
        }
    }

    @RequestMapping("/toList.do")
    public String toList() {
        return "listUser";
    }
}

jsp:

<%@ page contentType="text/html; charset=utf-8"
    pageEncoding="utf-8"%>
<html>
<head>
<title>Insert title here</title>
</head>
<body style="font-size:30px;">
    <form action="login.do" method="post">
        <fieldset>
            <legend>登录</legend>
            用户名:<input name="username"/>
            ${login_failed}
            <br/>
            密码:<input type="password" 
                name="pwd"/><br/>
            <input type="submit" value="确定"/>
        </fieldset>
    </form>
</body>
</html>

6.最后要记得配置下web.xml,将所有的请求交由前端控制器处理

<?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" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5">
  <servlet>
    <servlet-name>DispatcherServlet</servlet-name>
    <servlet-class>base.web.DispatcherServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>DispatcherServlet</servlet-name>
    <url-pattern>*.do</url-pattern>
  </servlet-mapping>
</web-app>

完成!

  • 6
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值