深度解讀springMVC底層實現
使用JDK8 ,idea2018.2, maven3.5.4
使用XML 解析 + 反射來寫一個springMVC框架:
如下思路實現;
。
首先需要一個前置控制器 DispatcherServlet,作為整個流程的核心,由它去呼叫其他元件,共同完成業務。主要元件有兩個:
一是 Controller,呼叫其業務方法 Method,執行業務邏輯;
二是 ViewResolver 檢視解析器,將業務方法的返回值解析為物理檢視 + 模型資料,返回客戶端。
Spring MVC 的實現流程:
客戶端請求被 DispatcherServlet(前端控制器)接收。
->根據 HandlerMapping 對映到 Handler。
->生成 Handler 和 HandlerInterceptor(如果有則生成)。
->Handler 和 HandlerInterceptor 以 HandlerExecutionChain 的形式一併返回給 DispatcherServlet。
->DispatcherServlet 通過 HandlerAdapter 呼叫 Handler 的方法做業務邏輯處理。
->返回一個 ModelAndView 物件給 DispatcherServlet。
->DispatcherServlet 將獲取的 ModelAndView 物件傳給 ViewResolver 檢視解析器,將邏輯檢視解析成物理檢視 View。
->ViewResolver 返回一個 View 給 DispatcherServlet。
->DispatcherServlet 根據 View 進行檢視渲染(將模型資料填充到檢視中)。
->DispatcherServlet 將渲染後的檢視響應給客戶端。
分析:
HTTP 請求是通過註解找到對應的 Controller 物件…;
Controller 的 Method 也是通過註解與 HTTP 請求對映的;
使用map 當做 ioC 容器,完成儲存所有引數與業務的class;
.業務邏輯:
初始化工作完成,接下來處理 HTTP 請求,業務流程如下:
DispatcherServlet 接收請求,通過對映從 IoC 容器中獲取對應的 Controller 物件;
根據對映獲取 Controller 物件對應的 Method;
呼叫 Method,獲取返回值;
將返回值傳給檢視解析器,返回物理檢視;
完成頁面跳轉。
doPost 方法處理 HTTP 請求:
解析 HTTP,分別得到 Controller 和 Method 對應的 URL
通過 URL 分別在 iocContainer 和 handlerMapping 中獲取對應的 Controller 及 Method
使用反射呼叫 Method,執行業務方法,獲取結果
結果傳給 MyViewResolver 進行解析,返回真正的物理檢視(JSP 頁面)
完成 JSP 的頁面跳轉
專案結構如圖
自定義註解:
@MyController
@MyRequestMapping
/**
* @auther SyntacticSugar
* @data 2018/11/13 0013下午 9:15
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyController {
String value() default "";
}
/**
* 自定義 @RequestMapping 註解
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyRequestMapping {
String value() default "";
}
定義一個核心控制器MyDispatcherServlet ;
/**
* @auther SyntacticSugar
* @data 2018/11/13 0013下午 9:20
* <p>
* 建立控制器
*/
public class MyDispatcherServlet extends HttpServlet {
//建立ioC 建立 handler存放容器
private HashMap<String, Object> ioC = new HashMap<>();
private HashMap<String, Method> handlerMapping = new HashMap<>();
//自定義檢視解析
private MyViewResolver myViewResolver;
@Override
public void init(ServletConfig config) throws ServletException {
// 把controller放到ioC中
scanController(config);
//初始化handler 對映
initHandlerMapping();
//載入檢視解析器
loadViewResolver(config);
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
this.doPost(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String handlerUri = req.getRequestURI().split("/")[2];
String methodUri = req.getRequestURI().split("/")[3];
//
Object o = ioC.get(handlerUri);
Method method = handlerMapping.get(methodUri);
// 使用反射機制,呼叫執行 業務
try {
String value = (String) method.invoke(o);
// 將邏輯檢視 轉化為 物理檢視,交給 view渲染返回前端
String result = myViewResolver.jspMapping(value);
req.getRequestDispatcher(result).forward(req,resp );
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* saxReader 解析springmvc.xml
* @param config
*/
private void scanController(ServletConfig config) {
SAXReader saxReader = new SAXReader();
try {
String path = config.getServletContext().getRealPath("") + "\\WEB-INF\\classes\\" +
config.getInitParameter("contextConfigLocation");
Document document = saxReader.read(path);
// 獲取根元素
Element rootElement = document.getRootElement();
Iterator iterator = rootElement.elementIterator();
// 遍歷 nodes 、sax解析每一行xml
while (iterator.hasNext()) {
Element next = (Element) iterator.next();
// 把每一個元素的name和 component-scan 比較,獲取base-package值
if (next.getName().equals("component-scan")) {
String packageName = next.attributeValue("base-package");
// 獲取包下 每個子包
List<String> classNames = getClassNames(packageName);
for (String className : classNames) {
/**
通過反射獲取clazz ,判斷class上是否存在MyController註解
* 若存在 、獲取MyRequestMapping 的 value , 並將其 裝入 自定義的ioC
*/
Class<?> clazz = Class.forName(className);
if (clazz.isAnnotationPresent(MyController.class)) {
MyRequestMapping annotation = clazz.getAnnotation(MyRequestMapping.class);
String value = annotation.value().substring(1);
// 放入ioC
ioC.put(value,clazz.newInstance() );
}
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 獲取 <context:component-scan base-package="com.baidu"/> 所有class 全路徑名
* @param packageName
* @return
*/
private List<String> getClassNames(String packageName) {
List<String> classNameList = new ArrayList<String>();
String path = packageName.replace(".", "/");
//已知存在包路徑,獲取每一級路徑下的file、 獲取類載入器,
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
URL url = classLoader.getResource(path);
//非空判斷
if (url != null) {
File[] files = new File(url.getPath()).listFiles();
//遍歷取值
for (File childFile : files) {
String className = packageName + "." + childFile.getName().replace(".class", "");
classNameList.add(className);
}
}
// return
return classNameList;
}
/**
* 初始化 handler
*/
private void initHandlerMapping() {
//從ioC中取出 MyController註解的 class
for (String s : ioC.keySet()) {
Class<?> clazz = ioC.get(s).getClass();
Method[] methods = clazz.getMethods();
//遍歷
for (Method method : methods) {
// 判斷哪一個被 @MyRequestMapping 註解標識
if (method.isAnnotationPresent(MyRequestMapping.class)) {
MyRequestMapping annotation = method.getAnnotation(MyRequestMapping.class);
String value = annotation.value().substring(1);
// 存入 handler
handlerMapping.put(value,method );
}
}
}
}
/**
* 載入自定義檢視 saxReader 解析springmvc.xml
* @param config
*/
private void loadViewResolver(ServletConfig config) {
SAXReader reader = new SAXReader();
try {
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<String,String> propertyMap = new HashMap<String,String>();
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) {
e.printStackTrace();
}
}
}
自定義一個檢視解析器,MyViewResolver
/**
* @auther SyntacticSugar
* @data 2018/11/13 0013下午 9:31
*
* 自定義檢視解析器 MyViewResolver
*/
public class MyViewResolver {
private String prefix;
private String suffix;
// 目標資源路徑
public String jspMapping(String value){
return this.prefix+value+this.suffix;
}
//setter getter
......
}
建立測試 TestController ,對自定義的springmvc 進行測試;
/**
* @auther SyntacticSugar
* @data 2018/11/13 0013下午 10:47
*/
@MyController
@MyRequestMapping("/testController")
public class TestController {
@MyRequestMapping("/test")
public String test(){
System.out.println("執行test相關業務");
return "index";
}
}
springmvc.xml配置:
<?xml version="1.0" encoding="UTF-8"?>
<beans>
<component-scan base-package="com.baidu"/>
<!-- 配置檢視解析器 ,攔截器 -->
<bean class="com.baidu.view.MyViewResolver">
<property name="prefix" value="/"/>
<property name="suffix" value=".jsp"/>
</bean>
</beans>
啟動tomcat訪問:
http://localhost:8080/SpringMVCImitate-master/testController/test
原始碼下載:
https://download.csdn.net/download/weixin_42323802/10783231