SpringMVC源码总结(⼀)HandlerMapping和HandlerAdapter⼊门
刚接触SpringMVC,对它的xml⽂件配置⼀直⽐较模模糊糊,最近花了⼀点时间稍微看了下源代码,再加上调试,开始逐渐理解它,⽹上的类似的内容有很多,写本⽂主要是⾃⼰加深⼀下理解。本⽂适合⽤过SpringMVC的开发者,⾔归正传,⾸先搭建⼀个最简单的⼯程体验⼀下。
该⼯程是基于maven的,pom配置不再说明,所使⽤的spring版本4.0.5。
⾸先是l⽂件配置,最简单的配置
Java代码
1. <!DOCTYPE web-app PUBLIC
2. "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
3. "java.sun/dtd/web-app_2_3.dtd" >
4.
5. <web-app>
6. <display-name>Archetype Created Web Application</display-name>
7. <servlet>
8. <servlet-name>mvc</servlet-name>
9. <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
10. <load-on-startup>1</load-on-startup>
11. </servlet>
12.
13. <servlet-mapping>
14. <servlet-name>mvc</servlet-name>
15. <url-pattern>/*</url-pattern>
16. </servlet-mapping>
17. </web-app>
然后是l⽂件的配置,上⾯配置DispatcherServlet会默认加载[servlet-name]-l⽂件。对于我的配置,会去加载l⽂件。
Java代码
1. <?xml version="1.0" encoding="UTF-8" ?>
2. <beans xmlns="/schema/beans" xmlns:xsi="www.w
2001/XMLSchema-
instance" xmlns:mvc="/schema/mvc" xmlns:util="/schema/util" xmlns:context="www.springframework.o
rg/schema/context"
3. xsi:schemaLocation="/schema/beans
4. /schema/beans/spring-beans-3.1.xsd
5. /schema/mvc
6. /schema/mvc/spring-mvc-3.1.xsd
7. /schema/util
8. /schema/util/spring-util-2.0.xsd
9. /schema/context
10. /schema/context/spring-context-3.2.xsd">
11.
12. <bean name="/index" class="com.lg.mvc.HomeAction"></bean>
13. <bean class="org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer">
14. <property name="templateLoaderPath" value="/WEB-INF/views" />
15. <property name="defaultEncoding" value="utf-8" />
16. <property name="freemarkerSettings">
17. <props>
18. <prop key="locale">zh_CN</prop>
19. </props>
20. </property>
21. </bean>
22. <bean class="org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver">
23. <property name="suffix" value=".html" />
24. <property name="contentType" value="text/html;charset=utf-8" />
25. <property name="requestContextAttribute" value="request" />
26. <property name="exposeRequestAttributes" value="true" />
27. <property name="exposeSessionAttributes" value="true" />
28. </bean>
mvc的controller29. </beans>
在该配置中定义了⼀个HomeAction的Bean。内容为:
Java代码
1. package com.lg.mvc;
2.
3. import javax.servlet.http.HttpServletRequest;
4. import javax.servlet.http.HttpServletResponse;
5.
6. import org.springframework.web.servlet.ModelAndView;
7. import org.springframework.web.servlet.mvc.Controller;
8.
9. public class HomeAction implements Controller{
10.
11. @Override
12. public ModelAndView handleRequest(HttpServletRequest request,
13. HttpServletResponse response) throws Exception {
14. return new ModelAndView("hello");
15. }
16. }
这是最原始的mvc做法,要继承Controller接⼝,先从原始的说起,最后再过渡到@Controller和@RequestMapping注解式的配置。它在l⽂件中的配置有⼀个关键的属性
name="/index"。
WEB-INF/view⽬录下有⼀个简单的hello.html,内容为:
Java代码
1. <html>
1. <html>
2. <head>
3.
4. </head>
5. <body>
6. hello lg !
7. </body>
8. </html>
⾄此该⼯程就写完了,部署到tomcat中,项⽬路径为/,运⾏⼀下。
访问 localhost:8080/index
⾄此整个⼯程就算搭建成功了。
下⾯就要说说原理了。
⽤过python Django框架的都知道Django对于访问⽅式的配置就是,⼀个url路径和⼀个函数配对,你访问这个url,就会直接调⽤这个函数,简单明了。对于java的⾯向对象来说,就要分两步⾛。第⼀步⾸先要到是哪个对象,即handler,本⼯程的handler则是HomeAction对象。第⼆步要到访问的函数,即HomeAction的handleRequest⽅法。所以就出现了两个源码接⼝ HandlerMapping和HandlerAdapter,前者负责第⼀步,后者负责第⼆步。借⽤⽹上的SpringMVC架构图。
HandlerMapping接⼝的实现(只举了我认识的⼏个):
BeanNameUrlHandlerMapping :通过对⽐url和bean的name到对应的对象
SimpleUrlHandlerMapping :也是直接配置url和对应bean,⽐BeanNameUrlHandlerMapping功能更多
DefaultAnnotationHandlerMapping : 主要是针对注解配置@RequestMapping的,已过时
RequestMappingHandlerMapping :取代了上⾯⼀个
HandlerAdapter 接⼝实现:
HttpRequestHandlerAdapter :要求handler实现HttpRequestHandler接⼝,该接⼝的⽅法为 void handleRequest(HttpServletRequest request, HttpServletResponse response)也就是 handler必须有⼀个handleRequest⽅法
SimpleControllerHandlerAdapter:要求handler实现Controller接⼝,该接⼝的⽅法为ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response),也就是本⼯程采⽤的
AnnotationMethodHandlerAdapter :和上⾯的DefaultAnnotationHandlerMapping配对使⽤的,也已过时
RequestMappingHandlerAdapter : 和上⾯的RequestMappingHandlerMapping配对使⽤,针对@RequestMapping
先简单的说下这个⼯程的流程,访问localhost:8080/index⾸先由DispatcherServlet进⾏转发,通过BeanNameUrlHandlerMapping(含有 /index->HomeAction的配置),到了HomeAction,然后再拿HomeAction和每个adapter进⾏适配,由于HomeAction实现了Controller接⼝,所以最终会有SimpleControllerHandlerAdapter来完成对HomeAction的handleRequest⽅法的调度。然后就顺利的执⾏了我们想要的⽅法,后⾯的内容不在本节中说明。
了解了⼤概流程,然后就需要看源代码了。
⾸先就是SpringMVC的⼊⼝类,DispatcherServlet,它实现了Servlet接⼝,不再详细说DispatcherServlet的细节,不然⼜是⼀⼤堆的内容。每次请求都会调⽤它的doService->doDispatch,我们关注的重点就在doDispatch⽅法中。
Java代码
1. protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exc
eption {
2. HttpServletRequest processedRequest = request;
3. HandlerExecutionChain mappedHandler = null;
4. boolean multipartRequestParsed = false;
5.
6. WebAsyncManager asyncManager = AsyncManager(request);
7.
8. try {
9. ModelAndView mv = null;
10. Exception dispatchException = null;
11.
12. try {
13. processedRequest = checkMultipart(request);
14. multipartRequestParsed = (processedRequest != request);
15. //这个是重点,第⼀步由HandlerMapping到对应的handler
16. // Determine handler for the current request.
17. mappedHandler = getHandler(processedRequest);
18. if (mappedHandler == null || Handler() == null) {
19. noHandlerFound(processedRequest, response);
20. return;
21. }
22.
23. // Determine handler adapter for the current request.
23. // Determine handler adapter for the current request.
24. //这是第⼆步,到合适的HandlerAdapter,然后由它来调度执⾏handler的⽅法
25. HandlerAdapter ha = Handler());
26.
27. // Process last-modified header, if supported by the handler.
28. String method = Method();
29. boolean isGet = "GET".equals(method);
30. if (isGet || "HEAD".equals(method)) {
31. long lastModified = ha.getLastModified(request, Handler());
32. if (logger.isDebugEnabled()) {
33. logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
34. }
35. if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
36. return;
37. }
38. }
39.
40. if (!mappedHandler.applyPreHandle(processedRequest, response)) {
41. return;
42. }
43.
44. try {
45. // Actually invoke the handler.
46. mv = ha.handle(processedRequest, response, Handler());
47. }
48. finally {
49. if (asyncManager.isConcurrentHandlingStarted()) {
50. return;
51. }
52. }
53.
54. applyDefaultViewName(request, mv);
55. mappedHandler.applyPostHandle(processedRequest, response, mv);
56. }
57. catch (Exception ex) {
58. dispatchException = ex;
59. }
60. processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
61. }
62. catch (Exception ex) {
63. triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
64. }
65. catch (Error err) {
66. triggerAfterCompletionWithError(processedRequest, response, mappedHandler, err);
67. }
68. finally {
69. if (asyncManager.isConcurrentHandlingStarted()) {
70. // Instead of postHandle and afterCompletion
71. mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
72. return;
73. }
74. // Clean up any resources used by a multipart request.
75. if (multipartRequestParsed) {
76. cleanupMultipart(processedRequest);
77. }
78. }
79. }
第⼀步详细查看:
Java代码
1. protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
2. for (HandlerMapping hm : this.handlerMappings) {
3. if (logger.isTraceEnabled()) {
4. ace(
5. "Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'");
6. }
7. HandlerExecutionChain handler = hm.getHandler(request);
8. if (handler != null) {
9. return handler;
10. }
11. }
12. return null;
13. }
可以看到就是通过遍历所有已注册的HandlerMapping来到对应的handler,然后构建出⼀个HandlerExecutionChain,它包含了handler和HandlerMapping本⾝的⼀些,如下Java代码
1. public class HandlerExecutionChain {
2.
3. private final Object handler;
4.
5. private HandlerInterceptor[] interceptors;
6.
7. private List<HandlerInterceptor> interceptorList;
8.
9. //其他代码省略
10. }
其中HandlerMapping的getHandler实现:
Java代码
1. public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
2. Object handler = getHandlerInternal(request);
3. if (handler == null) {
4. handler = getDefaultHandler();
5. }
6. if (handler == null) {
7. return null;
8. }
9. // Bean name or resolved handler?
10. if (handler instanceof String) {
11. String handlerName = (String) handler;
12. handler = getApplicationContext().getBean(handlerName);
13. }
14. return getHandlerExecutionChain(handler, request);
15. }
这⾥的getHandlerInternal(request)是个抽象⽅法,由具体的HandlerMapping来实现,获取到的handler如果为空,则获取默认配置的handler,如果handler为String类型,则表⽰这个则会去Spring容器⾥⾯去这样名字的bean。
再看下BeanNameUrlHandlerMapping的getHandlerInternal(request)的具体实现(通过⼀系列的接⼝设计,之后再好好看看这个设计,到BeanNameUrlHandlerMapping这只⽤实现该⽅法中的⼀部分),如下
Java代码
1. public class BeanNameUrlHandlerMapping extends AbstractDetectingUrlHandlerMapping {
2.
3. /**
4. * Checks name and aliases of the given bean for URLs, starting with "/".
5. */
6. @Override
7. protected String[] determineUrlsForHandler(String beanName) {
8. List<String> urls = new ArrayList<String>();
9. if (beanName.startsWith("/")) {
10. urls.add(beanName);
11. }
12. String[] aliases = getApplicationContext().getAliases(beanName);
13. for (String alias : aliases) {
14. if (alias.startsWith("/")) {
15. urls.add(alias);
16. }
17. }
18. StringArray(urls);
19. }
20.
21. }
这⾥⾯注释说,bean的name必须以/开头,它才处理,将信息存储在Map<String, Object> handlerMap中,对于本⼯程来说就是{'/index':HomeAction对象}。
⾄此这⾥完成了第⼀步,下⾯开始第⼆步,即⽅法HandlerAdapter ha = Handler());的具体实现:
Java代码
1. protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
2. for (HandlerAdapter ha : this.handlerAdapters) {
3. if (logger.isTraceEnabled()) {
4. ace("Testing handler adapter [" + ha + "]");
5. }
6. if (ha.supports(handler)) {
7. return ha;
8. }
9. }
10. throw new ServletException("No adapter for handler [" + handler +
11. "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
12. }
遍历所有的HandlerAdapter,判断他们是否⽀持这个handler。
我们来看下HttpRequestHandlerAdapter的supports(handler)⽅法:
Java代码
1. public class HttpRequestHandlerAdapter implements HandlerAdapter {
2.
3. @Override
4. public boolean supports(Object handler) {
5. //就是判断handler是否实现了HttpRequestHandler接⼝
6. return (handler instanceof HttpRequestHandler);
7. }
8.
9. @Override
10. public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
11. throws Exception {
12. //若handler实现了HttpRequestHandler接⼝,则调⽤该接⼝的⽅法,执⾏我们在该⽅法中写的业务逻辑
13. ((HttpRequestHandler) handler).handleRequest(request, response);
14. return null;
15. }
16.
17. @Override
18. public long getLastModified(HttpServletRequest request, Object handler) {
19. if (handler instanceof LastModified) {
19. if (handler instanceof LastModified) {
20. return ((LastModified) handler).getLastModified(request);
21. }
22. return -1L;
23. }
24.
25. }
同理SimpleControllerHandlerAdapter也是这样类似的逻辑
Java代码
1. public class SimpleControllerHandlerAdapter implements HandlerAdapter {
2.
3. @Override
4. public boolean supports(Object handler) {
5. return (handler instanceof Controller);
6. }
7.
8. @Override
9. public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
10. throws Exception {
11.
12. return ((Controller) handler).handleRequest(request, response);
13. }
14.
15. @Override
16. public long getLastModified(HttpServletRequest request, Object handler) {
17. if (handler instanceof LastModified) {
18. return ((LastModified) handler).getLastModified(request);
19. }
20. return -1L;
21. }
22.
23. }
剩余两个AnnotationMethodHandlerAdapter和RequestMappingHandlerAdapter就⽐较复杂,我也没看。
按照本⼯程的配置,则SimpleControllerHandlerAdapter是⽀持HomeAction的,然后就会执⾏SimpleControllerHandlerAdapter的handle(processedRequest, response, Handler())
⽅法。本质上就会调⽤HomeAction实现Controller接⼝的⽅法。⾄此就分析完了。
了解过程了之后,然后就是最重要的也是经常配置出问题的地⽅。DispatcherServlet的handlerMappings和handlerAdapters的来源问题。
DispatcherServlet初始化的时候,会调⽤⼀个⽅法如下:
Java代码
1. protected void initStrategies(ApplicationContext context) {
2. initMultipartResolver(context);
3. initLocaleResolver(context);
4. initThemeResolver(context);
5. //初始化⼀些HandlerMapping
6. initHandlerMappings(context);
7. //初始化⼀些HandlerAdapter
8. initHandlerAdapters(context);
9. initHandlerExceptionResolvers(context);
10. initRequestToViewNameTranslator(context);
11. initViewResolvers(context);
12. initFlashMapManager(context);
13. }
这⾥可以看到,它会初始化⼀些HandlerMapping和HandlerAdapter,这两个⽅法⾮常重要,理解了这两个⽅法你就会知道,配置不对问题出在哪⾥,下⾯具体看下这两个⽅法:
Java代码
1. private void initHandlerMappings(ApplicationContext context) {
2. this.handlerMappings = null;
3.
4. if (this.detectAllHandlerMappings) {
5. // Find all HandlerMappings in the ApplicationContext, including ancestor contexts.
6. Map<String, HandlerMapping> matchingBeans =
7. BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
8. if (!matchingBeans.isEmpty()) {
9. this.handlerMappings = new ArrayList<HandlerMapping>(matchingBeans.values());
10. // We keep HandlerMappings in sorted order.
11. OrderComparator.sort(this.handlerMappings);
12. }
13. }
14. else {
15. try {
16. HandlerMapping hm = Bean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
17. this.handlerMappings = Collections.singletonList(hm);
18. }
19. catch (NoSuchBeanDefinitionException ex) {
20. // Ignore, we'll add a default HandlerMapping later.
21. }
22. }
23.
24. // Ensure we have at least one HandlerMapping, by registering
25. // a default HandlerMapping if no other mappings are found.
26. if (this.handlerMappings == null) {
27. this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
28. if (logger.isDebugEnabled()) {
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论