我們平時(shí)在使用Spring MVC進(jìn)行WEB開(kāi)發(fā)的時(shí)候都會(huì)使用@Controller跟@RequestMapping注解定義控制器實(shí)體跟處理請(qǐng)求的方法的,讓我們從@Controller跟@ReuqstMapping這兩個(gè)注解開(kāi)始就看Spring MVC工作的
從JavaDoc上獲取有用的信息
我們先來(lái)看一下@Controller的JavaDoc吧,但因?yàn)閺腀Controller的JavaDoc上看不到什么有用的信息這里也就不貼它的代碼了.從@Controller的JavaDoc上來(lái)可以知道以下信息:
- 表明這個(gè)類在WEB中是一個(gè)Controller
- Spring在掃描類的時(shí)候會(huì)把它按照@Component一樣處理
- 和@RequestMapping注解配合使用
/**
* Annotation for mapping web requests onto methods in request-handling classes
* with flexible method signatures.
*
* <p>Both Spring MVC and Spring WebFlux support this annotation through a
* {@code RequestMappingHandlerMapping} and {@code RequestMappingHandlerAdapter}
* in their respective modules and package structure. For the exact list of
* supported handler method arguments and return types in each, please use the
* reference documentation links below:
* <ul>
* <li>Spring MVC
* <a >Method Arguments</a>
* and
* <a >Return Values</a>
* </li>
* </ul>
* @author Juergen Hoeller
* @author Arjen Poutsma
* @author Sam Brannen
* @since 2.5
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Mapping
public @interface RequestMapping
從@RequestMapping的JavaDoc可以知道Spring MVC是通過(guò)RequestMappingHandlerMapping跟RequestMappingHandlerAdapter來(lái)實(shí)現(xiàn)對(duì)@RequestMapping的支持的,以及對(duì)支持什么參數(shù)跟什么返回值(這些跟本文無(wú)關(guān)有興趣的同學(xué)可以點(diǎn)擊JavaDoc上的鏈接地址查看文檔)
@RequestMapping跟@Controller的注解信息是如何被讀取的
順著上面的注解信息查看RequestMappingHandlerMapping類的JavaDoc發(fā)現(xiàn)一句比較重要的線索Creates RequestMappingInfo instances from type and method-level @RequestMapping annotations in @Controller classes(讀取被@Controller標(biāo)記的類下被@RequestMapping標(biāo)記的方法的信息封裝成@RequestInfo)
從RequestMappingHandlerMapping上找到以下相關(guān)方法
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);
}
String prefix = getPathPrefix(handlerType);
if (prefix != null) {
info = RequestMappingInfo.paths(prefix).options(this.config).build().combine(info);
}
}
return info;
}
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);
}
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);
}
從上面的代碼Spring Mvc在讀取@RequestMapping的步驟如下:
- 先分別從方法跟類上面的@RequestMapping注解信息封裝成一個(gè)RequestMappingInfo 信息
1.1 RequestMappingInfo由多個(gè)RequestCondition組成,RequestCondition代表一個(gè)HttpServletRequest的匹配規(guī)則
1.2 RequestCondition有多種子類用于判斷不同的類型,例如RequestMethodsRequestCondition就是判斷Http請(qǐng)求的
1.3 兩個(gè)RequestMappingInfo整合成一個(gè)的時(shí)候會(huì)把Spring 定義的RequestCondition會(huì)合并在一起,正因?yàn)檫@樣在類上配置注解設(shè)配get并且方法上設(shè)置設(shè)配post的話這個(gè)方法可以處理get,post http請(qǐng)求
1.4 我們可以通過(guò)繼承RequestMappingHandlerMapping類并從寫(xiě)getCustomTypeCondition跟getCustomMethodCondition來(lái)定制自己需要的匹配規(guī)則,在將它注入到Spring ioc時(shí)設(shè)置@Order注解配置適當(dāng)?shù)膬?yōu)先順序讓它在RequestMappingHandlerMapping前設(shè)配http - 將從方法跟類上讀取到RequestMappingInfo整個(gè)一個(gè)
- 判斷一個(gè)類是否將映射的url添加前綴,可以通過(guò)WebMvcConfigurer類的configurePathMatch方法進(jìn)行配置,例如通過(guò)如下配置定義所有被@Wkx注解的Controller類都需要添加/wkx前綴
@Configuration
public class WebConfig implements WebMvcConfigurer
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
configurer.addPathPrefix("wkx", clazz -> clazz.isAnnotationPresent(Wkx.class));
}
}
RequestMappingHandlerMapping是在什么時(shí)候讀取@RequestMapping信息

通過(guò)idea的Call Hierarchy的知道RequestMappingHandlerMapping會(huì)在其afterPropertiesSet生命周期讀取的
public class AbstractHandlerMethodMapping {
protected void initHandlerMethods() {
// 1 獲取Spring ApplicationContext中所有的裝配Bean的名稱
for (String beanName : getCandidateBeanNames()) {
if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
// 2 對(duì)所有Bean讀取@RequestMapping信息
processCandidateBean(beanName);
}
}
handlerMethodsInitialized(getHandlerMethods());
}
}
protected void processCandidateBean(String beanName) {
Class<?> beanType = null;
try {
beanType = obtainApplicationContext().getType(beanName);
}
catch (Throwable ex) {
if (logger.isTraceEnabled()) {
logger.trace("Could not resolve type for bean '" + beanName + "'", ex);
}
}
// 3 對(duì)所有被@Controller類讀取@RequestMapping信息
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);
// 4 使用工具類讀取所有方法上的@RequestMapping信息 ps:國(guó)產(chǎn)開(kāi)源任務(wù)調(diào)度框架也是使用這個(gè)工具類來(lái)讀取@JobHandler注解的
Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
(MethodIntrospector.MetadataLookup<T>) method -> {
try {
// 5 終于輪到重頭戲了上面說(shuō)的RequestMappingHandlerMapping具體讀取@RequestMapping信息
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.forEach((method, mapping) -> {
Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
// 6 注冊(cè)前面讀取的RequestMappingInfo存儲(chǔ)起來(lái)
registerHandlerMethod(handler, invocableMethod, mapping);
});
}
}
}
將讀取的RequestMappingInfo保存起來(lái)
從前面我們知道Spring mvc在啟動(dòng)的時(shí)候會(huì)讀取@RequestMapping信息并存儲(chǔ)到內(nèi)存中,接下來(lái)我們看看是怎么存儲(chǔ)起來(lái)的
RequestMappingHandlerMapping 會(huì)先調(diào)用其父類的registerHandlerMethod方法,其父類會(huì)把注冊(cè)規(guī)則交給MappingRegistry實(shí)現(xiàn)
class MappingRegistry {
public void register(T mapping, Object handler, Method method) {
this.readWriteLock.writeLock().lock();
try {
HandlerMethod handlerMethod = createHandlerMethod(handler, method);
validateMethodMapping(handlerMethod, mapping);
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) {
name = getNamingStrategy().getName(handlerMethod, mapping);
addMappingName(name, handlerMethod);
}
CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping);
if (corsConfig != null) {
this.corsLookup.put(handlerMethod, corsConfig);
}
this.registry.put(mapping, new MappingRegistration<>(mapping, handlerMethod, directUrls, name));
}
finally {
this.readWriteLock.writeLock().unlock();
}
}
}
從上面的源碼我們注冊(cè)流程如下
- 先將method跟相關(guān)類實(shí)例封裝成一個(gè)HandlerMethod方便后續(xù)調(diào)用
- 以RequestMappingInfo為Key,HandlerMethod為Value映射到map中
- 以Url為Key,RequestMapping為多值Value映射到一個(gè)MultiValueMap中
- 以RequestMapping的name值為key,以HandlerMethod為Value映射到map中
- 獲取讀取方法上的類跟方法上的@CrossOrigin注解信息封裝成CorsConfiguration,并以HandlerMethod為Key,CorsConfiguration為Value映射到一個(gè)map中
看到這里可以猜測(cè)Spring通過(guò)HttpServletRequest的url是如何找到相關(guān)HandlerMethod并來(lái)處理請(qǐng)求的.有點(diǎn)類似與有點(diǎn)類似與電商系統(tǒng)中根據(jù)訂單號(hào)獲取訂單的商品信息的來(lái)進(jìn)行發(fā)貨的.假設(shè)訂單只有一個(gè)商品.首先根據(jù)訂單號(hào)(url)找到訂單(ReuestMapping),多個(gè)訂單(RequestMappingInfo)可以與一個(gè)商品(HandlerMethod)相關(guān)聯(lián),找到商品(HandlerMethod)之后查看是否有物流發(fā)貨配置(CorsConfiguration)某些獲取像生鮮只能走冷鏈