Request与Controller方法映射的创建

HandlerMapping

  该类会根据请求来返回对应的Controller方法实例给DispatcherServlet,因此相关的映射便是在HandlerMapping中实现的。

  由于DispatcherServlet会使用HandlerMapping的能力,所以在DispatcherServlet中会去对HandlerMapping进行初始化。

HandlerMapping的初始化:

  DispatcherServlet中的initHandlerMappings()方法会去对HandlerMapping进行初始化。该方法调用在该类的initStrategies方法中(initStrategies()方法是在onRefresh方法中调用的)

进入到DispatcherServlet中的initHandlerMappings()方法。

  (1)将类中存放HandlerMapping的handlerMappings成员变量置为空值

  (2)判断是否需要从容器里扫描已注册的HandlerMapping实现类并排序好存入handlerMappings成员变量中。(默认是true)

      True:

        (1)寻找IOC容器中HandlerMapping类型的Bean

            这里获取到的Bean有:RequestMappingHandlerMapping、BeanNameUrlHandlerMapping、RouterFunctionMapping

        (2)获取到的Bean集合不是空的话对其进行排序后存入handlerMappings成员变量中


RequestMappingHandlerMapping:

  负责处理RequestMapping标签的

  该类会先在容器中给创建出来,后续才能在DispatcherServlet的initHandlerMappings()方法中注入实例进去。

    在RequestMappingHandlerMapping创建的时候会去调用到父类(AbstractHandlerMethodMapping)的afterPropertiesSet()方法去调用initHandlerMethods() :去初始化映射关系

RequestMappingHandlerMapping的关系图:

该类实现了ApplicationContextAware和ServletContextAware,使之能获取到容器的能力,可以操作容器内部的bean。

该类实现类InitializingBean接口,该接口的afterPropertiesSet()方法是在bean初始化的时候执行的。所以在该类中可以对标记有RequestMapping的bean进行处理。

  进入到AbstractHandlerMethodMapping类中,该类实现InitializingBean接口的afterPropertiesSet()方法。方法中调用了initHandlerMethods()方法。该方法主要就是用来初始化处理方法实例的

protected void initHandlerMethods() {
        //遍历容器里所有的BeanName
        for (String beanName : getCandidateBeanNames()) {//***
            //忽略掉scopedTarget.打头的bean(session application request之类的作用域内的代理类)
            if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {//对bean进行筛选,
                processCandidateBean(beanName);//如果类上有Controller注解或RequestMapping注解:提取url与Controller映射关系
            }
        }
        handlerMethodsInitialized(getHandlerMethods());
}

首先我们进入到getCandidateBeanNames()方法,该方法会从容器中去获取所有的bean。

protected String[] getCandidateBeanNames() {
        //从root容器以及子容器里,或者仅从子容器里获取所有的Bean
        //在前期如果root容器里有bean被标记RequestMapping,下面的变量就会为true。
        return (this.detectHandlerMethodsInAncestorContexts ?
                BeanFactoryUtils.beanNamesForTypeIncludingAncestors(obtainApplicationContext(), Object.class) :
                obtainApplicationContext().getBeanNamesForType(Object.class));//只从子容器(servletContext)中查找所有Bean实例的名字
}

然后我们会对获取到的所有BeanName进行筛选(忽略过标了scopeTarget属性作用域的代理类),然后对筛选过后的BeanName用processCandidateBean()方法进行提取url与Controller映射关系。

processCandidateBean():该方法会去获取到bean的Class然后提取其url与Controller映射关系

  (1)根据bean名字获取Bean的Class对象

  (2)beanType !=null &&isHandler(beanType)  =  (Bean的Class不是空的 && Class上有@Controller或@RequestMapping注解)

    条件成立的话会调用detectHandlerMethods(beanName)方法提取其(目标Bean的)url与Controller映射关系,为后续提取并建立Controller方法和请求的映射做准备。

      (A)获取到对应的Bean的Class对象 (如果没有获取到对应的Class对象则不执行后面的逻辑)

      (B)获取目标Class对象的真正被代理类(因为Class对象可能是CGLIB创建的代理类,我们要获取到真正的代理对象)

      (C)MethodIntrospector.selectMethods()方法  :会返回一个(Map<Method, T> methods)存储容器以供下面去注册

        寻找方法上有@RequestMapping注解的Method实例。然后建立起(Key:)Controller方法实例与(Value:)RequestMappingInfo的映射关系 

//寻找方法上有@RequestMapping注解的Method实例
//key:方法实例  值:RequestMappingInfo实例
Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
  //在执行完selectMethods()方法之后就能建立起Controller方法实例与RequestMappingInfo的映射关系,
  //并将相关的映射保存到methods这个map里,key:方法实例 值:RequestMappingInfo实例
  (MethodIntrospector.MetadataLookup<T>) method -> {
    try {
       //返回的RequestMappingInfo
      return getMappingForMethod(method, userType);//***  //该方法会将目标方法上的RequestMappingInfo和目标类上的RequestMappingInfo合并成一个RequestMappingInfo然后配置上前缀返回(通常不需要配置前缀)
    }
       catch (Throwable ex) {
          throw new IllegalStateException("Invalid mapping on handler class [" +userType.getName() + "]: " + method, ex);
       }
   });

    (D)for循环将获取到的Method对象依次注册到HandlerMapping中{

        (1)获取到被AOP代理包装后的真实的方法实例

        (2)将目标类、真实执行的方法、RequestMappingInfo实例传递 到registerHandlerMethod()方法中去注册(注册进HandlerMethod中)

      }

//将获取到的Method对象依次注册到HandlerMapping中去
methods.forEach((method, mapping) -> {
      //由于可能获取到的是没包装过的被代理类的方法,所以重新获取到被AOP代理包装后的方法实例
       Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);//获取被AOP代理包装后的方法实例
      //handler:目标类    invocableMethod:真正执行的方法  mapping:RequestMappingInfo实例 
      registerHandlerMethod(handler, invocableMethod, mapping);//注册进HandlerMethod中  //***
});
registerHandlerMethod()方法:会去调用成员变量(mappingRegistry).register()去进行注册
我们跟进到this.mappingRegistry.register(mapping, handler, method)方法中:
  (1)上锁
  (2)校验要注册的方法的唯一性,即先前是否已经注册过同样的映射(有则抛异常)
  (3)注册RequestMappingInfo 和 HandlerMethod(往mappingLookup(成员变量)中存放进去)
  (4)注册请求路径与对应RequestMappingInfo(获取到mapping中所有的请求路径,然后for循环将url与mapping一 一对应的存入到urlLookup(成员变量)中)
  (5)
  (6)注册HandlerMethod与跨域信息
  (7)创建及注册MappingRegistation信息
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Feign Client和Controller都是用来处理HTTP请求的,但它们的用途和实现方式有所不同。 Feign Client是一个基于接口的HTTP客户端,它可以方便地调用其他服务的接口,类似于RPC。我们可以使用Feign Client来代替手动发送HTTP请求,从而简化代码的编写。 Controller则是一个用于处理HTTP请求的类,它包含了一系列方法,每个方法都对应着一个URL和HTTP请求方法。当我们的应用收到HTTP请求时,Spring会自动根据请求的URL和请求方法调用对应的Controller方法来处理请求。 在映射方面,Feign Client和Controller也有所不同。对于Feign Client,我们需要在接口上使用`@RequestMapping`等注解来指定接口的URL和HTTP请求方法,例如: ```java @FeignClient(name = "example-service") public interface ExampleService { @RequestMapping(method = RequestMethod.GET, value = "/example") String getExample(); } ``` 这个接口定义了一个名为`ExampleService`的Feign Client,其中`getExample()`方法对应着一个GET请求,请求的URL是`/example`。 对于Controller,我们则需要在Controller类上使用`@RequestMapping`等注解来指定Controller的URL前缀,以及在方法上使用`@RequestMapping`等注解来指定方法的URL和HTTP请求方法。例如: ```java @RestController @RequestMapping("/example") public class ExampleController { @GetMapping public String getExample() { return "Hello, world!"; } } ``` 这个Controller类定义了一个名为`ExampleController`的Controller,其中`/example`是Controller的URL前缀,`getExample()`方法对应着一个GET请求,请求的URL就是Controller的URL前缀。在这个例子中,`getExample()`方法返回字符串`"Hello, world!"`。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

kerry_x

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值