JSP學習筆記(6)—— 自定義MVC框架

仿照SpringMVC,實現一個輕量級MVC框架,知識涉及到了反射機制、註解的使用和一些第三方工具包的使用java

思路

主要的整體流程以下圖所示web

和以前同樣,咱們定義了一個DispatchServlet,用於攔截請求(這裏通常攔截.do結尾的url請求);ajax

以後,DispatchServlet會根據url,找到Controller中對應的方法並執行,返回一個結果。apache

咱們根據返回的結果,來DispatchServlet執行不一樣的操做(請求轉發、頁面重定向、返回json數據)json

看完這裏,整體的思路應該很明確了,問題來了:數組

  1. 如何實現讓DispatchServlet根據url去找到對應的方法?
  2. 如何根據返回的結果,讓DispatchServlet執行不一樣的操做?

對於第一個問題,咱們可使用註解來實現session

  1. 定義一個Controller註解,用來標記Controller類
  2. 定義一個RequestMapping註解,用來標記url匹配的方法

對於第二個問題,咱們能夠設置這樣的一套規則:mvc

  1. 返回的結果是String,且以redirect:開頭,則代表DispatchServlet要執行頁面重定向操做
  2. 返回的結果是String,不以redirect:開頭,則代表DispatchServlet要執行請求轉發操做
  3. 返回的結果是一個bean類,則自動將其轉爲json數據

通常咱們會讓doGet方法也調用doPost方法,因此咱們只須要重寫Servlet中的doPost方法app

由上面的思路,咱們能夠獲得細化的流程圖(也就是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 "";
}
相關文章
相關標籤/搜索