SpringMVC学习记录1

起因

以前大三暑假实习的时候看到公司用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分发请求所必须的。

http://kingliu.iteye.com/blog/1972973

但是实际上我去看了下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的配置文件里.

posted @ 2016-02-19 15:14  abcwt112  阅读(482)  评论(0编辑  收藏  举报