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的支持...