自己實現springmvc最關鍵的是要自己實現DispatcherServlet的方法,裡面包含 servlet的初始化 init 方法還有doGet和doPost方法。
步驟包含如下:
1.載入系統中的一些配置檔案,application.properties及web.xml當中配置的一些資訊.
2.掃描系統當中所有的類,類可以通過application.properties獲取配置,並且通過反射機制,實現初始化,並且放置到IOC容器當中去。
3.實現依賴注入,把含有@Autowired的欄位都依賴注入進其應該存在的類當中。
4.初始化HandlerMapping,就是把url和相應的Method關聯上
5.請求的派發,當請求到達伺服器的時候,需要讓不同的路徑分別對映到不同的方法上面去執行。
定義好關鍵配置application.properties掃描包路徑
scanPackage=com.myspringmvc.core
web.xml配置
為Servlet命名,並使用指定MyDispatcherServlet獲取application範圍內的引數,以便於在程式碼中直接使用對映引數,此處使用精確路徑匹配<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="/file/2020/09/15/20200915154945_10.jpg /file/2020/09/15/20200915154946_11.jpg <dependency> \t\t <groupId>javax.servlet</groupId> \t\t <artifactId>javax.servlet-api</artifactId> \t\t <version>3.0.1</version> \t\t <scope>provided</scope>\t\t</dependency> </dependencies>
<dependencies>\t <dependency> \t\t <groupId>javax.servlet</groupId> \t\t <artifactId>javax.servlet-api</artifactId> \t\t <version>3.0.1</version> \t\t <scope>provided</scope>\t\t</dependency> </dependencies>package com.myspringmvc.servlet;import java.io.File;import java.io.IOException;import java.io.InputStream;import java.lang.reflect.Method;import java.net.URL;import java.util.ArrayList;import java.util.Arrays;import java.util.HashMap;import java.util.List;import java.util.Map;import java.util.Map.Entry;import java.util.Properties;import javax.servlet.ServletConfig;import javax.servlet.ServletException;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import com.myspringmvc.annotation.MyController;import com.myspringmvc.annotation.MyRequestMapping;public class MyDispatcherServlet extends HttpServlet{\t\tprivate Properties properties = new Properties();\t\tprivate List<String> classNames = new ArrayList<>();\t\tprivate Map<String, Object> ioc = new HashMap<>();\t\tprivate Map<String, Method> handlerMapping = new HashMap<>();\t\tprivate Map<String, Object> controllerMap =new HashMap<>();\t\t@Override\tpublic void init(ServletConfig config) throws ServletException {\t\t\t\t//1.載入配置檔案\t\tdoLoadConfig(config.getInitParameter("contextConfigLocation"));\t\t\t\t//2.初始化所有相關聯的類,掃描使用者設定的包下面所有的類\t\tdoScanner(properties.getProperty("scanPackage"));\t\t\t\t//3.拿到掃描到的類,通過反射機制,例項化,並且放到ioc容器中(k-v beanName-bean) beanName預設是首字母小寫\t\tdoInstance();\t\t\t\t//4.初始化HandlerMapping(將url和method對應上)\t\tinitHandlerMapping();\t}\t\t\t@Override\tprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {\t\tthis.doPost(req,resp);\t}\t@Override\tprotected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {\t\ttry {\t\t\t//處理請求\t\t\tdoDispatch(req,resp);\t\t} catch (Exception e) {\t\t\tresp.getWriter().write("500!! Server Exception");\t\t}\t}\t\t\tprivate void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws Exception {\t\tif(handlerMapping.isEmpty()){\t\t\treturn;\t\t}\t\t\t\tString url =req.getRequestURI();\t\tString contextPath = req.getContextPath();\t\t\t\t//拼接url並把多個/替換成一個\t\turl=url.replace(contextPath, "").replaceAll("/+", "/");\t\t\t\tif(!this.handlerMapping.containsKey(url)){\t\t\tresp.getWriter().write("404 NOT FOUND!");\t\t\treturn;\t\t}\t\t\t\tMethod method =this.handlerMapping.get(url);\t\t\t\t//獲取方法的引數列表\t\tClass<?>[] parameterTypes = method.getParameterTypes();\t\t\t//獲取請求的引數\t\tMap<String, String[]> parameterMap = req.getParameterMap();\t\t\t\t//儲存引數值\t\tObject [] paramValues= new Object[parameterTypes.length];\t\t\t\t//方法的引數列表 for (int i = 0; i<parameterTypes.length; i++){ //根據引數名稱,做某些處理 String requestParam = parameterTypes[i].getSimpleName(); if (requestParam.equals("HttpServletRequest")){ //引數型別已明確,這邊強轉型別 \tparamValues[i]=req; continue; } if (requestParam.equals("HttpServletResponse")){ \tparamValues[i]=resp; continue; } if(requestParam.equals("String")){ \tfor (Entry<String, String[]> param : parameterMap.entrySet()) { \t\t\tString value =Arrays.toString(param.getValue()).replaceAll("\\\\[|\\\\]", "").replaceAll(",\\\\s", ","); \t\t\tparamValues[i]=value; \t\t} } } \t\t//利用反射機制來呼叫\t\ttry {\t\t\tmethod.invoke(this.controllerMap.get(url), paramValues);//obj是method所對應的例項 在ioc容器中\t\t} catch (Exception e) {\t\t\te.printStackTrace();\t\t}\t}\tprivate void doLoadConfig(String location){\t\t//把web.xml中的contextConfigLocation對應value值的檔案載入到留裡面\t\tInputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream(location);\t\ttry {\t\t\t//用Properties檔案載入檔案裡的內容\t\t\tproperties.load(resourceAsStream);\t\t} catch (IOException e) {\t\t\te.printStackTrace();\t\t}finally {\t\t\t//關流\t\t\tif(null!=resourceAsStream){\t\t\t\ttry {\t\t\t\t\tresourceAsStream.close();\t\t\t\t} catch (IOException e) {\t\t\t\t\te.printStackTrace();\t\t\t\t}\t\t\t}\t\t}\t\t\t}\t\tprivate void doScanner(String packageName) {\t\t//把所有的.替換成/\t\tURL url =this.getClass().getClassLoader().getResource("/"+packageName.replaceAll("\\\\.", "/"));\t\tFile dir = new File(url.getFile());\t\tfor (File file : dir.listFiles()) {\t\t\tif(file.isDirectory()){\t\t\t\t//遞迴讀取包\t\t\t\tdoScanner(packageName+"."+file.getName());\t\t\t}else{\t\t\t\tString className =packageName +"." +file.getName().replace(".class", "");\t\t\t\tclassNames.add(className);\t\t\t}\t\t}\t}\t\t\t\tprivate void doInstance() {\t\tif (classNames.isEmpty()) {\t\t\treturn;\t\t}\t\t\tfor (String className : classNames) {\t\t\ttry {\t\t\t\t//把類搞出來,反射來例項化(只有加@MyController需要例項化)\t\t\t\tClass<?> clazz =Class.forName(className);\t\t\t if(clazz.isAnnotationPresent(MyController.class)){\t\t\t\t\tioc.put(toLowerFirstWord(clazz.getSimpleName()),clazz.newInstance());\t\t\t\t}else{\t\t\t\t\tcontinue;\t\t\t\t}\t\t\t\t\t\t\t\t\t\t\t} catch (Exception e) {\t\t\t\te.printStackTrace();\t\t\t\tcontinue;\t\t\t}\t\t}\t}\tprivate void initHandlerMapping(){\t\tif(ioc.isEmpty()){\t\t\treturn;\t\t}\t\ttry {\t\t\tfor (Entry<String, Object> entry: ioc.entrySet()) {\t\t\t\tClass<? extends Object> clazz = entry.getValue().getClass();\t\t\t\tif(!clazz.isAnnotationPresent(MyController.class)){\t\t\t\t\tcontinue;\t\t\t\t}\t\t\t\t\t\t\t\t//拼url時,是controller頭的url拼上方法上的url\t\t\t\tString baseUrl ="";\t\t\t\tif(clazz.isAnnotationPresent(MyRequestMapping.class)){\t\t\t\t\tMyRequestMapping annotation = clazz.getAnnotation(MyRequestMapping.class);\t\t\t\t\tbaseUrl=annotation.value();\t\t\t\t}\t\t\t\tMethod[] methods = clazz.getMethods();\t\t\t\tfor (Method method : methods) {\t\t\t\t\tif(!method.isAnnotationPresent(MyRequestMapping.class)){\t\t\t\t\t\tcontinue;\t\t\t\t\t}\t\t\t\t\tMyRequestMapping annotation = method.getAnnotation(MyRequestMapping.class);\t\t\t\t\tString url = annotation.value();\t\t\t\t\t\t\t\t\t\turl =(baseUrl+"/"+url).replaceAll("/+", "/");\t\t\t\t\t//這裡應該放置例項和method \t\t\t\t\thandlerMapping.put(url,method);\t\t\t\t\tcontrollerMap.put(url,clazz.newInstance());\t\t\t\t\tSystem.out.println(url+","+method);\t\t\t\t}\t\t\t\t\t\t\t}\t\t\t\t\t} catch (Exception e) {\t\t\te.printStackTrace();\t\t}\t\t\t}\tprivate String toLowerFirstWord(String name){\t\tchar[] charArray = name.toCharArray();\t\tcharArray[0] += 32;\t\treturn String.valueOf(charArray);\t}\t\t\t}
自定義controller
@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface MyController { String value() default "";}
最後就是幾個註解了
表示給controller註冊別名
@Target({ElementType.TYPE,ElementType.METHOD})@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface MyRequestMapping { String value() default "";}
表示訪問該方法的url
為Servlet命名@Target({ElementType.TYPE,ElementType.METHOD})@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface MyRequestMapping { String value() default "";}
表示引數的別名,必填
@Target(ElementType.PARAMETER)@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface MyRequestParam { String value();}
打完收工