知道DispatcherServlet爲你們做了多少事嗎?

DispatcherServlet

DispatcherServlet是前置控制器,配置在web.xml文件中的。攔截匹配的請求,Servlet攔截匹配規則要自己定義,把攔截下來的請求,依據相應的規則分發到目標Controller來處理,是配置spring MVC的第一步。
DispatcherServlet是前端控制器設計模式的實現,提供Spring Web MVC的集中訪問點,而且負責職責的分派,而且與Spring IoC容器無縫集成,從而可以獲得Spring的所有好處。

簡單地說就是承上啓下,核心中的核心。

在這裏插入圖片描述

手寫一個DispatcherServlet

理清職責:
在這裏插入圖片描述
簡化版的SpringMVC

DispatcherServlet:

package core.web;

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

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 core.common.Handler;
import core.common.HandlerMapping;


public class DispatcherSerlvet extends HttpServlet {
	private static final long serialVersionUID = 1L;
	
	private HandlerMapping handlerMapping;
       
    @Override
    /**
     *  init方法只會執行一次,而且一定是在service方法執行之前先執行
     *  一般可以在init方法當中做一些初始化操作(也就是爲service方法的執行
     *  做一些準備工作)。
     *  在這兒,init方法主要做三件事:
     *  1、讀取配置文件中的處理器類名。
     *  2、將處理器類實例化。
     *  3、將處理器實例交給HandlerMapping來處理。
     */
	public void init() throws ServletException {
    	SAXReader saxReader = new SAXReader();
    	/*
    	 * 通過讀取初始化參數來獲得配置文件名
    	 * getInitParameter方法來自於GenericServlet
    	 * 是HttpServlet的父類
    	 */
    	String configLocation = getInitParameter("configLocation");
    	
    	// 先拿到方法區中的類對象,再拿到容器(比如Tomcat)提供的類加載器,再通過它的getResources方法去獲取資源
    	// 放在容器中以後,所有類的加載都會由容器提供的類加載器完成
    	// 放到容器中後,需要這樣讀取文件
    	InputStream in = getClass().getClassLoader().getResourceAsStream(configLocation);
    	try {
			Document doc = saxReader.read(in);
			Element root = doc.getRootElement();
			List<Element> elements = root.elements();
			// beans集合用於存放處理器實例
			List<Object>beans = new ArrayList<Object>();
			for (Element element : elements) {
				// 獲得處理器的類名
				String className = element.attributeValue("class");
				System.out.println("className:"+className);
				// 將處理器實例化
				Object bean = Class.forName(className).newInstance();
				beans.add(bean);
				
			}
			System.out.println("beans:"+beans);
			
			handlerMapping = new HandlerMapping();
			// 將處理器實例交給映射處理器來處理
			handlerMapping.process(beans);
			
		} catch (Exception e) {
			e.printStackTrace();
			throw new ServletException(e);
		}
	}



	@Override
	protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		String path = req.getRequestURI().substring(req.getContextPath().length());
		Handler handler = handlerMapping.getHandler(path);
		if (handler==null) {
			// 沒有對應處理器時返回錯誤碼404
			System.out.println("請求路徑爲:"+path+"沒有提供對應的處理器");
			resp.sendError(404);
			return;
		}
		
		// 獲取參數並激活方法
		Method method = handler.getMethod();
		Object obj = handler.getObj();
		try {
			Class[]types = method.getParameterTypes();
			Object rv = null;
			if (types.length>0) {
				Object[]params = new Object[types.length];
				for (int i = 0; i < types.length; i++) {
					if (types[i]==HttpServletRequest.class) {
						params[i]=req;
					}
					if (types[i]==HttpServletResponse.class) {
						params[i]=resp;
					}
				}
				rv = method.invoke(obj, params);
			}else {
				rv = method.invoke(obj);
			}
			
		
			
			String viewName = rv.toString();
			System.out.println("viewName:"+viewName);
			//重定向
			if (viewName.startsWith("redirect:")) {
				String redirectPath = req.getContextPath()+"/"+viewName.substring("redirect:".length());
				resp.sendRedirect(redirectPath);
			}else {
				
				// 轉發
				// 所有的絕對路徑,必須以/開頭。轉發的路徑是從應用名後開始的。
				req.getRequestDispatcher("/WEB-INF/"+viewName+".jsp").forward(req,resp);
				
			}
			
			
			
		} catch (Exception e) {
			e.printStackTrace();
			// 將異常扔給容器來處理
			throw new ServletException(e);
		}
	}


}

RequestMapping註解:

package core.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 用於設置請求路徑的註解
*@author  Edward
*@date  2020年6月28日---下午2:53:52
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD,ElementType.TYPE})
public @interface RequestMapping {

	public String value();
}

HandlerMapping:

package core.common;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import core.annotation.RequestMapping;

/**
 * 	映射處理器,
 * 	負責提供請求路徑與處理器及方法的對應關係
 * 	比如:請求路徑爲“/hello.do”應該由HelloController的hello方法來處理。
 * 
*@author  Edward
*@date  2020年6月29日---上午9:28:12
*/
public class HandlerMapping {
	/*
	 *  map存放請求路徑與處理器及方法的對應關係
	 *  Handler封裝有處理器實例及Method對象
	 */
	private Map<String, Handler>map = new HashMap<String, Handler>();
	
	
	
	/**
	 * 依據請求路徑返回對應的Handler對象
	 * @param path
	 * @return
	 */
	public Handler getHandler(String path) {
		return map.get(path);
	}
	
	/**
	 * 負責建立請求路徑與處理器及方法的對應關係
	 * @param beans
	 */
	public void process(List<Object> beans) {
		System.out.println("HandlerMapping.process()");
		for (Object obj : beans) {
			// 獲得加在類前的註解
			RequestMapping rm1 = obj.getClass().getAnnotation(RequestMapping.class);
			String path1 = "";
			if (rm1!=null) {
				path1 = rm1.value();
			}
			
			
			
			Method[]methods = obj.getClass().getDeclaredMethods();
			for (Method method : methods) {
				// 獲得加在方法前的註解
				RequestMapping rm = method.getAnnotation(RequestMapping.class);
				// 允許方法前不寫註解
				if (rm!=null) {
					// 獲得請求路徑
					String path = rm.value();
					System.out.println("path:"+path);
					// 將處理器實例及Method對象封裝到Handler對象裏面
					Handler handler = new Handler();
					handler.setObj(obj);
					handler.setMethod(method);
					// 將對應關係放入map
					map.put(path1+path, handler);
					
				}	
				
			}
		}
		System.out.println("map:"+map);
	}
	
}

Handler:

package core.common;
/**
 * 
 * 該類是爲方便利用java反射機制去調用處理器的方法而設計的。
*@author  Edward
*@date  2020年6月29日---上午10:18:57
*/

import java.lang.reflect.Method;

public class Handler {
	// obj是處理器實例
	private Object obj;
	private Method method;
	public Object getObj() {
		return obj;
	}
	public void setObj(Object obj) {
		this.obj = obj;
	}
	public Method getMethod() {
		return method;
	}
	public void setMethod(Method method) {
		this.method = method;
	}
	
	

}

核心就是這四個類,其他自便。
web.xml文件供參考,注意:
1、設置配置文件名
2、設置load on startup
3、設置請求攔截路徑

<?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" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5">

  <servlet>
    <servlet-name>DispatcherSerlvet</servlet-name>
    <servlet-class>core.web.DispatcherSerlvet</servlet-class>
    <!-- 
    	指定配置文件名
     -->
    <init-param>
    	<param-name>configLocation</param-name>
    	<param-value>smartmvc.xml</param-value>
    </init-param>
    <!-- 
    	配置啓動即加載,即容器啓動之後,會立即將該Servlet實例化,緊接着初始化 
    	在這裏,“1”表示優先級,值是一個大於等於零的整數,越小,優先級越高,
    	也就是說,先被創建。
    -->
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>DispatcherSerlvet</servlet-name>
    <url-pattern>*.do</url-pattern>
  </servlet-mapping>
</web-app>

另外還有很多Springmvc的功能可以實現,比如

		// TODO IOC也是可以做到的,需要開發兩個註解,利用反射
		// TODO ModelMap也可以實現,可以在dispatcherSerlvet裏面將獲得的參數放進request裏, addA相當於setA
		// TODO @RequestParam也可以實現,只需要獲取方法params前面的註解裏的值,再賦給參數
		// TODO 對thymeLeaf的支持...
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章