我們從兩個方面了解springmvc執行原理,首先我們去熟悉springmvc執行的過程,然后知道原理后通過手寫springmvc去深入了解代碼中執行過程。
(一)SpringMVC流程圖
(二)SpringMVC流程
1、 用戶發送請求至前端控制器DispatcherServlet。
2、 DispatcherServlet收到請求調用HandlerMapping處理器映射器。
3、 處理器映射器找到具體的處理器(可以根據xml配置、注解進行查找),生成處理器對象及處理器攔截器(如果有則生成)一並返回給DispatcherServlet。
4、 DispatcherServlet調用HandlerAdapter處理器適配器。
5、 HandlerAdapter經過適配調用具體的處理器(Controller,也叫后端控制器)。
6、 Controller執行完成返回ModelAndView。
7、 HandlerAdapter將controller執行結果ModelAndView返回給DispatcherServlet。
8、 DispatcherServlet將ModelAndView傳給ViewReslover視圖解析器。
9、 ViewReslover解析后返回具體View。
10、DispatcherServlet根據View進行渲染視圖(即將模型數據填充至視圖中)。
11、 DispatcherServlet響應用戶。
(三)SpringMVC核心組件講解
1、前端控制器DispatcherServlet
作用:接收請求,響應結果,相當於轉發器,中央處理器。有了dispatcherServlet減少了其它組件之間的耦合度。
用戶請求到達前端控制器,它就相當於mvc模式中的c,dispatcherServlet是整個流程控制的中心,由它調用其它組件處理用戶的請求,dispatcherServlet的存在降低了組件之間的耦合性。
2、處理器映射器HandlerMapping
作用:根據請求的url查找Handler
HandlerMapping負責根據用戶請求找到Handler即處理器,springmvc提供了不同的映射器實現不同的映射方式,例如:配置文件方式,實現接口方式,注解方式等。
3、處理器適配器HandlerAdapter
作用:按照特定規則(HandlerAdapter要求的規則)去執行Handler
通過HandlerAdapter對處理器進行執行,這是適配器模式的應用,通過擴展適配器可以對更多類型的處理器進行執行。
4、處理器Handler
Handler 是繼DispatcherServlet前端控制器的后端控制器,在DispatcherServlet的控制下Handler對具體的用戶請求進行處理。
由於Handler涉及到具體的用戶業務請求,所以一般情況需要工程師根據業務需求開發Handler。
5、視圖解析器View resolver(不需要工程師開發),由框架提供
作用:進行視圖解析,根據邏輯視圖名解析成真正的視圖(view)
View Resolver負責將處理結果生成View視圖,View Resolver首先根據邏輯視圖名解析成物理視圖名即具體的頁面地址,再生成View視圖對象,最后對View進行渲染將處理結果通過頁面展示給用戶。 springmvc框架提供了很多的View視圖類型,包括:jstlView、freemarkerView、pdfView等。
一般情況下需要通過頁面標簽或頁面模版技術將模型數據通過頁面展示給用戶,需要由工程師根據業務需求開發具體的頁面。
6、視圖View
View是一個接口,實現類支持不同的View類型(jsp、freemarker、pdf...)
(四)手寫springMvc框架思路:
1、配置web.xml,加載自定義的DispatcherServlet。
2、初始化階段,在DispatcherServlet類中,實現下面幾個步驟:
-
- 加載配置類。
- 掃描當前項目下的所有文件。
- 拿到掃描到的類,通過反射機制,實例化。並且放到ioc容器中。
- 初始化path與方法的映射。
- 獲取請求傳入的參數並處理參數通過初始化好的handlerMapping中拿出url對應的方法名,反射調用。
3、運行階段,每一次請求將會調用doGet或doPost方法,它會根據url請求去HandlerMapping中匹配到對應的Method,然后利用反射機制調用Controller中的url對應的方法,並得到結果返回。
(五)代碼階段:
1.web.xm加載

1 <?xml version="1.0" encoding="UTF-8"?> 2 <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5"> 3 <display-name>maven_handmvc</display-name> 4 <servlet> 5 <servlet-name>DispatcherServlet</servlet-name> 6 <servlet-class>com.zzw.cn.springmvc.dispathcer.DispatcherServlet</servlet-class> 7 </servlet> 8 <servlet-mapping> 9 <servlet-name>DispatcherServlet</servlet-name> 10 <url-pattern>/</url-pattern> 11 </servlet-mapping> 12 </web-app>
2.自定義DispatcherServlet

1 package com.zzw.cn.springmvc.dispathcer; 2 3 import com.zzw.cn.springmvc.annoation.AnController; 4 import com.zzw.cn.springmvc.annoation.AnRequestMapping; 5 import com.zzw.cn.utils.ClassUtils; 6 7 import javax.servlet.ServletConfig; 8 import javax.servlet.ServletException; 9 import javax.servlet.http.HttpServlet; 10 import javax.servlet.http.HttpServletRequest; 11 import javax.servlet.http.HttpServletResponse; 12 import java.io.IOException; 13 import java.lang.reflect.InvocationTargetException; 14 import java.lang.reflect.Method; 15 import java.util.List; 16 import java.util.Map; 17 import java.util.concurrent.ConcurrentHashMap; 18 19 /** 20 * @author Simple 21 * @date 14:34 2019/8/26 22 * @description 手寫springmvc框架流程 23 * <p> 24 * 思路:自定義DispatcherServlet 25 * 1.包掃描獲取包下面所有的類 26 * 2.初始化包下面所有的類 27 * 3.初始化HandlerMapping方法,將url和方法對應上 28 * 4.實現HttpServlet 重寫dopost方法 29 */ 30 31 public class DispatcherServlet extends HttpServlet { 32 33 //springmvc 容器存放bean 34 private ConcurrentHashMap<String, Object> mvcBeans = new ConcurrentHashMap<>(); 35 private ConcurrentHashMap<String, Object> mvcBeanUrl = new ConcurrentHashMap<>(); 36 private ConcurrentHashMap<String, String> mvcMethodUrl = new ConcurrentHashMap<>(); 37 38 39 @Override 40 public void init(ServletConfig config) { 41 String packagePath = "com.zzw.cn.springmvc"; 42 //1.進行報掃描獲取當前包下面所有的類 43 List<Class<?>> classes = comscanPackage(packagePath); 44 try { 45 //2.初始化springmvcbean 46 initSpringMvcBean(classes); 47 } catch (Exception e) { 48 e.printStackTrace(); 49 } 50 //3.將請求地址和方法進行映射 51 initHandMapping(mvcBeans); 52 } 53 54 55 public List<Class<?>> comscanPackage(String packagePath) { 56 List<Class<?>> classes = ClassUtils.getClasses(packagePath); 57 return classes; 58 } 59 60 /** 61 * 初始化sprignbean 62 * 63 * @param classes 64 * @throws Exception 65 */ 66 public void initSpringMvcBean(List<Class<?>> classes) throws Exception { 67 if (classes.size() == 0 || null == classes) { 68 throw new Exception("包掃描后的classes為null"); 69 } 70 71 for (Class<?> aClass : classes) { 72 //獲取被自定義注解的controller將其初始化到自定義sprignmvc容器中 73 AnController declaredAnnotation = aClass.getDeclaredAnnotation(AnController.class); 74 if (declaredAnnotation != null) { 75 //獲取類的名字 76 String beanid = lowerFirstCapse(aClass.getSimpleName()); 77 //獲取對象 78 Object beanObj = aClass.newInstance(); 79 //放入sprign容器 80 mvcBeans.put(beanid, beanObj); 81 } 82 } 83 84 } 85 86 /** 87 * 初始化HandlerMapping方法 88 * 89 * @param mvcBeans 90 */ 91 public void initHandMapping(ConcurrentHashMap<String, Object> mvcBeans) { 92 //遍歷springmvc 獲取注入的對象值 93 for (Map.Entry<String, Object> entry : mvcBeans.entrySet()) { 94 Object objValue = entry.getValue(); 95 Class<?> aClass = objValue.getClass(); 96 //獲取當前類 判斷是否有自定義的requestMapping注解 97 String mappingUrl = null; 98 AnRequestMapping anRequestMapping = aClass.getDeclaredAnnotation(AnRequestMapping.class); 99 if (anRequestMapping != null) { 100 mappingUrl = anRequestMapping.value(); 101 } 102 //獲取當前類所有方法,判斷方法上是否有注解 103 Method[] declaredMethods = aClass.getDeclaredMethods(); 104 for (Method method : declaredMethods) { 105 AnRequestMapping methodDeclaredAnnotation = method.getDeclaredAnnotation(AnRequestMapping.class); 106 if (methodDeclaredAnnotation != null) { 107 String methodUrl = methodDeclaredAnnotation.value(); 108 mvcBeanUrl.put(mappingUrl + methodUrl, objValue); 109 mvcMethodUrl.put(mappingUrl + methodUrl, method.getName()); 110 } 111 } 112 113 } 114 115 } 116 117 /** 118 * @param str 119 * @return 類名首字母小寫 120 */ 121 public static String lowerFirstCapse(String str) { 122 char[] chars = str.toCharArray(); 123 chars[0] += 32; 124 return String.valueOf(chars); 125 126 } 127 128 @Override 129 protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { 130 try { 131 doServelt(req, resp); 132 } catch (NoSuchMethodException e) { 133 e.printStackTrace(); 134 } catch (InvocationTargetException e) { 135 e.printStackTrace(); 136 } catch (IllegalAccessException e) { 137 e.printStackTrace(); 138 } 139 } 140 141 private void doServelt(HttpServletRequest req, HttpServletResponse resp) throws IOException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, ServletException { 142 //獲取請求地址 143 String requestUrl = req.getRequestURI(); 144 //查找地址所對應bean 145 Object object = mvcBeanUrl.get(requestUrl); 146 if (object == null) { 147 resp.getWriter().println("sorry http is not exit 404"); 148 return; 149 } 150 //獲取請求的方法 151 String methodName = mvcMethodUrl.get(requestUrl); 152 if (methodName == null) { 153 resp.getWriter().println("sorry method is not exit 404"); 154 return; 155 } 156 //通過構反射執行方法 157 Class<?> aClass = object.getClass(); 158 Method method = aClass.getMethod(methodName); 159 String invoke = (String) method.invoke(object); 160 // 獲取后綴信息 161 String suffix = ".jsp"; 162 // 頁面目錄地址 163 String prefix = "/"; 164 req.getRequestDispatcher(prefix + invoke + suffix).forward(req, resp); 165 } 166 167 @Override 168 protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { 169 this.doPost(req, resp); 170 } 171 172 173 }
3.ClassUtils工具類封裝

1 package com.zzw.cn.utils; 2 3 import java.io.File; 4 import java.io.FileFilter; 5 import java.io.IOException; 6 import java.net.JarURLConnection; 7 import java.net.URL; 8 import java.net.URLDecoder; 9 import java.util.ArrayList; 10 import java.util.Enumeration; 11 import java.util.List; 12 import java.util.jar.JarEntry; 13 import java.util.jar.JarFile; 14 15 public class ClassUtils { 16 17 /** 18 * 從包package中獲取所有的Class 19 * 20 * @param pack 21 * @return 22 */ 23 public static List<Class<?>> getClasses(String packageName) { 24 25 // 第一個class類的集合 26 List<Class<?>> classes = new ArrayList<Class<?>>(); 27 // 是否循環迭代 28 boolean recursive = true; 29 // 獲取包的名字 並進行替換 30 String packageDirName = packageName.replace('.', '/'); 31 // 定義一個枚舉的集合 並進行循環來處理這個目錄下的things 32 Enumeration<URL> dirs; 33 try { 34 dirs = Thread.currentThread().getContextClassLoader().getResources(packageDirName); 35 // 循環迭代下去 36 while (dirs.hasMoreElements()) { 37 // 獲取下一個元素 38 URL url = dirs.nextElement(); 39 // 得到協議的名稱 40 String protocol = url.getProtocol(); 41 // 如果是以文件的形式保存在服務器上 42 if ("file".equals(protocol)) { 43 // 獲取包的物理路徑 44 String filePath = URLDecoder.decode(url.getFile(), "UTF-8"); 45 // 以文件的方式掃描整個包下的文件 並添加到集合中 46 findAndAddClassesInPackageByFile(packageName, filePath, recursive, classes); 47 } else if ("jar".equals(protocol)) { 48 // 如果是jar包文件 49 // 定義一個JarFile 50 JarFile jar; 51 try { 52 // 獲取jar 53 jar = ((JarURLConnection) url.openConnection()).getJarFile(); 54 // 從此jar包 得到一個枚舉類 55 Enumeration<JarEntry> entries = jar.entries(); 56 // 同樣的進行循環迭代 57 while (entries.hasMoreElements()) { 58 // 獲取jar里的一個實體 可以是目錄 和一些jar包里的其他文件 如META-INF等文件 59 JarEntry entry = entries.nextElement(); 60 String name = entry.getName(); 61 // 如果是以/開頭的 62 if (name.charAt(0) == '/') { 63 // 獲取后面的字符串 64 name = name.substring(1); 65 } 66 // 如果前半部分和定義的包名相同 67 if (name.startsWith(packageDirName)) { 68 int idx = name.lastIndexOf('/'); 69 // 如果以"/"結尾 是一個包 70 if (idx != -1) { 71 // 獲取包名 把"/"替換成"." 72 packageName = name.substring(0, idx).replace('/', '.'); 73 } 74 // 如果可以迭代下去 並且是一個包 75 if ((idx != -1) || recursive) { 76 // 如果是一個.class文件 而且不是目錄 77 if (name.endsWith(".class") && !entry.isDirectory()) { 78 // 去掉后面的".class" 獲取真正的類名 79 String className = name.substring(packageName.length() + 1, name.length() - 6); 80 try { 81 // 添加到classes 82 classes.add(Class.forName(packageName + '.' + className)); 83 } catch (ClassNotFoundException e) { 84 e.printStackTrace(); 85 } 86 } 87 } 88 } 89 } 90 } catch (IOException e) { 91 e.printStackTrace(); 92 } 93 } 94 } 95 } catch (IOException e) { 96 e.printStackTrace(); 97 } 98 99 return classes; 100 } 101 102 /** 103 * 以文件的形式來獲取包下的所有Class 104 * 105 * @param packageName 106 * @param packagePath 107 * @param recursive 108 * @param classes 109 */ 110 public static void findAndAddClassesInPackageByFile(String packageName, String packagePath, final boolean recursive, 111 List<Class<?>> classes) { 112 // 獲取此包的目錄 建立一個File 113 File dir = new File(packagePath); 114 // 如果不存在或者 也不是目錄就直接返回 115 if (!dir.exists() || !dir.isDirectory()) { 116 return; 117 } 118 // 如果存在 就獲取包下的所有文件 包括目錄 119 File[] dirfiles = dir.listFiles(new FileFilter() { 120 // 自定義過濾規則 如果可以循環(包含子目錄) 或則是以.class結尾的文件(編譯好的java類文件) 121 @Override 122 public boolean accept(File file) { 123 return (recursive && file.isDirectory()) || (file.getName().endsWith(".class")); 124 } 125 }); 126 // 循環所有文件 127 for (File file : dirfiles) { 128 // 如果是目錄 則繼續掃描 129 if (file.isDirectory()) { 130 findAndAddClassesInPackageByFile(packageName + "." + file.getName(), file.getAbsolutePath(), recursive, 131 classes); 132 } else { 133 // 如果是java類文件 去掉后面的.class 只留下類名 134 String className = file.getName().substring(0, file.getName().length() - 6); 135 try { 136 // 添加到集合中去 137 classes.add(Class.forName(packageName + '.' + className)); 138 } catch (ClassNotFoundException e) { 139 e.printStackTrace(); 140 } 141 } 142 } 143 } 144 }
4.自定義注解類AnController

1 package com.zzw.cn.springmvc.annoation; 2 3 import java.lang.annotation.ElementType; 4 import java.lang.annotation.Retention; 5 import java.lang.annotation.RetentionPolicy; 6 import java.lang.annotation.Target; 7 8 /** 9 * @author Simple 10 * @date 14:06 2019/8/27 11 * @description 12 */ 13 @Target({ElementType.TYPE,ElementType.METHOD}) 14 @Retention(RetentionPolicy.RUNTIME) 15 public @interface AnController { 16 }
5.自定義注解類AnRequestMapping

1 package com.zzw.cn.springmvc.annoation; 2 3 import java.lang.annotation.*; 4 5 /** 6 * @author Simple 7 * @date 14:07 2019/8/27 8 * @description 9 */ 10 @Target({ElementType.METHOD, ElementType.TYPE}) 11 @Retention(RetentionPolicy.RUNTIME) 12 @Documented 13 public @interface AnRequestMapping { 14 String value() default ""; 15 }
6.HelloWorld類

1 package com.zzw.cn.springmvc.controller; 2 3 import com.zzw.cn.springmvc.annoation.AnController; 4 import com.zzw.cn.springmvc.annoation.AnRequestMapping; 5 6 /** 7 * @author Simple 8 * @date 15:15 2019/8/27 9 * @description 10 */ 11 @AnController 12 @AnRequestMapping(value = "/hello") 13 public class HelloWorld { 14 @AnRequestMapping("/method") 15 public String method(){ 16 return "index"; 17 } 18 }
7.index.jsp

1 <html> 2 <body> 3 <h2>Hello World!</h2> 4 </body> 5 </html>
8.訪問地址:http://localhost:8080/hello/method
9.成功結果:
10.錯誤結果
現在代碼已經完成了,也許開始你對很多東西不理解,但是沒關系可以當作了解,多敲敲。