前言:
前面的文章我們介紹過(guò)如何自己寫(xiě)一個(gè)MyBatis框架:
今天我們來(lái)寫(xiě)一個(gè)SpringMVC框架,相比于寫(xiě)MyBatis框架,SpringMVC要簡(jiǎn)單一些,只需要xml解析+反射就可以完成,不需要jdk動(dòng)態(tài)代理。
廢話(huà)不多說(shuō),直接開(kāi)始摟干貨。
要自己寫(xiě)框架,必須理解框架的底層原理和運行機制。那么我們首先來(lái)回顧SpringMVC的實(shí)現原理。
SpringMVC實(shí)現原理:
核心組件:
1.DispatcherServlet:前端控制器,是整個(gè)流程控制的核心,控制其他組件的執行,統一調度,降低組件之間的耦合性,相當于總指揮。
2.Handler:處理器,完成具體業(yè)務(wù)邏輯,相當于Servlet或Action。
3.HandlerMapping:DispatcherServlet接收到請求之后,通過(guò)HandlerMapping將不同的請求分發(fā)到不同的Handler。
4.HandlerInterceptor:處理器攔截器,是一個(gè)接口,如果我們需要做一些攔截處理,可以來(lái)實(shí)現這個(gè)接口。
5.HandlerExecutionChain:處理器執行鏈,包括兩部分內容:Handler和HandlerInterceptor(系統會(huì )有一個(gè)默認的HandlerInterceptor,如果需要額外攔截處理,可以添加攔截器設置)。
6.HandlerAdapter:處理器適配器,Handler執行業(yè)務(wù)方法之前,需要進(jìn)行一系列的操作包括表單數據的驗證,數據類(lèi)型的轉換,將表單數據封裝到JavaBean等等,這一系列的操作,都是由HandlerAdapter來(lái)完成,DispatcherServlet通過(guò)HandlerAdapter執行不同的Handler。
7.ModelAndView:裝載了模型數據和視圖信息,作為Handler的處理結果,返回給DispatcherServlet。
8.ViewResolver:視圖解析器,DispatcherServlet通過(guò)它將邏輯視圖解析成物理視圖,最終將渲染結果響應給客戶(hù)端。
以上就是SpringMVC的核心組件。那么這些組件之間是如何進(jìn)行交互的呢?
我們來(lái)看SpringMVC的實(shí)現流程:
1.客戶(hù)端請求被DispatcherServlet(前端控制器)接收。
2.根據HandlerMapping映射到Handler。
3.生成Handler和HandlerInterceptor(如果有則生成)。
4.Handler和HandlerInterceptor以HandlerExecutionChain的形式一并返回給DispatcherServlet。
5.DispatcherServlet通過(guò)HandlerAdapter調用Handler的方法做業(yè)務(wù)邏輯處理。
6.返回一個(gè)ModelAndView對象給DispatcherServlet。
7.DispatcherServlet將獲取的ModelAndView對象傳給ViewResolver視圖解析器,將邏輯視圖解析成物理視圖View。
8.ViewResolver返回一個(gè)View給DispatcherServlet。
9.DispatcherServlet根據View進(jìn)行視圖渲染(將模型數據填充到視圖中)。
10.DispatcherServlet將渲染后的視圖響應給客戶(hù)端。
通過(guò)以上的分析,大致可以將SpringMVC流程理解如下:
首先需要一個(gè)前置控制器DispatcherServlet,作為整個(gè)流程的核心,由它去調用其他組件,共同完成業(yè)務(wù)。主要組件有兩個(gè):一是Controller,調用其業(yè)務(wù)方法Method,執行業(yè)務(wù)邏輯。二是ViewResolver視圖解析器,將業(yè)務(wù)方法的返回值解析為物理視圖+模型數據,返回客戶(hù)端。
我們自己寫(xiě)框架就按照這個(gè)思路來(lái)。
初始化工作:
1.根據Spring IOC的思路,需要將參與業(yè)務(wù)的對象全部創(chuàng )建并保存,供流程調用。所以首先我們需要創(chuàng )建Controller對象,HTTP請求是通過(guò)注解找到對應的Controller對象,所以我們需要將所有的Controller與其注解建立關(guān)聯(lián),很顯然,使用key-value結構的Map集合來(lái)保存最合適不過(guò)了,這樣就模擬了IOC容器。
2.Controller的Method也是通過(guò)注解與HTTP請求映射的,同樣的,我們需要將所有的Method與其注解建立關(guān)聯(lián),HTTP直接通過(guò)注解的值找到對應的Method,這里也用Map集合保存。
3.實(shí)例化視圖解析器。
初始化工作完成,接下來(lái)處理HTTP請求,業(yè)務(wù)流程如下:
1.DispatcherServlet接收請求,通過(guò)映射從IOC容器中獲取對應的Controller對象。
2.根據映射獲取Controller對象對應的Method。
3.調用Method,獲取返回值。
4.將返回值傳給視圖解析器,返回物理視圖。
5.完成頁(yè)面跳轉。
思路捋清楚了,接下來(lái)開(kāi)始寫(xiě)代碼,我們需要創(chuàng )建以下類(lèi):
1.MyDispatcherServlet:模擬DispatcherServlet。
2.MyController:模擬Controller注解。
3.MyRequestMapping:模擬RequestMapping注解。
4.MyViewResolver:模擬ViewResolver視圖解析器。
創(chuàng )建MyDispatcherServlet,init方法完成初始化:
1.將Controller與注解進(jìn)行關(guān)聯(lián),保存到iocContainer中。
哪些Controller是需要添加到iocContainer中的?
必須同時(shí)滿(mǎn)足兩點(diǎn):
1.springmvc.xml中配置掃描的類(lèi)。
2.類(lèi)定義處添加了注解。
注意這兩點(diǎn)必須同時(shí)滿(mǎn)足。
代碼思路:
(1)解析springmvc.xml。
(2)獲取component-scan標簽配置的包下的所有類(lèi)。
(3)判斷若這些類(lèi)添加了@MyController注解,則創(chuàng )建實(shí)例對象,并且保存到iocContainer。
(4)@MyRequestMapping的值為鍵,Controller對象為值。
2.將Controller中的Method與注解進(jìn)行關(guān)聯(lián),保存到handlerMapping中。
代碼思路:
(1)遍歷iocContainer中的Controller實(shí)例對象。
(2)遍歷每一個(gè)Controller對象的Method。
(3)判斷Method是否添加了@MyRequestMapping注解,若添加,則進(jìn)行映射并保存。
(4)保存到handlerMapping中,@MyRequestMapping的值為鍵,Method為值。
3.實(shí)例化ViewResolver。
代碼思路:
(1)解析springmvc.xml。
(2)根據bean標簽的class屬性獲取需要實(shí)例化的MyViewResolver。
(3)使用反射創(chuàng )建實(shí)例化對象,同時(shí)獲取prefix和suffix屬性,以及setter方法。
(4)使用反射調用setter方法給屬性賦值,完成MyViewResolver的實(shí)例化。
doPost方法處理HTTP請求:
1.解析HTTP,分別得到Controller和Method對應的uri。
2.通過(guò)uri分別在iocContainer和handlerMapping中獲取對應的Controller以及Method。
3.使用反射調用Method,執行業(yè)務(wù)方法,獲取結果。
4.結果傳給MyViewResolver進(jìn)行解析,返回真正的物理視圖(JSP頁(yè)面)。
5.完成JSP的頁(yè)面跳轉。
代碼:
1.創(chuàng )建MyController注解,作用目標為類(lèi)。
package com.southwind.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 自定義Controller注解
* @author southwind
*
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyController {
String value() default '';
}
2.創(chuàng )建MyRequestMapping注解,作用目標為類(lèi)和方法。
package com.southwind.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 自定義RequestMapping注解
* @author southwind
*
*/
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyRequestMapping {
String value() default '';
}
3.創(chuàng )建MyDispatcherServlet,核心控制器,init完成初始化工作,doPost處理HTTP請求。
package com.southwind.servlet;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.HttpRetryException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
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 org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import com.southwind.annotation.MyController;
import com.southwind.annotation.MyRequestMapping;
import com.southwind.view.MyViewResolver;
/**
* DispatcherServlet
* @author southwind
*
*/
public class MyDispatcherServlet extends HttpServlet{
//模擬IOC容器,保存Controller實(shí)例對象
private Map iocContainer = new HashMap();
//保存handler映射
private Map handlerMapping = new HashMap();
//自定視圖解析器
private MyViewResolver myViewResolver;
@Override
public void init(ServletConfig config) throws ServletException {
// TODO Auto-generated method stub
//掃描Controller,創(chuàng )建實(shí)例對象,并存入iocContainer
scanController(config);
//初始化handler映射
initHandlerMapping();
//加載視圖解析器
loadViewResolver(config);
}
/**
* 掃描Controller
* @param config
*/
public void scanController(ServletConfig config){
SAXReader reader = new SAXReader();
try {
//解析springmvc.xml
String path = config.getServletContext().getRealPath('')+'\\WEB-INF\\classes\\'+config.getInitParameter('contextConfigLocation');
Document document = reader.read(path);
Element root = document.getRootElement();
Iterator iter = root.elementIterator();
while(iter.hasNext()){
Element ele = (Element) iter.next();
if(ele.getName().equals('component-scan')){
String packageName = ele.attributeValue('base-package');
//獲取base-package包下的所有類(lèi)名
List list = getClassNames(packageName);
for(String str:list){
Class clazz = Class.forName(str);
//判斷是否有MyController注解
if(clazz.isAnnotationPresent(MyController.class)){
//獲取Controller中MyRequestMapping注解的value
MyRequestMapping annotation = (MyRequestMapping) clazz.getAnnotation(MyRequestMapping.class);
String value = annotation.value().substring(1);
//Controller實(shí)例對象存入iocContainer
iocContainer.put(value, clazz.newInstance());
}
}
}
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
/**
* 獲取包下的所有類(lèi)名
* @param packageName
* @return
*/
public List getClassNames(String packageName) {
List classNameList = new ArrayList();
String packagePath = packageName.replace('.', '/');
ClassLoader loader = Thread.currentThread().getContextClassLoader();
URL url = loader.getResource(packagePath);
if(url != null){
File file = new File(url.getPath());
File[] childFiles = file.listFiles();
for(File childFile : childFiles){
String className = packageName+'.'+childFile.getName().replace('.class', '');
classNameList.add(className);
}
}
return classNameList;
}
/**
* 初始化handler映射
*/
public void initHandlerMapping(){
for(String str:iocContainer.keySet()){
Class clazz = iocContainer.get(str).getClass();
Method[] methods = clazz.getMethods();
for (Method method : methods) {
//判斷方式是否添加MyRequestMapping注解
if(method.isAnnotationPresent(MyRequestMapping.class)){
//獲取Method中MyRequestMapping注解的value
MyRequestMapping annotation = method.getAnnotation(MyRequestMapping.class);
String value = annotation.value().substring(1);
//method存入methodMapping
handlerMapping.put(value, method);
}
}
}
}
/**
* 加載自定義視圖解析器
* @param config
*/
public void loadViewResolver(ServletConfig config){
SAXReader reader = new SAXReader();
try {
//解析springmvc.xml
String path = config.getServletContext().getRealPath('')+'\\WEB-INF\\classes\\'+config.getInitParameter('contextConfigLocation');
Document document = reader.read(path);
Element root = document.getRootElement();
Iterator iter = root.elementIterator();
while(iter.hasNext()){
Element ele = (Element) iter.next();
if(ele.getName().equals('bean')){
String className = ele.attributeValue('class');
Class clazz = Class.forName(className);
Object obj = clazz.newInstance();
//獲取setter方法
Method prefixMethod = clazz.getMethod('setPrefix', String.class);
Method suffixMethod = clazz.getMethod('setSuffix', String.class);
Iterator beanIter = ele.elementIterator();
//獲取property值
Map propertyMap = new HashMap();
while(beanIter.hasNext()){
Element beanEle = (Element) beanIter.next();
String name = beanEle.attributeValue('name');
String value = beanEle.attributeValue('value');
propertyMap.put(name, value);
}
for(String str:propertyMap.keySet()){
//反射機制調用setter方法,完成賦值。
if(str.equals('prefix')){
prefixMethod.invoke(obj, propertyMap.get(str));
}
if(str.equals('suffix')){
suffixMethod.invoke(obj, propertyMap.get(str));
}
}
myViewResolver = (MyViewResolver) obj;
}
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
// TODO Auto-generated method stub
this.doPost(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
// TODO Auto-generated method stub
//獲取請求
String handlerUri = req.getRequestURI().split('/')[2];
//獲取Controller實(shí)例
Object obj = iocContainer.get(handlerUri);
String methodUri = req.getRequestURI().split('/')[3];
//獲取業(yè)務(wù)方法
Method method = handlerMapping.get(methodUri);
try {
//反射機制調用業(yè)務(wù)方法
String value = (String) method.invoke(obj);
//視圖解析器將邏輯視圖轉換為物理視圖
String result = myViewResolver.jspMapping(value);
//頁(yè)面跳轉
req.getRequestDispatcher(result).forward(req, resp);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
4.創(chuàng )建視圖解析器MyViewResolver。
package com.southwind.view;
/**
* 自定義視圖解析器
* @author southwind
*
*/
public class MyViewResolver {
private String prefix;
private String suffix;
public String getPrefix() {
return prefix;
}
public void setPrefix(String prefix) {
this.prefix = prefix;
}
public String getSuffix() {
return suffix;
}
public void setSuffix(String suffix) {
this.suffix = suffix;
}
public String jspMapping(String value){
return this.prefix+value+this.suffix;
}
}
5.創(chuàng )建TestController,處理業(yè)務(wù)請求。
package com.southwind.controller;
import com.southwind.annotation.MyController;
import com.southwind.annotation.MyRequestMapping;
@MyController
@MyRequestMapping(value = '/testController')
public class TestController {
@MyRequestMapping(value = '/test')
public String test(){
System.out.println('執行test相關(guān)業(yè)務(wù)');
return 'index';
}
}
6.測試。
跳轉index.jsp,同時(shí)控制臺打印業(yè)務(wù)日志,訪(fǎng)問(wèn)成功。
源碼:
github
https://github.com/southwind9801/SpringMVCImitate.git
專(zhuān)業(yè) 熱愛(ài) 專(zhuān)注
致力于最高效的Java學(xué)習
Java大聯(lián)盟
掃描下方二維碼,加入Java大聯(lián)盟
聯(lián)系客服