转载地址 :http://blog.csdn.net/j080624/article/details/56278461

为了降低文章篇幅,使得文章更目标化,简洁化,我们就不例举各种@RequestMapping的用法等内容了.

文章主要说明以下问题:

    1. Spring怎样处理@RequestMapping(怎样将请求路径映射到控制器类或方法)

    2. Spring怎样将请求分派给正确的控制器类或方法

    3. Spring如何实现灵活的控制器方法的

在Spring MVC 3.1 之前的版本中,Spring默认使用 DefaultAnnotationHandlerMapping,AnnotationMethodHandlerAdapter来处理 @RequestMapping注解和请求方法调用,而从3.1开始提供了一组新的API完成这些工作。相比之下,新的API更加的合理完善,开放,易拓 展,面向对象。这篇文章便是基于3.1的新API进行剖析的。

一、概念解析

在开始之前我们先了解下新的API中引入的新接口或者类,这会有助于后面的处理过程的理解。不得不说新的API提供了更多漂亮的抽象,你能感受到面向对象的魅力。

  1. RequestMappingInfo 这个类是对请求映射的一个抽象,它包含了请求路径,请求方法,请求头等信息。其实可以看做是@RequestMapping的一个对应类。

  2. HandlerMethod这个类封装了处理器实例(Controller Bean)和 处理方法实例(Method)以及方法参数数组(MethodParameter[])

  3. MethodParameter这个类从2.0就有了,它封装了方法某个参数的相关信息及行为,如该参数的索引,该参数所属方法实例或构造器实例,该参数的类型等。

  4. HandlerMapping 该接口的实现类用来定义请求和处理器之前的映射关系,其中只定义了一个方法getHandler。

  5. AbstractHandlerMethodMapping 这是HandlerMapping的一个基本实现类,该类定义了请求与HandlerMethod实例的映射关系。

  6. RequestMappingInfoHandlerMapping这个是AbstractHandlerMethodMapping的实现类,他维护了一个RequestMappingInfo和HandlerMethod的Map属性。

  7. RequestMappingHandlerMapping 这个是RequestMappingInfoHandlerMapping的子类,它将@RequestMapping注解转化为RequestMappingInfo实例,并为父类使用。也就是我们处理@RequestMapping的终点。

  8. InitializingBean 这个接口定义了其实现Bean在容器完成属性设置后可以执行自定义初始化操作,我们的AbstractHandlerMethodMapping便实现了这个接口,并且定义了一组自定义操作,就是用来检测处理我们的@RequestMapping注解。

概念讲的太多总不是什么好事。但明白了上述概念基本上就成功一半了,其中的实现相对@Autowired那篇简单多了。

二、InitialiZingBean.afterPropertySet()

我们从头开始,看看到底Spring是怎样检测并处理我们@RequestMapping注解的。不知大家还记不记的这段代码:

  1. Object exposedObject = bean;
  2. try {
  3. populateBean(beanName, mbd, instanceWrapper);
  4. if (exposedObject != null) {
  5. exposedObject = initializeBean(beanName, exposedObject, mbd);
  6. }
  7. }

这是BeanFactory创建Bean过程中需要执行的一段代码,其中populateBean方法便是@Autowired注解的处理过程,执行的属性的自动注入等操作。因为initializeBean方法当时与主题无关没有讲,不过这时它便是我们关注的焦点了。

上面概念中我们讲到InitiaizingBean接口,它的实现Bean会在容器完成属性注入后执行一个自定义操作,这不就满足initializeBean方法的执行唤醒嘛,我们来看它的实现:

  1. protected Object initializeBean(final String beanName, final Object bean, RootBeanDefinition mbd) {
  2. if (System.getSecurityManager() != null) {
  3. AccessController.doPrivileged(new PrivilegedAction<Object>() {
  4. public Object run() {
  5. invokeAwareMethods(beanName, bean);
  6. return null;
  7. }
  8. }, getAccessControlContext());
  9. }
  10. else {//这里检测当前Bean是否实现一些列Aware接口,并调用相关方法,我们不关心。
  11. invokeAwareMethods(beanName, bean);
  12. }
  13. Object wrappedBean = bean;
  14. if (mbd == null || !mbd.isSynthetic()) {//BeanPostProcessor 的回调,不关心
  15. wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
  16. }
  17. try {
  18. invokeInitMethods(beanName, wrappedBean, mbd);//这是我们需要关心的,下面看下它的实现
  19. }
  20. if (mbd == null || !mbd.isSynthetic()) {//BeanPostProcessor 的回调,不关心
  21. wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
  22. }
  23. return wrappedBean;
  24. }

我们接着来看下invokeInitMethods方法的实现:

  1. protected void invokeInitMethods(String beanName, final Object bean, RootBeanDefinition mbd)
  2. throws Throwable {
  3. //是否是InitializingBean的实例
  4. boolean isInitializingBean = (bean instanceof InitializingBean);
  5. if (isInitializingBean &&
  6. (mbd == null || !mbd.isExternallyManagedInitMethod("afterPropertiesSet"))) {
  7. if (System.getSecurityManager() != null) {
  8. try {
  9. AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() {
  10. public Object run() throws Exception {//利用系统安全管理器调用
  11. ((InitializingBean) bean).afterPropertiesSet();
  12. return null;
  13. }
  14. }, getAccessControlContext());
  15. }
  16. }
  17. else {//调用InitializingBean的afterPropertiesSet方法。
  18. ((InitializingBean) bean).afterPropertiesSet();
  19. }
  20. }
  21. //调用自定义初始化方法。。。省略,不关心
  22. }

上一篇关于<mvc:annotation-driven/>的文章我们说过了,当在配置文件中加上该标记后,Spring(3.1后)会默认为我们注册RequestMappingHandlerMapping等Bean定义。而RequestMappingHandlerMapping实现了InitializingBean接口,因此,在初始化并装配该Bean实例时,执行到上述代码是,便会执行他的afterPropertySet方法。我们接下来看看他的afterPropertySet方法:

  1. public void afterPropertiesSet() {
  2. initHandlerMethods();
  3. }
  4. //Scan beans in the ApplicationContext, detect and register handler methods.
  5. protected void initHandlerMethods() {
  6. //扫描所有注册的Bean
  7. String[] beanNames = (this.detectHandlerMethodsInAncestorContexts ?
  8. BeanFactoryUtils.beanNamesForTypeIncludingAncestors(getApplicationContext(),
  9. Object.class) : getApplicationContext().getBeanNamesForType(Object.class));
  10. //遍历这些Bean,依次判断是否是处理器,并检测其HandlerMethod
  11. for (String beanName : beanNames) {
  12. if (isHandler(getApplicationContext().getType(beanName))){
  13. detectHandlerMethods(beanName);
  14. }
  15. }
  16. //这个方法是个空实现,不管他
  17. handlerMethodsInitialized(getHandlerMethods());
  18. }

它直接调用了initHandlerMethods()方法,并且该方法被描述为:扫描ApplicationContext中的beans,检测并注册处理器方法。we are close。

三、检测@RequestMapping

我们再看它是怎样判断是否是处理器的,以及怎么detect Handler Methods 的:

  1. @Override
  2. protected boolean isHandler(Class<?> beanType) {
  3. return ((AnnotationUtils.findAnnotation(beanType, Controller.class) != null) ||
  4. (AnnotationUtils.findAnnotation(beanType, RequestMapping.class) != null));
  5. }

啊哈,很简单,就是看看有没有被@Controller或者@RequestMapping注解标记

  1. protected void detectHandlerMethods(final Object handler) {
  2. Class<?> handlerType = (handler instanceof String) ?
  3. getApplicationContext().getType((String) handler) : handler.getClass();
  4. final Class<?> userType = ClassUtils.getUserClass(handlerType);
  5. Set<Method> methods = HandlerMethodSelector.selectMethods(userType, new MethodFilter(){
  6. public boolean matches(Method method) {//只选择被@RequestMapping标记的方法
  7. return getMappingForMethod(method, userType) != null;
  8. }
  9. });
  10. for (Method method : methods) {
  11. //根据方法上的@RequestMapping来创建RequestMappingInfo实例。
  12. T mapping = getMappingForMethod(method, userType);
  13. //注册请求映射
  14. registerHandlerMethod(handler, method, mapping);
  15. }
  16. }

整个的检测过程大致清楚了:1)遍历Handler中的所有方法,找出其中被@RequestMapping注解标记的方法。2)然后遍历这些方法,生成RequestMappingInfo实例。3)将RequestMappingInfo实例以及处理器方法注册到缓存中。

下面我们看看细节:

  1. @Override
  2. protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
  3. RequestMappingInfo info = null;
  4. //获取方法method上的@RequestMapping实例。
  5. RequestMapping methodAnnotation =
  6. AnnotationUtils.findAnnotation(method, RequestMapping.class);
  7. if (methodAnnotation != null) {//方法被注解了
  8. RequestCondition<?> methodCondition = getCustomMethodCondition(method);//始终返回null
  9. info = createRequestMappingInfo(methodAnnotation, methodCondition);//创建MappingInfo
  10. //检查方法所属的类有没有@RequestMapping注解
  11. RequestMapping typeAnnotation = AnnotationUtils.findAnnotation(handlerType,
  12. RequestMapping.class);
  13. if (typeAnnotation != null) {//有类层次的@RequestMapping注解
  14. RequestCondition<?> typeCondition = getCustomTypeCondition(handlerType);//null
  15. //将类层次的RequestMapping和方法级别的RequestMapping结合
  16. info = createRequestMappingInfo(typeAnnotation, typeCondition).combine(info);
  17. }
  18. }
  19. return info;
  20. }

很清晰吧,先获取方法上的@RequestMapping信息,然后获取类级别上的@RequestMapping 信息,然后将两者结合,这里我们有必要再了解下怎样创建RequestMappingInfo对象的(包括他的内部结构),以及怎样将类级别的request mapping信息和方法级别的进行结合的?

  1. private RequestMappingInfo createRequestMappingInfo(RequestMapping annotation,
  2. RequestCondition<?> customCondition) {
  3. return new RequestMappingInfo(
  4. new PatternsRequestCondition(annotation.value(), getUrlPathHelper(), getPathMatcher(),
  5. this.useSuffixPatternMatch, this.useTrailingSlashMatch, this.fileExtensions),
  6. new RequestMethodsRequestCondition(annotation.method()),
  7. new ParamsRequestCondition(annotation.params()),
  8. new HeadersRequestCondition(annotation.headers()),
  9. new ConsumesRequestCondition(annotation.consumes(), annotation.headers()),
  10. new ProducesRequestCondition(annotation.produces(), annotation.headers(),
  11. getContentNegotiationManager()),
  12. customCondition
  13. );
  14. }

其中涉及到了几个类,我们大致了解下含义:

  • PatternRequestCondition 它其实就是URL模式的封装,它包含了一个URL模式的Set集合。其实就是@RequestMapping注解中的value值得封装。

  • RequestMethodRequestCondition 它是@RequestMapping 注解中method属性的封装

  • ParamsRequestCondition 它是@RequestMapping注解中params属性的封装

等等,依次类推。因此RequestMappingInfo其实就是对@RquestMapping 的封装。

下面我们再看看怎样进行Combine的:

  1. public RequestMappingInfo combine(RequestMappingInfo other) {
  2. PatternsRequestCondition patterns = this.patternsCondition.combine(other.patternsCondition);
  3. RequestMethodsRequestCondition methods = this.methodsCondition.combine(other.methodsCondition);
  4. ParamsRequestCondition params = this.paramsCondition.combine(other.paramsCondition);
  5. HeadersRequestCondition headers = this.headersCondition.combine(other.headersCondition);
  6. ConsumesRequestCondition consumes = this.consumesCondition.combine(other.consumesCondition);
  7. ProducesRequestCondition produces = this.producesCondition.combine(other.producesCondition);
  8. RequestConditionHolder custom = this.customConditionHolder.combine(other.customConditionHolder);
  9. return new RequestMappingInfo(patterns, methods, params, headers, consumes,
  10. produces, custom.getCondition());
  11. }<span style="white-space:pre;">    </span>

很清晰,对每一个元素都进行combine操作,我们这里只看PatternRequestCondition是怎么结合的,就是看看怎样合并url的。其他没太大必要。

  1. public PatternsRequestCondition combine(PatternsRequestCondition other) {
  2. Set<String> result = new LinkedHashSet<String>();
  3. if (!this.patterns.isEmpty() && !other.patterns.isEmpty()) {
  4. for (String pattern1 : this.patterns) {
  5. for (String pattern2 : other.patterns) {
  6. result.add(this.pathMatcher.combine(pattern1, pattern2));
  7. }
  8. }
  9. }
  10. else if (!this.patterns.isEmpty()) {
  11. result.addAll(this.patterns);
  12. }
  13. else if (!other.patterns.isEmpty()) {
  14. result.addAll(other.patterns);
  15. }
  16. else {
  17. result.add("");
  18. }
  19. return new PatternsRequestCondition(result, this.urlPathHelper, this.pathMatcher,
  20. this.useSuffixPatternMatch,this.useTrailingSlashMatch, this.fileExtensions);
  21. }

1)两个pattern都存在是,调用PathMatcher的combine方法合并两个pattern。

2)只有一个有时,使用这个。

3)两个都没有时,为空“”。

现在真正的url拼接是由PathMatcher来完成的了。我们就不看他的代码了就是一串if else的组合,重点是考虑进各种情况,我们来看下方法的注释吧:

清晰,全面吧,有兴趣的可以看一下代码,这里不讲了。

四、注册请求映射

上面我们已经讲了@RequestMapping的检测和处理,并且根据@RequestMapping生成了RequestMappingInfo实例,那Spring必定需要将这些信息保存起来,以处理我们的请求。

第三节中我们提到一个方法还没有分析,就是registerHandlerMethod 方法:

  1. protected void registerHandlerMethod(Object handler, Method method, T mapping) {
  2. HandlerMethod handlerMethod;
  3. if (handler instanceof String) {
  4. String beanName = (String) handler;
  5. handlerMethod = new HandlerMethod(beanName, getApplicationContext(), method);
  6. }
  7. else {
  8. handlerMethod = new HandlerMethod(handler, method);
  9. }
  10. //上面几行是根据新的处理器实例,方法实例,RequestMappingInfo来生成新的HandlerMethod实例
  11. //下面是从缓存中查看是否有存在的HandlerMethod实例,如果有并且不相等则抛出异常
  12. HandlerMethod oldHandlerMethod = handlerMethods.get(mapping);
  13. if (oldHandlerMethod != null && !oldHandlerMethod.equals(handlerMethod)) {
  14. throw new IllegalStateException();
  15. }
  16. //handlerMethods 是一个Map键是RequestMappingInfo对象,值是HandlerMethod实例
  17. //因此一个HandlerMethod实例可能处理多个mapping,而一个mapping实例只能由一个method处理
  18. this.handlerMethods.put(mapping, handlerMethod);
  19. //这里获取mapping实例中的所有url。
  20. Set<String> patterns = getMappingPathPatterns(mapping);
  21. for (String pattern : patterns) {
  22. if (!getPathMatcher().isPattern(pattern)) {
  23. //urlMap也是Map,键是url 模式,值是RequestMappingInfo实例
  24. //因此一个mapping实例可能对应多个pattern,但是一个pattern只能对应一个mapping实例
  25. this.urlMap.add(pattern, mapping);
  26. }
  27. }
  28. }

这里可能稍微有点绕,其实道理很简单,当请求到达时,去urlMap中需找匹配的url,以及获取对应mapping实例,然后去handlerMethods中获取匹配HandlerMethod实例。

五、承上启下

篇幅有些长了,超出字数限制了,只能分成两篇了..........................

这章只分析了我们前面三个问题中的第一个,但是已经相当接近了。下一篇我们来讲,Spring怎样处理客户发来的请求,以及方法调用的。

Spring MVC — @RequestMapping原理讲解-1的更多相关文章

  1. Spring MVC简单原理

    Spring MVC原理 针对有Java Web基础.Spring基础和Spring MVC使用经验者. 前言 目前基于Java的web后端,Spring生态应该是比较常见了.虽然现在流行前后端分离, ...

  2. spring Mvc 执行原理 及 xml注解配置说明 (六)

    Spring MVC 执行原理 在 Spring Mvc 访问过程里,每个请求都首先经过 许多的过滤器,经 DispatcherServlet 处理; 一个Spring MVC工程里,可以配置多个的 ...

  3. (4.1)Spring MVC执行原理和基于Java的配置过程

    一.Spring MVC执行原理和基于Java配置的配置过程 (一)Spring MVC执行过程,大致为7步. 所有的请求都会经过Spring的一个单例的DispacherServlet. Dispa ...

  4. Spring MVC执行原理和基于Java的配置过程

    一.Spring MVC执行原理和基于Java配置的配置过程 (一)Spring MVC执行过程,大致为7步. 所有的请求都会经过Spring的一个单例的DispacherServlet. Dispa ...

  5. Spring MVC工作原理(好用版)

    Spring MVC工作原理 参考: SpringMVC工作原理 - 平凡希 - 博客园https://www.cnblogs.com/xiaoxi/p/6164383.html SpringMVC的 ...

  6. Spring MVC工作原理及源码解析(三) HandlerMapping和HandlerAdapter实现原理及源码解析

    1.HandlerMapping实现原理及源码解析 在前面讲解Spring MVC工作流程的时候我们说过,前端控制器收到请求后会调⽤处理器映射器(HandlerMapping),处理器映射器根据请求U ...

  7. Spring MVC工作原理 及注解说明

    SpringMVC框架介绍 1) spring MVC属于SpringFrameWork的后续产品,已经融合在Spring Web Flow里面. Spring 框架提供了构建 Web 应用程序的全功 ...

  8. Spring入门(十三):Spring MVC常用注解讲解

    在使用Spring MVC开发Web应用程序时,控制器Controller的开发非常重要,虽然说视图(JSP或者是Thymeleaf)也很重要,因为它才是直接呈现给用户的,不过由于现在前端越来越重要, ...

  9. Spring MVC 入门示例讲解

    在本例中,我们将使用Spring MVC框架构建一个入门级web应用程序.Spring MVC 是Spring框架最重要的的模块之一.它以强大的Spring IoC容器为基础,并充分利用容器的特性来简 ...

随机推荐

  1. ECharts 报表事件联动系列一:刷新页面

    本示例实现了以下功能: 1.点击刷新按钮,仅刷新柱状图,而不是整个页面 2.点击柱状内容刷新柱状图,并更新title 3.点击X轴,Y轴更新title,并弹出alert. 源码代码如下: <!D ...

  2. [luogu P3275] [SCOI2011]糖果

    [luogu P3275] [SCOI2011]糖果 题目描述 幼儿园里有N个小朋友,lxhgww老师现在想要给这些小朋友们分配糖果,要求每个小朋友都要分到糖果.但是小朋友们也有嫉妒心,总是会提出一些 ...

  3. web服务器/应用服务器/http服务器/中间件

    web服务器:只处理html静态页面不处理动态页面,如apache/nginx/iis等. 应用服务器:能处理html静态页面也能处理动态页面,如tomcat/weblogic/websphere/j ...

  4. 【转】关于TCP 半连接队列和全连接队列

    摘要: # 关于TCP 半连接队列和全连接队列 > 最近碰到一个client端连接异常问题,然后定位分析并查阅各种资料文章,对TCP连接队列有个深入的理解 > > 查资料过程中发现没 ...

  5. Web应用的统一异常处理(二十四)

    我们在做Web应用的时候,请求处理过程中发生错误是非常常见的情况.Spring Boot提供了一个默认的映射:/error,当处理中抛出异常之后,会转到该请求中处理,并且该请求有一个全局的错误页面用来 ...

  6. 同步socket处理

    1.socket类是TCP通信的基本类,调用成员函数connect()可以连接到一个指定的通信端点,连接成功后用local_endpoint()和remote_endpoint()获得连接两端的端点信 ...

  7. std::string find 的返回值

    std::string  的方法 find,返回值类型是std::string::size_type, 对应的是查找对象在字符串中的位置(从0开始), 如果未查找到,该返回值是一个很大的数据(4294 ...

  8. win10打文件预览功能

  9. MAVEN ECLIPSE JAR工程

    在eclipse 空白处点击鼠标右键选择新建 project 选择maven project: 选择Create a simple project Group ID: Artifact ID:创建项目 ...

  10. 【资料收集】PCA降维

    重点整理: PCA(Principal Components Analysis)即主成分分析,是图像处理中经常用到的降维方法 1.原始数据: 假定数据是二维的 x=[2.5, 0.5, 2.2, 1. ...