介绍
- 在mvc的配置文件中如果设置了 mvc:annotation-driven/标签,会自动注册RequestMappingHandlerMapping的BeanDefinition到容器中
- RequestMappingHandlerMapping用于扫描所有@Controller类下所有@RequestMapping的方法
源码分析
类图
以下类图需要查看源码的地方
- 实现了InitializingBean接口,所以需要查看afterPropertiesSet方法
- 继承了AbstractHandlerMethodMapping,在子类中调用了父类的afterPropertiesSet方法
生命周期方法分析
这里主要是查看到了实现InitializingBean接口,所以查看对应的afterPropertiesSet方法,主要的处理还是在父类
@Override
@SuppressWarnings("deprecation")
public void afterPropertiesSet() {
// 构建RequestMappingInfo配置对象
this.config = new RequestMappingInfo.BuilderConfiguration();
// 是否尾部斜杠匹配
this.config.setTrailingSlashMatch(useTrailingSlashMatch());
// 设置内容协商管理器,根据请求获取对应的媒体类型
this.config.setContentNegotiationManager(getContentNegotiationManager());
if (getPatternParser() != null && this.defaultPatternParser &&
(this.useSuffixPatternMatch || this.useRegisteredSuffixPatternMatch)) {
setPatternParser(null);
}
// 设置路径解析器
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();
}
- 在父类主要的功能就是初始化HandleMehotd方法,其实就是初始化了所有@Controller类中的@ReuqestMapping标注的方法
- 主要流程是,获取了所有的bean的名称,然后在循环遍历去处理(如果bean的名称是scopedTarget.开头的则不处理,这代表当前bean是scope代理bean)
@Override
public void afterPropertiesSet() {
// 初始化处理方法集合
initHandlerMethods();
}
protected void initHandlerMethods() {
// 获取所有的bean对象
for (String beanName : getCandidateBeanNames()) {
if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
// 处理所有的bean
processCandidateBean(beanName);
}
}
// handlerMethod方法初始化
handlerMethodsInitialized(getHandlerMethods());
}
获取所有候选bean的名称
如果detectHandlerMethodsInAncestorContexts(检测祖先上下文中)属性为true,则会从子容器和父容器拿到所有的bean,否则只从本容器中拿到所有的bean
protected String[] getCandidateBeanNames() {
return (this.detectHandlerMethodsInAncestorContexts ?
BeanFactoryUtils.beanNamesForTypeIncludingAncestors(obtainApplicationContext(), Object.class) :
obtainApplicationContext().getBeanNamesForType(Object.class));
}
处理候选bean
首先会获取bean的类型,然后在检查是否为Handler(也就是类上是否存在@Controller注解),然后在去检测所有的HandlerMethods方法
protected void processCandidateBean(String beanName) {
Class<?> beanType = null;
try {
// 获取bean的类型
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);
}
}
// 类型不为空,并且查看是Handler
if (beanType != null && isHandler(beanType)) {
// 检测HandlerMethod方法集合
detectHandlerMethods(beanName);
}
}
查看是否为Handler
- isHandler为模板方法,由子类实现RequestMappingHandlerMapping
- 如果类上存在@Controller注解则表示为Handler
@Override
protected boolean isHandler(Class<?> beanType) {
return AnnotatedElementUtils.hasAnnotation(beanType, Controller.class);
}
检测处理方法HnalderMehotd
首先会获取当前类的类型,然后会遍历类中所有方法,然后通过getMappingForMethod方法构建了一个ReuqestMappingInfo对象,最后把获取到的ReuqestMappingInfo集合,通过registerHandlerMethod方法注册了进去
protected void detectHandlerMethods(Object handler) {
Class<?> handlerType = (handler instanceof String beanName ?
obtainApplicationContext().getType(beanName) : handler.getClass());
if (handlerType != null) {
Class<?> userType = ClassUtils.getUserClass(handlerType);
Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
(MethodIntrospector.MetadataLookup<T>) method -> {
try {
// 根据方法获取mapping
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));
}
// 注册handleMehotd
methods.forEach((method, mapping) -> {
Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
registerHandlerMethod(handler, invocableMethod, mapping);
});
}
}
获取Mapping
- 首先会根据方法去构建一个RequestMappingInfo,然后在回根据类去构建一个RequestMappingInfo(类上因也能写@RequestMapping注解),最后合并
- 还会根据处理类去获取路径前缀,如果前缀存在,直接使用前缀
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);
}
// 路径未指定,则设置为/
if (info.isEmptyMapping()) {
info = info.mutate().paths("", "/").options(this.config).build();
}
// 获取路径前缀,存在前缀,直接替换设置路径为前缀
String prefix = getPathPrefix(handlerType);
if (prefix != null) {
info = RequestMappingInfo.paths(prefix).options(this.config).build().combine(info);
}
}
return info;
}
- 创建RequestMappingInfo对象
此方法的流程如下
- 获取自定义条件
- 获取注解描述列表,只获取@RequestMapping和@HttpExchange注解
- 获取所有的@RequestMapping注解的描述对象,如果不为空,则通过第一个@RequestMapping创建RequestMappingInfo对象
- 获取所有的@HttpExchange注解的描述对象,如果不为空,则通过第一个@HttpExchange创建RequestMappingInfo对象
private RequestMappingInfo createRequestMappingInfo(AnnotatedElement element) {
RequestMappingInfo requestMappingInfo = null;
// 获取自定义条件
RequestCondition<?> customCondition = (element instanceof Class<?> clazz ?
getCustomTypeCondition(clazz) : getCustomMethodCondition((Method) element));
// 获取注解描述列表,只获取@RequestMapping和@HttpExchange注解
List<AnnotationDescriptor> descriptors = getAnnotationDescriptors(element);
List<AnnotationDescriptor> requestMappings = descriptors.stream()
.filter(desc -> desc.annotation instanceof RequestMapping).toList();
if (!requestMappings.isEmpty()) {
if (requestMappings.size() > 1 && logger.isWarnEnabled()) {
logger.warn("Multiple @RequestMapping annotations found on %s, but only the first will be used: %s"
.formatted(element, requestMappings));
}
requestMappingInfo = createRequestMappingInfo((RequestMapping) requestMappings.get(0).annotation, customCondition);
}
List<AnnotationDescriptor> httpExchanges = descriptors.stream()
.filter(desc -> desc.annotation instanceof HttpExchange).toList();
if (!httpExchanges.isEmpty()) {
Assert.state(requestMappingInfo == null,
() -> "%s is annotated with @RequestMapping and @HttpExchange annotations, but only one is allowed: %s"
.formatted(element, Stream.of(requestMappings, httpExchanges).flatMap(List::stream).toList()));
Assert.state(httpExchanges.size() == 1,
() -> "Multiple @HttpExchange annotations found on %s, but only one is allowed: %s"
.formatted(element, httpExchanges));
requestMappingInfo = createRequestMappingInfo((HttpExchange) httpExchanges.get(0).annotation, customCondition);
}
return requestMappingInfo;
}
- 通过注解@RequestMapping或@HttpExchange创建RequestMappingInfo对象
- 获取了注解上的相关属性,然后放入了RequestMappingInfo的构造器,最后通过构造器创建出了对应的RequestMappingInfo
- 相关的参数最后都会被构建成对应的Condition(条件),在RequestMappingHandlerMapping中,会去根据相关的条件来查看请求是否符合当前的RequestMappingInfo,获取的参数如下
- path:请求路径
- method:请求方式
- params:请求参数
- headers:支持的请求头
- consumes:支持的媒体类型
- produces:返回的媒体类型
- name:映射名字、
- 最后查看是否有自定义条件,如果有则设置进去,然后在把config()设置进去,构造对象
- config对象中里面包含了一些属性,如是否包含后缀匹配、解析器
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);
}
// 把config设置进去,然后构造
return builder.options(this.config).build();
}
构建RequestMappingInfo对象
- 这里会根据参数创建不同的Condition,这些Condition都会根据请求来判断是否符合条件来判定当前的RequestMappingInfo是否满足要求,所有的Condition都实现了RequestCondition,都会根据getMatchingCondition方法查看是否返回了对象来查看是否符合条件
- 下面是相关参数对应的Condition
- paths:PathPatternsRequestCondition
- methods:RequestMethodsRequestCondition
- params:ParamsRequestCondition
- headers:HeadersRequestCondition
- consumes:ConsumesRequestCondition
- produces:ProducesRequestCondition
- customCondition:RequestConditionHolder
public RequestMappingInfo build() {
PathPatternsRequestCondition pathPatternsCondition = null;
PatternsRequestCondition patternsCondition = null;
// 获取路径格式解析器
PathPatternParser parser = this.options.getPatternParserToUse();
if (parser != null) {
// 创建路径格式条件对象,主要用于查看和请求路径是否匹配
pathPatternsCondition = (ObjectUtils.isEmpty(this.paths) ?
EMPTY_PATH_PATTERNS :
new PathPatternsRequestCondition(parser, this.paths));
}
else {
patternsCondition = (ObjectUtils.isEmpty(this.paths) ?
EMPTY_PATTERNS :
new PatternsRequestCondition(
this.paths, null, this.options.pathMatcher,
this.options.useSuffixPatternMatch(), this.options.useTrailingSlashMatch(),
this.options.getFileExtensions()));
}
ContentNegotiationManager manager = this.options.getContentNegotiationManager();
return new RequestMappingInfo(
this.mappingName, pathPatternsCondition, patternsCondition,
ObjectUtils.isEmpty(this.methods) ?
EMPTY_REQUEST_METHODS : new RequestMethodsRequestCondition(this.methods),
ObjectUtils.isEmpty(this.params) ?
EMPTY_PARAMS : new ParamsRequestCondition(this.params),
ObjectUtils.isEmpty(this.headers) ?
EMPTY_HEADERS : new HeadersRequestCondition(this.headers),
ObjectUtils.isEmpty(this.consumes) && !this.hasContentType ?
EMPTY_CONSUMES : new ConsumesRequestCondition(this.consumes, this.headers),
ObjectUtils.isEmpty(this.produces) && !this.hasAccept ?
EMPTY_PRODUCES : new ProducesRequestCondition(this.produces, this.headers, manager),
this.customCondition != null ?
new RequestConditionHolder(this.customCondition) : EMPTY_CUSTOM, this.options);
}
注册Mapping
- 会把所有的Mehotd和RequestMappingInfo映射Map,注册到MappingRegistry中
- MappingRegistry是内部类,属于AbstractHandlerMethodMapping
private final MappingRegistry mappingRegistry = new MappingRegistry();
protected void registerHandlerMethod(Object handler, Method method, T mapping) {
this.mappingRegistry.register(mapping, handler, method);
}
注册RequestMappingInfo对象的流程如下
- 首先会创建HandlerMethod对象,包含了执行类和方法
- 校验MethodMapping,目的就是查看是否有重复的HandlerMethod
- 启用方法验证,内部会检测是否校验返回值或者请求参数
- 获取直接请求路径放入pathLookup属性中
- 获取跨域配置
- 创建MappingRegistration对象,放入registry属性中
// RequestMapping和MappingRegistration对象的映射
private final Map<T, MappingRegistration<T>> registry = new HashMap<>();
// 存储了请求路径和RequestMapping的映射
private final MultiValueMap<String, T> pathLookup = new LinkedMultiValueMap<>();
// 存储了名称和HandlerMethod方法的映射
private final Map<String, List<HandlerMethod>> nameLookup = new ConcurrentHashMap<>();
// 存储了HandlerMethod和跨域配置映射
private final Map<HandlerMethod, CorsConfiguration> corsLookup = new ConcurrentHashMap<>();
public void register(T mapping, Object handler, Method method) {
this.readWriteLock.writeLock().lock();
try {
// 创建handlerMethod
HandlerMethod handlerMethod = createHandlerMethod(handler, method);
// 校验MethodMapping,就是看是否有重复的
validateMethodMapping(handlerMethod, mapping);
// 启用方法验证,内部会检测是否校验返回值或者请求参数
handlerMethod = handlerMethod.createWithValidateFlags();
// 获取直接请求路径,不包含RESTFul路径
Set<String> directPaths = AbstractHandlerMethodMapping.this.getDirectPaths(mapping);
for (String path : directPaths) {
this.pathLookup.add(path, mapping);
}
// 添加映射名称,,规则为类名大写 + # + 方法名,比如类是TestController,那就是TC#getBody
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();
corsConfig.validateAllowPrivateNetwork();
this.corsLookup.put(handlerMethod, corsConfig);
}
// 创建MappingRegistration对象,放入map中
this.registry.put(mapping,
new MappingRegistration<>(mapping, handlerMethod, directPaths, name, corsConfig != null));
}
finally {
this.readWriteLock.writeLock().unlock();
}
}
创建HandlerMethod
如果handler是字符串,则会把上下文传递进去,方便后面获取到对应的bean
protected HandlerMethod createHandlerMethod(Object handler, Method method) {
if (handler instanceof String beanName) {
return new HandlerMethod(beanName,
obtainApplicationContext().getAutowireCapableBeanFactory(),
obtainApplicationContext(),
method);
}
return new HandlerMethod(handler, method);
}
验证HandlerMethod
主要的作用就是是否开启参数校验和返回值校验
public HandlerMethod createWithValidateFlags() {
return new HandlerMethod(this, null, true);
}
private HandlerMethod(HandlerMethod handlerMethod, @Nullable Object handler, boolean initValidateFlags) {
super(handlerMethod);
this.bean = (handler != null ? handler : handlerMethod.bean);
this.beanFactory = handlerMethod.beanFactory;
this.messageSource = handlerMethod.messageSource;
this.beanType = handlerMethod.beanType;
// 校验参数信息
this.validateArguments = (initValidateFlags ?
MethodValidationInitializer.checkArguments(this.beanType, getMethodParameters()) :
handlerMethod.validateArguments);
// 检查返回值
this.validateReturnValue = (initValidateFlags ?
MethodValidationInitializer.checkReturnValue(this.beanType, getBridgedMethod()) :
handlerMethod.validateReturnValue);
this.responseStatus = handlerMethod.responseStatus;
this.responseStatusReason = handlerMethod.responseStatusReason;
this.resolvedFromHandlerMethod = handlerMethod;
this.description = handlerMethod.toString();
}
- 参数校验
首先会检查当前环境中是否存在@Validator,并且当前类上没有@Validated
- 参数上存在@Constraint注解,@NotNull、@NotBlank等都属于
- 参数上存在@Valid注解,并且参数是List、Object[]、Map三种类型
- 第三种没搞懂
private static final boolean BEAN_VALIDATION_PRESENT =
ClassUtils.isPresent("jakarta.validation.Validator", HandlerMethod.class.getClassLoader());
private static final Predicate<MergedAnnotation<? extends Annotation>> CONSTRAINT_PREDICATE =
MergedAnnotationPredicates.typeIn("jakarta.validation.Constraint");
private static final Predicate<MergedAnnotation<? extends Annotation>> VALID_PREDICATE =
MergedAnnotationPredicates.typeIn("jakarta.validation.Valid");
public static boolean checkArguments(Class<?> beanType, MethodParameter[] parameters) {
// @Validator类在当前虚拟机存在,并且不存在@Validated注解
if (BEAN_VALIDATION_PRESENT && AnnotationUtils.findAnnotation(beanType, Validated.class) == null) {
for (MethodParameter param : parameters) {
// 存在@Constraint注解,如@NotNull等
MergedAnnotations merged = MergedAnnotations.from(param.getParameterAnnotations());
if (merged.stream().anyMatch(CONSTRAINT_PREDICATE)) {
return true;
}
// 存在@Valid注解,参数类型是集合类
Class<?> type = param.getParameterType();
if (merged.stream().anyMatch(VALID_PREDICATE) && isIndexOrKeyBasedContainer(type)) {
return true;
}
// 用于获取集合元素的注解信息
merged = MergedAnnotations.from(getContainerElementAnnotations(param));
// 查看是否存在@Constraint注解
if (merged.stream().anyMatch(CONSTRAINT_PREDICATE)) {
return true;
}
}
}
return false;
}
- 返回值校验
首先会检查当前环境中是否存在@Validator,并且当前类上没有@Validated,然后会检查方法上面是否存在@Constraint注解或者@Valid注解
public static boolean checkReturnValue(Class<?> beanType, Method method) {
if (BEAN_VALIDATION_PRESENT && AnnotationUtils.findAnnotation(beanType, Validated.class) == null) {
MergedAnnotations merged = MergedAnnotations.from(method, MergedAnnotations.SearchStrategy.TYPE_HIERARCHY);
return merged.stream().anyMatch(CONSTRAINT_PREDICATE.or(VALID_PREDICATE));
}
return false;
}
添加映射名称
首先会生成一个名称,规则为类名大写 + # + 方法名,比如类是TestController,那就是TC#getBody,然后把名称和HandlerMehod的关系放入到nameLookup属性集合中
private final Map<String, List<HandlerMethod>> nameLookup = new ConcurrentHashMap<>();
private void addMappingName(String name, HandlerMethod handlerMethod) {
List<HandlerMethod> oldList = this.nameLookup.get(name);
if (oldList == null) {
// 创建一个空的集合
oldList = Collections.emptyList();
}
// 查看是否存在
for (HandlerMethod current : oldList) {
if (handlerMethod.equals(current)) {
return;
}
}
List<HandlerMethod> newList = new ArrayList<>(oldList.size() + 1);
newList.addAll(oldList);
newList.add(handlerMethod);
// 存入集合中
this.nameLookup.put(name, newList);
}
跨域配置
会获取@CrossOrigin注解的信息,然后构建出一个跨域配置类,最后把HandlerMehotd对象和跨域配置的映射关系放入到了corsLookup集合属性中
- 初始化跨域配置
- 会从类和当前方法上获取CrossOrigin注解,然后把相关的属性设置到跨域配置对象中,并且在设置属性的时候,还会去解析占位符
- 如果没有指定请求方式,则会设置当前接口处理的请求方式
protected CorsConfiguration initCorsConfiguration(Object handler, Method method, RequestMappingInfo mappingInfo) {
HandlerMethod handlerMethod = createHandlerMethod(handler, method);
Class<?> beanType = handlerMethod.getBeanType();
CrossOrigin typeAnnotation = AnnotatedElementUtils.findMergedAnnotation(beanType, CrossOrigin.class);
CrossOrigin methodAnnotation = AnnotatedElementUtils.findMergedAnnotation(method, CrossOrigin.class);
// 在方法和类上获取不到@CrossOrigin注解
if (typeAnnotation == null && methodAnnotation == null) {
return null;
}
CorsConfiguration config = new CorsConfiguration();
updateCorsConfig(config, typeAnnotation);
updateCorsConfig(config, methodAnnotation);
// 如果没有指定请求方式,则会设置当前接口处理的请求方式
if (CollectionUtils.isEmpty(config.getAllowedMethods())) {
for (RequestMethod allowedMethod : mappingInfo.getMethodsCondition().getMethods()) {
config.addAllowedMethod(allowedMethod.name());
}
}
return config.applyPermitDefaultValues();
}
- 校验跨域信息
- 如果设置了allowCredentials属性为true,则不允许设置allowedOrigins为*
- 如果设置了allowPrivateNetwork属性为true,则不允许设置allowedOrigins为*
创建MappingRegistration
执行完一系列的流程后,会创建一个MappingRegistration对象,里面包含以下属性
- mapping:RequestMappingInfo对象
- handlerMethod:HandlerMethod对象
- directPaths:直接请求路径
- name:生成的名称,如TC#index
- corsConfig:布尔值,是否存在跨域配置
MappingRegistry的相关属性
- registry:RequestMapping和MappingRegistration对象的映射
- pathLookup:存储了请求路径和RequestMapping的映射
- nameLookup:存储了名称和HandlerMethod方法的映射
- corsLookup:存储了HandlerMethod和跨域配置映射
小结
- RequestMappingHandlerMapping的主要处理是通过生命周期方法afterPropertiesSet开始的,主要的处理还是通过父类的afterPropertiesSet方法
- 处理分为两大步骤
- 获取所有的带有**@Controller注解的bean,然后找到所有带有@RequestMapping和@HttpExchange注解的方法,构建成RequestMapping**对象
- 把获取到的所有的RequestMapping对象,通过AbstractHandlerMethodMapping下的mappingRegistry(AbstractHandlerMethodMapping.MappingRegistry)属性的register方法进行注册,并创建出了HandlerMethod对象
其他用法
@HttpExchange的使用
@HttpExchange能和@RequestMapping的效果达到一致
/**
* 用于测试@HttpExchange
*
* @author lbh
* @version 1.0
* @date 2024/7/17 上午11:07
*/
@Controller
@HttpExchange("test4")
public class TestController4 {
@GetExchange("index")
public String index() {
return "index";
}
}
BeanNameUrl的使用
- 可以通过指定bean名称前缀为/来指定路径,然后实现Controller接口
- 这里我们这个控制器对应的url就是/test3
/**
* 测试控制器
*
* @author yuwen
* @version 1.0
* @date 2024/7/1 下午8:50
*/
@Validated
@Controller("/test3")
public class TestController3 implements org.springframework.web.servlet.mvc.Controller {
@Override
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
response.getWriter().println("hello");
return null;
}
}
手动注册
通过拿到RequestMappingHandlerMapping对象,然后构造出RequestMappingInfo,使用registerMapping注册(看过上面的源码分析就很明白为什么这么做可以了)
public class WebConfig {
@Autowired
public void setHandlerMapping(RequestMappingHandlerMapping mapping, TestController5 handler)
throws NoSuchMethodException {
RequestMappingInfo info = RequestMappingInfo
.paths("/test5/index").methods(RequestMethod.GET).build();
Method method = TestController5.class.getMethod("index");
mapping.registerMapping(info, handler, method);
}
}
@Validated
@Controller
@Component
@Import(WebConfig.class)
public class TestController5 {
public String index() {
return "index";
}
}