目录
- 一、DispatcherServlet
- 1.@RequestMapping作用
- 1.1 原始JavaWeb的缺陷
- 1.2 @RequestMapping
- 2.DispatcherServlet配置
- 2.1 基于web.xml
- 2.2 基于AbstractDispatcherServletInitializer类
- 3. DispatcherServlet源码
- 1.1 继承关系
- 1.2 doDispatch()方法
- 二、SpringMVC执行流程
一、DispatcherServlet
1.@RequestMapping作用
1.1 原始JavaWeb的缺陷
在原始的JavaWeb中,我们通过在web.xml中配置servlet类的类路径-URL映射关系,从而实现Servlet和URL的一一对应关系:
<!--servlet描述信息--><servlet><servlet-name></servlet-name><!--这个位置是带有包名的全限定类名--><servlet-class></servlet-class></servlet><!--servlet映射信息--><servlet-mapping><servlet-name></servlet-name><!--这里给Servlet类规定一个URL路径,路径名必须以 / 开始--><url-pattern></url-pattern></servlet-mapping><!--原理:服务器通过URL中的url-pattern寻找这个路径的servlet-name,通过servlet-name匹配对应的class文件--><!--实际上web服务器启动时将所有servlet以键值对的方式url-pattern,servlet-class添加到一个map集合中-->此后为了简化xml的配置,我们使用了@WebServlet注解:
@WebServlet(name="hello",urlPatterns="/dept/ServletA",loadOnStartup=1,initParams={@WebInitParam(name="10",value="asd")})classServletAextendsHttpServlet{@OverrideprotectedvoiddoGet(HttpServletRequestrequest,HttpServletResponseresponse)throwsServletException,IOException{}@OverrideprotectedvoiddoPost(HttpServletRequestrequest,HttpServletResponseresponse)throwsServletException,IOException{}}1.2 @RequestMapping
然而原始的JavaWeb实现servlet仍然较为复杂,因为一个servlet类仅能对应的一个URL,容易造成类爆炸。
所以SpringMVC使用了DispatcherServlet,DispatcherServlet继承自HttpServlet,是SpringMVC中唯一的Servlet,负责拦截所有请求,根据维护的HandlerMapping(记录URL-具体方法Handler 之间的映射关系)找到IoC容器中处理该请求的@RequestMapping(URL)方法并执行。被@RequestMapping()修饰的类都是普通的JavaBean,不是Servlet。
@Controller//实例化Bean到IoC容器@RequestMapping("/a")//访问路径publicclassReqAction{//接收get请求@RequestMapping(value="/req.action",method=RequestMethod.GET)publicStringreq1(){System.out.println("我处理get请求");return"main";}//接收post请求@RequestMapping(value="/req.action",method=RequestMethod.POST)publicStringreq2(){System.out.println("我处理post请求");return"main";}}2.DispatcherServlet配置
DispatcherServlet继承自HttpServlet,是SpringMVC中唯一的Servlet,所以配置方式和JavaWeb中servlet的配置方式相同。不同点是原始的Servlet只负责处理<url-pattern>的单个请求,而DispatcherServlet需要处理所有请求,所以<url-pattern>要配置为/,表示该serlvet处理所有请求。
被@RequestMapping()修饰的类都是普通的JavaBean,不是Servlet。由于DispatcherServlet需要知道都有哪些Bean使用了@RequestMapping(),才能在接受请求时使用对应的方法处理请求,所以需要获取所有配置了@RequestMapping()的类,这时候应该能想到IoC容器了,直接实例化一个DispatcherServlet专用的IoC容器,通过包扫描的方式将配置了@RequestMapping()的类(也就是扫描controller包中标注了@Component的类)加入到IoC容器,然后将IoC容器提交给DispatcherServlet,通过遍历每个单例的方法维护DispatcherServlet的Map<URL,Method>集合。
需要说明:只扫描controller包下的Bean就可以获取所有@RequestMapping()的前提是你得在controller包下的@Component()类用@RequestMapping(),如果你在其他位置用@RequestMapping(),那就要同时扫描对应的包注入到IoC容器,这里只扫描controller包下的@Component()仅仅是基于良好的项目规范,因为正常来讲就是在controller包下的@Component()类用@RequestMapping(),所以巧妙地复用了这个规范从而能扫描到所有@RequestMapping()的类到IoC容器。
点击跳转:Spring IoC笔记
2.1 基于web.xml
首先扫描controller包下的Bean注入到IoC容器,然后采用<init-param>标签的形式将IoC容器传给DispatcherServlet。
<!--servlet描述信息--><servlet><servlet-name>DispatcherServlet</servlet-name><!--这个位置是带有包名的全限定类名,这里的DispatcherServlet不是自己写的类,是Springmvc包中的类--><servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class><load-on-startup>1</load-on-startup><!--配置servlet的局部参数,DispatcherServlet要求传入IoC容器中用于处理响应的Bean对象--><init-param><param-name>contextConfigLocation</param-name><!--springmvc.xml中仅配置了包扫描,扫描处理请求的类(方法配置了@RequestMapping(URL)的类),扫描controller包下就可以了--><param-value>classpath:springmvc.xml</param-value></init-param></servlet><!--servlet映射信息--><servlet-mapping><servlet-name>DispatcherServlet</servlet-name><!--这里给Servlet类规定一个URL路径,路径名必须以/开始,在JavaWeb中我们是对每个servlet配置具体的URL,而在SpringMVC中由于只有一个Servlet,所以我们使用该Servlet来匹配所有URL--><url-pattern>/</url-pattern></servlet-mapping><?xml version="1.0" encoding="UTF-8"?><beansxmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"><!--配置扫描的包路径扫描注解,从而创建对象到IoC容器中--><context:component-scanbase-package="org.example.controller"/></bean>2.2 基于AbstractDispatcherServletInitializer类
对于springmvc.xml文件中的内容,在SpringIoC容器中已经学习过了,直接使用配置类封装:
// 封装了springmvc.xml文件@Configuration// 封装了xml文件中的文件头@ComponentScan("org.example.controller")// 封装了了包扫描publicclassRequestMappingComponentScan{}对于web.xml文件中对于DispatcherServlet的配置,SpringMVC提供了AbstractDispatcherServletInitializer类,通过继承该类实现三个抽象方法,完成serlvet处理请求URL路径的定义、将IoC容器传给DispatcherServlet。
publicclassMyDispatcherServletextendsAbstractDispatcherServletInitializer{// 配置Servlet处理哪些URL@OverrideprotectedString[]getServletMappings(){returnnewString[]{"/"};}// 将IoC容器传给DispatcherServlet,这样DispatcherServlet就知道有哪些@RequestMapping()方法了@OverrideprotectedWebApplicationContextcreateServletApplicationContext(){// 实例化一个新的IoC容器AnnotationConfigWebApplicationContextcontext=newAnnotationConfigWebApplicationContext();// 将@RequestMapping注解的类添加到IoC容器中context.register(RequestMappingComponentScan.class);// 返回IoC容器returncontext;}@OverrideprotectedWebApplicationContextcreateRootApplicationContext(){returnnull;}}3. DispatcherServlet源码
1.1 继承关系
可以看到DispatcherServlet本质是实现了Servlet接口、继承了HttpServlet,Tomcat本质还是调用Servlet的service()方法完成请求的处理,而HttpServlet的service()方法会根据请求类型调用对应的doGet()、doPost()方法,所以FrameworkServlet重写了doGet()、doPost()方法,全都调用processRequest()方法,processRequest()方法也是基于模板方法模式调用抽象方法doService(),所以就要求子类必须实现doService()方法,所以DispatcherServlet继承了FrameworkServlet重写了doService()方法,调用doDispatch()方法。
简单来说,Tomcat会调用DispatcherServlet的doDispatch()方法来处理请求。
publicclassDispatcherServletextendsFrameworkServlet{protectedvoiddoService(HttpServletRequestrequest,HttpServletResponseresponse)throwsException{try{this.doDispatch(request,response);}}}publicabstractclassFrameworkServletextendsHttpServletimplementsApplicationContextAware{protectedfinalvoiddoGet(HttpServletRequestrequest,HttpServletResponseresponse)throwsServletException,IOException{this.processRequest(request,response);}protectedfinalvoiddoPost(HttpServletRequestrequest,HttpServletResponseresponse)throwsServletException,IOException{this.processRequest(request,response);}protectedfinalvoiddoPut(HttpServletRequestrequest,HttpServletResponseresponse)throwsServletException,IOException{this.processRequest(request,response);}protectedfinalvoidprocessRequest(HttpServletRequestrequest,HttpServletResponseresponse)throwsServletException,IOException{try{this.doService(request,response);}}}publicabstractclassHttpServletextendsGenericServlet{// Tomcat调用该方法进行请求的处理publicvoidservice(ServletRequestreq,ServletResponseres)throwsServletException,IOException{if(reqinstanceofHttpServletRequest&&resinstanceofHttpServletResponse){HttpServletRequestrequest=(HttpServletRequest)req;HttpServletResponseresponse=(HttpServletResponse)res;// 模板方法模式调用HttpServlet自定义的service()方法this.service(request,response);}else{thrownewServletException("non-HTTP request or response");}}// 根据请求类型执行对应类型的方法,子类继承HttpServlet需要重写doGet()、doPost()等方法protectedvoidservice(HttpServletRequestreq,HttpServletResponseresp)throwsServletException,IOException{Stringmethod=req.getMethod();if(method.equals("GET")){lastModified=this.getLastModified(req);if(lastModified==-1L){this.doGet(req,resp);}else{longifModifiedSince=req.getDateHeader("If-Modified-Since");if(ifModifiedSince<lastModified){this.maybeSetLastModified(resp,lastModified);this.doGet(req,resp);}else{resp.setStatus(304);}}}elseif(method.equals("HEAD")){lastModified=this.getLastModified(req);this.maybeSetLastModified(resp,lastModified);this.doHead(req,resp);}elseif(method.equals("POST")){this.doPost(req,resp);}elseif(method.equals("PUT")){this.doPut(req,resp);}elseif(method.equals("DELETE")){this.doDelete(req,resp);}elseif(method.equals("OPTIONS")){this.doOptions(req,resp);}elseif(method.equals("TRACE")){this.doTrace(req,resp);}else{StringerrMsg=lStrings.getString("http.method_not_implemented");Object[]errArgs=newObject[]{method};errMsg=MessageFormat.format(errMsg,errArgs);resp.sendError(501,errMsg);}}protectedvoiddoGet(HttpServletRequestreq,HttpServletResponseresp)throwsServletException,IOException{Stringprotocol=req.getProtocol();Stringmsg=lStrings.getString("http.method_get_not_supported");resp.sendError(this.getMethodNotSupportedCode(protocol),msg);}protectedvoiddoPost(HttpServletRequestreq,HttpServletResponseresp)throwsServletException,IOException{Stringprotocol=req.getProtocol();Stringmsg=lStrings.getString("http.method_post_not_supported");resp.sendError(this.getMethodNotSupportedCode(protocol),msg);}}publicabstractclassGenericServletimplementsServlet,ServletConfig,Serializable{}publicinterfaceServlet{}1.2 doDispatch()方法
doDispatch()方法中,通过getHandler()获取拦截器、请求URL对应的Handler(@RequestMapping方法),然后调用handle()方法执行拦截器和Handler。
protectedvoiddoDispatch(HttpServletRequestrequest,HttpServletResponseresponse)throwsException{HttpServletRequestprocessedRequest=request;HandlerExecutionChainmappedHandler=null;booleanmultipartRequestParsed=false;WebAsyncManagerasyncManager=WebAsyncUtils.getAsyncManager(request);try{try{ModelAndViewmv=null;ObjectdispatchException=null;try{processedRequest=this.checkMultipart(request);multipartRequestParsed=processedRequest!=request;mappedHandler=this.getHandler(processedRequest);if(mappedHandler==null){this.noHandlerFound(processedRequest,response);return;}HandlerAdapterha=this.getHandlerAdapter(mappedHandler.getHandler());Stringmethod=request.getMethod();booleanisGet=HttpMethod.GET.matches(method);if(isGet||HttpMethod.HEAD.matches(method)){longlastModified=ha.getLastModified(request,mappedHandler.getHandler());if((newServletWebRequest(request,response)).checkNotModified(lastModified)&&isGet){return;}}if(!mappedHandler.applyPreHandle(processedRequest,response)){return;}mv=ha.handle(processedRequest,response,mappedHandler.getHandler());if(asyncManager.isConcurrentHandlingStarted()){return;}this.applyDefaultViewName(processedRequest,mv);mappedHandler.applyPostHandle(processedRequest,response,mv);}catch(Exceptionvar20){dispatchException=var20;}catch(Throwablevar21){dispatchException=newServletException("Handler dispatch failed: "+String.valueOf(var21),var21);}this.processDispatchResult(processedRequest,response,mappedHandler,mv,(Exception)dispatchException);}catch(Exceptionvar22){triggerAfterCompletion(processedRequest,response,mappedHandler,var22);}catch(Throwablevar23){triggerAfterCompletion(processedRequest,response,mappedHandler,newServletException("Handler processing failed: "+String.valueOf(var23),var23));}}finally{if(asyncManager.isConcurrentHandlingStarted()){if(mappedHandler!=null){mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest,response);}asyncManager.setMultipartRequestParsed(multipartRequestParsed);}elseif(multipartRequestParsed||asyncManager.isMultipartRequestParsed()){this.cleanupMultipart(processedRequest);}}}二、SpringMVC执行流程
- 客户端向服务器发送HTTP请求,执行
Filter的过滤操作,根据请求的URL调用对应Servlet,由于SpringMVC只有一个Servlet且配置的响应路径是all,所以调用DispatcherServlet。(这一步是Tomcat执行的,下面才是SpringMVC执行流程) - 符合拦截规则的URI被DispatcherServlet控制器捕获。
- DispatcherServlet对请求的URL进行解析,调用HandlerMapping匹配到Handler(标注
@RequestMapping的方法)。 - 执行拦截器HandlerInterceptor的
preHandle()方法。 - 将请求参数转换为Handler的形参。
- 调用Handler,返回结果。
- 执行拦截器HandlerInterceptor的
postHandle()方法。 - 如果使用了
@ResponseBody,调用Jackon将返回值序列化为JSON字符串。 - 将JSON字符串写入HttpServletResponse的响应体
Body中。 - 执行拦截器HandlerInterceptor的
afterCompletion()方法。 - 将响应(状态码、响应头、JSON 数据)返回给前端。