自定义WEB MVC框架 二 简单扫描和映射绑定

来源:互联网 发布:手机禁网软件 编辑:程序博客网 时间:2024/06/05 01:59

主控制器-第二版

  上一篇简单的定下一个结构,这次要把它完善起来,至少能跑通。
  首先假定所有的Controller类(请求处理器,如果是很古老的写法,那么应该是每一个Servlet)都在一个指定的包下:com.bubbling.test。然后在主控制器启动的时候,去扫描这个包,将所有的Controller对应的url进行映射绑定,这里先不做太多,后面会改成由开发者指定位置。
  绑定之后,客户端只要发起请求,那么主控制器会根据请求路径进行解析,找到对应的Controller和处理方法,之后将HttpServletRequest和HttpServletResponse对象注入到Controller中,这里暂时将两个成员放置在基类BaseController中,由所有派生类继承,那么需要做如下改进:

package com.bubbling.framework.dispatcher;import java.io.IOException;import java.lang.reflect.InvocationTargetException;import java.lang.reflect.Method;import java.util.HashMap;import java.util.Map;import javax.servlet.ServletException;import javax.servlet.annotation.MultipartConfig;import javax.servlet.annotation.WebServlet;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import com.bubbling.util.ReflectionUtil;/** * @author 胡楠 *  *         主控制器,做请求分发 * */@WebServlet(name = "maindispatcher", urlPatterns = "/", loadOnStartup = 1)@MultipartConfigpublic class MainDispatcher extends HttpServlet {    private static final long serialVersionUID = 2885097167484409970L;    /**     * 保存所有Controller映射     */    private static Map<String, String> requestMapping = new HashMap<String, String>();    private String rootName;    /**     * 添加Controller映射     *      * @param controllerName     * @param className     */    public static void addMapping(String controllerName, String className) {        requestMapping.put(controllerName, className);    }    @Override    public void init() throws ServletException {        rootName = getServletContext().getRealPath("/") + "WEB-INF\\classes\\";        ControllerBind.autoBind(rootName, "com.bubbling.test");    }    @SuppressWarnings({ "rawtypes", "unchecked" })    @Override    protected void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {        String servletPath = req.getServletPath();        String controller = PathParser.getController(servletPath);        String action = PathParser.getAction(servletPath);        try {            Class clazz = Class.forName(requestMapping.get(controller));            Object obj = clazz.newInstance();            ReflectionUtil.setSuperValue(obj, "request", req);            ReflectionUtil.setSuperValue(obj, "response", res);            Method method = clazz.getMethod(action);            method.invoke(obj);        } catch (ClassNotFoundException e) {            e.printStackTrace();        } catch (InstantiationException e) {            e.printStackTrace();        } catch (IllegalAccessException e) {            e.printStackTrace();        } catch (IllegalArgumentException e) {            e.printStackTrace();        } catch (InvocationTargetException e) {            e.printStackTrace();        } catch (NoSuchMethodException e) {            e.printStackTrace();        } catch (SecurityException e) {            e.printStackTrace();        }    }}

包扫描

  在主控制器中添加一个Map来维护所有的Controller映射,并提供addMapping方法添加映射,而后在init方法中扫描Controller包,进行绑定:

package com.bubbling.framework.dispatcher;import java.io.File;import com.bubbling.framework.action.BaseController;import com.bubbling.framework.action.annotation.ControllerMapping;public class ControllerBind {    /**     * 暂时不考虑该包下还有其他包,未来会修正成迭代取得Class,并且会开发配置Controller类所在位置的方法     *      * @param packageName     *            指定的Controller所在包名     */    @SuppressWarnings({ "rawtypes", "unchecked" })    public static void autoBind(String rootName, String packageName) {        String realPath = rootName + packageName.replace(".", "\\");        File packageFile = new File(realPath);        if (packageFile != null) {            if (!packageFile.isDirectory()) {                return;            }        }        File[] classFiles = packageFile.listFiles();        for (File classFile : classFiles) {            String fileName = classFile.getName();            String className = packageName + "." + fileName.substring(0, fileName.lastIndexOf("."));            Class clazz;            try {                clazz = Class.forName(className);                if (clazz.isAnnotationPresent(ControllerMapping.class)) {                    ControllerMapping cm = (ControllerMapping) clazz.getAnnotation(ControllerMapping.class);                    MainDispatcher.addMapping(cm.value(), className);                } else {                    Object obj = clazz.newInstance();                    if (obj instanceof BaseController) {                        fileName = classFile.getName();                        String controllerName = fileName.substring(0, fileName.lastIndexOf("."));                        MainDispatcher.addMapping(controllerName, className);                    }                }            } catch (ClassNotFoundException e) {                e.printStackTrace();            } catch (InstantiationException e) {                e.printStackTrace();            } catch (IllegalAccessException e) {                e.printStackTrace();            }        }    }}

为父类属性注入

  ControllerBind中aotuBind方法目前仅仅以File的方式来取得所有Controller类,其实这是不对的,因为没有办法确定这个包下是否仍有其他包,所以未来会修改成按协议迭代、枚举集合的方式来读取Controller类,并支持jar的解析,在初期我就不修改了。
  这里还要提一点,因为HttpServletRequest和HttpServletResponse对象被安置在BaseController中:

package com.bubbling.framework.action;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;/** * @author 胡楠 *  *         请求处理基类,启动时做包扫描,实现映射装绑 * */public abstract class BaseController{    protected HttpServletRequest request;    protected HttpServletResponse response;}

  所以在请求到来之后,我们定位到具体的Controller之后,需要将这两个对象注入,那么就需要在反射工具中添加一个新方法,为了给父类属性赋值:

package com.bubbling.util;    ...    ...    ...    /**     * 通过反射给对象的指定字段赋值     *      * @param target     *            目标对象     * @param fieldName     *            字段的名称     * @param value     *            值     */    public static void setSuperValue(Object target, String fieldName, Object value) {        Class<?> clazz = target.getClass();        Field f;        try {            f = clazz.getSuperclass().getDeclaredField(fieldName);            f.setAccessible(true);            f.set(target, value);        } catch (NoSuchFieldException e) {            e.printStackTrace();        } catch (SecurityException e) {            e.printStackTrace();        } catch (IllegalArgumentException e) {            e.printStackTrace();        } catch (IllegalAccessException e) {            e.printStackTrace();        }    }}

请求解析工具

  最后为了方便解析请求,新增一个请求的解析类:

package com.bubbling.framework.dispatcher;import com.bubbling.util.StringUtil;/** * @author 胡楠 *  *         MainDispatcher获取请求后,解析请求 * */public class PathParser {    private PathParser() {        throw new AssertionError();    }    public static String[] parseParams(String params) {        if (StringUtil.isEmpty(params)) {            return null;        }        return params.split("-");    }    public static String getParamas(String servletPath) {        String[] data = parsePath(servletPath);        if (data.length < 2) {            return null;        }        return data[1];    }    public static String getAction(String servletPath) {        String[] data = parsePath(servletPath);        if (data.length < 2) {            return null;        }        return data[1];    }    public static String getController(String servletPath) {        String[] data = parsePath(servletPath);        if (data.length < 1) {            return null;        }        return data[0];    }    public static String[] parsePath(String servletPath) {        if (!StringUtil.isEmpty(servletPath)) {            String[] maps = servletPath.split("\\/");            if (maps.length > 0) {                String[] ret = new String[maps.length - 1];                System.arraycopy(maps, 1, ret, 0, ret.length);                return ret;            }        }        return null;    }}

初步测试

  到此为止,基本框架都创建完毕了,写一个测试类来试试看,因为上文中说过了所有的Controller类暂时都安置在com.bubbling.test包下,所以测试类如下:

package com.bubbling.test;import java.io.IOException;import java.io.PrintWriter;import com.bubbling.framework.action.BaseController;import com.bubbling.framework.action.annotation.ActionMapping;import com.bubbling.framework.action.annotation.ControllerMapping;@ControllerMapping("mytest")public class MyTestController extends BaseController {    @ActionMapping(action = "test")    public void test() {        try {            PrintWriter pw = response.getWriter();            pw.println("successful");        } catch (IOException e) {            e.printStackTrace();        }    }    @Override    public String toString() {        return "MyTestController [request=" + request + ", response=" + response + "]";    }}

  部署到tomcat下,然后提交请求:

http://localhost:8080/BubblingWEB/mytest/test

  观察结果:
这里写图片描述

  可以看到虽然主控制器中的逻辑并没有很多,但是我们得到了想要的结果,请求能够被正确的解析,并且执行正确的处理逻辑。
  结尾大福楠还是有话要说的,虽然市面上成熟的WEB框架不胜枚举,但是我们未尝不可自己来摸索一下,一个是能更好的理解底层实现,一个是通过摸索对Servlet的运行原理更加了解,虽然才写了一点点东西,但是已经涉及到了很多设计模式,这些都是很宝贵的经验。
  后面我会把这个框架逐步的完善,让它变得更适用,实用。并且我会尝试使用JNDI等方式来实现数据源的配置,使框架的移植使用更加便捷,我也会尝试实现一个简陋的持久层框架,配合WEB框架完成一个小项目。

0 0
原创粉丝点击