Spring MVC源码之RequestMappingHandlerMapping分析

282 阅读11分钟

介绍

  • 在mvc的配置文件中如果设置了 mvc:annotation-driven/标签,会自动注册RequestMappingHandlerMapping的BeanDefinition到容器中
  • RequestMappingHandlerMapping用于扫描所有@Controller类下所有@RequestMapping的方法

源码分析

类图

以下类图需要查看源码的地方

  1. 实现了InitializingBean接口,所以需要查看afterPropertiesSet方法
  2. 继承了AbstractHandlerMethodMapping,在子类中调用了父类的afterPropertiesSet方法

RequestMappingHandlerMapping.png

生命周期方法分析

这里主要是查看到了实现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对象

此方法的流程如下

  1. 获取自定义条件
  2. 获取注解描述列表,只获取@RequestMapping和@HttpExchange注解
  3. 获取所有的@RequestMapping注解的描述对象,如果不为空,则通过第一个@RequestMapping创建RequestMappingInfo对象
  4. 获取所有的@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对象的流程如下

  1. 首先会创建HandlerMethod对象,包含了执行类和方法
  2. 校验MethodMapping,目的就是查看是否有重复的HandlerMethod
  3. 启用方法验证,内部会检测是否校验返回值或者请求参数
  4. 获取直接请求路径放入pathLookup属性中
  5. 获取跨域配置
  6. 创建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

  1. 参数上存在@Constraint注解,@NotNull、@NotBlank等都属于
  2. 参数上存在@Valid注解,并且参数是List、Object[]、Map三种类型
  3. 第三种没搞懂
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集合属性中

image.png

  • 初始化跨域配置
  • 会从类和当前方法上获取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对象,里面包含以下属性

  1. mapping:RequestMappingInfo对象
  2. handlerMethod:HandlerMethod对象
  3. directPaths:直接请求路径
  4. name:生成的名称,如TC#index
  5. corsConfig:布尔值,是否存在跨域配置
MappingRegistry的相关属性
  1. registry:RequestMapping和MappingRegistration对象的映射
  2. pathLookup:存储了请求路径和RequestMapping的映射
  3. nameLookup:存储了名称和HandlerMethod方法的映射
  4. 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";
    }
}