RequestMapping注解原理
RequestMapping注解的核心处理逻辑都在RequestMappingHandlerMapping中。
先上类图
RequestMappingHandlerMapping 介绍
RequestMappingHandlerMapping是SpringMVC中的一个重要组件,作用是扫描@Controller、@RequestMapping注解修饰的类,然后生成请求与方法的对应关系,当有一个 HTTP 请求进入 SpringMVC 时,就会通过请求找到对应的方法进行执行。
可以简单的想象一下,在RequestMappingHandlerMapping会维护一个Map<String,Handle>,key 存放的是URI,value 存放的是对应处理的handle,例如:
map.put(“GET /user”,UserController#get)
map.put(“POST /user”,UserController#create)
加载流程
- 流程图
在项目启动时,会触发上述加载流程。具体如下:
- 方法入口:RequestMappingHandlerMapping实现了InitializingBean接口,在应用启动时会触发afterPropertiesSet方法。
– RequestMappingHandlerMapping.java
- 接着调用父类AbstractHandlerMethodMapping,在initHandlerMethods方法中,会遍历所有候选的 Bean,并通过processCandidateBean方法进行处理。
– AbstractHandlerMethodMapping.java
protected void initHandlerMethods() {
String[] var1 = this.getCandidateBeanNames();
int var2 = var1.length;
//遍历所有候选的bean name
for(int var3 = 0; var3 < var2; ++var3) {
String beanName = var1[var3];
if (!beanName.startsWith("scopedTarget.")) {
//处理bean
this.processCandidateBean(beanName);
}
}
this.handlerMethodsInitialized(this.getHandlerMethods());
}
- 在processCandidateBean方法中,会调用RequestMappingHandlerMapping中实现的isHandler判断Bean是否为@Controller、@RequestMapping注解修饰的类,是的话调用detectHandlerMethods来检查类中的Handler method
– AbstractHandlerMethodMapping.java
protected void processCandidateBean(String beanName) {
Class<?> beanType = null;
try {
beanType = this.obtainApplicationContext().getType(beanName);
} catch (Throwable var4) {
if (this.logger.isTraceEnabled()) {
this.logger.trace("Could not resolve type for bean '" + beanName + "'", var4);
}
}
// 调用实现类的 isHandler方法
if (beanType != null && this.isHandler(beanType)) {
// 处理符合条件的bean
this.detectHandlerMethods(beanName);
}
}
– RequestMappingHandlerMapping.java
// 判断Bean是否为@Controller、@RequestMapping注解修饰的类
protected boolean isHandler(Class<?> beanType) {
return AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) || AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class);
}
- detectHandlerMethods中会遍历类中所有方法,通过getMappingForMethod方法筛选出@RequestMapping注解修饰的方法,然后解析成method->mapping的 Map 结构存起来,再遍历使用registerHandlerMethod方法注册到 SpringMVC 中。
– AbstractHandlerMethodMapping.java
protected void detectHandlerMethods(Object handler) {
Class<?> handlerType = handler instanceof String ? this.obtainApplicationContext().getType((String)handler) : handler.getClass();
if (handlerType != null) {
Class<?> userType = ClassUtils.getUserClass(handlerType);
// 获取该类中的所有方法
Map<Method, T> methods = MethodIntrospector.selectMethods(userType, (method) -> {
try {
// 匿名内部类,最终会调用RequestMappingHandlerMapping中具体实现方法,筛选出被@RequestMapping修饰的方法
return this.getMappingForMethod(method, userType);
} catch (Throwable var4) {
throw new IllegalStateException("Invalid mapping on handler class [" + userType.getName() + "]: " + method, var4);
}
});
if (this.logger.isTraceEnabled()) {
this.logger.trace(this.formatMappings(userType, methods));
}
// 把符合条件的方法进行注册
methods.forEach((method, mapping) -> {
Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
//注册逻辑
this.registerHandlerMethod(handler, invocableMethod, mapping);
});
}
}
- 通过registerHandlerMethod将对应的关系存放到mappingRegistry对象中,里面有很多的 Map 用于存储映射关系。
– AbstractHandlerMethodMapping.java
public void register(T mapping, Object handler, Method method) {
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,实际上就是bean name+method,在拦截器中就是暴露的这个对象
HandlerMethod handlerMethod = AbstractHandlerMethodMapping.this.createHandlerMethod(handler, method);
this.validateMethodMapping(handlerMethod, mapping);
// 将mapping对象和handlerMethod关系存放至mappingLookup
this.mappingLookup.put(mapping, handlerMethod);
List<String> directUrls = this.getDirectUrls(mapping);
Iterator var6 = directUrls.iterator();
while(var6.hasNext()) {
String url = (String)var6.next();
// 将非通配符形式的路径与mapping对象关系存放至urlLookup
this.urlLookup.add(url, mapping);
}
String name = null;
if (AbstractHandlerMethodMapping.this.getNamingStrategy() != null) {
name = AbstractHandlerMethodMapping.this.getNamingStrategy().getName(handlerMethod, mapping);
this.addMappingName(name, handlerMethod);
}
CorsConfiguration corsConfig = AbstractHandlerMethodMapping.this.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();
}
}
通过源码可以得知,目前有这两个mappingLookup和urlLookup对象存放了请求映射关系,在请求到来的时候就会通过这两个Map去寻找要执行的方法。
请求流程
springMVC 流程图
请求的入口由DispatcherServlet统一接管,核心处理方法为 doDispatch
– DispatcherServlet.java
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
processedRequest = this.checkMultipart(request);
multipartRequestParsed = processedRequest != request;
// 获取拦截器链HandlerExecutionChain,其中包含了处理请求的controller方法、拦截器方法等。
mappedHandler = this.getHandler(processedRequest);
if (mappedHandler == null) {
this.noHandlerFound(processedRequest, response);
return;
}
HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if ((new ServletWebRequest(request, response)).checkNotModified(lastModified) && isGet) {
return;
}
}
// 1、执行preHandle()方法
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// 2、执行controller中的方法
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
// 3、执行postHandle()方法
this.applyDefaultViewName(processedRequest, mv);
mappedHandler.applyPostHandle(processedRequest, response, mv);
} catch (Exception var20) {
dispatchException = var20;
} catch (Throwable var21) {
dispatchException = new NestedServletException("Handler dispatch failed", var21);
}
// 4、调用afterCompletion()方法
this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);
} catch (Exception var22) {
// 4、内部调用了afterCompletion()方法,就算报异常也会调用afterCompletion()方法
this.triggerAfterCompletion(processedRequest, response, mappedHandler, var22);
} catch (Throwable var23) {
// 4、内部调用了afterCompletion()方法,就算报异常也会调用afterCompletion()方法
this.triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", var23));
}
} finally {
if (asyncManager.isConcurrentHandlingStarted()) {
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
} else if (multipartRequestParsed) {
this.cleanupMultipart(processedRequest);
}
}
}
在getHandler方法中就是对应的逻辑了,代码如下:
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
if (this.handlerMappings != null) {
//遍历handlerMappings,只要能根据请求匹配到一个handler就返回
for (HandlerMapping mapping : this.handlerMappings) {
HandlerExecutionChain handler = mapping.getHandler(request);
if (handler != null) {
return handler;
}
}
}
return null;
}
这里值得一提的是handlerMappings是一组HandlerMapping接口的实现,SpringMVC默认提供的是org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping,如果有需要我们也可以自定义一个HandlerMapping实现来处理请求。
接着一路跟踪源码,直到AbstractHandlerMethodMapping#lookupHandlerMethod(String lookupPath, HttpServletRequest request)方法,就可以看到具体的实现了。
– AbstractHandlerMethodMapping.java
//先直接使用URI进行匹配,适用于没使用通配符修饰的接口路径,对应urlLookup
List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
if (directPathMatches != null) {
//路径匹配到之后,还要根据method、header、consume、produce等等条件继续进行匹配
addMatchingMappings(directPathMatches, matches, request);
}
if (matches.isEmpty()) {
//如果没匹配到,再通过通配符的方式去匹配,对应mappingLookup
addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);
}
自定义注解
实现自定义注解实现RequestMapping的功能。
定义注解
// 用于标识对外暴露的接口类
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface OpenApi {
@AliasFor("prefix")
String[] value() default {};
@AliasFor("value")
String[] prefix() default {};
// 默认POST方法
RequestMethod[] method() default {RequestMethod.POST};
}
//DisableOpenApi:用于标记接口类中不对外暴露的方法
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DisableOpenApi {
}
核心处理逻辑
/**
* OpenApiHandlerMapping
*/
public class OpenApiHandlerMapping extends RequestMappingHandlerMapping {
private RequestMappingInfo.BuilderConfiguration config = new RequestMappingInfo.BuilderConfiguration();
/**
* 判断是否是OpenApi注解的类
*
* @param beanType
* @return
*/
@Override
protected boolean isHandler(Class<?> beanType) {
return AnnotatedElementUtils.hasAnnotation(beanType, OpenApi.class);
}
/**
* 构造每个方法的请求信息
*
* @param method
* @param handlerType
* @return
*/
@Override
protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
//根据方法构造,如果方法上没有请求path,直接返回null
RequestMappingInfo info = createRequestMappingInfoByMethod(method);
if (info != null) {
//获取类上的prefix,和方法上的path拼接在一起
RequestMappingInfo typeInfo = createRequestMappingInfoByClass(handlerType);
if (typeInfo != null) {
info = typeInfo.combine(info);
}
}
return info;
}
private RequestMappingInfo createRequestMappingInfoByClass(AnnotatedElement element) {
OpenApi OpenApi = AnnotatedElementUtils.findMergedAnnotation(element, OpenApi.class);
return bizOpenApi != null ? RequestMappingInfo.paths(bizOpenApi.value()).build() : null;
}
private RequestMappingInfo createRequestMappingInfoByMethod(AnnotatedElement element) {
// DisableOpenApi标记的方法不对外暴露
if (AnnotatedElementUtils.findMergedAnnotation(element, DisableOpenApi.class) != null) {
return null;
}
RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(element, RequestMapping.class);
if (requestMapping == null) {
return null;
}
RequestMappingInfo.Builder builder = RequestMappingInfo.paths(super.resolveEmbeddedValuesInPatterns(requestMapping.path()))
.methods(requestMapping.method())
.params(requestMapping.params())
.headers(requestMapping.headers())
.consumes(requestMapping.consumes())
.produces(requestMapping.produces())
.mappingName(requestMapping.name());
return builder.options(this.config).build();
}
@Override
public void afterPropertiesSet() {
//设置优先级
this.setOrder(100);
super.afterPropertiesSet();
}
}
配置类
关键:配置类继承DelegatingWebMvcConfiguration的目的是为了把spring mvc的拦截器添加进来,使得我们配置的拦截器对自定义注解依然生效。(关于spring拦截器的原理将在下一章解析)
@Configuration
public class OpenApiConfig extends DelegatingWebMvcConfiguration {
@Bean
public BizOpenApiHandlerMapping bizOpenApiHandlerMapping(
@Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager,
@Qualifier("mvcConversionService") FormattingConversionService conversionService,
@Qualifier("mvcResourceUrlProvider") ResourceUrlProvider resourceUrlProvider) {
OpenApiHandlerMapping mapping = new OpenApiHandlerMapping();
mapping.setInterceptors(this.getInterceptors(conversionService, resourceUrlProvider));
mapping.setContentNegotiationManager(contentNegotiationManager);
mapping.setCorsConfigurations(this.getCorsConfigurations());
return mapping;
}
}
效果
localhost:8080/test/hello 可以正常访问
localhost:8080/test/hello2 无法访问到
@RestController
@OpenApi("/test")
public class ApiController {
@PostMapping("/hello")
public void hello() {
System.out.println("hello");
}
@PostMapping("/hello2")
@DisableOpenApi
public void hello() {
System.out.println("hello");
}
参考文章
自定义SpringMVC中的RequestMappingHandlerMapping
RequestMapping原理分析
自定义注解实现@RequestMapping路由功能