Spring源码深度解析(郝佳)-学习-源码解析-Spring MVC(三)-Controller 解析

        在之前的博客中Spring源码深度解析(郝佳)-学习-源码解析-Spring MVC(一),己经对 Spring MVC 的框架做了详细的分析,但是有一个问题,发现举的例子不常用,因为我们在实际开发项目的过程中,肯定不会自己去配置url 和 handler之间的关系,也肯定不会用ModelAndView来返回视图对象,基于上篇博客最后的问题,今天,我们来研究在企业是如何使用 Spring MVC的,当然,互联网的今天,我相信大部分公司都己经使用 Spring Boot 来开发项目了,但是经典的 Spring MVC源码,还是值得我们去理解和研究的。话不多说,我们来看例子吧。

1. 创建controller
//处理登录请求的后端控制器
//注意:@RequestParam注解中的required注解对表单提交中的属性是没有用的,就算不填它也会默认为空字符串,它只对GET请求中
//在url后加的key-value的属性有限制作用
@Controller
@RequestMapping(value = {"/test"})
public class UserController {


    private static final String CURRENT_USER = "Now_user";

    //如果是GET方法请求的话,就直接给用户返回登录的页面,此页面表单请求的方法为POST
    @RequestMapping(value = {"/login"},method = {RequestMethod.GET})
    public ModelAndView LoginGet(){
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.setViewName("index");
        return modelAndView;
    }


    @RequestMapping(value = {"/register"},method = {RequestMethod.GET})
    public String register(User user){
        System.out.println(JSON.toJSONString(user));
        return "registersuccess";
    }


    @RequestMapping(value = {"/query"},method = {RequestMethod.GET})
    public String query(String username,User user){
        System.out.println(username);
        System.out.println(JSON.toJSONString(user));
        return "registersuccess";
    }

    @RequestMapping(value = {"/detail"})
    public String detail(@RequestBody  User user){
        System.out.println(JSON.toJSONString(user));
        return "registersuccess";
    }

    @RequestMapping(value = {"/detailInfo"})
    public String detailInfo(User user){
        System.out.println(JSON.toJSONString(user));
        return "registersuccess";
    }


    // http://localhost:8080/test/login.htm?userName=zhangsan&password=lizi
    @RequestMapping(value = {"login"},method = {RequestMethod.POST})
    //让请求的url后面必须跟上一个叫做userName的属性,是用户的用户名
    public ModelAndView LoginPost(@RequestParam(value = "userName") String userName,
                                  //请求的url后必须跟上password属性,为用户当前的密码
                                  @RequestParam(value = "password") String password,//Spring MVC框架集成了Servlet请求响应等一系列参数,可以在有需要的时候使用            
                                  HttpServletRequest request, HttpServletResponse response,
                                  HttpSession session, RedirectAttributes redirectAttributes) {

        //这里是和后端交互的代码,如果是用户登录的话就在数据库中查找对应的用户信息
        if (userName.isEmpty() || password.isEmpty()) {
            ModelAndView modelAndView = new ModelAndView();
            modelAndView.addObject("error", "用户名或密码为空");
            modelAndView.setViewName("index");
            return modelAndView;
        }

        //到了这里就说明用户登录成功了
        System.out.println("===========================:" + userName + "密码是:" + password);
        //使用session进行会话跟踪
        session.setAttribute(CURRENT_USER, userName);

        //创建模型与视图类,返回给前端控制器
        ModelAndView modelAndView = new ModelAndView();
		//重定向的时候,因为是客户端重新的请求,所以参数是不会被传到重定向页面的
        //所以使用此方法,可以把属性放到一个叫做FlashMap的Map中
        redirectAttributes.addFlashAttribute("userName", userName);
        redirectAttributes.addFlashAttribute("password", password);

        redirectAttributes.addAttribute("uname", userName);
        redirectAttributes.addAttribute("pwd", password);
        //使用重定向的时候不能写jsp的名字,要写url映射的路径
        modelAndView.setViewName("redirect:/test/main");
        return modelAndView;
    }
}
@Controller
@RequestMapping(value = {"/test"})
public class MainController {

    @RequestMapping(value = {"/main"}, method = {RequestMethod.GET})
    public ModelAndView GetMain(
            HttpServletRequest request, HttpSession session) {

        ModelAndView modelAndView = new ModelAndView();

        //对FlashMap中的参数进行提取,有两种方法
        //第一种,使用RequestContextUtils(请求工具包),因为在重定向后FlashMap会把表单中的属性
        //放在重定向新的请求中,所以可以获得请求中的FlashMap
        Map<String, ?> map = RequestContextUtils.getInputFlashMap(request);
        //把FlashMap直接放入模型,传给前端控制器
        modelAndView.addAllObjects(map);
        //视图名传入
        modelAndView.setViewName("test/main");
        System.out.println("==========================get ");
        return modelAndView;
    }

	//第二种:使用@ModelAttribute注解
    //因为FlashMap是处理这个url的初始化数据模型,所以可以通过这个注解拿到FlashMap的属性
    @RequestMapping(value = {"/main"}, method = {RequestMethod.POST})
    public String PostMain(
            @ModelAttribute(value = "userName") String userName,
                           @ModelAttribute(value = "password") String password) {
        System.out.println("userName:" + userName + ",password:" + password);
        return "test/main";
    }
}
2.创建实体
@Data
public class UserInfo implements Serializable {
    private int age ;
    private String desc;
}
@Data
public class User {
    private String username;

    private String password;

    private UserInfo userInfo;
}
3.创建 web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:web="http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
         version="3.1" xmlns="http://xmlns.jcp.org/xml/ns/javaee">
    <display-name>spring_tiny</display-name>


    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:spring_101_200/config_181_190/spring185.xml</param-value>
    </context-param>



    <servlet>
        <servlet-name>spring_tiny</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>


    <servlet-mapping>
        <servlet-name>spring_tiny</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>


    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>


    <!-- 自己配置描述文件,需要多少个描述文件就配置多少 -->
    <jsp-config>
        <!-- 配置c描述文件-对应c标签,这里的taglib-uri对应jsp中引入的uri -->
        <taglib>
            <taglib-uri>http://www.codecoord.com</taglib-uri>
            <taglib-location>/WEB-INF/c.tld</taglib-location>
        </taglib>
    </jsp-config>
</web-app>
4. 创建 Spring xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <mvc:annotation-driven />
    <context:component-scan base-package="com.spring_101_200.test_181_190.test_185_spring_mvc"></context:component-scan>

    <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/jsp/"></property>
        <property name="suffix" value=".jsp"></property>
    </bean>
    
</beans>

        我们不先急于测试,因为在Spring源码深度解析(郝佳)-学习-源码解析-Spring MVC(一)这篇博客中很多的己经解析了 Spring MVC 的大体框架及实现原理,今天,我们对上一篇博客进行补充。那我们今天来弄清楚下面这个问题.

  • 我们配置的 Controller及方法,是在什么时候被注册为Handler 的?
  • Request 请求时,是如何查找到对应的Handler?
  • 如果 Handler 没有查找到,该如何处理?

        粗略的估计一下,应该就这几个问题吧,下面我们来看看mvc:annotation-driven注解的解析
<mvc:annotation-driven ></mvc:annotation-driven>

在这里插入图片描述
        关于如何找到自定义标签解析器的源码分析,在之前的博客中,分析不下3-5次,这里就不再赘述了,直接通过全局搜索找到解析器AnnotationDrivenBeanDefinitionParser,在解析之前,我们来看看<mvc:annotation-driven />标签的结构。

<mvc:annotation-driven>
    <mvc:path-matching registered-suffixes-only="true" path-helper="xx" suffix-pattern="true" trailing-slash="true"></mvc:path-matching>
    <mvc:argument-resolvers>
        <bean class="xxx" ></bean>
        <bean class="bbb"></bean>
    </mvc:argument-resolvers>
    <mvc:async-support default-timeout="30" task-executor="xx"></mvc:async-support>
    <mvc:message-converters>
        <bean class="xxx"></bean>
    </mvc:message-converters>
    <mvc:return-value-handlers>
        <bean class="xx"></bean>
    </mvc:return-value-handlers>
</mvc:annotation-driven>

        基于此结构,我们来看看 Spring 源码是如何解析这些标签的。

AnnotationDrivenBeanDefinitionParser.java
class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser {

    public static final String CONTENT_NEGOTIATION_MANAGER_BEAN_NAME = "mvcContentNegotiationManager";

    private static final boolean javaxValidationPresent =
            ClassUtils.isPresent("javax.validation.Validator", AnnotationDrivenBeanDefinitionParser.class.getClassLoader());

    private static boolean romePresent =
            ClassUtils.isPresent("com.rometools.rome.feed.WireFeed", AnnotationDrivenBeanDefinitionParser.class.getClassLoader());

    private static final boolean jaxb2Present =
            ClassUtils.isPresent("javax.xml.bind.Binder", AnnotationDrivenBeanDefinitionParser.class.getClassLoader());

    private static final boolean jackson2Present =
            ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", AnnotationDrivenBeanDefinitionParser.class.getClassLoader()) &&
                    ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", AnnotationDrivenBeanDefinitionParser.class.getClassLoader());

    private static final boolean jackson2XmlPresent =
            ClassUtils.isPresent("com.fasterxml.jackson.dataformat.xml.XmlMapper", AnnotationDrivenBeanDefinitionParser.class.getClassLoader());

    private static final boolean gsonPresent =
            ClassUtils.isPresent("com.google.gson.Gson", AnnotationDrivenBeanDefinitionParser.class.getClassLoader());


    @Override
    public BeanDefinition parse(Element element, ParserContext parserContext) {
        Object source = parserContext.extractSource(element);

        CompositeComponentDefinition compDefinition = new CompositeComponentDefinition(element.getTagName(), source);
        parserContext.pushContainingComponent(compDefinition);
		//设置内容协商(Content Negotiation),也就是控制器返回的相同数据的多个表示(或视图)
        RuntimeBeanReference contentNegotiationManager = getContentNegotiationManager(element, source, parserContext);
		//注册RequestMappingHandlerMapping
        RootBeanDefinition handlerMappingDef = new RootBeanDefinition(RequestMappingHandlerMapping.class);
        handlerMappingDef.setSource(source);
        handlerMappingDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
        //Handler匹配url 的优先级,order越小,越优先匹配
        handlerMappingDef.getPropertyValues().add("order", 0);
        handlerMappingDef.getPropertyValues().add("contentNegotiationManager", contentNegotiationManager);
        String methodMappingName = parserContext.getReaderContext().registerWithGeneratedName(handlerMappingDef);
		//配置将键值对写到路径中映射作为controller中的方法参数
        if (element.hasAttribute("enable-matrix-variables")) {
            Boolean enableMatrixVariables = Boolean.valueOf(element.getAttribute("enable-matrix-variables"));
            handlerMappingDef.getPropertyValues().add("removeSemicolonContent", !enableMatrixVariables);
        }
        else if (element.hasAttribute("enableMatrixVariables")) {
            Boolean enableMatrixVariables = Boolean.valueOf(element.getAttribute("enableMatrixVariables"));
            handlerMappingDef.getPropertyValues().add("removeSemicolonContent", !enableMatrixVariables);
        }
		//配置url路径匹配器
        configurePathMatchingProperties(handlerMappingDef, element, parserContext);

		//跨域设置
        RuntimeBeanReference corsConfigurationsRef = MvcNamespaceUtils.registerCorsConfigurations(null, parserContext, source);
        handlerMappingDef.getPropertyValues().add("corsConfigurations", corsConfigurationsRef);
		//定义自定义请求参数转换器
        RuntimeBeanReference conversionService = getConversionService(element, source, parserContext);
        //属性验证器
        RuntimeBeanReference validator = getValidator(element, source, parserContext);
        //错误或者异常消息处理器
        RuntimeBeanReference messageCodesResolver = getMessageCodesResolver(element);
		//注册ConfigurableWebBindingInitializer
        RootBeanDefinition bindingDef = new RootBeanDefinition(ConfigurableWebBindingInitializer.class);
        bindingDef.setSource(source);
        bindingDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
        bindingDef.getPropertyValues().add("conversionService", conversionService);
        bindingDef.getPropertyValues().add("validator", validator);
        bindingDef.getPropertyValues().add("messageCodesResolver", messageCodesResolver);

		//允许注册实现了HttpMessageConverter接口的bean,来对requestbody 或responsebody中的数据进行解析
        ManagedList<?> messageConverters = getMessageConverters(element, source, parserContext);
        //允许注册实现了WebArgumentResolver接口的bean,来对handlerMethod中的用户自定义的参数或annotation进行解析 
        ManagedList<?> argumentResolvers = getArgumentResolvers(element, parserContext);
        //允许注册实现了HandlerMethodReturnValueHandler接口的bean,来对handler method的特定的返回类型做处理
        ManagedList<?> returnValueHandlers = getReturnValueHandlers(element, parserContext);
        //设置异步超时时间
        String asyncTimeout = getAsyncTimeout(element);
        //获取异步执行器
        RuntimeBeanReference asyncExecutor = getAsyncExecutor(element);
        //获取实现了CallableProcessingInterceptor异步执行拦截器
        ManagedList<?> callableInterceptors = getCallableInterceptors(element, source, parserContext);
         //获取实现了DeferredResultProcessingInterceptor异步执行拦截器
        ManagedList<?> deferredResultInterceptors = getDeferredResultInterceptors(element, source, parserContext);
		//设置RequestMappingHandlerAdapter适配器
        RootBeanDefinition handlerAdapterDef = new RootBeanDefinition(RequestMappingHandlerAdapter.class);
        handlerAdapterDef.setSource(source);
        handlerAdapterDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
        handlerAdapterDef.getPropertyValues().add("contentNegotiationManager", contentNegotiationManager);
        handlerAdapterDef.getPropertyValues().add("webBindingInitializer", bindingDef);
        handlerAdapterDef.getPropertyValues().add("messageConverters", messageConverters);
        //添加 requestBody增强
        addRequestBodyAdvice(handlerAdapterDef);
        //添加 responseBody增强
        addResponseBodyAdvice(handlerAdapterDef);
		//重定向后,如何去掉url后面的地址设置
        if (element.hasAttribute("ignore-default-model-on-redirect")) {
            Boolean ignoreDefaultModel = Boolean.valueOf(element.getAttribute("ignore-default-model-on-redirect"));
            handlerAdapterDef.getPropertyValues().add("ignoreDefaultModelOnRedirect", ignoreDefaultModel);
        }
        else if (element.hasAttribute("ignoreDefaultModelOnRedirect")) {
            // "ignoreDefaultModelOnRedirect" spelling is deprecated
            Boolean ignoreDefaultModel = Boolean.valueOf(element.getAttribute("ignoreDefaultModelOnRedirect"));
            handlerAdapterDef.getPropertyValues().add("ignoreDefaultModelOnRedirect", ignoreDefaultModel);
        }
        if (argumentResolvers != null) {
            handlerAdapterDef.getPropertyValues().add("customArgumentResolvers", argumentResolvers);
        }
        if (returnValueHandlers != null) {
            handlerAdapterDef.getPropertyValues().add("customReturnValueHandlers", returnValueHandlers);
        }
        if (asyncTimeout != null) {
            handlerAdapterDef.getPropertyValues().add("asyncRequestTimeout", asyncTimeout);
        }
        if (asyncExecutor != null) {
            handlerAdapterDef.getPropertyValues().add("taskExecutor", asyncExecutor);
        }

        handlerAdapterDef.getPropertyValues().add("callableInterceptors", callableInterceptors);
        handlerAdapterDef.getPropertyValues().add("deferredResultInterceptors", deferredResultInterceptors);
        String handlerAdapterName = parserContext.getReaderContext().registerWithGeneratedName(handlerAdapterDef);

        String uriCompContribName = "mvcUriComponentsContributor";
        //注册CompositeUriComponentsContributorFactoryBean
        RootBeanDefinition uriCompContribDef = new RootBeanDefinition(CompositeUriComponentsContributorFactoryBean.class);
        uriCompContribDef.setSource(source);
        uriCompContribDef.getPropertyValues().addPropertyValue("handlerAdapter", handlerAdapterDef);
        uriCompContribDef.getPropertyValues().addPropertyValue("conversionService", conversionService);
        parserContext.getReaderContext().getRegistry().registerBeanDefinition(uriCompContribName, uriCompContribDef);

		//注册ConversionServiceExposingInterceptor拦截器
        RootBeanDefinition csInterceptorDef = new RootBeanDefinition(ConversionServiceExposingInterceptor.class);
        csInterceptorDef.setSource(source);
        csInterceptorDef.getConstructorArgumentValues().addIndexedArgumentValue(0, conversionService);
        //注册MappedInterceptor拦截器
        RootBeanDefinition mappedCsInterceptorDef = new RootBeanDefinition(MappedInterceptor.class);
        mappedCsInterceptorDef.setSource(source);
        mappedCsInterceptorDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
        mappedCsInterceptorDef.getConstructorArgumentValues().addIndexedArgumentValue(0, (Object) null);
        mappedCsInterceptorDef.getConstructorArgumentValues().addIndexedArgumentValue(1, csInterceptorDef);
        String mappedInterceptorName = parserContext.getReaderContext().registerWithGeneratedName(mappedCsInterceptorDef);
		//注册ExceptionHandlerExceptionResolver解析器
        RootBeanDefinition exceptionHandlerExceptionResolver = new RootBeanDefinition(ExceptionHandlerExceptionResolver.class);
        exceptionHandlerExceptionResolver.setSource(source);
        exceptionHandlerExceptionResolver.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
        exceptionHandlerExceptionResolver.getPropertyValues().add("contentNegotiationManager", contentNegotiationManager);
        exceptionHandlerExceptionResolver.getPropertyValues().add("messageConverters", messageConverters);
        exceptionHandlerExceptionResolver.getPropertyValues().add("order", 0);
        addResponseBodyAdvice(exceptionHandlerExceptionResolver);

        String methodExceptionResolverName =
                parserContext.getReaderContext().registerWithGeneratedName(exceptionHandlerExceptionResolver);
		//ResponseStatusExceptionResolver异常解析器
        RootBeanDefinition responseStatusExceptionResolver = new RootBeanDefinition(ResponseStatusExceptionResolver.class);
        responseStatusExceptionResolver.setSource(source);
        responseStatusExceptionResolver.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
        responseStatusExceptionResolver.getPropertyValues().add("order", 1);
        String responseStatusExceptionResolverName =
                parserContext.getReaderContext().registerWithGeneratedName(responseStatusExceptionResolver);

		//DefaultHandlerExceptionResolver异常解析器
        RootBeanDefinition defaultExceptionResolver = new RootBeanDefinition(DefaultHandlerExceptionResolver.class);
        defaultExceptionResolver.setSource(source);
        defaultExceptionResolver.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
        defaultExceptionResolver.getPropertyValues().add("order", 2);
        String defaultExceptionResolverName =
                parserContext.getReaderContext().registerWithGeneratedName(defaultExceptionResolver);

        parserContext.registerComponent(new BeanComponentDefinition(handlerMappingDef, methodMappingName));
        parserContext.registerComponent(new BeanComponentDefinition(handlerAdapterDef, handlerAdapterName));
        parserContext.registerComponent(new BeanComponentDefinition(uriCompContribDef, uriCompContribName));
        parserContext.registerComponent(new BeanComponentDefinition(exceptionHandlerExceptionResolver, methodExceptionResolverName));
        parserContext.registerComponent(new BeanComponentDefinition(responseStatusExceptionResolver, responseStatusExceptionResolverName));
        parserContext.registerComponent(new BeanComponentDefinition(defaultExceptionResolver, defaultExceptionResolverName));
        parserContext.registerComponent(new BeanComponentDefinition(mappedCsInterceptorDef, mappedInterceptorName));
        
        // Ensure BeanNameUrlHandlerMapping (SPR-8289) and default HandlerAdapters are not "turned off"
        MvcNamespaceUtils.registerDefaultComponents(parserContext, source);

        parserContext.popAndRegisterContainingComponent();

        return null;
    }


}
private RuntimeBeanReference getContentNegotiationManager(Element element, Object source, ParserContext parserContext) {
    RuntimeBeanReference contentNegotiationManagerRef;
    if (element.hasAttribute("content-negotiation-manager")) {
        contentNegotiationManagerRef = new RuntimeBeanReference(element.getAttribute("content-negotiation-manager"));
    }
    else {
        RootBeanDefinition factoryBeanDef = new RootBeanDefinition(ContentNegotiationManagerFactoryBean.class);
        factoryBeanDef.setSource(source);
        factoryBeanDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
        factoryBeanDef.getPropertyValues().add("mediaTypes", getDefaultMediaTypes());

        String beanName = CONTENT_NEGOTIATION_MANAGER_BEAN_NAME;
        parserContext.getReaderContext().getRegistry().registerBeanDefinition(beanName , factoryBeanDef);
        parserContext.registerComponent(new BeanComponentDefinition(factoryBeanDef, beanName));
        contentNegotiationManagerRef = new RuntimeBeanReference(beanName);
    }
    return contentNegotiationManagerRef;
}
private void configurePathMatchingProperties(RootBeanDefinition handlerMappingDef,
                                             Element element, ParserContext parserContext) {
    Element pathMatchingElement = DomUtils.getChildElementByTagName(element, "path-matching");
    if (pathMatchingElement != null) {
        Object source = parserContext.extractSource(element);
        if (pathMatchingElement.hasAttribute("suffix-pattern")) {
            Boolean useSuffixPatternMatch = Boolean.valueOf(pathMatchingElement.getAttribute("suffix-pattern"));
            handlerMappingDef.getPropertyValues().add("useSuffixPatternMatch", useSuffixPatternMatch);
        }
        if (pathMatchingElement.hasAttribute("trailing-slash")) {
            Boolean useTrailingSlashMatch = Boolean.valueOf(pathMatchingElement.getAttribute("trailing-slash"));
            handlerMappingDef.getPropertyValues().add("useTrailingSlashMatch", useTrailingSlashMatch);
        }
        //url后缀是否唯一
        if (pathMatchingElement.hasAttribute("registered-suffixes-only")) {
            Boolean useRegisteredSuffixPatternMatch = Boolean.valueOf(pathMatchingElement.getAttribute("registered-suffixes-only"));
            handlerMappingDef.getPropertyValues().add("useRegisteredSuffixPatternMatch", useRegisteredSuffixPatternMatch);
        }
        RuntimeBeanReference pathHelperRef = null;
        if (pathMatchingElement.hasAttribute("path-helper")) {
            pathHelperRef = new RuntimeBeanReference(pathMatchingElement.getAttribute("path-helper"));
        }
        pathHelperRef = MvcNamespaceUtils.registerUrlPathHelper(pathHelperRef, parserContext, source);
        handlerMappingDef.getPropertyValues().add("urlPathHelper", pathHelperRef);

        RuntimeBeanReference pathMatcherRef = null;
        if (pathMatchingElement.hasAttribute("path-matcher")) {
            pathMatcherRef = new RuntimeBeanReference(pathMatchingElement.getAttribute("path-matcher"));
        }
        pathMatcherRef = MvcNamespaceUtils.registerPathMatcher(pathMatcherRef, parserContext, source);
        handlerMappingDef.getPropertyValues().add("pathMatcher", pathMatcherRef);
    }
}
public static RuntimeBeanReference registerCorsConfigurations(Map<String, CorsConfiguration> corsConfigurations, ParserContext parserContext, Object source) {
    if (!parserContext.getRegistry().containsBeanDefinition("mvcCorsConfigurations")) {
        RootBeanDefinition corsConfigurationsDef = new RootBeanDefinition(LinkedHashMap.class);
        corsConfigurationsDef.setSource(source);
        corsConfigurationsDef.setRole(2);
        if (corsConfigurations != null) {
            corsConfigurationsDef.getConstructorArgumentValues().addIndexedArgumentValue(0, corsConfigurations);
        }
        parserContext.getReaderContext().getRegistry().registerBeanDefinition("mvcCorsConfigurations", corsConfigurationsDef);
        parserContext.registerComponent(new BeanComponentDefinition(corsConfigurationsDef, "mvcCorsConfigurations"));
    }
    else if (corsConfigurations != null) {
        BeanDefinition corsConfigurationsDef = parserContext.getRegistry().getBeanDefinition("mvcCorsConfigurations");
        corsConfigurationsDef.getConstructorArgumentValues().addIndexedArgumentValue(0, corsConfigurations);
    }
    return new RuntimeBeanReference("mvcCorsConfigurations");
}
private RuntimeBeanReference getConversionService(Element element, Object source, ParserContext parserContext) {
    RuntimeBeanReference conversionServiceRef;
    if (element.hasAttribute("conversion-service")) {
        conversionServiceRef = new RuntimeBeanReference(element.getAttribute("conversion-service"));
    }
    else {
        RootBeanDefinition conversionDef = new RootBeanDefinition(FormattingConversionServiceFactoryBean.class);
        conversionDef.setSource(source);
        conversionDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
        String conversionName = parserContext.getReaderContext().registerWithGeneratedName(conversionDef);
        parserContext.registerComponent(new BeanComponentDefinition(conversionDef, conversionName));
        conversionServiceRef = new RuntimeBeanReference(conversionName);
    }
    return conversionServiceRef;
}
private RuntimeBeanReference getValidator(Element element, Object source, ParserContext parserContext) {
    if (element.hasAttribute("validator")) {
        return new RuntimeBeanReference(element.getAttribute("validator"));
    }
    else if (javaxValidationPresent) {
        RootBeanDefinition validatorDef = new RootBeanDefinition(
                "org.springframework.validation.beanvalidation.OptionalValidatorFactoryBean");
        validatorDef.setSource(source);
        validatorDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
        String validatorName = parserContext.getReaderContext().registerWithGeneratedName(validatorDef);
        parserContext.registerComponent(new BeanComponentDefinition(validatorDef, validatorName));
        return new RuntimeBeanReference(validatorName);
    }
    else {
        return null;
    }
}
private RuntimeBeanReference getMessageCodesResolver(Element element) {
    if (element.hasAttribute("message-codes-resolver")) {
        return new RuntimeBeanReference(element.getAttribute("message-codes-resolver"));
    }
    else {
        return null;
    }
}
private ManagedList<?> getMessageConverters(Element element, Object source, ParserContext parserContext) {
    Element convertersElement = DomUtils.getChildElementByTagName(element, "message-converters");
    ManagedList<? super Object> messageConverters = new ManagedList<Object>();
    if (convertersElement != null) {
    	//注册自定义messageConverter
        messageConverters.setSource(source);
        for (Element beanElement : DomUtils.getChildElementsByTagName(convertersElement, "bean", "ref")) {
            Object object = parserContext.getDelegate().parsePropertySubElement(beanElement, null);
            messageConverters.add(object);
        }
    }
	//注册默认messageConverter
    if (convertersElement == null || Boolean.valueOf(convertersElement.getAttribute("register-defaults"))) {
        messageConverters.setSource(source);
        messageConverters.add(createConverterDefinition(ByteArrayHttpMessageConverter.class, source));

        RootBeanDefinition stringConverterDef = createConverterDefinition(StringHttpMessageConverter.class, source);
        stringConverterDef.getPropertyValues().add("writeAcceptCharset", false);
        messageConverters.add(stringConverterDef);

        messageConverters.add(createConverterDefinition(ResourceHttpMessageConverter.class, source));
        messageConverters.add(createConverterDefinition(SourceHttpMessageConverter.class, source));
        messageConverters.add(createConverterDefinition(AllEncompassingFormHttpMessageConverter.class, source));

        if (romePresent) {
            messageConverters.add(createConverterDefinition(AtomFeedHttpMessageConverter.class, source));
            messageConverters.add(createConverterDefinition(RssChannelHttpMessageConverter.class, source));
        }

        if (jackson2XmlPresent) {
            RootBeanDefinition jacksonConverterDef = createConverterDefinition(MappingJackson2XmlHttpMessageConverter.class, source);
            GenericBeanDefinition jacksonFactoryDef = createObjectMapperFactoryDefinition(source);
            jacksonFactoryDef.getPropertyValues().add("createXmlMapper", true);
            jacksonConverterDef.getConstructorArgumentValues().addIndexedArgumentValue(0, jacksonFactoryDef);
            messageConverters.add(jacksonConverterDef);
        }
        else if (jaxb2Present) {
            messageConverters.add(createConverterDefinition(Jaxb2RootElementHttpMessageConverter.class, source));
        }

        if (jackson2Present) {
            RootBeanDefinition jacksonConverterDef = createConverterDefinition(MappingJackson2HttpMessageConverter.class, source);
            GenericBeanDefinition jacksonFactoryDef = createObjectMapperFactoryDefinition(source);
            jacksonConverterDef.getConstructorArgumentValues().addIndexedArgumentValue(0, jacksonFactoryDef);
            messageConverters.add(jacksonConverterDef);
        }
        else if (gsonPresent) {
            messageConverters.add(createConverterDefinition(GsonHttpMessageConverter.class, source));
        }
    }
    return messageConverters;
}
private String getAsyncTimeout(Element element) {
    Element asyncElement = DomUtils.getChildElementByTagName(element, "async-support");
    return (asyncElement != null) ? asyncElement.getAttribute("default-timeout") : null;
}

private RuntimeBeanReference getAsyncExecutor(Element element) {
    Element asyncElement = DomUtils.getChildElementByTagName(element, "async-support");
    if (asyncElement != null) {
        if (asyncElement.hasAttribute("task-executor")) {
            return new RuntimeBeanReference(asyncElement.getAttribute("task-executor"));
        }
    }
    return null;
}
private ManagedList<?> getCallableInterceptors(Element element, Object source, ParserContext parserContext) {
    ManagedList<? super Object> interceptors = new ManagedList<Object>();
    Element asyncElement = DomUtils.getChildElementByTagName(element, "async-support");
    if (asyncElement != null) {
        Element interceptorsElement = DomUtils.getChildElementByTagName(asyncElement, "callable-interceptors");
        if (interceptorsElement != null) {
            interceptors.setSource(source);
            for (Element converter : DomUtils.getChildElementsByTagName(interceptorsElement, "bean")) {
                BeanDefinitionHolder beanDef = parserContext.getDelegate().parseBeanDefinitionElement(converter);
                beanDef = parserContext.getDelegate().decorateBeanDefinitionIfRequired(converter, beanDef);
                interceptors.add(beanDef);
            }
        }
    }
    return interceptors;
}
private ManagedList<?> getDeferredResultInterceptors(Element element, Object source, ParserContext parserContext) {
    ManagedList<? super Object> interceptors = new ManagedList<Object>();
    Element asyncElement = DomUtils.getChildElementByTagName(element, "async-support");
    if (asyncElement != null) {
        Element interceptorsElement = DomUtils.getChildElementByTagName(asyncElement, "deferred-result-interceptors");
        if (interceptorsElement != null) {
            interceptors.setSource(source);
            for (Element converter : DomUtils.getChildElementsByTagName(interceptorsElement, "bean")) {
                BeanDefinitionHolder beanDef = parserContext.getDelegate().parseBeanDefinitionElement(converter);
                beanDef = parserContext.getDelegate().decorateBeanDefinitionIfRequired(converter, beanDef);
                interceptors.add(beanDef);
            }
        }
    }
    return interceptors;
}
protected void addRequestBodyAdvice(RootBeanDefinition beanDef) {
    if (jackson2Present) {
        beanDef.getPropertyValues().add("requestBodyAdvice",
                new RootBeanDefinition(JsonViewRequestBodyAdvice.class));
    }
}

protected void addResponseBodyAdvice(RootBeanDefinition beanDef) {
    if (jackson2Present) {
        beanDef.getPropertyValues().add("responseBodyAdvice",
                new RootBeanDefinition(JsonViewResponseBodyAdvice.class));
    }
}
public static void registerDefaultComponents(ParserContext parserContext, Object source) {
	//注册BeanNameUrlHandlerMapping
	registerBeanNameUrlHandlerMapping(parserContext, source);
	//注册HttpRequestHandlerAdapter
	registerHttpRequestHandlerAdapter(parserContext, source);
	//注册SimpleControllerHandlerAdapter
	registerSimpleControllerHandlerAdapter(parserContext, source);
}
private static void registerBeanNameUrlHandlerMapping(ParserContext parserContext, Object source) {
    if (!parserContext.getRegistry().containsBeanDefinition(BeanNameUrlHandlerMapping.class.getName())){
        RootBeanDefinition beanNameMappingDef = new RootBeanDefinition(BeanNameUrlHandlerMapping.class);
        beanNameMappingDef.setSource(source);
        beanNameMappingDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
        //Handler匹配url 的优先级,order越小,越优先匹配
        beanNameMappingDef.getPropertyValues().add("order", 2); // consistent with WebMvcConfigurationSupport
        RuntimeBeanReference corsConfigurationsRef = MvcNamespaceUtils.registerCorsConfigurations(null, parserContext, source);
        beanNameMappingDef.getPropertyValues().add("corsConfigurations", corsConfigurationsRef);
        parserContext.getRegistry().registerBeanDefinition(BeanNameUrlHandlerMapping.class.getName(), beanNameMappingDef);
        parserContext.registerComponent(new BeanComponentDefinition(beanNameMappingDef, BeanNameUrlHandlerMapping.class.getName()));
    }
}
private static void registerHttpRequestHandlerAdapter(ParserContext parserContext, Object source) {
    if (!parserContext.getRegistry().containsBeanDefinition(HttpRequestHandlerAdapter.class.getName())) {
        RootBeanDefinition handlerAdapterDef = new RootBeanDefinition(HttpRequestHandlerAdapter.class);
        handlerAdapterDef.setSource(source);
        handlerAdapterDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
        parserContext.getRegistry().registerBeanDefinition(HttpRequestHandlerAdapter.class.getName(), handlerAdapterDef);
        parserContext.registerComponent(new BeanComponentDefinition(handlerAdapterDef, HttpRequestHandlerAdapter.class.getName()));
    }
}
private static void registerSimpleControllerHandlerAdapter(ParserContext parserContext, Object source) {
	if (!parserContext.getRegistry().containsBeanDefinition(SimpleControllerHandlerAdapter.class.getName())) {
		RootBeanDefinition handlerAdapterDef = new RootBeanDefinition(SimpleControllerHandlerAdapter.class);
		handlerAdapterDef.setSource(source);
		handlerAdapterDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
		parserContext.getRegistry().registerBeanDefinition(SimpleControllerHandlerAdapter.class.getName(), handlerAdapterDef);
		parserContext.registerComponent(new BeanComponentDefinition(handlerAdapterDef, SimpleControllerHandlerAdapter.class.getName()));
	}
}

        解析完<mvc:annotation-driven />标签后,只是一个简单的标签,Spring 却给我们做了很多的事情,为我们添加了许多默认的配置,那哪一个配置是 Controller 注册为 Handler的 bean呢?在代码中寻寻觅觅,最终找到了RequestMappingHandlerMapping的afterPropertiesSet方法实现了Controller注册为Handler的逻辑,为什么会调用bean 的afterPropertiesSet方法呢?那来看看 bean 的生命周期吧。也就是 bean 在加入容器之前会调用afterPropertiesSet()方法。有了这个知识点,那我们就可以放心大胆的来分析RequestMappingHandlerMapping的afterPropertiesSet方法了。
在这里插入图片描述

RequestMappingHandlerMapping.java
public void afterPropertiesSet() {
	//创建BuilderConfiguration对象
    this.config = new RequestMappingInfo.BuilderConfiguration();
    this.config.setPathHelper(getUrlPathHelper());
    this.config.setPathMatcher(getPathMatcher());
    this.config.setSuffixPatternMatch(this.useSuffixPatternMatch);
    this.config.setTrailingSlashMatch(this.useTrailingSlashMatch);
    this.config.setRegisteredSuffixPatternMatch(this.useRegisteredSuffixPatternMatch);
    this.config.setContentNegotiationManager(getContentNegotiationManager());

    super.afterPropertiesSet();
}

        上述代码中RequestMappingHandlerMapping中的urlPathHelper,pathMatcher,useSuffixPatternMatch,useTrailingSlashMatch,useRegisteredSuffixPatternMatch,contentNegotiationManager是怎样来的呢?我们来看 configurePathMatchingProperties这个方法,在这个方法中,将<mvc:path-matching />内的suffix-pattern,trailing-slash,registered-suffixes-only,path-helper,path-matcher等属性注入到RequestMappingHandlerMapping中。

<mvc:annotation-driven>
    <mvc:path-matching
            suffix-pattern="true"
            trailing-slash="false"
            registered-suffixes-only="true"
            path-helper="pathHelper"
            path-matcher="pathMatcher"/>
</mvc:annotation-driven>
AbstractHandlerMethodMapping.java
public void afterPropertiesSet() {
	initHandlerMethods();
}
AbstractHandlerMethodMapping.java
protected void initHandlerMethods() {
	if (logger.isDebugEnabled()) {
		logger.debug("Looking for request mappings in application context: " + getApplicationContext());
	}
	//获取到所有的 beanNames
	String[] beanNames = (this.detectHandlerMethodsInAncestorContexts ?
			BeanFactoryUtils.beanNamesForTypeIncludingAncestors(getApplicationContext(), Object.class) :
			getApplicationContext().getBeanNamesForType(Object.class));
	//遍历容器中所有的 beanName
	for (String name : beanNames) {
		//如果 beanName不以scopedTarget.开头,bean中有注解Controller或RequestMapping注解
		if (!name.startsWith("scopedTarget.") && isHandler(getApplicationContext().getType(name))) {
			detectHandlerMethods(name);
		}
	}
	//预留给子类实现
	handlerMethodsInitialized(getHandlerMethods());
}
protected void detectHandlerMethods(final Object handler) {
	Class<?> handlerType =
			(handler instanceof String ? getApplicationContext().getType((String) handler) : handler.getClass());

	//避免重复调用getMappingForMethod来重建RequestMappingInfo实例
	final Map<Method, T> mappings = new IdentityHashMap<Method, T>();
	final Class<?> userType = ClassUtils.getUserClass(handlerType);

	Set<Method> methods = HandlerMethodSelector.selectMethods(userType, new MethodFilter() {
		@Override
		public boolean matches(Method method) {
			T mapping = getMappingForMethod(method, userType);
			if (mapping != null) {
				mappings.put(method, mapping);
				return true;
			}
			else {
				return false;
			}
		}
	});
	
	for (Method method : methods) {
		registerHandlerMethod(handler, method, mappings.get(method));
	}
}
public static Set<Method> selectMethods(final Class<?> handlerType, final MethodFilter handlerMethodFilter) {
	final Set<Method> handlerMethods = new LinkedHashSet<Method>();
	Set<Class<?>> handlerTypes = new LinkedHashSet<Class<?>>();
	Class<?> specificHandlerType = null;
	if (!Proxy.isProxyClass(handlerType)) {
		handlerTypes.add(handlerType);
		specificHandlerType = handlerType;
	}
	handlerTypes.addAll(Arrays.asList(handlerType.getInterfaces()));
	//遍历Handler的所有接口及Handler本身自己,为什么要遍历接口
	//因为 jdk1.8在接口中允许声明默认方法
	for (Class<?> currentHandlerType : handlerTypes) {
		final Class<?> targetClass = (specificHandlerType != null ? specificHandlerType : currentHandlerType);
		ReflectionUtils.doWithMethods(currentHandlerType, new ReflectionUtils.MethodCallback() {
			@Override
			public void doWith(Method method) {
				Method specificMethod = ClassUtils.getMostSpecificMethod(method, targetClass);
				//获取方法的桥接方法
				Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(specificMethod);
				if (handlerMethodFilter.matches(specificMethod) &&
						(bridgedMethod == specificMethod || !handlerMethodFilter.matches(bridgedMethod))) {
					handlerMethods.add(specificMethod);
				}
			}
		}, ReflectionUtils.USER_DECLARED_METHODS);
	}
	return handlerMethods;
}
protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
	RequestMappingInfo info = createRequestMappingInfo(method);
	if (info != null) {
		RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType);
		if (typeInfo != null) {
			info = typeInfo.combine(info);
		}
	}
	return info;
}

        在上述方法中,如果只有 Method配置了RequestMapping,则直接返回,如果Controller 也配置了 RequestMapping注解,则构建Controller 对应的RequestMappingInfo,再调用其combine方法,combine方法是如何实现,请看后面的分析。

private RequestMappingInfo createRequestMappingInfo(AnnotatedElement element) {
	//方法中是否有RequestMaping注解
	RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(element, RequestMapping.class);
	RequestCondition<?> condition = (element instanceof Class<?> ?
			getCustomTypeCondition((Class<?>) element) : getCustomMethodCondition((Method) element));
	return (requestMapping != null ? createRequestMappingInfo(requestMapping, condition) : null);
}
protected RequestMappingInfo createRequestMappingInfo(
		RequestMapping requestMapping, RequestCondition<?> customCondition) {
	//将 RequestMaping注解中值封装到RequestMappingInfo中
	return RequestMappingInfo
			.paths(resolveEmbeddedValuesInPatterns(requestMapping.path()))
			.methods(requestMapping.method())
			.params(requestMapping.params())
			.headers(requestMapping.headers())
			.consumes(requestMapping.consumes())
			.produces(requestMapping.produces())
			.mappingName(requestMapping.name())
			.customCondition(customCondition)
			.options(this.config)
			.build();
}

        上述方法其实很简单,只是将 RequestMaping 注解中的值封装到RequestMappingInfo对象中,但是值得注意的一点是resolveEmbeddedValuesInPatterns方法的使用,Spring考虑得没有我们简单,对于RequestMaping 中的 path 值,一般我们会直接写成/login,/register等形式,但是 Spring 支持${xxx.yyy} 的写法,那xxx.yyy的值从哪里取呢?从我们的配置文件中取,我们来看一个例子。

1.在 Spring 配置文件中加入PropertyPlaceholderConfigurer
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
    <property
            name="locations">
        <array>
            <value>classpath:spring_101_200/config_181_190/spring_185/url-config.properties</value>
        </array>
    </property>
</bean>

2. 添加url-config.properties文件

文件内容为 lo=/loginout

3. 准备 Handler
@RequestMapping(value ="${lo}",method = {RequestMethod.GET})
public String loginout(User user){
    System.out.println(JSON.toJSONString(user));
    return "loginout";
}

        ${lo}何时被解析成/loginout呢?我们来继续跟进代码

RequestMappingHandlerMapping.java
protected String[] resolveEmbeddedValuesInPatterns(String[] patterns) {
	if (this.embeddedValueResolver == null) {
		return patterns;
	}
	else {
		String[] resolvedPatterns = new String[patterns.length];
		for (int i = 0; i < patterns.length; i++) {
			resolvedPatterns[i] = this.embeddedValueResolver.resolveStringValue(patterns[i]);
		}
		return resolvedPatterns;
	}
}

        上述方法中有一个需要弄明白的就是embeddedValueResolver变量,会不会为空,又是从哪里注入的,先来看一下RequestMappingHandlerMapping的类结构。
在这里插入图片描述

        从上图中我们得知RequestMappingHandlerMapping实现了EmbeddedValueResolverAware接口,实现了这个接口又会怎样呢?我们继续来看 bean 的生命周期完整图
在这里插入图片描述
        在 bean 的生命周期中会调用ApplicationContextAwareProcessor 的postProcessBeforeInitialization方法,而在postProcessBeforeInitialization方法中会调用invokeAwareInterfaces方法,我们来看看invokeAwareInterfaces方法的实现。

ApplicationContextAwareProcessor.java
private void invokeAwareInterfaces(Object bean) {
	if (bean instanceof Aware) {
		if (bean instanceof EnvironmentAware) {
			((EnvironmentAware) bean).setEnvironment(this.applicationContext.getEnvironment());
		}
		if (bean instanceof EmbeddedValueResolverAware) {
			((EmbeddedValueResolverAware) bean).setEmbeddedValueResolver(
					new EmbeddedValueResolver(this.applicationContext.getBeanFactory()));
		}
		if (bean instanceof ResourceLoaderAware) {
			((ResourceLoaderAware) bean).setResourceLoader(this.applicationContext);
		}
		if (bean instanceof ApplicationEventPublisherAware) {
			((ApplicationEventPublisherAware) bean).setApplicationEventPublisher(this.applicationContext);
		}
		if (bean instanceof MessageSourceAware) {
			((MessageSourceAware) bean).setMessageSource(this.applicationContext);
		}
		if (bean instanceof ApplicationContextAware) {
			((ApplicationContextAware) bean).setApplicationContext(this.applicationContext);
		}
	}
}

        我们可以看到RequestMappingHandlerMapping实现了EmbeddedValueResolverAware接口,因此在上述方法中会调用RequestMappingHandlerMapping实例的setEmbeddedValueResolver方法将EmbeddedValueResolver对象注入。我们继续回到之前的代码。

EmbeddedValueResolver.java
private static class EmbeddedValueResolver implements StringValueResolver {

	private final ConfigurableBeanFactory beanFactory;

	public EmbeddedValueResolver(ConfigurableBeanFactory beanFactory) {
		this.beanFactory = beanFactory;
	}

	@Override
	public String resolveStringValue(String strVal) {
		return this.beanFactory.resolveEmbeddedValue(strVal);
	}
}
AbstractBeanFactory.java
public String resolveEmbeddedValue(String value) {
	String result = value;
	for (StringValueResolver resolver : this.embeddedValueResolvers) {
		if (result == null) {
			return null;
		}
		result = resolver.resolveStringValue(result);
	}
	return result;
}

        我相信大家代码跟进到这里,心里又范低估了,embeddedValueResolvers这个又是哪里来的?我们还是先来看PropertyPlaceholderConfigurer的类结构。
在这里插入图片描述
        在PropertyPlaceholderConfigurer的类结构中,我们看到他最终实现了BeanFactoryPostProcessor接口。实现这个接口有什么用呢?请听我尾尾道来。在refresh方法中,调用了一个方法invokeBeanFactoryPostProcessors方法,意思是调用 bean 工厂的所有 BeanFactoryPostProcessor实例。

PostProcessorRegistrationDelegate.java
protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {
	PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());
}

PostProcessorRegistrationDelegate.java
public static void invokeBeanFactoryPostProcessors(
		ConfigurableListableBeanFactory beanFactory, List<BeanFactoryPostProcessor> beanFactoryPostProcessors) {
	...
	String[] postProcessorNames =
			beanFactory.getBeanNamesForType(BeanFactoryPostProcessor.class, true, false);

	List<BeanFactoryPostProcessor> priorityOrderedPostProcessors = new ArrayList<BeanFactoryPostProcessor>();
	List<String> orderedPostProcessorNames = new ArrayList<String>();
	List<String> nonOrderedPostProcessorNames = new ArrayList<String>();
	for (String ppName : postProcessorNames) {
		if (processedBeans.contains(ppName)) {
			// skip - already processed in first phase above
		}
		else if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {
			priorityOrderedPostProcessors.add(beanFactory.getBean(ppName, BeanFactoryPostProcessor.class));
		}
		else if (beanFactory.isTypeMatch(ppName, Ordered.class)) {
			orderedPostProcessorNames.add(ppName);
		}
		else {
			nonOrderedPostProcessorNames.add(ppName);
		}
	}

	sortPostProcessors(beanFactory, priorityOrderedPostProcessors);
	invokeBeanFactoryPostProcessors(priorityOrderedPostProcessors, beanFactory);
	...
}

	

        上述方法也很简单,就是获取所有实现BeanFactoryPostProcessor接口的 bean,根据注解排序,再调用invokeBeanFactoryPostProcessors方法。那么invokeBeanFactoryPostProcessors方法是如何实现的呢?

PostProcessorRegistrationDelegate.java
private static void invokeBeanFactoryPostProcessors(
		Collection<? extends BeanFactoryPostProcessor> postProcessors, ConfigurableListableBeanFactory beanFactory) {

	for (BeanFactoryPostProcessor postProcessor : postProcessors) {
		postProcessor.postProcessBeanFactory(beanFactory);
	}
}
	

        invokeBeanFactoryPostProcessors内部实现也非常简单,就是遍历所有的BeanFactoryPostProcessor,并调用其postProcessBeanFactory方法。在本例中,我们关注的是PropertyPlaceholderConfigurer类的postProcessBeanFactory内部实现。

PropertyResourceConfigurer.java
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
	try {
		//合并属性
		Properties mergedProps = mergeProperties();
		//转化属性
		convertProperties(mergedProps);
		//处理属性
		processProperties(beanFactory, mergedProps);
	}
	catch (IOException ex) {
		throw new BeanInitializationException("Could not load properties", ex);
	}
}	
protected void processProperties(ConfigurableListableBeanFactory beanFactoryToProcess, Properties props)
		throws BeansException {
	//创建PlaceholderResolvingStringValueResolver对象
	StringValueResolver valueResolver = new PlaceholderResolvingStringValueResolver(props);
	doProcessProperties(beanFactoryToProcess, valueResolver);
}
protected void doProcessProperties(ConfigurableListableBeanFactory beanFactoryToProcess,
		StringValueResolver valueResolver) {

	BeanDefinitionVisitor visitor = new BeanDefinitionVisitor(valueResolver);

	String[] beanNames = beanFactoryToProcess.getBeanDefinitionNames();
	for (String curName : beanNames) {
		if (!(curName.equals(this.beanName) && beanFactoryToProcess.equals(this.beanFactory))) {
			BeanDefinition bd = beanFactoryToProcess.getBeanDefinition(curName);
			try {
				visitor.visitBeanDefinition(bd);
			}
			catch (Exception ex) {
				throw new BeanDefinitionStoreException(bd.getResourceDescription(), curName, ex.getMessage(), ex);
			}
		}
	}
	beanFactoryToProcess.resolveAliases(valueResolver);

	beanFactoryToProcess.addEmbeddedValueResolver(valueResolver);
}

        我们终于看到我们想看到的方法addEmbeddedValueResolver,下面来看看其内部实现。

AbstractBeanFactory.java
public void addEmbeddedValueResolver(StringValueResolver valueResolver) {
	Assert.notNull(valueResolver, "StringValueResolver must not be null");
	this.embeddedValueResolvers.add(valueResolver);
}

        在这个方法中,我们终于看到了在之前processProperties方法中 new 的PlaceholderResolvingStringValueResolver对象被加入到embeddedValueResolvers集合中,在之前的resolveEmbeddedValue方法中,将调用PlaceholderResolvingStringValueResolver对象的resolveStringValue方法,去除url的前缀(${)和后缀(}),再从配置文件url-config.properties中获取对应的值。关于RequestMapping 中配置了${xxx.yyy}形式的 url,这里己经解析完了,我们继续在创建RequestMappingInfo之后的分析。

RequestMappingInfo.java
public RequestMappingInfo combine(RequestMappingInfo other) {
	//如果两者名字都不为空,则以#分隔
	String name = combineNames(other);
	//如果两者的 patterns 都不为空
	PatternsRequestCondition patterns = this.patternsCondition.combine(other.patternsCondition);
	RequestMethodsRequestCondition methods = this.methodsCondition.combine(other.methodsCondition);
	ParamsRequestCondition params = this.paramsCondition.combine(other.paramsCondition);
	HeadersRequestCondition headers = this.headersCondition.combine(other.headersCondition);
	ConsumesRequestCondition consumes = this.consumesCondition.combine(other.consumesCondition);
	ProducesRequestCondition produces = this.producesCondition.combine(other.producesCondition);
	RequestConditionHolder custom = this.customConditionHolder.combine(other.customConditionHolder);

	return new RequestMappingInfo(name, patterns,
			methods, params, headers, consumes, produces, custom.getCondition());
}
private String combineNames(RequestMappingInfo other) {
	if (this.name != null && other.name != null) {
		String separator = RequestMappingInfoHandlerMethodMappingNamingStrategy.SEPARATOR;
		return this.name + separator + other.name;
	}
	else if (this.name != null) {
		return this.name;
	}
	else {
		return (other.name != null ? other.name : null);
	}
}
PatternsRequestCondition.java
public PatternsRequestCondition combine(PatternsRequestCondition other) {
	Set<String> result = new LinkedHashSet<String>();
	if (!this.patterns.isEmpty() && !other.patterns.isEmpty()) {
		for (String pattern1 : this.patterns) {
			for (String pattern2 : other.patterns) {
				result.add(this.pathMatcher.combine(pattern1, pattern2));
			}
		}
	}
	else if (!this.patterns.isEmpty()) {
		result.addAll(this.patterns);
	}
	else if (!other.patterns.isEmpty()) {
		result.addAll(other.patterns);
	}
	else {
		result.add("");
	}
	return new PatternsRequestCondition(result, this.pathHelper, this.pathMatcher, this.useSuffixPatternMatch,
			this.useTrailingSlashMatch, this.fileExtensions);
}

        上述代码中浅显易懂,就是pathMatcher是从哪里赋值的呢?我们来看看PatternsRequestCondition的构造函数

private PatternsRequestCondition(Collection<String> patterns, UrlPathHelper urlPathHelper,
		PathMatcher pathMatcher, boolean useSuffixPatternMatch, boolean useTrailingSlashMatch,
		List<String> fileExtensions) {

	this.patterns = Collections.unmodifiableSet(prependLeadingSlash(patterns));
	this.pathHelper = (urlPathHelper != null ? urlPathHelper : new UrlPathHelper());
	this.pathMatcher = (pathMatcher != null ? pathMatcher : new AntPathMatcher());
	this.useSuffixPatternMatch = useSuffixPatternMatch;
	this.useTrailingSlashMatch = useTrailingSlashMatch;
	if (fileExtensions != null) {
		for (String fileExtension : fileExtensions) {
			if (fileExtension.charAt(0) != '.') {
				fileExtension = "." + fileExtension;
			}
			this.fileExtensions.add(fileExtension);
		}
	}
}

        如果用户没有设定,则使用默认的AntPathMatcher对象作为pathMatcher的值,我们跟进AntPathMatcher中看看combine如何实现。

AntPathMatcher.java
/**
 * Combine two patterns into a new pattern.
 * <p>This implementation simply concatenates the two patterns, unless
 * the first pattern contains a file extension match (e.g., {@code *.html}).
 * In that case, the second pattern will be merged into the first. Otherwise,
 * an {@code IllegalArgumentException} will be thrown.
 * <h3>Examples</h3>
 * <table border="1">
 * <tr><th>Pattern 1</th><th>Pattern 2</th><th>Result</th></tr>
 * <tr><td>{@code null}</td><td>{@code null}</td><td> </td></tr>
 * <tr><td>/hotels</td><td>{@code null}</td><td>/hotels</td></tr>
 * <tr><td>{@code null}</td><td>/hotels</td><td>/hotels</td></tr>
 * <tr><td>/hotels</td><td>/bookings</td><td>/hotels/bookings</td></tr>
 * <tr><td>/hotels</td><td>bookings</td><td>/hotels/bookings</td></tr>
 * <tr><td>/hotels/*</td><td>/bookings</td><td>/hotels/bookings</td></tr>
 * <tr><td>/hotels/**</td><td>/bookings</td><td>/hotels/**/bookings</td></tr>
 * <tr><td>/hotels</td><td>{hotel}</td><td>/hotels/{hotel}</td></tr>
 * <tr><td>/hotels/*</td><td>{hotel}</td><td>/hotels/{hotel}</td></tr>
 * <tr><td>/hotels/**</td><td>{hotel}</td><td>/hotels/**/{hotel}</td></tr>
 * <tr><td>/*.html</td><td>/hotels.html</td><td>/hotels.html</td></tr>
 * <tr><td>/*.html</td><td>/hotels</td><td>/hotels.html</td></tr>
 * <tr><td>/*.html</td><td>/*.txt</td><td>{@code IllegalArgumentException}</td></tr>
 * </table>
 * @param pattern1 the first pattern
 * @param pattern2 the second pattern
 * @return the combination of the two patterns
 * @throws IllegalArgumentException if the two patterns cannot be combined
 */
@Override
public String combine(String pattern1, String pattern2) {
	if (!StringUtils.hasText(pattern1) && !StringUtils.hasText(pattern2)) {
		return "";
	}
	if (!StringUtils.hasText(pattern1)) {
		return pattern2;
	}
	if (!StringUtils.hasText(pattern2)) {
		return pattern1;
	}

	boolean pattern1ContainsUriVar = (pattern1.indexOf('{') != -1);
	if (!pattern1.equals(pattern2) && !pattern1ContainsUriVar && match(pattern1, pattern2)) {
		// /* + /hotel -> /hotel ; "/*.*" + "/*.html" -> /*.html
		// However /user + /user -> /usr/user ; /{foo} + /bar -> /{foo}/bar
		return pattern2;
	}

	// /hotels/* + /booking -> /hotels/booking
	// /hotels/* + booking -> /hotels/booking
	if (pattern1.endsWith(this.pathSeparatorPatternCache.getEndsOnWildCard())) {
		return concat(pattern1.substring(0, pattern1.length() - 2), pattern2);
	}

	// /hotels/** + /booking -> /hotels/**/booking
	// /hotels/** + booking -> /hotels/**/booking
	if (pattern1.endsWith(this.pathSeparatorPatternCache.getEndsOnDoubleWildCard())) {
		return concat(pattern1, pattern2);
	}

	int starDotPos1 = pattern1.indexOf("*.");
	if (pattern1ContainsUriVar || starDotPos1 == -1 || this.pathSeparator.equals(".")) {
		// simply concatenate the two patterns
		return concat(pattern1, pattern2);
	}

	String ext1 = pattern1.substring(starDotPos1 + 1);
	int dotPos2 = pattern2.indexOf('.');
	String file2 = (dotPos2 == -1 ? pattern2 : pattern2.substring(0, dotPos2));
	String ext2 = (dotPos2 == -1 ? "" : pattern2.substring(dotPos2));
	boolean ext1All = (ext1.equals(".*") || ext1.equals(""));
	boolean ext2All = (ext2.equals(".*") || ext2.equals(""));
	if (!ext1All && !ext2All) {
		throw new IllegalArgumentException("Cannot combine patterns: " + pattern1 + " vs " + pattern2);
	}
	String ext = (ext1All ? ext2 : ext1);
	return file2 + ext;
}
private String concat(String path1, String path2) {
	boolean path1EndsWithSeparator = path1.endsWith(this.pathSeparator);
	boolean path2StartsWithSeparator = path2.startsWith(this.pathSeparator);

	if (path1EndsWithSeparator && path2StartsWithSeparator) {
		return path1 + path2.substring(1);
	}
	else if (path1EndsWithSeparator || path2StartsWithSeparator) {
		return path1 + path2;
	}
	else {
		return path1 + this.pathSeparator + path2;
	}
}

        从上述代码中来看,AntPathMatcher 的 combine方法主要是将两个 url 给 concat 起来,但是 concat 方法的实现也为我们考虑到了多种情况。

  • url1以/结尾,url2以/开头,则去除 url2开头的/,再将 url1+url2。
  • url1以/结尾,url2不以/开头,直接 url1+url2。
  • url1不以/结尾,url2不以/开头,则 url1+"/"+url2。

        combine方法中,Spring 为我们考虑得更多,如url1为/*.html,url2为/hotels,最终组合成/hotels.html的 url,有兴趣的同学可以自己去研究一下combine方法。接下来,我们来看看 RequestMapping中其他属性的 combine 方法。如RequestMethodsRequestCondition的combine方法

RequestMethodsRequestCondition.java
public RequestMethodsRequestCondition combine(RequestMethodsRequestCondition other) {
	//先将 Controller 的 RequestMapping注解的method属性加入到 set 集体中
	Set<RequestMethod> set = new LinkedHashSet<RequestMethod>(this.methods);
	//先将 method 的 RequestMapping注解的method属性加入到 set 集体中
	set.addAll(other.methods);
	return new RequestMethodsRequestCondition(set);
}

        在上述 combine 方法中,先加入的是 Controller 的 RequestMapping 中的属性,再用 Method 方法中的 RequestMapping 注解属性去覆盖,如果Controller 的 RequestMapping也配置了 method属性为 a,Method 方法中的 RequestMapping也配置了method 属性为 b,最终 a 会被 b 覆盖,最重 method属性值为 b。其他属性的combine 的实现也大同小异,这里就不做过多分析了。
        通过combine 方法,我们最终得到了组合的RequestMappingInfo对象。最终将保存到mappings集合中。

AbstractHandlerMethodMapping.java
protected void registerHandlerMethod(Object handler, Method method, T mapping) {
	this.mappingRegistry.register(mapping, handler, method);
}
AbstractHandlerMethodMapping.java
public void register(T mapping, Object handler, Method method) {

	this.readWriteLock.writeLock().lock();
	try {
		//构建HandlerMethod对象
		HandlerMethod handlerMethod = createHandlerMethod(handler, method);
		//判断mapping对应的 Handler 是不是唯一
		assertUniqueMethodMapping(handlerMethod, mapping);
		//将handlerMethod以mapping为 key 保存到mappingLookup中
		this.mappingLookup.put(mapping, handlerMethod);

		List<String> directUrls = getDirectUrls(mapping);
		for (String url : directUrls) {
			this.urlLookup.add(url, mapping);
		}

		String name = null;
		if (getNamingStrategy() != null) {
			//如果 Handler为 MainController
			//method 为 main
			//name为 MC#main
			name = getNamingStrategy().getName(handlerMethod, mapping);
			addMappingName(name, handlerMethod);
		}
		//如果 handler或方法中有CrossOrigin注解,则进行跨域相关配置
		CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping);
		if (corsConfig != null) {
			this.corsLookup.put(handlerMethod, corsConfig);
		}

		this.registry.put(mapping, new MappingRegistration<T>(mapping, handlerMethod, directUrls, name));
	}
	finally {
		this.readWriteLock.writeLock().unlock();
	}
}
private void assertUniqueMethodMapping(HandlerMethod newHandlerMethod, T mapping) {
	HandlerMethod handlerMethod = this.mappingLookup.get(mapping);
	if (handlerMethod != null && !handlerMethod.equals(newHandlerMethod)) {
		throw new IllegalStateException(
				"Ambiguous mapping. Cannot map '" +	newHandlerMethod.getBean() + "' method \n" +
						newHandlerMethod + "\nto " +	mapping + ": There is already '" +
						handlerMethod.getBean() + "' bean method\n" + handlerMethod + " mapped.");
	}
}

private List<String> getDirectUrls(T mapping) {
	List<String> urls = new ArrayList<String>(1);
	for (String path : getMappingPathPatterns(mapping)) {
		//如果 url 中没有*或 ?  ,将 url直接加入到 urls 中
		if (!getPathMatcher().isPattern(path)) {
			urls.add(path);
		}
	}
	return urls;
}

private void addMappingName(String name, HandlerMethod handlerMethod) {

	List<HandlerMethod> oldList = this.nameLookup.containsKey(name) ?
			this.nameLookup.get(name) : Collections.<HandlerMethod>emptyList();
	for (HandlerMethod current : oldList) {
		if (handlerMethod.equals(current)) {
			return;
		}
	}

	if (logger.isTraceEnabled()) {
		logger.trace("Mapping name=" + name);
	}

	List<HandlerMethod> newList = new ArrayList<HandlerMethod>(oldList.size() + 1);
	newList.addAll(oldList);
	newList.add(handlerMethod);
	this.nameLookup.put(name, newList);

	if (newList.size() > 1) {
		if (logger.isTraceEnabled()) {
			logger.trace("Mapping name clash for handlerMethods=" + newList +
					". Consider assigning explicit names.");
		}
	}
}
RequestMappingHandlerMapping.java
protected CorsConfiguration initCorsConfiguration(Object handler, Method method, RequestMappingInfo mappingInfo) {
	HandlerMethod handlerMethod = createHandlerMethod(handler, method);
	CrossOrigin typeAnnotation = AnnotationUtils.findAnnotation(handlerMethod.getBeanType(), CrossOrigin.class);
	CrossOrigin methodAnnotation = AnnotationUtils.findAnnotation(method, CrossOrigin.class);

	if (typeAnnotation == null && methodAnnotation == null) {
		return null;
	}

	CorsConfiguration config = new CorsConfiguration();
	//将 Controller 中的CrossOrigin注解中的信息更新到 config 中
	updateCorsConfig(config, typeAnnotation);
	//将 Method 中的CrossOrigin注解中的信息更新到 config 中
	updateCorsConfig(config, methodAnnotation);
	
	//下面主要是关于一些默认值的设置了
	if (CollectionUtils.isEmpty(config.getAllowedOrigins())) {
		config.setAllowedOrigins(Arrays.asList( { "*" }));
	}
	if (CollectionUtils.isEmpty(config.getAllowedMethods())) {
		for (RequestMethod allowedMethod : mappingInfo.getMethodsCondition().getMethods()) {
			config.addAllowedMethod(allowedMethod.name());
		}
	}
	if (CollectionUtils.isEmpty(config.getAllowedHeaders())) {
		config.setAllowedHeaders(Arrays.asList({ "*" }));
	}
	if (config.getAllowCredentials() == null) {
		config.setAllowCredentials(true);
	}
	if (config.getMaxAge() == null) {
		config.setMaxAge(1800);
	}
	return config;
}

private void updateCorsConfig(CorsConfiguration config, CrossOrigin annotation) {
	if (annotation == null) {
		return;
	}
	for (String origin : annotation.origins()) {
		config.addAllowedOrigin(origin);
	}
	for (RequestMethod method : annotation.methods()) {
		config.addAllowedMethod(method.name());
	}
	for (String header : annotation.allowedHeaders()) {
		config.addAllowedHeader(header);
	}
	for (String header : annotation.exposedHeaders()) {
		config.addExposedHeader(header);
	}

	String allowCredentials = annotation.allowCredentials();
	if ("true".equalsIgnoreCase(allowCredentials)) {
		config.setAllowCredentials(true);
	}
	else if ("false".equalsIgnoreCase(allowCredentials)) {
		config.setAllowCredentials(false);
	}
	else if (!allowCredentials.isEmpty()) {
		throw new IllegalStateException("@CrossOrigin's allowCredentials value must be \"true\", \"false\", "
				+ "or an empty string (\"\"); current value is [" + allowCredentials + "].");
	}

	if (annotation.maxAge() >= 0 && config.getMaxAge() == null) {
		config.setMaxAge(annotation.maxAge());
	}
}

        代码进行到这里,终于完完整整的将RequestMapping 解析完成。下面来看看url映射是如何找到 HandlerMethod 的,我们来到DispatcherServlet的getHandler方法

DispatcherServlet.java
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
	for (HandlerMapping hm : this.handlerMappings) {
		if (logger.isTraceEnabled()) {
			logger.trace(
					"Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'");
		}
		HandlerExecutionChain handler = hm.getHandler(request);
		if (handler != null) {
			return handler;
		}
	}
	return null;
}

在这里插入图片描述
        从上图中,我们得知handlerMappings集合中有RequestMappingHandlerMapping和BeanNameUrlHandlerMapping,而这两个handler 是从哪里注入的呢?首先,我们看<mvc:annotation-driven />标签解析,在分别在parse方法中和registerBeanNameUrlHandlerMapping方法中注册了RequestMappingHandlerMapping和BeanNameUrlHandlerMapping,而又在 DispacherServlet 的 init 方法中,将值注入。

DispacherServlet.java
private void initHandlerMappings(ApplicationContext context) {
	this.handlerMappings = null;

	if (this.detectAllHandlerMappings) {
		//从容器中获取所有实现了HandlerMapping的 bean
		Map<String, HandlerMapping> matchingBeans =
				BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
		if (!matchingBeans.isEmpty()) {
			this.handlerMappings = new ArrayList<HandlerMapping>(matchingBeans.values());
			//以order 值排序,越小,越优先匹配
			AnnotationAwareOrderComparator.sort(this.handlerMappings);
		}
	}
	else {
		try {
			HandlerMapping hm = context.getBean("handlerMapping", HandlerMapping.class);
			this.handlerMappings = Collections.singletonList(hm);
		}
		catch (NoSuchBeanDefinitionException ex) {
		}
	}

	if (this.handlerMappings == null) {
		//获取默认的 HandlerMapper 
		//org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
		//	org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping
		this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
		if (logger.isDebugEnabled()) {
			logger.debug("No HandlerMappings found in servlet '" + getServletName() + "': using default");
		}
	}
}

在这里插入图片描述
在这里插入图片描述

        detectAllHandlerMappings的默认值为 true,显然RequestMappingHandlerMapping和BeanNameUrlHandlerMapping都实现了HandlerMapping接口和Ordered接口,而在parse方法中和registerBeanNameUrlHandlerMapping方法中分别为RequestMappingHandlerMapping和BeanNameUrlHandlerMapping设置了order 值分别是0和2,而上述sort方法的排序原则是order 值越小,越排在前面,所以RequestMappingHandlerMapping优先匹配 url,下面我们进入RequestMappingHandlerMapping的getHandler方法。

AbstractHandlerMapping.java
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
	Object handler = getHandlerInternal(request);
	if (handler == null) {
		handler = getDefaultHandler();
	}
	if (handler == null) {
		return null;
	}
	
	if (handler instanceof String) {
		String handlerName = (String) handler;
		handler = getApplicationContext().getBean(handlerName);
	}

	HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
	if (CorsUtils.isCorsRequest(request)) {
		CorsConfiguration globalConfig = this.corsConfigSource.getCorsConfiguration(request);
		CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
		CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig);
		executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
	}
	return executionChain;
}
AbstractHandlerMethodMapping.java
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
	String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
	if (logger.isDebugEnabled()) {
		logger.debug("Looking up handler method for path " + lookupPath);
	}
	this.mappingRegistry.acquireReadLock();
	try {
		HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
		if (logger.isDebugEnabled()) {
			if (handlerMethod != null) {
				logger.debug("Returning handler method [" + handlerMethod + "]");
			}
			else {
				logger.debug("Did not find handler method for [" + lookupPath + "]");
			}
		}
		return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
	}
	finally {
		this.mappingRegistry.releaseReadLock();
	}
}
AbstractHandlerMethodMapping.java
private static final HandlerMethod PREFLIGHT_AMBIGUOUS_MATCH =
			new HandlerMethod(new EmptyHandler(), ClassUtils.getMethod(EmptyHandler.class, "handle"));


protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
	List<Match> matches = new ArrayList<Match>();
	//先通过url 到urlLookup中取
	List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
	if (directPathMatches != null) {
		addMatchingMappings(directPathMatches, matches, request);
	}
	if (matches.isEmpty()) {
		//如果从urlLookup中没有取到,则遍历所有的 mapping
		addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);
	}

	if (!matches.isEmpty()) {
		Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
		Collections.sort(matches, comparator);
		if (logger.isTraceEnabled()) {
			logger.trace("Found " + matches.size() + " matching mapping(s) for [" +
					lookupPath + "] : " + matches);
		}
		Match bestMatch = matches.get(0);
		if (matches.size() > 1) {
			if (CorsUtils.isPreFlightRequest(request)) {
				return PREFLIGHT_AMBIGUOUS_MATCH;
			}
			Match secondBestMatch = matches.get(1);
			//比较两个方法,如果两个方法一样的匹配,那就抛出异常
			// 如 method1的 url为/test/**
			//method2的 url 为 test/login
			//请求的 url 匹配到 method2 
			if (comparator.compare(bestMatch, secondBestMatch) == 0) {
				//如果两方法的优先级相同,抛出异常,如
				//method1 的url为/b/{xx}
				//method2 的url为/b/{yy}
				//此时request 的请求 url为http://localhost:8080/test/b/1
				//将抛出
				//org.springframework.web.util.NestedServletException: Request processing failed; nested exception is java.lang.IllegalStateException: Ambiguous handler methods mapped for HTTP path 'http://localhost:8080/test/b/1': {public org.springframework.web.servlet.ModelAndView com.spring_101_200.test_181_190.test_185_spring_mvc.UserController.b1(java.lang.Integer), public org.springframework.web.servlet.ModelAndView com.spring_101_200.test_181_190.test_185_spring_mvc.UserController.b2(java.lang.Integer)}异常
				Method m1 = bestMatch.handlerMethod.getMethod();
				Method m2 = secondBestMatch.handlerMethod.getMethod();
				throw new IllegalStateException("Ambiguous handler methods mapped for HTTP path '" +
						request.getRequestURL() + "': {" + m1 + ", " + m2 + "}");
			}
		}
		
		handleMatch(bestMatch.mapping, lookupPath, request);
		return bestMatch.handlerMethod;
	}
	else {
		return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request);
	}
}
private void addMatchingMappings(Collection<T> mappings, List<Match> matches, HttpServletRequest request) {
	for (T mapping : mappings) {
		T match = getMatchingMapping(mapping, request);
		if (match != null) {
			matches.add(new Match(match, this.mappingRegistry.getMappings().get(mapping)));
		}
	}
}
RequestMappingInfoHandlerMapping.java
protected RequestMappingInfo getMatchingMapping(RequestMappingInfo info, HttpServletRequest request) {
	return info.getMatchingCondition(request);
}
PatternsRequestCondition.java
public RequestMappingInfo getMatchingCondition(HttpServletRequest request) {
	//判断 method 是否匹配,如果 request 是 get 请求,而 RequestMappingInfo 是 post 请求,将返回 null
	RequestMethodsRequestCondition methods = this.methodsCondition.getMatchingCondition(request);
	//request中的 params表达式是否匹配
	ParamsRequestCondition params = this.paramsCondition.getMatchingCondition(request);
	//request中的 headers表达式是否匹配
	HeadersRequestCondition headers = this.headersCondition.getMatchingCondition(request);
	//请求的提交内容类型(Content-Type)是否匹配
	ConsumesRequestCondition consumes = this.consumesCondition.getMatchingCondition(request);
	//返回值类型及字符编码是否匹配
	ProducesRequestCondition produces = this.producesCondition.getMatchingCondition(request);

	if (methods == null || params == null || headers == null || consumes == null || produces == null) {
		//如果是跨域请求
		if (CorsUtils.isPreFlightRequest(request)) {
			methods = getAccessControlRequestMethodCondition(request);
			if (methods == null || params == null) {
				return null;
			}
		}
		else {
			return null;
		}
	}

	PatternsRequestCondition patterns = this.patternsCondition.getMatchingCondition(request);
	if (patterns == null) {
		return null;
	}

	RequestConditionHolder custom = this.customConditionHolder.getMatchingCondition(request);
	if (custom == null) {
		return null;
	}
	//如果所有的匹配成功,则构建RequestMappingInfo返回
	return new RequestMappingInfo(this.name, patterns,
			methods, params, headers, consumes, produces, custom.getCondition());
}
public PatternsRequestCondition getMatchingCondition(HttpServletRequest request) {

	if (this.patterns.isEmpty()) {
		return this;
	}
	//获取 request 的 url
	String lookupPath = this.pathHelper.getLookupPathForRequest(request);
	List<String> matches = getMatchingPatterns(lookupPath);

	return matches.isEmpty() ? null :
		new PatternsRequestCondition(matches, this.pathHelper, this.pathMatcher, this.useSuffixPatternMatch,
				this.useTrailingSlashMatch, this.fileExtensions);
}
public List<String> getMatchingPatterns(String lookupPath) {
	List<String> matches = new ArrayList<String>();
	for (String pattern : this.patterns) {
		String match = getMatchingPattern(pattern, lookupPath);
		if (match != null) {
			matches.add(match);
		}
	}
	Collections.sort(matches, this.pathMatcher.getPatternComparator(lookupPath));
	return matches;
}

private String getMatchingPattern(String pattern, String lookupPath) {
	if (pattern.equals(lookupPath)) {
		return pattern;
	}
	//使用后缀匹配
	if (this.useSuffixPatternMatch) {
		//如果fileExtensions不为空
		if (!this.fileExtensions.isEmpty() && lookupPath.indexOf('.') != -1) {
			//遍历fileExtensions中所有后缀
			for (String extension : this.fileExtensions) {
				//将pattern + extension与 url 匹配
				if (this.pathMatcher.match(pattern + extension, lookupPath)) {
					return pattern + extension;
				}
			}
		}
		else {
			//pattern+"*"与 url 匹配
			boolean hasSuffix = pattern.indexOf('.') != -1;
			if (!hasSuffix && this.pathMatcher.match(pattern + ".*", lookupPath)) {
				return pattern + ".*";
			}
		}
	}
	//普通匹配
	if (this.pathMatcher.match(pattern, lookupPath)) {
		return pattern;
	}
	//使用后缀/进行匹配,如果 pattern没有/,则用 pattern+"/"与 url 匹配
	if (this.useTrailingSlashMatch) {
		if (!pattern.endsWith("/") && this.pathMatcher.match(pattern + "/", lookupPath)) {
			return pattern +"/";
		}
	}
	return null;
}
AntPathMatcher.java
public boolean match(String pattern, String path) {
	return doMatch(pattern, path, true, null);
}
protected boolean doMatch(String pattern, String path, boolean fullMatch, Map<String, String> uriTemplateVariables) {
	if (path.startsWith(this.pathSeparator) != pattern.startsWith(this.pathSeparator)) {
		return false;
	}
	String[] pattDirs = tokenizePattern(pattern);
	String[] pathDirs = tokenizePath(path);

	int pattIdxStart = 0;
	int pattIdxEnd = pattDirs.length - 1;
	int pathIdxStart = 0;
	int pathIdxEnd = pathDirs.length - 1;

	// Match all elements up to the first **
	while (pattIdxStart <= pattIdxEnd && pathIdxStart <= pathIdxEnd) {
		String pattDir = pattDirs[pattIdxStart];
		if ("**".equals(pattDir)) {
			break;
		}
		if (!matchStrings(pattDir, pathDirs[pathIdxStart], uriTemplateVariables)) {
			return false;
		}
		pattIdxStart++;
		pathIdxStart++;
	}

	if (pathIdxStart > pathIdxEnd) {
		// Path is exhausted, only match if rest of pattern is * or **'s
		if (pattIdxStart > pattIdxEnd) {
			return (pattern.endsWith(this.pathSeparator) ? path.endsWith(this.pathSeparator) :
					!path.endsWith(this.pathSeparator));
		}
		if (!fullMatch) {
			return true;
		}
		if (pattIdxStart == pattIdxEnd && pattDirs[pattIdxStart].equals("*") && path.endsWith(this.pathSeparator)) {
			return true;
		}
		for (int i = pattIdxStart; i <= pattIdxEnd; i++) {
			if (!pattDirs[i].equals("**")) {
				return false;
			}
		}
		return true;
	}
	else if (pattIdxStart > pattIdxEnd) {
		// String not exhausted, but pattern is. Failure.
		return false;
	}
	else if (!fullMatch && "**".equals(pattDirs[pattIdxStart])) {
		// Path start definitely matches due to "**" part in pattern.
		return true;
	}

	// up to last '**'
	while (pattIdxStart <= pattIdxEnd && pathIdxStart <= pathIdxEnd) {
		String pattDir = pattDirs[pattIdxEnd];
		if (pattDir.equals("**")) {
			break;
		}
		if (!matchStrings(pattDir, pathDirs[pathIdxEnd], uriTemplateVariables)) {
			return false;
		}
		pattIdxEnd--;
		pathIdxEnd--;
	}
	if (pathIdxStart > pathIdxEnd) {
		// String is exhausted
		for (int i = pattIdxStart; i <= pattIdxEnd; i++) {
			if (!pattDirs[i].equals("**")) {
				return false;
			}
		}
		return true;
	}

	while (pattIdxStart != pattIdxEnd && pathIdxStart <= pathIdxEnd) {
		int patIdxTmp = -1;
		for (int i = pattIdxStart + 1; i <= pattIdxEnd; i++) {
			if (pattDirs[i].equals("**")) {
				patIdxTmp = i;
				break;
			}
		}
		if (patIdxTmp == pattIdxStart + 1) {
			// '**/**' situation, so skip one
			pattIdxStart++;
			continue;
		}
		// Find the pattern between padIdxStart & padIdxTmp in str between
		// strIdxStart & strIdxEnd
		int patLength = (patIdxTmp - pattIdxStart - 1);
		int strLength = (pathIdxEnd - pathIdxStart + 1);
		int foundIdx = -1;

		strLoop:
		for (int i = 0; i <= strLength - patLength; i++) {
			for (int j = 0; j < patLength; j++) {
				String subPat = pattDirs[pattIdxStart + j + 1];
				String subStr = pathDirs[pathIdxStart + i + j];
				if (!matchStrings(subPat, subStr, uriTemplateVariables)) {
					continue strLoop;
				}
			}
			foundIdx = pathIdxStart + i;
			break;
		}

		if (foundIdx == -1) {
			return false;
		}

		pattIdxStart = patIdxTmp;
		pathIdxStart = foundIdx + patLength;
	}

	for (int i = pattIdxStart; i <= pattIdxEnd; i++) {
		if (!pattDirs[i].equals("**")) {
			return false;
		}
	}

	return true;
}

        关于 doMatch 方法如何匹配,感兴趣的同学可以写个 main方法,测试一下,就一目了然了,这里就不再做过多的分析了。接下来,我们继续来看handleMatch方法的处理。

protected void handleMatch(RequestMappingInfo info, String lookupPath, HttpServletRequest request) {
	super.handleMatch(info, lookupPath, request);

	String bestPattern;
	Map<String, String> uriVariables;
	Map<String, String> decodedUriVariables;

	Set<String> patterns = info.getPatternsCondition().getPatterns();
	if (patterns.isEmpty()) {
		bestPattern = lookupPath;
		uriVariables = Collections.emptyMap();
		decodedUriVariables = Collections.emptyMap();
	}
	else {
		bestPattern = patterns.iterator().next();
		//获取url地址中的变量和值的映射
		//如bestPattern为 b/{id},lookupPath为 b/1
		//则uriVariables为{"id",1}
		uriVariables = getPathMatcher().extractUriTemplateVariables(bestPattern, lookupPath);
		//对uriVariables变量的值进行解码
		decodedUriVariables = getUrlPathHelper().decodePathVariables(request, uriVariables);
	}

	request.setAttribute(HandlerMapping.class.getName() + ".bestMatchingPattern", bestPattern);
	//request 中设置 uri 临时变量
	request.setAttribute(HandlerMapping.class.getName() + ".uriTemplateVariables", decodedUriVariables);

	//如果配置了enable-matrix-variables="true"
	if (isMatrixVariableContentAvailable()) {
		//1.Controller中方法为
		//@RequestMapping(value = "/m1/owners/{ownerId}/pets/{petId}", method = RequestMethod.GET)
		//public String findPet(@MatrixVariable Map<String, String> matrixVars,
        //              @MatrixVariable(pathVar = "petId") Map<String, String> petMatrixVars) {
		// 		...
    	//	return "user_list";
		//}
		//2.请求 url 为
		//	http://localhost:8080/test/m1/owners/q=11,12/pets/21;q=22;s=23
		//3.uriVariables变量为
		// 	{"ownerId":"q=11,12","petId":"21;q=22;s=23"}
		//4.matrixVars变量为
		//	{"ownerId":{"q":["11","12"]},"petId":{"q":["22"],"s":["23"]}}
		Map<String, MultiValueMap<String, String>> matrixVars = extractMatrixVariables(request, uriVariables);
		request.setAttribute(HandlerMapping.class.getName() + ".matrixVariables", matrixVars);
	}

	if (!info.getProducesCondition().getProducibleMediaTypes().isEmpty()) {
		//如:
		//1.Controller
		//@RequestMapping(value="/accounts", method= RequestMethod.GET,produces={"application/xml", "application/json"})
		//public @ResponseBody List<Account> list() {
		//	...
	    //	return accounts;
		//}
		//2.请求 url
		//	http://localhost:8080/test/accounts.json
		//3.mediaTypes值为
		//[{"concrete":true,"parameters":{},"qualityValue":1.0,"subtype":"json","type":"application","wildcardSubtype":false,"wildcardType":false}]
		Set<MediaType> mediaTypes = info.getProducesCondition().getProducibleMediaTypes();
		request.setAttribute(HandlerMapping.class.getName() + ".producibleMediaTypes", mediaTypes);
	}
}
protected void handleMatch(T mapping, String lookupPath, HttpServletRequest request) {
	request.setAttribute(HandlerMapping.class.getName() + ".pathWithinHandlerMapping", lookupPath);
}

        在handleMatch方法中,主要是做一些 url处理的收尾工作,将 url中携带的方法参数封装到 request 请求中。而下面我们来看看,对于 url 没有找到合适的 Handler情况,又是如何处理的呢?

RequestMappingInfoHandlerMapping.java
protected HandlerMethod handleNoMatch(Set<RequestMappingInfo> requestMappingInfos,
		String lookupPath, HttpServletRequest request) throws ServletException {

	Set<String> allowedMethods = new LinkedHashSet<String>(4);

	Set<RequestMappingInfo> patternMatches = new HashSet<RequestMappingInfo>();
	Set<RequestMappingInfo> patternAndMethodMatches = new HashSet<RequestMappingInfo>();

	for (RequestMappingInfo info : requestMappingInfos) {
		if (info.getPatternsCondition().getMatchingCondition(request) != null) {
			//url匹配上了,加入到patternMatches中
			patternMatches.add(info);
			if (info.getMethodsCondition().getMatchingCondition(request) != null) {
				//url匹配上了,同时 method 也匹配上了,加入到patternAndMethodMatches中
				patternAndMethodMatches.add(info);
			}
			else {
				//url匹配上了,同时 method没有匹配上 ,加入allowedMethods中
				for (RequestMethod method : info.getMethodsCondition().getMethods()) {
					allowedMethods.add(method.name());
				}
			}
		}
	}
	//如果patternMatches为空,则patternAndMethodMatches,allowedMethods都为空,直接返回空
	if (patternMatches.isEmpty()) {
		return null;
	}
	//如果 url 匹配上了,但是 method 没有匹配上,抛出HttpRequestMethodNotSupportedException异常
	//如方法要求 POST 方法,但是我们以 GET 方法请求,则抛出下面异常
	else if (patternAndMethodMatches.isEmpty() && !allowedMethods.isEmpty()) {
		throw new HttpRequestMethodNotSupportedException(request.getMethod(), allowedMethods);
	}

	Set<MediaType> consumableMediaTypes;
	Set<MediaType> producibleMediaTypes;
	List<String[]> paramConditions;

	//除去上面的情况,那么就是 url 匹配成功,method 也匹配成功,但是还是没有找到 Handler ,那么肯定是
	//produces={"application/xml", "application/json"},consumes = {"/bbc/cc"}
	//produces或consumes不匹配导致的了
	if (patternAndMethodMatches.isEmpty()) {
		consumableMediaTypes = getConsumableMediaTypes(request, patternMatches);
		producibleMediaTypes = getProducibleMediaTypes(request, patternMatches);
		paramConditions = getRequestParams(request, patternMatches);
	}
	else {
		consumableMediaTypes = getConsumableMediaTypes(request, patternAndMethodMatches);
		producibleMediaTypes = getProducibleMediaTypes(request, patternAndMethodMatches);
		paramConditions = getRequestParams(request, patternAndMethodMatches);
	}

	//如果consumes不匹配,抛出HttpMediaTypeNotSupportedException异常
	if (!consumableMediaTypes.isEmpty()) {
		MediaType contentType = null;
		if (StringUtils.hasLength(request.getContentType())) {
			try {
				contentType = MediaType.parseMediaType(request.getContentType());
			}
			catch (InvalidMediaTypeException ex) {
				throw new HttpMediaTypeNotSupportedException(ex.getMessage());
			}
		}
		throw new HttpMediaTypeNotSupportedException(contentType, new ArrayList<MediaType>(consumableMediaTypes));
	}
	//如果produces不匹配,抛出HttpMediaTypeNotAcceptableException异常
	else if (!producibleMediaTypes.isEmpty()) {
		throw new HttpMediaTypeNotAcceptableException(new ArrayList<MediaType>(producibleMediaTypes));
	}
	else if (!CollectionUtils.isEmpty(paramConditions)) {
		throw new UnsatisfiedServletRequestParameterException(paramConditions, request.getParameterMap());
	}
	else {
		return null;
	}
}

        纵观handleNoMatch方法,没有做什么实质性逻辑,只是为了精准的打印出Handler没有找到的异常原因。

        相信通过这篇博客,大家对 Controller 及 RequestMapping这两个注解的配置使用及原理己经有了深刻的理解,下面我们来看看整篇博客的流程。

流程:
1)parse():AnnotationDrivenBeanDefinitionParser里方法,解析<mvc:annotation-driven ></mvc:annotation-driven>标签
	1)注册RequestMappingHandlerMapping
	2)注册content-negotiation-manager内容协商
	3)注册urlPathHelper和pathMatcher
	4)注册跨域请求相关配置
	5)注册conversion-service,默认是FormattingConversionServiceFactoryBean
	6)注册validator,如果没有配置,则看是否能反射调用javax.validation.Validator,如果能,则注册OptionalValidatorFactoryBean
	7)如果配置了message-codes-resolver,则注册相关 bean
	8)注册ConfigurableWebBindingInitializer的bean
	9)如果配置了message-converters,则注册相关的 bean
	10)如果配置了register-defaults
		1)注册ByteArrayHttpMessageConverter
		2)注册StringHttpMessageConverter
		3)注册ResourceHttpMessageConverter
		4)注册SourceHttpMessageConverter
		5)注册AllEncompassingFormHttpMessageConverter
		6)如果能反射调用com.rometools.rome.feed.WireFeed
			1)注册AtomFeedHttpMessageConverter
			2)注册RssChannelHttpMessageConverter
		7)如果能反射调用com.fasterxml.jackson.dataformat.xml.XmlMapper
			1)注册MappingJackson2XmlHttpMessageConverter
		8)如果能反射调用javax.xml.bind.Binder
			1)注册Jaxb2RootElementHttpMessageConverter
		9)如果能反射调用com.fasterxml.jackson.databind.ObjectMapper和com.fasterxml.jackson.core.JsonGenerator
			1)注册MappingJackson2HttpMessageConverter
		10)如果能反射调用com.google.gson.Gson
			1)注册GsonHttpMessageConverter
	11)如果配置了argument-resolvers,注册下面所有的 bean元素到容器
	12)如果配置了return-value-handlers,注册下面的所有 bean元素到容器
	13)如果配置了async-support
		1)设置default-timeout 默认超时时间
		2)设置task-executor
		3)注册callable-interceptors下的所有的 bean
		4)注册deferred-result-interceptors下所有的 bean
	14)注册RequestMappingHandlerAdapter
	15)配置ignore-default-model-on-redirect或ignoreDefaultModelOnRedirect
	16)注册CompositeUriComponentsContributorFactoryBean
	17)注册ConversionServiceExposingInterceptor
	18)注册MappedInterceptor
	19)注册ExceptionHandlerExceptionResolver
	20)注册ResponseStatusExceptionResolver
	21)注册DefaultHandlerExceptionResolver
2)afterPropertiesSet():RequestMappingHandlerMapping bean 的生命其中调用afterPropertiesSet方法。
	1)super.afterPropertiesSet():调用父类AbstractHandlerMethodMapping的afterPropertiesSet方法
		1)initHandlerMethods():初始化 Handler
			1)detectHandlerMethods():遍历所有的 bean,对有Controller和RequestMapping的 bean 处理
				1)selectMethods():遍历 bean 中所有的方法
					1)matches():验证方法是不是 HandlerMethod
						1)getMappingForMethod(method, controllorType):获取 RequestMappingInfo,如果不为空,则是HandlerMethod
							1)createRequestMappingInfo(method):创建 method的RequestMappingInfo
							2)createRequestMappingInfo(handlerType):创建handlerType的RequestMappingInfo
							3)combine(methodRequestMappingInfo,handlerTypeRequestMappingInfo):将method的RequestMappingInfo和handlerType的RequestMappingInfo合并
							4)mappings.put(method, mapping):将合并后的RequestMappingInfo保存到mappings中
						2)registerHandlerMethod():注册所有的 Handler,Method,RequestMappingInfo
							1)createHandlerMethod(handler, method):构建handlerMethod
							2)assertUniqueMethodMapping(handlerMethod, mapping):验证mapping的唯一性
							3)mappingLookup.put(mapping, handlerMethod):保存handlerMethod到mappingLookup
							4)urlLookup.add(url, mapping):保存mapping到urlLookup	
							5)addMappingName(name, handlerMethod):如果有HandlerMethodMappingNamingStrategy,则nameLookup.put(name, handlerMethod)
							6)initCorsConfiguration():初始化跨域参数返回corsConfig
								1)如果corsConfig != null,则corsLookup.put(handlerMethod, corsConfig)
							7)registry.put(mapping, new MappingRegistration(mapping, handlerMethod, directUrls, name)):注册 mapping到registry中
			2)handlerMethodsInitialized():预留给子类 
3)doDispatch():servlet 转发
	1)getHandler():获取handler
		1)getHandler():获取RequestMappingHandlerMapping handler
			1)getHandlerInternal():获取 HandlerMethod
				1)getLookupPathForRequest():获取请求的 url
				2)lookupHandlerMethod():获取 HandlerMethod
					1)addMatchingMappings():获取匹配的 HandlerMethod
						1)getMatchingMapping():获取RequestMappingInfo
							1)handleMatch():找到合适的 RequestMappingInfo
								1)extractUriTemplateVariables():处理@PathVariable注解
								2)extractMatrixVariables():处理@MatrixVariable注解
							2)handleNoMatch():没有找到合适的RequestMappingInfo
								1)Method 不匹配,抛出HttpRequestMethodNotSupportedException异常
								2)ContentType不匹配,抛出HttpMediaTypeNotSupportedException
								3)produces不匹配,抛出HttpMediaTypeNotAcceptableException异常

本文的 github 地址
https://github.com/quyixiao/spring_tiny/tree/master/src/main/java/com/spring_101_200/test_181_190/test_185_spring_mvc

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值