手寫源碼(三):本身實現SpringMVC

手寫SpringMVC

SpringMVC原理

  • SpringMVC基於Servlet實現的(單例的,Servlet會產生線程安全問題)
    SpringMVC的請求流程

實現步驟

  • 建立DispatcherServlet攔截全部的請求
  • 初始化,重寫Servlet的init方法
    • 掃包,獲取須要注入到SpringMVC容器中的類(如@Controller註解`)
    • 把URL和方法經行關聯:利用反射機制找到Controller類上的方法是否存在@RequestMapping註解,存在就把方法名稱存起來,把URL和方法對應起來
  • 處理請求 重寫GET和POST方法
    • 獲取請求URL,去URLBean裏獲取實例,再去URLMethod裏獲取方法實例,使用反射執行方法

Maven依賴

<dependencies>
        <!--servlet依賴-->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.1.0</version>
            <scope>provided</scope>
        </dependency>
        <!--各類工具-->
        <dependency>
            <groupId>commons-lang</groupId>
            <artifactId>commons-lang</artifactId>
            <version>2.5</version>
        </dependency>
    </dependencies>
複製代碼

定義本身的註解@ExtController@ExtRequestMapping,定義一個本身的Controller

註解以下html

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ExtController {
}
複製代碼
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface ExtRequestMapping {
    String value() default "";
}
複製代碼

本身定義的controller以下java

@ExtController
@ExtRequestMapping("/")
public class IndexController {

    @ExtRequestMapping
    public String getIndex() {
        System.out.println("自定義的MVC框架");
        return "index";
    }
}
複製代碼

重點!構建DispatcherServlet

因爲是個Web項目,咱們在Web.xml裏面配置好本身的DispatcherServlet的映射web

<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0">
    <servlet>
        <servlet-name>disaptcher</servlet-name>
        <servlet-class>com.libi.mvc.servlet.ExtDispatcherServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>disaptcher</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
</web-app>
複製代碼

第一步、在init方法裏掃包,判斷是否有Controller註解,再把URL和映射經行關聯

  • 在DispatcherServlet裏定義三個Bean容器
/**mvc的Bean容器,存入Controller對象*/
    private ConcurrentHashMap<String, Object> controllerBeans = new ConcurrentHashMap<String, Object>();
    /**mvc的請求地址,把Url和Controller創建映射*/
    private ConcurrentHashMap<String, Object> urlBeans = new ConcurrentHashMap<String, Object>();
    /**mvc的請求方法名稱,把Url和方法名創建映射*/
    private ConcurrentHashMap<String, Object> urlMethods = new ConcurrentHashMap<String, Object>();
複製代碼
  • 在重寫了Servlet的init方法裏掃描指定包下面全部帶有@ExtController註解的類,實例化後放在mvc的bean容器裏
public void init() throws ServletException {
        //掃描Controller包下面的類,而且找到全部帶@Controller的註解倒到Bean容器裏
        List<Class<?>> classes = ClassUtils.getClasses("com.controller");
        try {
            findClassMvcAnnoation(classes);
        } catch (Exception e) {
            e.printStackTrace();
        } 
    }
    
    /**找到帶有Controller註解的類而後初始化放入bean容器裏*/
     private void findClassMvcAnnoation(List<Class<?>> classes) throws IllegalAccessException, InstantiationException {
         for (Class<?> classInfo : classes) {
             ExtController extController = classInfo.getAnnotation(ExtController.class);
             if (extController != null) {
                 String beanId = ClassUtils.toLowerCaseFirstOne(classInfo.getSimpleName());
                 mvcBeans.put(beanId, classInfo.newInstance());
             }
         }
     }
複製代碼

第二步 把method和url進行映射

  • 利用反射機制獲取有@RequestMapping的類和方法,把URL和方法經行關聯,放在Map裏
/**將URL映射和方法關聯起來*/
    private void handleMapping() {
        //遍歷Controller,判斷類上是否有RequestMapping註解
        for (Map.Entry<String, Object> entry : controllerBeans.entrySet()) {
            Object controller = entry.getValue();
            //判斷類是否加上了@RequestMapping註解
            Class<?> classInfo = controller.getClass();
            ExtRequestMapping classRequestMapping = classInfo.getDeclaredAnnotation(ExtRequestMapping.class);
            String baseUrl = "";
            if (classRequestMapping != null) {
                //獲取Url映射的地址
                baseUrl = classRequestMapping.value();
            }
            //判斷方法上是否加上了RequestMapping
            Method[] methods = classInfo.getDeclaredMethods();
            for (Method method : methods) {
                //獲取方法上的映射
                ExtRequestMapping methodRequestMapping = method.getDeclaredAnnotation(ExtRequestMapping.class);
                if (methodRequestMapping != null) {
                    String methodUrl = baseUrl + methodRequestMapping.value();
                    //裝入映射表
                    System.out.println("put url:"+methodUrl);
                    urlBeans.put(methodUrl, controller);
                    urlMethods.put(methodUrl, method);
                }
            }
        }
    }
複製代碼

上面這一步在init()方法裏,建立完controller實例以後執行api

第三步 重寫doPost方法

  • 利用反射機制獲取到須要的方法,利用反射機制執行存在Map裏的方法
/**處理請求*/
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //獲取請求url地址
        String url = req.getRequestURI();
        System.out.println("url:"+url);
        //從map集合中獲取controller
        Object controller = urlBeans.get(url);
        if (controller == null) {
            //到這裏說明頁面不存在
            resp.getWriter().println("404 not find url");
            return;
        }
        //從map中獲取方法,調用而且獲取返回結果
        Method method = urlMethods.get(url);
        if (method == null) {
            //到這裏說明頁面不存在
            resp.getWriter().println("404 not find method");
            return;
        }
        String result = (String) methodInvoke(method, controller);
        resp.getWriter().println(result);
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req,resp);
    }

    private Object methodInvoke(Method method, Object controller) {
        Object result = null;
        try {
            result = method.invoke(controller);
        } catch (IllegalAccessException | InvocationTargetException e) {
            e.printStackTrace();
        }
        return result;
    }
複製代碼

寫到這裏的效果以下 安全

訪問正常的效果

訪問404的效果

第四步 配置視圖解析器

/**視圖解析器*/
    private void extResourceViewResolver(String pageName, HttpServletRequest request,HttpServletResponse response) throws ServletException, IOException {
        String prefix = "/WEB-INF/view/";
        String suffix = ".jsp";
        request.getRequestDispatcher(prefix+pageName+suffix).forward(request,response);
    }
複製代碼

以上代碼在doGet裏執行bash

/**處理請求*/
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //獲取請求url地址
        String url = req.getRequestURI();
        System.out.println("url:"+url);
        //從map集合中獲取controller
        Object controller = urlBeans.get(url);
        if (controller == null) {
            //到這裏說明頁面不存在
            resp.getWriter().println("404 not find url");
            return;
        }
        //從map中獲取方法,調用而且獲取返回結果
        Method method = urlMethods.get(url);
        if (method == null) {
            //到這裏說明頁面不存在
            resp.getWriter().println("404 not find method");
            return;
        }
        String result = (String) methodInvoke(method, controller);
        //-----------------使用視圖轉換器渲染頁面
        extResourceViewResolver(result,req,resp);
        //------------------
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req,resp);
    }
複製代碼

/WEB-INF/view/index.jsp內容以下mvc

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>index</title>
</head>
<body>
<h1>本身手寫mvc——index.jsp</h1>
</body>
</html>
複製代碼

執行後效果以下 app

使用視圖解析器的效果
相關文章
相關標籤/搜索