Spring源码学习:SpringMVC(3)mvcannotation-driven标签解析【RequestMappingHandlerMapping生成】

目录

  • 前言
  • mvc:annotation-driven标签概述
  • mvc:annotation-driven标签解析【RequestMappingHandlerMapping生成】
    • AnnotationDrivenBeanDefinitionParser#parse (解析入口)
    • RequestMappingHandlerMapping的实例化
      • 类图
      • afterPropertiesSet
      • AbstractHandlerMethodMapping#afterPropertiesSet
      • processCandidateBean
      • isHandler
      • detectHandlerMethods
      • MethodIntrospector#selectMethods
      • ReflectionUtils#doWithMethods
      • getMappingForMethod
      • createRequestMappingInfo
      • registerHandlerMethod
    • 总结

前言

​ 上一篇我们已经完成了springmvc中子容器的初始化,子容器里面一般是一些和web相关的组件,其中的配置文件中就有mvc:annotation-driven这个标签,这里主要对这个标签来进行剖析,看下它里面干了一些什么,其实也是为了后面我们能通过请求url找到对应处理器方法完成的一个铺垫

mvc:annotation-driven标签概述

​ mvc:annotation-driven标签默认会开启SpringMVC的注解驱动模式,默认注册一个RequestMappingHandlerMapping、一个RequestMappingHandlerAdapter、一个ExceptionHandlerExceptionResolver。以支持对使用了 @RequestMapping 、 @ExceptionHandler 及其他注解的控制器方法的请求处理。

mvc:annotation-driven标签解析【RequestMappingHandlerMapping生成】

​ 首先这是一个XML标签,所以我们需要到Spring中refresh()核心方法中的obtainFreshBeanFactory()中里面的parseBeanDefinitions(root,delegate)方法那里打断点,找到解析 mvc:annotation-driven这个标签的逻辑

在这里插入图片描述

注意:node一定得是 mvc:annotation-driven这个标签

	public BeanDefinition parseCustomElement(Element ele) {return parseCustomElement(ele, null);}public BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) {//解析节点的命名空间String namespaceUri = getNamespaceURI(ele);if (namespaceUri == null) {return null;}//解析命名空间,得到一个命名空间处理器//重点NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);if (handler == null) {error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);return null;}//开始解析//主线 重点return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));\}public BeanDefinition parse(Element element, ParserContext parserContext) {//通过定义的标签属性(如:component-scan)获取对应的BeanDefinitionParser解析对象BeanDefinitionParser parser = findParserForElement(element, parserContext);//执行解析   AnnotationDrivenBeanDefinitionParser.parsereturn (parser != null ? parser.parse(element, parserContext) : null);}

​ 关于定位自定义标签解析的过程,以后的IOC中会说明的,这里直接打开AnnotationDrivenBeanDefinitionParser类并定位到其parse方法

AnnotationDrivenBeanDefinitionParser#parse (解析入口)

/*** 解析 mvc:annotation-driven 标签*/
@Override
@Nullable
public BeanDefinition parse(Element element, ParserContext parserContext) {Object source = parserContext.extractSource(element);XmlReaderContext readerContext = parserContext.getReaderContext();CompositeComponentDefinition compDefinition = new CompositeComponentDefinition(element.getTagName(), source);parserContext.pushContainingComponent(compDefinition);/*** 获取协商内容视图配置*/RuntimeBeanReference contentNegotiationManager = getContentNegotiationManager(element, source, parserContext);/*** 创建RequestMappingHandlerMapping的RootBeanDefinition* 从这里也可以看出,开启mvc:annotation-driven标签后,* 将会默认注册RequestMappingHandlerMapping作为默认的HandlerMapping*/RootBeanDefinition handlerMappingDef = new RootBeanDefinition(RequestMappingHandlerMapping.class);handlerMappingDef.setSource(source);handlerMappingDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);handlerMappingDef.getPropertyValues().add("order", 0);handlerMappingDef.getPropertyValues().add("contentNegotiationManager", contentNegotiationManager);// 是否开启矩阵变量if (element.hasAttribute("enable-matrix-variables")) {Boolean enableMatrixVariables = Boolean.valueOf(element.getAttribute("enable-matrix-variables"));handlerMappingDef.getPropertyValues().add("removeSemicolonContent", !enableMatrixVariables);}// 解析path-matching路径匹配标签configurePathMatchingProperties(handlerMappingDef, element, parserContext);readerContext.getRegistry().registerBeanDefinition(HANDLER_MAPPING_BEAN_NAME , handlerMappingDef);// 解析cors跨域标签RuntimeBeanReference corsRef = MvcNamespaceUtils.registerCorsConfigurations(null, parserContext, source);handlerMappingDef.getPropertyValues().add("corsConfigurations", corsRef);// 解析conversion-service数据转换、格式化标签RuntimeBeanReference conversionService = getConversionService(element, source, parserContext);// 解析validator标签RuntimeBeanReference validator = getValidator(element, source, parserContext);// 解析message-codes-resolver标签RuntimeBeanReference messageCodesResolver = getMessageCodesResolver(element);/*** 创建ConfigurableWebBindingInitializer的RootBeanDefinition对象* 并将上一步解析的conversionService、validator、messageCodesResolver* 作为属性注入到该对象中*/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);// 解析message-converters标签ManagedList<?> messageConverters = getMessageConverters(element, source, parserContext);// 解析argument-resolvers标签ManagedList<?> argumentResolvers = getArgumentResolvers(element, parserContext);// 解析return-value-handlers标签ManagedList<?> returnValueHandlers = getReturnValueHandlers(element, parserContext);// 解析async-support标签String asyncTimeout = getAsyncTimeout(element);// 解析async-support的task-executor子标签RuntimeBeanReference asyncExecutor = getAsyncExecutor(element);// 解析async-support的callable-interceptors子标签ManagedList<?> callableInterceptors = getCallableInterceptors(element, source, parserContext);// 解析async-support的deferred-result-interceptors子标签ManagedList<?> deferredResultInterceptors = getDeferredResultInterceptors(element, source, parserContext);/*** 创建RequestMappingHandlerAdapter的RootBeanDefinition* 从这里也可以看出,开启mvc:annotation-driven标签后,* 将会默认注册RequestMappingHandlerAdapter作为默认的HandlerAdapter* 并将上面解析的内容绑定到该HandlerAdapter中*/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);addRequestBodyAdvice(handlerAdapterDef);addResponseBodyAdvice(handlerAdapterDef);if (element.hasAttribute("ignore-default-model-on-redirect")) {Boolean ignoreDefaultModel = Boolean.valueOf(element.getAttribute("ignore-default-model-on-redirect"));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);readerContext.getRegistry().registerBeanDefinition(HANDLER_ADAPTER_BEAN_NAME , handlerAdapterDef);/*** 创建CompositeUriComponentsContributorFactoryBean的RootBeanDefinition* CompositeUriComponentsContributorFactoryBean是一个工厂bean,* 可以用来获取RequestMappingHandlerAdapter中的HandlerMethodArgumentResolver配置*/RootBeanDefinition uriContributorDef = new RootBeanDefinition(CompositeUriComponentsContributorFactoryBean.class);uriContributorDef.setSource(source);uriContributorDef.getPropertyValues().addPropertyValue("handlerAdapter", handlerAdapterDef);uriContributorDef.getPropertyValues().addPropertyValue("conversionService", conversionService);String uriContributorName = MvcUriComponentsBuilder.MVC_URI_COMPONENTS_CONTRIBUTOR_BEAN_NAME;readerContext.getRegistry().registerBeanDefinition(uriContributorName, uriContributorDef);/*** 创建ConversionServiceExposingInterceptor的RootBeanDefinition* 主要用来解析spring:eval标签*/RootBeanDefinition csInterceptorDef = new RootBeanDefinition(ConversionServiceExposingInterceptor.class);csInterceptorDef.setSource(source);csInterceptorDef.getConstructorArgumentValues().addIndexedArgumentValue(0, conversionService);RootBeanDefinition mappedInterceptorDef = new RootBeanDefinition(MappedInterceptor.class);mappedInterceptorDef.setSource(source);mappedInterceptorDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);mappedInterceptorDef.getConstructorArgumentValues().addIndexedArgumentValue(0, (Object) null);mappedInterceptorDef.getConstructorArgumentValues().addIndexedArgumentValue(1, csInterceptorDef);String mappedInterceptorName = readerContext.registerWithGeneratedName(mappedInterceptorDef);/*** 创建ExceptionHandlerExceptionResolver的RootBeanDefinition*/RootBeanDefinition methodExceptionResolver = new RootBeanDefinition(ExceptionHandlerExceptionResolver.class);methodExceptionResolver.setSource(source);methodExceptionResolver.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);methodExceptionResolver.getPropertyValues().add("contentNegotiationManager", contentNegotiationManager);methodExceptionResolver.getPropertyValues().add("messageConverters", messageConverters);methodExceptionResolver.getPropertyValues().add("order", 0);addResponseBodyAdvice(methodExceptionResolver);if (argumentResolvers != null) {methodExceptionResolver.getPropertyValues().add("customArgumentResolvers", argumentResolvers);}if (returnValueHandlers != null) {methodExceptionResolver.getPropertyValues().add("customReturnValueHandlers", returnValueHandlers);}String methodExResolverName = readerContext.registerWithGeneratedName(methodExceptionResolver);/*** 创建ResponseStatusExceptionResolver的RootBeanDefinition**/RootBeanDefinition statusExceptionResolver = new RootBeanDefinition(ResponseStatusExceptionResolver.class);statusExceptionResolver.setSource(source);statusExceptionResolver.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);statusExceptionResolver.getPropertyValues().add("order", 1);String statusExResolverName = readerContext.registerWithGeneratedName(statusExceptionResolver);/*** 创建DefaultHandlerExceptionResolver的RootBeanDefinition* 该类是HandlerExceptionResolver的默认实现,可以解析http异常并将相应的http状态码返回* 例如:404*/RootBeanDefinition defaultExceptionResolver = new RootBeanDefinition(DefaultHandlerExceptionResolver.class);defaultExceptionResolver.setSource(source);defaultExceptionResolver.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);defaultExceptionResolver.getPropertyValues().add("order", 2);String defaultExResolverName = readerContext.registerWithGeneratedName(defaultExceptionResolver);/*** 将上面创建的RootBeanDefinition以组件形式纳入SpringIOC容器*/parserContext.registerComponent(new BeanComponentDefinition(handlerMappingDef, HANDLER_MAPPING_BEAN_NAME));parserContext.registerComponent(new BeanComponentDefinition(handlerAdapterDef, HANDLER_ADAPTER_BEAN_NAME));parserContext.registerComponent(new BeanComponentDefinition(uriContributorDef, uriContributorName));parserContext.registerComponent(new BeanComponentDefinition(mappedInterceptorDef, mappedInterceptorName));parserContext.registerComponent(new BeanComponentDefinition(methodExceptionResolver, methodExResolverName));parserContext.registerComponent(new BeanComponentDefinition(statusExceptionResolver, statusExResolverName));parserContext.registerComponent(new BeanComponentDefinition(defaultExceptionResolver, defaultExResolverName));// Ensure BeanNameUrlHandlerMapping (SPR-8289) and default HandlerAdapters are not "turned off"// 注册默认组件MvcNamespaceUtils.registerDefaultComponents(parserContext, source);parserContext.popAndRegisterContainingComponent();return null;
}

那么接下来我们需要总结一下,如果mvc:annotation-driven没有配置任何子标签的话,Spring会如何处理呢?

RootBeanDefinition handlerMappingDef = new RootBeanDefinition(RequestMappingHandlerMapping.class);
RootBeanDefinition bindingDef = new RootBeanDefinition(ConfigurableWebBindingInitializer.class);
RootBeanDefinition handlerAdapterDef = new RootBeanDefinition(RequestMappingHandlerAdapter.class);
RootBeanDefinition uriContributorDef = new RootBeanDefinition(CompositeUriComponentsContributorFactoryBean.class);
RootBeanDefinition csInterceptorDef = new RootBeanDefinition(ConversionServiceExposingInterceptor.class);
RootBeanDefinition mappedInterceptorDef = new RootBeanDefinition(MappedInterceptor.class);
RootBeanDefinition methodExceptionResolver = new RootBeanDefinition(ExceptionHandlerExceptionResolver.class);
RootBeanDefinition statusExceptionResolver = new RootBeanDefinition(ResponseStatusExceptionResolver.class);
RootBeanDefinition defaultExceptionResolver = new RootBeanDefinition(DefaultHandlerExceptionResolver.class);

可以看到即使不做任何子标签的配置,SpringMVC默认也会创建上述9个内部bean的实例。

RequestMappingHandlerMapping的实例化

类图

在这里插入图片描述

​ 上图信息比较多,我们查找关键信息。可以看到这个类间接实现了HandlerMapping接口,是HandlerMapping类型的实例。

除此之外还实现了ApplicationContextAwareIntitalzingBean 这两个接口。在这里简要介绍一下这两个接口:

如果一个类实现了ApplicationContextAware接口,Spring容器在初始化该类时候会自动回调该类的setApplicationContext()方法。这个接口主要用来让实现类得到Spring 容器上下文信息。

如果一个bean实现了IntitalzingBean接口,Spring 容器初始化bean时会回调afterPropertiesSet()方法。这个接口的主要作用是让bean在初始化时可以实现一些自定义的操作。

RequestMappingHandlerMapping实现了InitializingBean接口,当设置完属性后肯定会回调afterPropertiesSet方法,再看afterPropertiesSet方法逻辑。

afterPropertiesSet

	public void afterPropertiesSet() {// 创建 BuilderConfigurationthis.config = new RequestMappingInfo.BuilderConfiguration();this.config.setUrlPathHelper(getUrlPathHelper());this.config.setPathMatcher(getPathMatcher());this.config.setSuffixPatternMatch(useSuffixPatternMatch());this.config.setTrailingSlashMatch(useTrailingSlashMatch());this.config.setRegisteredSuffixPatternMatch(useRegisteredSuffixPatternMatch());this.config.setContentNegotiationManager(getContentNegotiationManager());super.afterPropertiesSet();}

上面调用了父类AbstractHandlerMethodMapping的afterPropertiesSet()方法,沿调用栈继续查看。

AbstractHandlerMethodMapping#afterPropertiesSet

	public void afterPropertiesSet() {initHandlerMethods();}protected void initHandlerMethods() {// 获取所有的 BeanNamesfor (String beanName : getCandidateBeanNames()) {// 判断不是已 scopedTarget 开头if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {//对含有注解的bean进行处理,获取handler函数信息。processCandidateBean(beanName);}}//简单的日志记录handlerMethodsInitialized(getHandlerMethods());}

processCandidateBean是核心方法,该方法内部完成了bean的筛选和对某个Controller内部所有handlerMethod的探测。

processCandidateBean

protected void processCandidateBean(String beanName) {Class<?> beanType = null;try {// 获取具体的类型.beanType = obtainApplicationContext().getType(beanName);}catch (Throwable ex) {// An unresolvable bean type, probably from a lazy bean - let's ignore it.// 一个无法解析的bean类型,可能来自一个lazy bean-让我们忽略它。// 日志打印...if (logger.isTraceEnabled()) {logger.trace("Could not resolve type for bean '" + beanName + "'", ex);}}// 不是null 并且 类型是存在 @Controller 或者 @RequestMapping  注解if (beanType != null && isHandler(beanType)) {//如果当前bean是一个handler,那么需要探测出该handler内部所有handlerMethod实现detectHandlerMethods(beanName);}}

如果当前bean是一个handler的话则进入detectHandlerMethods(beanName);

isHandler

	@Overrideprotected boolean isHandler(Class<?> beanType) {// 存在 Controller注解 或者存在 RequestMapping 注解 ..return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));}

上面逻辑很简单,就是判断该bean是否有@Controller@RequestMapping注解,然后返回判断结果。

如果含有这两个注解之一就进入detectHandlerMethods()方法进行处理。

detectHandlerMethods

protected void detectHandlerMethods(Object handler) {// 如果传递是 String 则 获取其类型 ,如果是是class 则直接返回Class<?> handlerType = (handler instanceof String ?obtainApplicationContext().getType((String) handler) : handler.getClass());if (handlerType != null) {//对类型再次进行处理,主要是针对cglibClass<?> userType = ClassUtils.getUserClass(handlerType);//遍历方法,对注解中的信息进行处理,得到RequestMappingInfo对象,得到methods数组Map<Method, T> methods = MethodIntrospector.selectMethods(userType,(MethodIntrospector.MetadataLookup<T>) method -> {try {// 里面主要是  获取 方法 和类上的 @RequestMapping 将其合并.// 如果没有的话则会返回 null,而由于是 lambda 这里主要是制订过滤规则// 如果返回了 null 则 selectMethods 不会将其放入到Map中。return getMappingForMethod(method, userType);}catch (Throwable ex) {throw new IllegalStateException("Invalid mapping on handler class [" +userType.getName() + "]: " + method, ex);}});if (logger.isTraceEnabled()) {logger.trace(formatMappings(userType, methods));}//遍历methods[Method,{path}]methods.forEach((method, mapping) -> {//对方法的可访问性进行校验,如private,static,SpringProxy, //获取最终请求路径Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);// 注册,注册到全局的MappingRegistry实例里registerHandlerMethod(handler, invocableMethod, mapping);});}}

​ 上面方法中用了几个回调,可能看起来比较复杂,我们先看MethodIntrospector.selectMethods,该方法最终会经过筛选完成一个method-》RequestMappingInfo(@RequestMapping注解信息)的映射

MethodIntrospector#selectMethods

public static <T> Map<Method, T> selectMethods(Class<?> targetType, final MetadataLookup<T> metadataLookup) {final Map<Method, T> methodMap = new LinkedHashMap<>();Set<Class<?>> handlerTypes = new LinkedHashSet<>();Class<?> specificHandlerType = null;//把自身类添加到handlerTypes中if (!Proxy.isProxyClass(targetType)) {specificHandlerType = ClassUtils.getUserClass(targetType);handlerTypes.add(specificHandlerType);}//获取该bean所有的接口,并添加到handlerTypes中handlerTypes.addAll(ClassUtils.getAllInterfacesForClassAsSet(targetType));//对自己及所有实现接口进行遍历for (Class<?> currentHandlerType : handlerTypes) {final Class<?> targetClass = (specificHandlerType != null ? specificHandlerType : currentHandlerType);//获取函数映射信息ReflectionUtils.doWithMethods(currentHandlerType, method -> {Method specificMethod = ClassUtils.getMostSpecificMethod(method, targetClass);// //回调inspect()方法,这里就会调用到外面的getMappingForMethod函数来生成RequestMappingInfo  T result = metadataLookup.inspect(specificMethod);if (result != null) {Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(specificMethod);if (bridgedMethod == specificMethod || metadataLookup.inspect(bridgedMethod) == null) {methodMap.put(specificMethod, result);}}}, ReflectionUtils.USER_DECLARED_METHODS);}return methodMap;}

MethodIntrospector.selectMethods的作用可以简单看成是遍历了handler类内部的所有方法,包括其父类和实现接口里面的所有方法,然后交给注册进来的回调接口进行处理,回调接口的返回值作为生成的映射信息,如果返回值不为空,就和当前method组成一条记录,放入map中; 遍历完所有方法后,返回该map集合。

selectMethods完成方法筛选的关键就在于目标方法经过回调接口处理过后,返回值是否为空,如果为空,说明当前方法需要被过滤掉

我们看到又调用了ReflectionUtils.doWithMethods()

ReflectionUtils#doWithMethods

public static void doWithMethods(Class<?> clazz, MethodCallback mc, @Nullable MethodFilter mf) {// Keep backing up the inheritance hierarchy.Method[] methods = getDeclaredMethods(clazz, false);for (Method method : methods) {if (mf != null && !mf.matches(method)) {continue;}try {//这里调用的doWith方法就会回到调用方法那里的lambda方法里面mc.doWith(method);}catch (IllegalAccessException ex) {throw new IllegalStateException("Not allowed to access method '" + method.getName() + "': " + ex);}}if (clazz.getSuperclass() != null && (mf != USER_DECLARED_METHODS || clazz.getSuperclass() != Object.class)) {doWithMethods(clazz.getSuperclass(), mc, mf);}else if (clazz.isInterface()) {for (Class<?> superIfc : clazz.getInterfaces()) {doWithMethods(superIfc, mc, mf);}}}

doWith中执行metadataLookup.inspect方法,也就是会去执行getMappingForMethod。该方法会根据method来构建RequestMappingInfo,该对象记录了匹配这个method的所有需要满足的条件

可以简单将上面理解为遍历当前handler类及其实现接口,并获取其中所有的方法,最后进入getMappingForMethod方法中

getMappingForMethod

	@Override@Nullableprotected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {RequestMappingInfo info = createRequestMappingInfo(method);if (info != null) {RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType);if (typeInfo != null) {info = typeInfo.combine(info);}String prefix = getPathPrefix(handlerType);if (prefix != null) {info = RequestMappingInfo.paths(prefix).options(this.config).build().combine(info);}}return info;}

​ 进入createRequestMappingInfo(method),内部会判断是否存在RequestMapping注解,如果存在才创建,可以看到先根据方法创建一个,之后再根据类创建一个,最后会合并一下,因为需要匹配的上请求url是需要类上的path+方法上的path一起来完成

createRequestMappingInfo

	private RequestMappingInfo createRequestMappingInfo(AnnotatedElement element) {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, @Nullable RequestCondition<?> customCondition) {RequestMappingInfo.Builder builder = RequestMappingInfo.paths(resolveEmbeddedValuesInPatterns(requestMapping.path())).methods(requestMapping.method()).params(requestMapping.params()).headers(requestMapping.headers()).consumes(requestMapping.consumes()).produces(requestMapping.produces()).mappingName(requestMapping.name());if (customCondition != null) {builder.customCondition(customCondition);}return builder.options(this.config).build();}

上面把RequestMapping注解中的信息都放到一个RequestMappingInfo实例中后返回。之后返回的话最终会到selectMethods方法里面,判断metadataLookup.inspect的返回值是否为null,不为null则将方法和RequestMappingInfo的信息维护到map中,下面就是注册RequestMappingInfo

registerHandlerMethod

protected void registerHandlerMethod(Object handler, Method method, T mapping) {this.mappingRegistry.register(mapping, handler, method);}public void register(T mapping, Object handler, Method method) {// Assert that the handler method is not a suspending one.if (KotlinDetector.isKotlinType(method.getDeclaringClass())) {Class<?>[] parameterTypes = method.getParameterTypes();if ((parameterTypes.length > 0) && "kotlin.coroutines.Continuation".equals(parameterTypes[parameterTypes.length - 1].getName())) {throw new IllegalStateException("Unsupported suspending handler method detected: " + method);}}this.readWriteLock.writeLock().lock();try {//处理方法的对象HandlerMethod handlerMethod = createHandlerMethod(handler, method);//判断映射的唯一性validateMethodMapping(handlerMethod, mapping);//将mapping信息和控制器方法对应this.mappingLookup.put(mapping, handlerMethod);//将path与处理器映射(一个方法可能可以处理多个url)List<String> directUrls = getDirectUrls(mapping);for (String url : directUrls) {this.urlLookup.add(url, mapping);}//控制器名的大写英文缩写#方法名String name = null;if (getNamingStrategy() != null) {name = getNamingStrategy().getName(handlerMethod, mapping);addMappingName(name, handlerMethod);}//跨域请求相关配置CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping);if (corsConfig != null) {this.corsLookup.put(handlerMethod, corsConfig);}//将所有配置统一注册到registry中this.registry.put(mapping, new MappingRegistration<>(mapping, handlerMethod, directUrls, name));}finally {this.readWriteLock.writeLock().unlock();}}

​ 可以看到将Mehtod和Handler给封装了一层,变成了HandlerMethod,然后最终维护了3个map(不包括跨域)

  • this.mappingLookup : 保存requestMappingInfo和控制器方法的映射
  • this.urlLookup :保存url和多个控制器方法的映射(主要是因为restFul风格)
  • this.registry : 保存requestMappingInfo和registration的映射
	class MappingRegistry {//保存RequestMappingInfo和MappingRegistration的映射关系private final Map<T, MappingRegistration<T>> registry = new HashMap<>();//保存RequestMappingInfo和HandlerMethod的映射关系private final Map<T, HandlerMethod> mappingLookup = new LinkedHashMap<>();//保存请求路径和RequestMappingInfo的映射关系private final MultiValueMap<String, T> urlLookup = new LinkedMultiValueMap<>();//保存handlerMethodName和handlerMethod的映射关系  private final Map<String, List<HandlerMethod>> nameLookup = new ConcurrentHashMap<>();//保存handlerMethod和跨域配置的映射关系private final Map<HandlerMethod, CorsConfiguration> corsLookup = new ConcurrentHashMap<>();//读写锁 private final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();...

总结

  1. mvc:annotation-driven标签在子容器初始化的过程中会完成扫描,会注入一些bean,其中就包括RequestMappingHandlerMapping
  2. RequestMappingHandlerMapping实现了InitializingBean接口,所有会在其他bean执行生命周期的时候调用其afterPropertiesSet方法
  3. 然后判断当前bean是否为一个controller,主要针对类上是否存在@Controller 或者 @RequestMapping注解
  4. 之后会通过遍历当前类以及其实现的接口类型,获取其中的方法,判断方法是否存在@RequestMapping注解,如果存在则创建RequestMappingInfo实例,该实例中存放了@RequestMappingInfo注解信息(需要注意,创建@RequestMappingInfo会进行两次,一次是根据方法的,一次是根据类,之后会合并为一个,主要为了path的完整),最后会将RequestMappingInfo和相对应的method保存到map中
  5. 通过上一步的map来完成注册,将信息保存到其父类AbstractHandlerMethodMapping类内部MappingRegistry中的三个map中

    以上:内容部分参考:《Spring源码深度解析》
    如有侵扰,联系删除。 内容仅用于自我记录学习使用。如有错误,欢迎各位大佬指正

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/435118.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

MySQL数据库——索引

目录 什么是索引&#xff08;Index&#xff09;&#xff1f; 怎样加索引&#xff1f; 索引的特点 索引类型 主键索引(Primary Key) 辅助索引&#xff08;二级索引&#xff09; 聚集索引和非聚集索引 聚集索引 非聚集索引 单列索引和联合索引 单列索引 联合索引 创…

mac Wireshark You do not have permission to capture on device “rvio“.

原因&#xff1a; 权限不足 解决方案&#xff1a; 打开终端在终端输入 whoamin (会在终端显示本机的实际用户名字) 例如&#xff1a;xiaoming进入 /dev 目录 cd /dev输入命令&#xff1a;ls -la | grep bp输入命令&#xff1a;sudo chown whoamin xiaoming:admin bp*重新打开 …

Python(五)-函数

目录 函数的定义与调用 特点 语法格式 函数的参数 函数的返回值 函数嵌套调用 变量的作用域 局部变量 全局变量 函数的多种参数 位置参数 关键字参数 默认参数 可变参数 函数的定义与调用 python函数需要使用def关键字来定义,需要先定义,后调用 特点: 先定义…

【数据结构与算法】LeetCode:二分查找

文章目录 二分查找二分查找搜索插入位置 &#xff08;Hot 100&#xff09;x 的平方根搜索二维矩阵&#xff08;Hot 100&#xff09;在排序数组中查找元素的第一个和最后一个位置 &#xff08;Hot 100&#xff09;搜索旋转排序数组 &#xff08;Hot 100&#xff09;寻找旋转排序…

【Ubuntu】minicom安装、配置、使用以及退出

目录 1 安装 2 配置 3 使用 4 退出 minicom是一个串口通信的工具&#xff0c;以root权限登录系统&#xff0c;可用来与串口设备通信。 1 安装 sudo apt-get install minicom 2 配置 使用如下命令进入配置界面&#xff1a; sudo minicon -s 进入配置界面后&#xff0c;…

【STM32】 TCP/IP通信协议--LwIP介绍

一、前言 TCP/IP是干啥的&#xff1f;它跟SPI、IIC、CAN有什么区别&#xff1f;它如何实现stm32的通讯&#xff1f;如何去配置&#xff1f;为了搞懂这些问题&#xff0c;查询资料可解决如下疑问&#xff1a; 1.为什么要用以太网通信? 以太网(Ethernet) 是指遵守 IEEE 802.3 …

OCR Fusion: EasyOCR/Tesseract/PaddleOCR/TrOCR/GOT

文章目录 前言一、基类 OCRExecutorBase二、EasyOCR1.安装2.模型下载3.DEMO 三、Tesseract1.安装2.使用问题3.DEMO 四、PaddleOCR1.安装2.DEMO 五、PaddleOCR&#xff08;PyTorch移植版&#xff09;1.代码整理2.DEMO 六、TrOCR1.安装2.模型下载3.DEMO 七、GOT1.安装2.模型下载3…

TCP\IP标准与OSI标准

TCP/IP 模型和 OSI 模型都是用于描述网络体系结构的模型&#xff0c;但它们的设计理念和层次结构有所不同。TCP/IP 模型更注重实际实现&#xff0c;而 OSI 模型更注重抽象和标准化。 1. OSI 模型 (Open Systems Interconnection Model) OSI 模型是一个七层模型&#xff0c;从…

UFS 3.1架构简介

整个UFS协议栈可以分为三层:应用层(UFS Application Layer(UAP)),传输层(UFS Transport Layer(UTP)),链路层(UIC InterConnect Layer(UIC))。应用层发出SCSI命令(UFS没有自己的命令使用的是简化的SCSI命令),在传输层将SCSI分装为UPIU,再经过链路层将命令发送给Devices。下…

vue3实现打字机的效果,可以换行

之前看了很多文章,效果是实现了,就是没有自动换行的效果,参考了文章写了一个,先上个效果图,卡顿是因为模仿了卡顿的效果,还是很丝滑的 目录 效果图:代码如下 效果图: ![请添加图片描述](https://i-blog.csdnimg.cn/direct/d8ef33d83dd3441a87d6d033d9e7cafa.gif 代码如下 原…

Vue(16)——Vue3.3新特性

defineOptions 在 Vue 3.3 之前&#xff0c;如果需要在 <script setup> 中设置组件名&#xff0c;通常需要在额外的 <script> 标签中使用 Options API 进行配置。defineOptions 是 Vue 3.3 版本中引入的一个宏&#xff08;macro&#xff09;&#xff0c;它主要用于…

初识Linux以及Linux的基本命令

千呼万唤始出来&#xff0c;Linux系列的文章从今天起开始不定期更新&#xff0c;闲话少叙&#xff0c;我们直接进入正题 初识Linux 这一部分我不打算给大家讲Linux的发展史啥的&#xff0c;直接从系统方面开始介绍 首先&#xff0c;我们平时用win10或win11所看到的桌面以及各…

element ui中当el-dialog需要做全屏时,.fullscreen样式修改问题

element ui 饿了么UI中el-dialog样式修改问题 场景解决方法就是&#xff1a;去掉底部样式中的scoped,然后再进行页面级样式的更改即可。 场景 最近在使用element-ui时&#xff0c;使用到了弹窗组件&#xff1a; element-ui 官网链接地址&#xff1a; element-ui 官网链接地址…

基于springboot+小程序的自习室选座与门禁管理系统(自习室1)(源码+sql脚本+视频导入教程+文档)

&#x1f449;文末查看项目功能视频演示获取源码sql脚本视频导入教程视频 1 、功能描述 1、管理员实现了首页、基础数据管理、论坛管理、公告信息管理、用户管理、座位管理等 2、用户实现了在论坛模块通过发帖与评论帖子的方式进行信息讨论&#xff0c;也能对账户进行在线充值…

数据集-目标检测系列-吸烟检测数据集 smoking cigarette >> DataBall

数据集-目标检测系列-吸烟检测数据集 smoking cigarette >> DataBall 数据集-目标检测系列-吸烟检测数据集 &#xff08;smoking cigarette&#xff09; 数据量&#xff1a;1W 数据项目地址&#xff1a; gitcode: https://gitcode.com/DataBall/DataBall-detections-…

双链表的插入删除遍历

双链表的插入操作 双链表的删除操作 双链表的遍历操作

mit6824-01-MapReduce详解

文章目录 MapReduce简述编程模型执行流程执行流程排序保证Combiner函数Master数据结构 容错性Worker故障Master故障 性能提升定制分区函数局部性执行缓慢的worker(slow workers) 常见问题总结回顾参考链接 MapReduce简述 MapReduce是一个在多台机器上并行计算大规模数据的软件架…

netty编程之实现websocket客户端并发送二进制消息

写在前面 源码。 本文看下netty如何实现websocket客户端并发送二进制消息。 ws的server端参考这篇文章。 1&#xff1a;正文 抽象类AbstractWebsocketClient定义了发送二进制数据的方法&#xff1a; public abstract class AbstractWebsocketClient implements Closeable {…

【Immich部署与访问】自托管媒体文件备份服务 Immich 本地化部署与远程访问存储数据

文章目录 前言1.关于Immich2.安装Docker3.本地部署Immich4.Immich体验5.安装cpolar内网穿透6.创建远程链接公网地址7.使用固定公网地址远程访问 前言 本篇文章介绍如何在本地搭建lmmich图片管理软件&#xff0c;并结合cpolar内网穿透实现公网远程访问到局域网内的lmmich&#…

【C++】STL详解之string类

目录 什么是STL STL的版本 STL的六大组件 STL的缺陷 一.string的定义方式 二. string的插入 1.使用push_back进行尾插 2.使用insert插入 三.string的拼接 四.string的删除 1.使用pop_back进行尾删 2.使用erase进行删除 五.string的查找 1.使用find正向搜索第一个…