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