欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2022-05-18 21:13:11
仿照SpringMVC,实现一个轻量级MVC框架,知识涉及到了反射机制、注解的使用和一些第三方工具包的使用 思路 主要的总体流程如下图所示 和之前一样,我们定义了一个DispatchServlet,用于拦截请求(这里一般拦截.do结尾的url请求); 之后,DispatchServlet会根据url ......

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

思路

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

JSP学习笔记(6)—— 自定义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方法中的流程)

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

关键点实现

处理请求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 "";
}