• JSP学习笔记(6)—— 自定义MVC框架


    仿照SpringMVC,实现一个轻量级MVC框架,知识涉及到了反射机制、注解的使用和一些第三方工具包的使用

    思路

    主要的总体流程如下图所示

    和之前一样,我们定义了一个DispatchServlet,用于拦截请求(这里一般拦截.do结尾的url请求);

    之后,DispatchServlet会根据url,找到Controller中对应的方法并执行,返回一个结果。

    我们根据返回的结果,来DispatchServlet执行不同的操作(请求转发、页面重定向、返回json数据)

    看完这里,总体的思路应该很明确了,问题来了:

    1. 如何实现让DispatchServlet根据url去找到对应的方法?
    2. 如何根据返回的结果,让DispatchServlet执行不同的操作?

    对于第一个问题,我们可以使用注解来实现

    1. 定义一个Controller注解,用来标记Controller类
    2. 定义一个RequestMapping注解,用来标记url匹配的方法

    对于第二个问题,我们可以设置这样的一套规则:

    1. 返回的结果是String,且以redirect:开头,则表明DispatchServlet要执行页面重定向操作
    2. 返回的结果是String,不以redirect:开头,则表明DispatchServlet要执行请求转发操作
    3. 返回的结果是一个bean类,则自动将其转为json数据

    一般我们会让doGet方法也调用doPost方法,所以我们只需要重写Servlet中的doPost方法

    由上面的思路,我们可以得到细化的流程图(也就是doPost方法中的流程)

    关键点实现

    处理请求url

    //获得url地址
    String servletPath = req.getServletPath();
    String requestUrl = StringUtils.substringBefore(servletPath,".do");

    使用注解找到对应的方法

    之前有说过,我们定义了两个注解,一个是Controller和RequestMapping

    标记一个类和方法

    @Controller
    public class UserController{
        @RequestMapping("/user/login")
        public String login(HttpServletRequest request){
            return "login.jsp";
        }
        ...
    }

    之后我们就可以使用Lang3开源库去查找类中是否有Controller标记,然后再去寻找是否被RequestMapping标记的方法,并把RequestMapping注解上的标记的url、Controller全类名和方法名存起来

    这里我是用来一个类ClassMapping来存放全类名和方法名,之后,使用url作为key,ClassMapping对象作为value,存入一个HashMap中

    这里虽然也可以放在doPost方法中,但是会造成资源的浪费,因为doPost方法可能会被执行多次。所以,更好的做法是,可以放在Servlet的初始化方法init()里面,这样,只会执行一次。

    我封装了一个配置类Configuration,专门用来查找url及其对应的方法

    判断类是否包含有Controller注解

    controllerClass.isAnnotationPresent(Controller.class)

    获得类中包含有RequestMapping注解的方法列表

    Method[] methods = MethodUtils.getMethodsWithAnnotation(controllerClass, RequestMapping.class, true, true);

    更多代码请看下面的代码部分Configuration类

    传参以及反射调用方法

    先获得方法的参数列表类型,之后,把对象转入到Object数组中,反射调用方法

    避免错误,之前还需有个判空操作和是否包含有key,下面的贴出的代码就省略了

    ClassMapping classMapping = classMappingMap.get(requestUrl);
    Class<?> controllerClass = classMapping.getControllerClass();
    Method method = classMapping.getMethod();
    
    //获得方法的参数类型列表,之后根据此类型列表,对request传来的参数进行处理
    Class<?>[] parameterTypes = method.getParameterTypes();
    //存放着之后需要传入到方法的参数值
    Object[] paramValues = new Object[parameterTypes.length];
    
    //对request传来的参数进行处理,将参数传给controller中的对应的方法,调用该方法
    try {
        //实例化controller类
        Object o = controllerClass.newInstance();
    
        for (int i = 0; i < parameterTypes.length; i++) {
            //这里我们只考虑了四种情况,所以Controller种的方法参数类型也只有四种
            if (ClassUtils.isAssignable(parameterTypes[i], HttpServletRequest.class)) {
                paramValues[i] = req;
            } else if (ClassUtils.isAssignable(parameterTypes[i], HttpServletResponse.class)) {
                paramValues[i] = resp;
            } else if (ClassUtils.isAssignable(parameterTypes[i], HttpSession.class)) {
                paramValues[i] = req.getSession(true);
            } else {
                //转为JavaBean
                if (parameterTypes[i] != null) {
                    //获得request传来的参数值,转为javabean
                    Map<String, String[]> parameterMap = req.getParameterMap();
    
                    //实例化这个JavaBean类型
                    Object bean = parameterTypes[i].newInstance();
                    //把数值快速转为bean
                    BeanUtils.populate(bean, parameterMap);
                    paramValues[i] =bean;
                }
            }
        }
    
        //调用方法,获得返回值,根据返回值,执行页面跳转或者是返回json数据等操作
        Object returnValue = MethodUtils.invokeMethod(o, true,method.getName(), paramValues);

    根据返回结果执行不同操作

    这里使用Google的gson框架,用于把实体类或者是List转为json数据

    if (returnValue instanceof String) {
        String value = (String) returnValue;
    
        if (value.startsWith("redirect:")) {
            //重定向
            resp.sendRedirect(req.getContextPath()+ StringUtils.substringAfter(value,"redirect:"));
        } else {
            //请求转发
            req.getRequestDispatcher(value).forward(req,resp);
        }
    } else {
        //返回一个对象
        if (returnValue != null) {
            //转为json,ajax操作
            String json = new Gson().toJson(o);
            resp.getWriter().print(json);
        }

    使用注意点

    不要忘了配置Servlet,和之前的Servlet一样,可以使用配置web.xml或者是注解方式进行配置

    在方法RequestMapping上的注解上的需要有"/开头",网页的请求url不需要"/"开头,但是需要.do结尾

    由于我们只实现了四种类型的封装,所以Controller类中里面的方法参数只能是四种类型,request、response、session、一个JavaBean类

    代码

    DispatchServlet

    package mvc;
    
    import com.google.gson.Gson;
    import org.apache.commons.beanutils.BeanUtils;
    import org.apache.commons.lang3.ClassUtils;
    import org.apache.commons.lang3.StringUtils;
    import org.apache.commons.lang3.reflect.MethodUtils;
    import org.apache.log4j.Logger;
    
    import java.io.IOException;
    import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Method;
    import java.util.Map;
    
    import javax.servlet.ServletConfig;
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import javax.servlet.http.HttpSession;
    
    /**
     * @author StarsOne
     * @date Create in  2019/8/9 0009 10:11
     * @description
     */
    public class DispatcherServlet extends HttpServlet {
        private Map<String, ClassMapping> classMappingMap =null;
        private Logger logger = Logger.getLogger(DispatcherServlet.class);
        @Override
        public void init(ServletConfig config) throws ServletException {
            super.init(config);
            //在servlet的初始化获得map数据
            classMappingMap = new Configuration().config();
        }
    
        @Override
        protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            doPost(req, resp);
        }
    
        @Override
        protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    
            //获得url地址
            String servletPath = req.getServletPath();
            String requestUrl = StringUtils.substringBefore(servletPath,".do");
            //根据url地址去和map中的匹配,找到对应的controller类以及方法,之后反射传参调用
    
            if (classMappingMap != null && classMappingMap.containsKey(requestUrl)) {
                ClassMapping classMapping = classMappingMap.get(requestUrl);
                Class<?> controllerClass = classMapping.getControllerClass();
                Method method = classMapping.getMethod();
    
                //获得方法的参数类型列表,之后根据此类型列表,对request传来的参数进行处理
                Class<?>[] parameterTypes = method.getParameterTypes();
                //存放着之后需要传入到方法的参数值
                Object[] paramValues = new Object[parameterTypes.length];
    
                //对request传来的参数进行处理,将参数传给controller中的对应的方法,调用该方法
                try {
                    //实例化controller类
                    Object o = controllerClass.newInstance();
    
                    for (int i = 0; i < parameterTypes.length; i++) {
    
                        if (ClassUtils.isAssignable(parameterTypes[i], HttpServletRequest.class)) {
                            paramValues[i] = req;
                        } else if (ClassUtils.isAssignable(parameterTypes[i], HttpServletResponse.class)) {
                            paramValues[i] = resp;
                        } else if (ClassUtils.isAssignable(parameterTypes[i], HttpSession.class)) {
                            paramValues[i] = req.getSession(true);
                        } else {
                            //转为JavaBean
                            if (parameterTypes[i] != null) {
                                //获得request传来的参数值,转为javabean
                                Map<String, String[]> parameterMap = req.getParameterMap();
    
                                //实例化这个JavaBean类型
                                Object bean = parameterTypes[i].newInstance();
                                //把数值快速转为bean
                                BeanUtils.populate(bean, parameterMap);
                                paramValues[i] =bean;
                            }
                        }
                    }
    
                    //调用方法,获得返回值,根据返回值,执行页面跳转或者是返回json数据等操作
                    Object returnValue = MethodUtils.invokeMethod(o, true,method.getName(), paramValues);
    
                    if (returnValue instanceof String) {
                        String value = (String) returnValue;
    
                        if (value.startsWith("redirect:")) {
                            //重定向
                            resp.sendRedirect(req.getContextPath()+ StringUtils.substringAfter(value,"redirect:"));
                        } else {
                            //请求转发
                            req.getRequestDispatcher(value).forward(req,resp);
                        }
                    } else {
                        //返回一个对象
                        if (returnValue != null) {
                            //转为json,ajax操作
                            String json = new Gson().toJson(o);
                            resp.getWriter().print(json);
                        }
                    }
                } catch (InstantiationException e) {
                    logger.error("实例化Controller对象错误!");
                } catch (IllegalAccessException e) {
                    logger.error("非法访问方法!");
                } catch (InvocationTargetException e) {
                    logger.error("invocation target exception");
                } catch (NoSuchMethodException e) {
                    logger.error("调用的方法不存在!");
                }
    
            } else {
                throw new RuntimeException("url不存在" + requestUrl);
            }
        }
    }
    

    ClassMapping

    用来存放全类名和方法名

    package mvc;
    
    import java.lang.reflect.Method;
    
    /**
     * 类Class和对应的方法Method
     * @author StarsOne
     * @date Create in  2019/8/8 0008 22:13
     * @description
     */
    public class ClassMapping {
        private Class<?> controllerClass;
        private Method method;
    
        public ClassMapping(Class<?> controllerClass, Method method) {
            this.controllerClass = controllerClass;
            this.method = method;
        }
    
        public Class<?> getControllerClass() {
            return controllerClass;
        }
    
        public void setControllerClass(Class<?> controllerClass) {
            this.controllerClass = controllerClass;
        }
    
        public Method getMethod() {
            return method;
        }
    
        public void setMethod(Method method) {
            this.method = method;
        }
    
        @Override
        public String toString() {
            return controllerClass.getName() + "." + method.getName();
        }
    }
    

    Configuration

    package mvc;
    
    import org.apache.commons.lang3.ClassUtils;
    import org.apache.commons.lang3.StringUtils;
    import org.apache.commons.lang3.reflect.MethodUtils;
    import org.apache.log4j.Logger;
    
    import java.io.File;
    import java.lang.reflect.Method;
    import java.net.URISyntaxException;
    import java.util.Collections;
    import java.util.HashMap;
    import java.util.Map;
    import java.util.ResourceBundle;
    
    import mvc.Annotation.Controller;
    import mvc.Annotation.RequestMapping;
    
    /**
     * @author StarsOne
     * @date Create in  2019/8/8 0008 22:11
     * @description
     */
    public class Configuration {
        Logger logger = Logger.getLogger(Configuration.class);
    
        private String getControllerPackage() {
            return ResourceBundle.getBundle("package").getString("packagePath");
        }
        public Map<String, ClassMapping> config() {
            Map<String, ClassMapping> classMappingMap = Collections.synchronizedMap(new HashMap<>());
    
            try {
                //根据资源文件中定义的包名,找到控制器的文件夹,得到类名
                File file = new File(getClass().getResource("/").toURI());
                String controllerPackage = getControllerPackage();
                String controllerFullPath = file.getPath() + File.separator +controllerPackage.replaceAll("\.",File.separator);
                //controller类所在的文件夹
                file = new File(controllerFullPath);
                //过滤文件,只找class文件
                String[] classNames = file.list((dir, name) -> name.endsWith(".class"));
    
                for (String className : classNames) {
                    //拼接全类名
                    String controllerFullName = controllerPackage + "." + StringUtils.substringBefore(className,".class");
    
                    Class controllerClass = ClassUtils.getClass(controllerFullName);
                    //类是否有controller注解
                    if (controllerClass.isAnnotationPresent(Controller.class)) {
                        //找到controller类中标明RequestMapping注解的所有方法
                        Method[] methods = MethodUtils.getMethodsWithAnnotation(controllerClass, RequestMapping.class, true, true);
    
                        for (Method method : methods) {
                            //获得注解上的路径
                            String path = method.getAnnotation(RequestMapping.class).value();
                            //路径为key,保存
                            classMappingMap.put(path,new ClassMapping(controllerClass,method));
                        }
                    }
                }
    
            } catch (URISyntaxException | ClassNotFoundException e) {
                e.printStackTrace();
            }
            return classMappingMap;
        }
    
        public static void main(String[] args) {
            new Configuration().config();
        }
    }
    

    注解

    Controller

    package mvc.annotation;
    
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    /**
     * @author stars-one at 2019-08-09 08:50
     */
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface Controller {
        String value() default "";
    }
    

    RequestMapping

    package mvc.annotation;
    
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    /**
     * @author stars-one at 2019-08-09 08:50
     */
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface RequestMapping {
        String value() default "";
    }
  • 相关阅读:
    计算机硬件基础
    元类
    内置函数
    单例模式的三种实现方式
    字符编码
    odoo权限
    odoo api介绍
    odoo 二次开发小记不定时更新
    js与jQuery区别
    Cookie, LocalStorage 与 SessionStorage说明
  • 原文地址:https://www.cnblogs.com/chaoyang123/p/11549291.html
Copyright © 2020-2023  润新知