1、SpringMVC的理解
1)談談對Spring MVC的了解
MVC 是模型(Model)、視圖(View)、控制器(Controller)的簡寫,其核心思想是通過將業務邏輯、數據、顯示分離來組織代碼。
-
Model:數據模型,JavaBean的類,用來進行數據封裝。
-
View:指JSP、HTML用來展示數據給用戶
-
Controller:用來接收用戶的請求,整個流程的控制器。用來進行數據校驗等
2)Spring MVC的核心組件
DispatcherServlet
:核心的中央處理器,負責接收請求、分發,并給予客戶端響應。
HandlerMapping
:處理器映射器,根據 URL 去匹配查找能處理的 Handler
,并會將請求涉及到的攔截器和 Handler
一起封裝。
HandlerAdapter
:處理器適配器,根據 HandlerMapping
找到的 Handler
,適配執行對應的 Handler
;
Handler
:請求處理器,處理實際請求的處理器。
ViewResolver
:視圖解析器,根據 Handler
返回的邏輯視圖 / 視圖,解析并渲染真正的視圖,并傳遞給 DispatcherServlet
響應客戶端
3)Spring MVC的工作流程
Spring MVC的工作流程如下:
-
用戶發送請求至前端控制器DispatcherServlet
-
DispatcherServlet收到請求調用處理器映射器HandlerMapping。
-
處理器映射器根據請求url找到具體的處理器,生成處理器執行鏈HandlerExecutionChain(包括處理器對象和處理器攔截器)一并返回給DispatcherServlet。
-
DispatcherServlet根據處理器Handler獲取處理器適配器HandlerAdapter執行HandlerAdapter處理一系列的操作,如:參數封裝,數據格式轉換,數據驗證等操作
-
執行處理器Handler(Controller,也叫頁面控制器)。
-
Handler執行完成返回ModelAndView
-
HandlerAdapter將Handler執行結果ModelAndView返回到DispatcherServlet
-
DispatcherServlet將ModelAndView傳給ViewReslover視圖解析器
-
ViewReslover解析后返回具體View
-
DispatcherServlet對View進行渲染視圖(即將模型數據model填充至視圖中)。
-
DispatcherServlet響應用戶。
2、代碼實現
1)測試代碼
import com.heaboy.mvc.XxhhMvc;public class Main {static {String path = Main.class.getResource("").getPath();String packageName = Main.class.getPackage().getName();XxhhMvc.scanner(path,packageName);}public static void main(String[] args) {XxhhMvc.exec("","");XxhhMvc.exec("test","index1");XxhhMvc.exec("test","");XxhhMvc.exec("test","asdfasdfasdf");XxhhMvc.exec("test","");}
}
輸出結果:
index -> index
test->index1
test->index
沒有這個方法 404
test->index
2)定義注解
import java.lang.annotation.*;/*** controller聲明*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @ interface Controller {
}
@Documented
:- 這個元注解表明
@Controller
注解應該被javadoc或類似的工具記錄。也就是說,當你在編寫Java文檔時,@Controller
注解的信息會被包含在生成的文檔中。這對于理解代碼中的注解用途非常有幫助。
- 這個元注解表明
@Retention(RetentionPolicy.RUNTIME)
:- 這個元注解指定了
@Controller
注解的保留策略。RetentionPolicy.RUNTIME
意味著這個注解在運行時仍然保留,因此它可以通過反射(Reflection)被讀取。這對于那些需要在運行時通過注解來獲取信息或行為的框架(如Spring MVC)來說是非常重要的。
- 這個元注解指定了
@Target(ElementType.TYPE)
:- 這個元注解指定了
@Controller
注解可以應用的Java元素類型。ElementType.TYPE
表明這個注解只能用于類、接口(包括注解類型)或枚舉聲明上。這意味著你不能將這個注解用于方法、字段等其他元素上。
- 這個元注解指定了
public @interface Controller
:- 這行聲明了
@Controller
是一個注解(Annotation),并且它是公開的(public),意味著它可以被任何其他類訪問。@interface
關鍵字用于聲明注解類型,與聲明接口(interface)類似,但注解(Annotation)不包含方法實現。
- 這行聲明了
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE,ElementType.METHOD})
public @interface RequestMapping {/**** @return*/String value() default "";
}
@Documented
:- 如前所述,這個元注解表明?
@RequestMapping
?注解應該被 javadoc 或類似的工具記錄。這有助于在生成的文檔中包含注解的信息,從而幫助開發者理解代碼中的注解用途。
- 如前所述,這個元注解表明?
@Retention(RetentionPolicy.RUNTIME)
:- 這個元注解指定了?
@RequestMapping
?注解的保留策略為?RUNTIME
,意味著這個注解在運行時仍然保留,因此它可以通過反射被讀取。這對于 Spring MVC 框架在運行時解析注解信息并映射請求到相應的處理器方法至關重要。
- 這個元注解指定了?
@Target({ElementType.TYPE, ElementType.METHOD})
:- 這個元注解指定了?
@RequestMapping
?注解可以應用的 Java 元素類型。在這個例子中,它指定了注解可以應用于類(TYPE
)和方法(METHOD
)上。這允許開發者在類級別上定義基礎的請求路徑,然后在方法級別上進一步細化這個路徑。
- 這個元注解指定了?
public @interface RequestMapping
:- 這行聲明了?
@RequestMapping
?是一個公開的注解類型。
- 這行聲明了?
String value() default "";
:- 這是?
@RequestMapping
?注解的一個屬性(也稱為元素)。它定義了請求的 URL 路徑模式。value
?是這個屬性的名稱,而?default ""
?表示如果在使用注解時沒有指定?value
?屬性,它將默認為空字符串。但是,在 Spring MVC 中,更常見的做法是使用?path
?屬性(盡管?value
?和?path
?在?@RequestMapping
?中是等價的,可以互換使用)。
- 這是?
3)SpringMVC核心類
第一步:掃描并注冊MVC組件
static {String path = Main.class.getResource("").getPath();String packageName = Main.class.getPackage().getName();SpringMvc.scanner(path,packageName);}
首先獲取當前類的路徑和包名,然后根據當前類路徑和包名進行SpringMvc組件掃描
SpringMvc首先定義兩個全局HashMap如下:
private static HashMap<String, Map<String,Method>> map=new HashMap<>();
//用來存儲類上的@RequestMapping的值->(方法上的@RequestMapping的值->對應的方法)private static HashMap<String, Object> objMap=new HashMap<>();
//用來存儲類上的@RequestMapping的值->對應類的實例
SpringMvc類的scanner方法如下:?
public static void scanner(String path,String packageName){List<String> paths = traverseFolder2(path);for (String p : paths) {p=p.substring(path.length()-1); //得到文件名try {String className=packageName+"."+p.replaceAll( Matcher.quoteReplacement(File.separator),"."); //得到包名加文件名String replace = className.replace(".class", ""); //得到去掉.class后綴的包全限定名Class<?> cl = ClassLoader.getSystemClassLoader().loadClass(replace); //獲取類的class對象if(isController(cl)){if(isRequestMapping(cl)){RequestMapping requestMapping = getRequestMapping(cl);if(map.containsKey(requestMapping.value())){throw new RuntimeException("類多注解值:"+requestMapping.value());}else {map.put(requestMapping.value(),new HashMap<>());objMap.put(requestMapping.value(),cl.newInstance());}Method[] declaredMethods = cl.getDeclaredMethods();for (Method declaredMethod : declaredMethods) {if(isRequestMapping(declaredMethod)){RequestMapping mapping = getRequestMapping(declaredMethod);if(map.get(requestMapping.value()).containsKey(mapping.value())){throw new RuntimeException("方法多注解值:"+requestMapping.value());}else {map.get(requestMapping.value()).put(mapping.value(),declaredMethod);}}}}else {throw new RuntimeException("類無requestMapping");}}} catch (ClassNotFoundException e) {e.printStackTrace();} catch (IllegalAccessException e) {e.printStackTrace();} catch (InstantiationException e) {e.printStackTrace();}}}
首先根據路徑遍歷文件夾,找到所有的class文件,將類文件路徑列表放到paths中
private static List<String> traverseFolder2(String path) {File file = new File(path);List<String> classFiles=new ArrayList<>();if (file.exists()) {LinkedList<File> list = new LinkedList<File>();File[] files = file.listFiles();for (File file2 : files) {if (file2.isDirectory()) {list.add(file2);} else {classFiles.add(file2.getAbsolutePath());}}File temp_file;while (!list.isEmpty()) {temp_file = list.removeFirst();files = temp_file.listFiles();for (File file2 : files) {if (file2.isDirectory()) {list.add(file2);} else {classFiles.add(file2.getAbsolutePath());}}}} else {}return classFiles;}
然后處理每個類文件路徑:對于paths
列表中的每個路徑p
,代碼執行以下操作:
-
提取文件名,得到xxx.class
-
構造全限定類名:通過將包名
packageName
、文件分隔符替換為點(.
),以及去除.class
后綴,來構造類的全限定名。 -
加載類:使用
ClassLoader.getSystemClassLoader().loadClass(replace);
加載類。 -
檢查是否為控制器:
isController(cl)
方法檢查該類是否是一個控制器(即是否有@Controller注解)。
private static boolean isController(Class cl){Annotation annotation = cl.getAnnotation(Controller.class);if(annotation!=null){return true;}return false;}
-
檢查類上是否有
@RequestMapping
注解:isRequestMapping(cl)
方法。
private static boolean isRequestMapping(Class cl){Annotation annotation = cl.getAnnotation(RequestMapping.class);if(annotation!=null){return true;}return false;}
-
處理類上的
@RequestMapping
:如果類上有@RequestMapping
注解,則提取其值(通常是URL路徑模式),并檢查是否已經在映射中注冊了相同的值。如果沒有,則將該類的實例(通過cl.newInstance()
)和該類的方法映射添加到相應的?objMap映射中。 -
處理類中的方法:遍歷類的所有聲明的方法,檢查它們是否也有
@RequestMapping
注解。如果有,則將這些方法映射到它們各自的URL路徑上,在map中存儲。
private static boolean isRequestMapping(Method method){Annotation annotation = method.getAnnotation(RequestMapping.class);if(annotation!=null){return true;}return false;}
-
異常處理:如果在處理過程中發現任何問題(如類加載失敗、類沒有
@RequestMapping
注解、或者同一個URL路徑被多個類或方法注冊),則拋出RuntimeException
。
第二步:使用類路徑和方法路徑找到對應的controller執行方法
public static void exec(String classPath,String methodPath){if(objMap.get(classPath)==null){System.out.println("沒有這個類 404");}else {if(map.get(classPath).get(methodPath)==null){System.out.println("沒有這個方法 404");}else {try {map.get(classPath).get(methodPath).invoke(objMap.get(classPath));} catch (IllegalAccessException e) {e.printStackTrace();} catch (InvocationTargetException e) {e.printStackTrace();}}}}
由于在第一步已經將帶有@Controller注解的類中帶有@RequestMapping注解的類和其方法都存儲在objMap和map中,這一步直接在其尋找,如存在該映射路徑,則直接利用反射調用對應方法執行
本案例提供了兩個controller,如下
import com.xxhh.annotation.Controller;
import com.xxhh.annotation.RequestMapping;@Controller
@RequestMapping("test")
public class TestController {@RequestMappingpublic String index(){System.out.println("test->index");return "";}@RequestMapping("index1")public String index1(){System.out.println("test->index1");return "";}
}
import com.xxhh.annotation.Controller;
import com.xxhh.annotation.RequestMapping;@Controller
@RequestMapping
public class IndexController {@RequestMappingpublic void index(){System.out.println("index -> index");}
}
最終執行結果如下:
index -> index
test->index1
test->index
沒有這個方法 404
test->index
以上就是簡單實現SpringMVC的全部源碼,如有錯誤,歡迎指正!!!?