在之前的博客中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