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
类型的实例。
除此之外还实现了ApplicationContextAware
和IntitalzingBean
这两个接口。在这里简要介绍一下这两个接口:
如果一个类实现了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();...
总结
- mvc:annotation-driven标签在子容器初始化的过程中会完成扫描,会注入一些bean,其中就包括RequestMappingHandlerMapping
- RequestMappingHandlerMapping实现了InitializingBean接口,所有会在其他bean执行生命周期的时候调用其afterPropertiesSet方法
- 然后判断当前bean是否为一个controller,主要针对类上是否存在@Controller 或者 @RequestMapping注解
- 之后会通过遍历当前类以及其实现的接口类型,获取其中的方法,判断方法是否存在@RequestMapping注解,如果存在则创建RequestMappingInfo实例,该实例中存放了@RequestMappingInfo注解信息(需要注意,创建@RequestMappingInfo会进行两次,一次是根据方法的,一次是根据类,之后会合并为一个,主要为了path的完整),最后会将RequestMappingInfo和相对应的method保存到map中
- 通过上一步的map来完成注册,将信息保存到其父类AbstractHandlerMethodMapping类内部MappingRegistry中的三个map中
以上:内容部分参考:《Spring源码深度解析》
如有侵扰,联系删除。 内容仅用于自我记录学习使用。如有错误,欢迎各位大佬指正
相关文章:

Spring源码学习:SpringMVC(3)mvcannotation-driven标签解析【RequestMappingHandlerMapping生成】
目录 前言mvc:annotation-driven标签概述mvc:annotation-driven标签解析【RequestMappingHandlerMapping生成】AnnotationDrivenBeanDefinitionParser#parse (解析入口)RequestMappingHandlerMapping的实例化类图afterPropertiesSetAbstractHandlerMetho…...

2024 Redis 全部
1. 单机部署 1.1 检查环境,创建目录。 # 本地运行,不需要考虑安装的原因,可以卸载防火墙 # 关闭防火墙 systemctl stop firewalld.service# 查看防火强状态 firewall-cmd --state# redis 是基于gcc 环境的,查看是否有 gcc 环境 …...
[SDX35+WCN6856]SDX35 + WCN6856 WiFi可以up起来之后无法扫描到SSID
SDX35 SDX35介绍 SDX35设备是一种多模调制解调器芯片,支持 4G/5G sub-6 技术。它是一个4nm芯片专为实现卓越的性能和能效而设计。它包括一个 1.9 GHz Cortex-A7 应用处理器。 SDX35主要特性 ■ 3GPP Rel. 17 with 5G Reduced Capability (RedCap) support. Backward compati…...

VisualStudio如何卸载Resharper插件?
本来按理说,卸载插件应该就是在扩展下的已安装插件中,找到该插件,点一下就会出现卸载的按钮的。 没想到这个Resharper这么吊,卸载按钮居然是个灰色的,意思就是此路不通,有特权的。 那么这种情况下&#x…...

Unity Debug时出现请选择unity实例
Unity Debug时出现请选择unity实例 问题描述 出现请选择unity实例,并且选择框里为空。 出现原因 你打开了两个Unity工程,在附加时,不知道加在哪个Unity工程上。 解决方法 在调试窗口中点击“附加Unity调试程序”,然后在弹出…...

国庆出行新宠:南卡Pro5骨传导耳机,让旅途不再孤单
国庆长假即将来临,对于热爱旅行和户外运动的朋友们来说,一款适合旅行使用的耳机无疑是提升旅途体验的神器。今天,我要向大家推荐一款特别适合国庆出行的耳机——南卡Runner Pro5骨传导耳机。作为一名热爱旅游的体验者,我强烈推荐南…...
2024.09.18 leetcode 每日一题
二进制求和 给你两个二进制字符串 a 和 b ,以二进制字符串的形式返回它们的和。 https://leetcode.cn/problems/add-binary/description/ 代码一,尝试使用笨办法,会造成溢出 class Solution { public:string addBinary(string a, string …...

快递物流短信API接口代码
官网:快递鸟 API参数 用户信息类 一.短信模版 1.接口说明 使用快递鸟短信功能时,预先设置好短信模板和对应的发送规则,快递鸟短信API将根据设置的好的模板和规则,进行短信的发送和反馈。 (1)仅支持Json格式。 (2)请求指令810…...

人工智能-机器学习-深度学习-分类与算法梳理
目前人工智能的概念层出不穷,容易搞混,理清脉络,有益新知识入脑。 为便于梳理,本文只有提纲,且笔者准备仓促,敬请勘误,不甚感激。 请看右边目录索引 。 人工智能 三大派系 符号主义(Symbolists…...
Xinference:深度学习模型推理与优化指南
目录 1. 什么是 Xinference? 2. 使用 Xinference 进行模型推理 2.1 安装 Xinference 2.2 模型推理示例 3. 提高模型推理的效率和性能 3.1 模型量化 3.2 并行处理 3.3 批量处理 4. 启用网页端(如果支持) 5. 在 CPU 中的注意事项 6. …...

Windows 2003系统的防护技巧,禁止IPC$空连接
一、修改管理员帐号和新建“陷阱”帐号 多年以来,微软一直在强调建议重命名Administrator账号并禁用Guest账号,提高计算机的安全性。Windows Server 2003系统,Guest 账号是默认禁用的,管理员账号默认是Administrator,…...

Kubernetes 深入浅出系列 | 容器剖析之容器基本实现原理
一、容器基本实现原理 Docker 主要通过如下三个方面来实现容器化: ① 使用操作系统的 namespace 隔离系统资源技术,通过隔离 网络、PID 进程、系统信号量、文件系统挂载、主机名和域名,来实现在同一宿主机系统中,运行不同的容器&…...

【学习笔记】TLS/SSL握手
前言:本篇将介绍TLS握手的实际握手过程,TLS握手创建了Client和Server之间“被保护的通道”,2个单向通道用来保护批量数据的传输(通过Confidentiality、Integrity和Authentication),一个通道是从Client到Ser…...
ESP32-TFT_eSPI.h文件的使用心得(包含画图相关函数)
目录 前言 环境:arduino 芯片:ESP32 一、Arduino中的使用 #include <TFT_eSPI.h> TFT_eSPI tft TFT_eSPI();tft.init();//初始化tft.setRotation(0); //屏幕旋转方向tft.fillScreen(TFT_BLACK);//底色tft.setTextSize(2); …...

vite分目录打包以及去掉默认的.gz 文件
1.vite打包情况介绍: 1.1vite在不进行任何配置的情况下,会将除开public的所有引用到资源打包编译添加哈希值至assets文件夹中(非引用文件以及行内样式图片未被打包编译资源会被treeSharp直接忽略不打包), 1.2w…...

Tensorflow 2.0 cnn训练cifar10 准确率只有0.1 [已解决]
cifar10 准确率只有0.1 问题描述踩坑解决办法 问题描述 如果你看的是北京大学曹健老师的tensorflow2.0,你在class5的部分可能会遇见这个问题 import matplotlib.pyplot as plt import tensorflow as tf from tensorflow.keras.layers import Dense, Dropout,MaxPooling2D,Fla…...

828华为云征文 | 在华为云上通过Docker容器部署Elasticsearch并进行性能评测
目录 前言 1. 华为云X实例介绍及优势 1.1 柔性算力 1.2 vCPU和内存的灵活配比 1.3 成本效益与性能 2. 安装并运行 Docker 2.1 修改仓库配置文件 2.2 安装 Docker 2.3 启动 Docker 3. 使用Docker部署Elasticsearch 3.1 拉取Elasticsearch镜像 3.2 启动Elasticsearch…...
生态位模型降重创新专题系列【2025
本内容旨在丰富最大熵模型的分析内容,并针对目前文章存在的问题:(1)分析内容单一,重复度高和查重率高,(2)建模流程过于简单,结果可信度评估方法过于单一等;推…...

LeetCode234. 回文链表(2024秋季每日一题 26)
给你一个单链表的头节点 head ,请你判断该链表是否为回文链表。如果是,返回 true ;否则,返回 false 。 示例 1: 输入:head [1,2,2,1] 输出:true 示例 2: 输入:hea…...
项目(石头剪刀布游戏双循环)
while (true) { #region 猜拳游戏主题逻辑 // 定义猜拳次数 int count 3; //定义用户赢得次数 int winCount 0;// 初始值为零表示用户一次没饿赢 int sysCou…...

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器的上位机配置操作说明
LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器专为工业环境精心打造,完美适配AGV和无人叉车。同时,集成以太网与语音合成技术,为各类高级系统(如MES、调度系统、库位管理、立库等)提供高效便捷的语音交互体验。 L…...

装饰模式(Decorator Pattern)重构java邮件发奖系统实战
前言 现在我们有个如下的需求,设计一个邮件发奖的小系统, 需求 1.数据验证 → 2. 敏感信息加密 → 3. 日志记录 → 4. 实际发送邮件 装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其…...
前端倒计时误差!
提示:记录工作中遇到的需求及解决办法 文章目录 前言一、误差从何而来?二、五大解决方案1. 动态校准法(基础版)2. Web Worker 计时3. 服务器时间同步4. Performance API 高精度计时5. 页面可见性API优化三、生产环境最佳实践四、终极解决方案架构前言 前几天听说公司某个项…...
可靠性+灵活性:电力载波技术在楼宇自控中的核心价值
可靠性灵活性:电力载波技术在楼宇自控中的核心价值 在智能楼宇的自动化控制中,电力载波技术(PLC)凭借其独特的优势,正成为构建高效、稳定、灵活系统的核心解决方案。它利用现有电力线路传输数据,无需额外布…...

P3 QT项目----记事本(3.8)
3.8 记事本项目总结 项目源码 1.main.cpp #include "widget.h" #include <QApplication> int main(int argc, char *argv[]) {QApplication a(argc, argv);Widget w;w.show();return a.exec(); } 2.widget.cpp #include "widget.h" #include &q…...

tree 树组件大数据卡顿问题优化
问题背景 项目中有用到树组件用来做文件目录,但是由于这个树组件的节点越来越多,导致页面在滚动这个树组件的时候浏览器就很容易卡死。这种问题基本上都是因为dom节点太多,导致的浏览器卡顿,这里很明显就需要用到虚拟列表的技术&…...

Unsafe Fileupload篇补充-木马的详细教程与木马分享(中国蚁剑方式)
在之前的皮卡丘靶场第九期Unsafe Fileupload篇中我们学习了木马的原理并且学了一个简单的木马文件 本期内容是为了更好的为大家解释木马(服务器方面的)的原理,连接,以及各种木马及连接工具的分享 文件木马:https://w…...

嵌入式学习笔记DAY33(网络编程——TCP)
一、网络架构 C/S (client/server 客户端/服务器):由客户端和服务器端两个部分组成。客户端通常是用户使用的应用程序,负责提供用户界面和交互逻辑 ,接收用户输入,向服务器发送请求,并展示服务…...

Linux 内存管理实战精讲:核心原理与面试常考点全解析
Linux 内存管理实战精讲:核心原理与面试常考点全解析 Linux 内核内存管理是系统设计中最复杂但也最核心的模块之一。它不仅支撑着虚拟内存机制、物理内存分配、进程隔离与资源复用,还直接决定系统运行的性能与稳定性。无论你是嵌入式开发者、内核调试工…...
C#学习第29天:表达式树(Expression Trees)
目录 什么是表达式树? 核心概念 1.表达式树的构建 2. 表达式树与Lambda表达式 3.解析和访问表达式树 4.动态条件查询 表达式树的优势 1.动态构建查询 2.LINQ 提供程序支持: 3.性能优化 4.元数据处理 5.代码转换和重写 适用场景 代码复杂性…...