/* 2 功能:生成博客目录的JS工具 3 测试:IE8,火狐,google测试通过 6 */ 7 var BlogDirectory = { 8 /* 9 获取元素位置,距浏览器左边界的距离(left)和距浏览器上边界的距离(top) 10 */ 11 getElementPosition:function (ele) { 12 var topPosition = 0; 13 var leftPosition = 0; 14 while (ele){ 15 topPosition += ele.offsetTop; 16 leftPosition += ele.offsetLeft; 17 ele = ele.offsetParent; 18 } 19 return {top:topPosition, left:leftPosition}; 20 }, 21 22 /* 23 获取滚动条当前位置 24 */ 25 getScrollBarPosition:function () { 26 var scrollBarPosition = document.body.scrollTop || document.documentElement.scrollTop; 27 return scrollBarPosition; 28 }, 29 30 /* 31 移动滚动条,finalPos 为目的位置,internal 为移动速度 32 */ 33 moveScrollBar:function(finalpos, interval) { 34 35 //若不支持此方法,则退出 36 if(!window.scrollTo) { 37 return false; 38 } 39 40 //窗体滚动时,禁用鼠标滚轮 41 window.onmousewheel = function(){ 42 return false; 43 }; 44 45 //清除计时 46 if (document.body.movement) { 47 clearTimeout(document.body.movement); 48 } 49 50 var currentpos =BlogDirectory.getScrollBarPosition();//获取滚动条当前位置 51 52 var dist = 0; 53 if (currentpos == finalpos) {//到达预定位置,则解禁鼠标滚轮,并退出 54 window.onmousewheel = function(){ 55 return true; 56 } 57 return true; 58 } 59 if (currentpos < finalpos) {//未到达,则计算下一步所要移动的距离 60 dist = Math.ceil((finalpos - currentpos)/10); 61 currentpos += dist; 62 } 63 if (currentpos > finalpos) { 64 dist = Math.ceil((currentpos - finalpos)/10); 65 currentpos -= dist; 66 } 67 68 var scrTop = BlogDirectory.getScrollBarPosition();//获取滚动条当前位置 69 window.scrollTo(0, currentpos);//移动窗口 70 if(BlogDirectory.getScrollBarPosition() == scrTop)//若已到底部,则解禁鼠标滚轮,并退出 71 { 72 window.onmousewheel = function(){ 73 return true; 74 } 75 return true; 76 } 77 78 //进行下一步移动 79 var repeat = "BlogDirectory.moveScrollBar(" + finalpos + "," + interval + ")"; 80 document.body.movement = setTimeout(repeat, interval); 81 }, 82 83 htmlDecode:function (text){ 84 var temp = document.createElement("div"); 85 temp.innerHTML = text; 86 var output = temp.innerText || temp.textContent; 87 temp = null; 88 return output; 89 }, 90 91 /* 92 创建博客目录, 93 id表示包含博文正文的 div 容器的 id, 94 mt 和 st 分别表示主标题和次级标题的标签名称(如 H2、H3,大写或小写都可以!), 95 interval 表示移动的速度 96 */ 97 createBlogDirectory:function (id, mt, st, interval){ 98 //获取博文正文div容器 99 var elem = document.getElementById(id); 100 if(!elem) return false; 101 //获取div中所有元素结点 102 var nodes = elem.getElementsByTagName("*"); 103 //创建博客目录的div容器 104 var divSideBar = document.createElement('DIV'); 105 divSideBar.className = 'sideBar'; 106 divSideBar.setAttribute('id', 'sideBar'); 107 var divSideBarTab = document.createElement('DIV'); 108 divSideBarTab.setAttribute('id', 'sideBarTab'); 109 divSideBar.appendChild(divSideBarTab); 110 var h2 = document.createElement('H2'); 111 divSideBarTab.appendChild(h2); 112 var txt = document.createTextNode('目录导航'); 113 h2.appendChild(txt); 114 var divSideBarContents = document.createElement('DIV'); 115 divSideBarContents.style.display = 'none'; 116 divSideBarContents.setAttribute('id', 'sideBarContents'); 117 divSideBar.appendChild(divSideBarContents); 118 //创建自定义列表 119 var dlist = document.createElement("dl"); 120 divSideBarContents.appendChild(dlist); 121 var num = 0;//统计找到的mt和st 122 mt = mt.toUpperCase();//转化成大写 123 st = st.toUpperCase();//转化成大写 124 //遍历所有元素结点 125 for(var i=0; i

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 "";
}
posted @ 2019-09-19 14:22  我的人生  阅读(132)  评论(0编辑  收藏  举报