知道DispatcherServlet为你们做了多少事吗?

    技术2022-07-10  114

    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的支持...
    Processed: 0.059, SQL: 9