起因
以前大三暑假实习的时候看到公司用SpringMVC而不是Struts2,老司机告诉我SpringMVC各种方便,各种解耦.
然后我自己试了试..好像是蛮方便的....
基本上在Spring的基础上配置一小段就可以了....
但是我一直搞不明白...像我用Struts2的时候需要继承框架的Action父类,所以请求可以委派过来...但是SpringMVC自己写的Controller完全就是POJO.那为什么请求还可以委派过来涅?
最近有点空...稍微研究了一下SpringMVC....算是对它有了点新的理解...
mvc:annotation-driven到底干了啥?
SpringMVC插入的一小段配置中有一段就是mvc:annotation-driven......这个神奇的配置到底干了啥?为什么加了就可以用@Controller来接受请求了呢?why????
查阅资料发现.这个配置会加载2个bean
<mvc:annotation-driven /> 会自动注册DefaultAnnotationHandlerMapping与AnnotationMethodHandlerAdapter 两个bean,是spring MVC为@Controllers分发请求所必须的。
但是实际上我去看了下SpringMVC的代码..发现这2个类以及过期了(我的SPringMVC版本是4.1.7)...现在实际上现在注册的是RequestMappingHandlerMapping和RequestMappingHandlerAdapter..如下图
这里之所以@Controller能起作用主要是靠RequestMappingHandlerMapping.....所以我看了一下RequestMappingHandlerMapping的代码...
RequestMappingHandlerMapping
RequestMappingHandlerMapping的是实现了InitializingBean接口的
所以当各种bean初始化以后,属性被设置以后RequestMappingHandlerMapping的afterPropertiesSet方法会被调用.(请参考bean的生命周期http://www.cnblogs.com/zrtqsk/p/3735273.html).
1 /** 2 * Detects handler methods at initialization. 3 */ 4 @Override 5 public void afterPropertiesSet() { 6 initHandlerMethods(); 7 } 8 9 /** 10 * Scan beans in the ApplicationContext, detect and register handler methods. 11 * @see #isHandler(Class) 12 * @see #getMappingForMethod(Method, Class) 13 * @see #handlerMethodsInitialized(Map) 14 */ 15 protected void initHandlerMethods() { 16 if (logger.isDebugEnabled()) { 17 logger.debug("Looking for request mappings in application context: " + getApplicationContext()); 18 } 19 20 String[] beanNames = (this.detectHandlerMethodsInAncestorContexts ? 21 BeanFactoryUtils.beanNamesForTypeIncludingAncestors(getApplicationContext(), Object.class) : 22 getApplicationContext().getBeanNamesForType(Object.class)); 23 24 for (String beanName : beanNames) { 25 if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX) && 26 isHandler(getApplicationContext().getType(beanName))){ 27 detectHandlerMethods(beanName); 28 } 29 } 30 handlerMethodsInitialized(getHandlerMethods()); 31 }
afterPropertiesSet会调用initHandlerMethods
initHandlerMethods里22行会把所有的bean都取出来.然后26行一个一个去做isHandler方法
1 @Override 2 protected boolean isHandler(Class<?> beanType) { 3 return ((AnnotationUtils.findAnnotation(beanType, Controller.class) != null) || 4 (AnnotationUtils.findAnnotation(beanType, RequestMapping.class) != null)); 5 }
从isHandler方法里可以看到因为我们自己写的Controller上面有annotation @Controller所以isHandler返回是true...
从代码中我们额外还可以发现...如果我们的Controller上面有注解@RequestMapping....那我们不写@Controller也是可以的....(⊙﹏⊙)b
有@Controller注解的类会接下去做detectHandlerMethods方法(27行).
1 /** 2 * Look for handler methods in a handler. 3 * @param handler the bean name of a handler or a handler instance 4 */ 5 protected void detectHandlerMethods(final Object handler) { 6 Class<?> handlerType = 7 (handler instanceof String ? getApplicationContext().getType((String) handler) : handler.getClass()); 8 9 // Avoid repeated calls to getMappingForMethod which would rebuild RequestMappingInfo instances 10 final Map<Method, T> mappings = new IdentityHashMap<Method, T>(); 11 final Class<?> userType = ClassUtils.getUserClass(handlerType); 12 13 Set<Method> methods = HandlerMethodSelector.selectMethods(userType, new MethodFilter() { 14 @Override 15 public boolean matches(Method method) { 16 T mapping = getMappingForMethod(method, userType); 17 if (mapping != null) { 18 mappings.put(method, mapping); 19 return true; 20 } 21 else { 22 return false; 23 } 24 } 25 }); 26 27 for (Method method : methods) { 28 registerHandlerMethod(handler, method, mappings.get(method)); 29 } 30 }
看这个方法的名字我们大概也能知道它是干啥的...第16行getMappingForMethod会去把这个Controller里标注了@RequestMapping的方法全部找出来..如下面代码片段
1 @Override 2 protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) { 3 RequestMappingInfo info = null; 4 RequestMapping methodAnnotation = AnnotationUtils.findAnnotation(method, RequestMapping.class); 5 if (methodAnnotation != null) { 6 RequestCondition<?> methodCondition = getCustomMethodCondition(method); 7 info = createRequestMappingInfo(methodAnnotation, methodCondition); 8 RequestMapping typeAnnotation = AnnotationUtils.findAnnotation(handlerType, RequestMapping.class); 9 if (typeAnnotation != null) { 10 RequestCondition<?> typeCondition = getCustomTypeCondition(handlerType); 11 info = createRequestMappingInfo(typeAnnotation, typeCondition).combine(info); 12 } 13 } 14 return info; 15 }
全部找出来以后再做27行-29行
1 for (Method method : methods) { 2 registerHandlerMethod(handler, method, mappings.get(method)); 3 }
把这个标注为@RequestMapping的方法注册一下.....
总结
1.这样就能解释为了我们标注了@Controller的方法里面的标注了@RequestMapping的方法能够接受到请求...即使我们没有继承SpringMVC的任何类...
这全靠RequestMappingHandlerMapping.它参与了Spring bean的生命周期...会将所有bean一个一个检测...只要这个bean被标注了Controller注解.就会将@RequestMapping标注的方法取出来并注册..
2.至此以前另外一个疑问也有了解答..为什么@Controller标注的bean与<mvc:annotation-driven>标签都需要配置在SpringMVC的配置里而不是Spring里.(全部配置在Spring里也可以.必须都在一起.)
首先, Spring不需要注入Controller的bean,Controller的bean只有SpringMVC需要用到.但这个不是重点.
重点是其次, mvc:annotation-driven会注入参与Spring bean生命周期的特殊的bean,他们会检测Controller的bean.
如果情况1:这些特殊的bean是由Spring加载的,而Controller bean是由SpringMVC加载的.那这些特殊的bean是检测不到Controller bean的.因为Spring父环境是由ServletContextListener加载的,必定先于DispatcherServlet加载的子环境.所以创建Spring的XMLWebApplicationContext的时候Controller bean都还未加载.
情况2:Controller bean由Spring加载,<mvc:annotation-driven>写在SpringMVC里.
AbstractHandlerMethodMapping.class
1 String[] beanNames = (this.detectHandlerMethodsInAncestorContexts ? 2 BeanFactoryUtils.beanNamesForTypeIncludingAncestors(getApplicationContext(), Object.class) : 3 getApplicationContext().getBeanNamesForType(Object.class));
detectHandlerMethodsInAncestorContexts默认是false.所以SpringMVC的xmlWebApplicationContext不会去父容器找bean.只会在自身这里找Object.class.所以也不会包括Controller bean.那结果可想而知. 解决办法是可以通过设置detectHandlerMethodsInAncestorContexts 为true来解决.
但是最简单的办法就是@Controller标注的bean与<mvc:annotation-driven>标签都配置在SpringMVC的配置文件里.