1. 程式人生 > 其它 >SpringMVC--@RequestMapping註解標註方法解析

SpringMVC--@RequestMapping註解標註方法解析

SpringMVC--@RequestMapping註解標註方法解析

本文是基於springboot進行原始碼追蹤分析

問題

  • @RequestMapping註釋的類及方法,Spring是何時,何種方式解析成url與方法的對映關係的?

背景

  • @RequestMapping註解的解析識別工作是由RequestMappingHandlerMapping類去完成的,會生成對應的RequestMappingInfo例項
  • RequestMappingHandlerMapping類的位置是在org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration.EnableWebMvcConfiguration
    通過@Bean註解宣告的
  • org.springframework.context.support.AbstractApplicationContext#invokeBeanFactoryPostProcessors方法解析通過@Bean標記的方法,將對應物件轉換為org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader.ConfigurationClassBeanDefinition註冊到org.springframework.beans.factory.support.DefaultListableBeanFactory
  • org.springframework.beans.factory.support.DefaultListableBeanFactory#preInstantiateSingletons方法中例項化物件,並在org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#invokeInitMethods方法中呼叫org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping#afterPropertiesSet
    方法實現@RequestMapping註解類及方法的解析與註冊

org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration中定義了WEB MVC相關的自動配置類,就比如org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMappingorg.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter等等的例項化,

解析

類繼承圖

過程

在初始化RequestMappingHandlerMapping物件的時候,因為實現了org.springframework.beans.factory.InitializingBean#afterPropertiesSet方法,所以會呼叫org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#invokeInitMethods方法時,會呼叫RequestMappingHandlerMapping#afterPropertiesSet方法。

	// RequestMappingHandlerMapping類中相關程式碼

	public void afterPropertiesSet() {

		this.config = new RequestMappingInfo.BuilderConfiguration();
		this.config.setTrailingSlashMatch(useTrailingSlashMatch());
		this.config.setContentNegotiationManager(getContentNegotiationManager());

		if (getPatternParser() != null) {
			this.config.setPatternParser(getPatternParser());
			Assert.isTrue(!this.useSuffixPatternMatch && !this.useRegisteredSuffixPatternMatch,
					"Suffix pattern matching not supported with PathPatternParser.");
		}
		else {
			this.config.setSuffixPatternMatch(useSuffixPatternMatch());
			this.config.setRegisteredSuffixPatternMatch(useRegisteredSuffixPatternMatch());
			this.config.setPathMatcher(getPathMatcher());
		}
		// 呼叫父類的方法進行具體的解析
		super.afterPropertiesSet();
	}

在自身類的重寫方法中進行了一系列的配置,同時呼叫了父類(org.springframework.web.servlet.handler.AbstractHandlerMethodMapping)的afterPropertiesSet方法,而具體的解析方法就在父類中。

	// AbstractHandlerMethodMapping中相關程式碼	

	public void afterPropertiesSet() {
		initHandlerMethods();
	}
	/**
	 * Scan beans in the ApplicationContext, detect and register handler methods.
	 * @see #getCandidateBeanNames()
	 * @see #processCandidateBean
	 * @see #handlerMethodsInitialized
	 */
	protected void initHandlerMethods() {
		for (String beanName : getCandidateBeanNames()) {
			if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
                //處理每個可能的bean
				processCandidateBean(beanName);
			}
		}
		handlerMethodsInitialized(getHandlerMethods());
	}

	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.
			if (logger.isTraceEnabled()) {
				logger.trace("Could not resolve type for bean '" + beanName + "'", ex);
			}
		}
        // beanType上是否由@Controller或者@RequestMapping,如果有則說明是一個待解析的RequestMappingInfo
		if (beanType != null && isHandler(beanType)) {
			detectHandlerMethods(beanName);
		}
	}

	protected void detectHandlerMethods(Object handler) {
		Class<?> handlerType = (handler instanceof String ?
				obtainApplicationContext().getType((String) handler) : handler.getClass());

		if (handlerType != null) {
			Class<?> userType = ClassUtils.getUserClass(handlerType);
            //獲取該handler內所有的Method與RquestMappingInfo對映關係
			Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
					(MethodIntrospector.MetadataLookup<T>) method -> {
						try {
							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));
			}
			else if (mappingsLogger.isDebugEnabled()) {
				mappingsLogger.debug(formatMappings(userType, methods));
			}
			methods.forEach((method, mapping) -> {
				Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
                //按method將RequestMappingInfo進行註冊
				registerHandlerMethod(handler, invocableMethod, mapping);
			});
		}
	}

AbstractHandlerMethodMapping#detectHandlerMethods方法中,獲取當前bean的所有method與RequestMapping對映關係,並進行註冊。

現在繼續看AbstractHandlerMethodMapping#getMappingForMethod,根據方法名即可猜測,這裡就是通過method獲取RequestMappingInfo,具體的實現方法在RequestMappingHandlerMapping#createRequestMappingInfo

// org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping#getMappingForMethod實現程式碼

	/**
	 * Uses method and type-level @{@link RequestMapping} annotations to create
	 * the RequestMappingInfo.
	 * @return the created RequestMappingInfo, or {@code null} if the method
	 * does not have a {@code @RequestMapping} annotation.
	 * @see #getCustomMethodCondition(Method)
	 * @see #getCustomTypeCondition(Class)
	 */
	@Override
	@Nullable
	protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
        // 根據method建立對應的RquestMappingInfo
		RequestMappingInfo info = createRequestMappingInfo(method);
		if (info != null) {
            // 如果方法所在類上也標註了@RequestMapping,則建立類對應的RequestMappingInfo
			RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType);
			if (typeInfo != null) {
                // 將類與方法的requestMappingInfo進行合併,可以理解為獲取完整的url路徑
				info = typeInfo.combine(info);
			}
			String prefix = getPathPrefix(handlerType);
			if (prefix != null) {
				info = RequestMappingInfo.paths(prefix).options(this.config).build().combine(info);
			}
		}
        // 返回完整url的RequestMappingInfo物件
		return info;
	}

獲取到對應的RequestMappingInfo之後,就需要進行註冊了,下面看註冊邏輯org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#registerHandlerMethod

	public void register(T mapping, Object handler, Method method) {
			this.readWriteLock.writeLock().lock();
			try {
                // 根據方法生成對應的HandlerMethod,包含了method對應的bean例項,對應的method
				HandlerMethod handlerMethod = createHandlerMethod(handler, method);
                // 校驗url路徑是否有重複,如果重複會丟擲異常
				validateMethodMapping(handlerMethod, mapping);

				Set<String> directPaths = AbstractHandlerMethodMapping.this.getDirectPaths(mapping);
				for (String path : directPaths) {
					this.pathLookup.add(path, mapping);
				}

				String name = null;
				if (getNamingStrategy() != null) {
					name = getNamingStrategy().getName(handlerMethod, mapping);
					addMappingName(name, handlerMethod);
				}

				CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping);
				if (corsConfig != null) {
					corsConfig.validateAllowCredentials();
					this.corsLookup.put(handlerMethod, corsConfig);
				}

                //MappingRegistration包含RequestMappingInfo、HandlerMethod等
                //Map<T, MappingRegistration<T>> registry = new HashMap<>();
				this.registry.put(mapping,
						new MappingRegistration<>(mapping, handlerMethod, directPaths, name, corsConfig != null));
			}
			finally {
				this.readWriteLock.writeLock().unlock();
			}
		}

總結

  • 契機:給需要暴漏介面的方法、類上新增@RequestMapping註解

  • 時機:由於org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping間接實現了org.springframework.beans.factory.InitializingBean#afterPropertiesSet方法,所以在RequestMappingHandlerMapping物件初始化的時候,會呼叫自身的org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping#afterPropertiesSet方法,自身進行一系列配置之後,就會呼叫父類org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#afterPropertiesSet的方法進行@RequestMapping標註方法的解析

  • 解析:主體流程都是在AbstractHandlerMethodMapping類中的方法,具體的實現通過抽象方法的形式讓子類RequestMappingHandlerMapping進行實現。具體根據method生成對應的RequestMappingInfo是在RequestMappingHandlerMapping類中的方法org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping#getMappingForMethod。過程中會先解析標註了@RequestMapping的方法,生成方法對應的RequestMappingInfo例項;如果方法所在的類上也有@RequestMapping標註的註解,生成類對應的RequestMappingInfo例項,然後將兩者進行合併,也就是生成完整的url對映物件

  • 註冊:在解析完所有的方法之後,將RequestMappingInfo進行註冊,註冊容器位於AbstractHandlerMethodMapping類中,容器為 MappingRegistry mappingRegistry = new MappingRegistry(),而MappingRegistry 中實際儲存的容器為Map<T, MappingRegistration<T>> registry = new HashMap<>()。其中key為對應的RequestMappingInfo,value為MappingRegistration,其中MappingRegistration包含了RequestMappingInfoHandlerMethod