Backend Development 6 min read

How to Create Custom HandlerMapping with Annotations in Spring Boot

This article explains how to simplify Spring MVC interface calls by defining custom annotations, implementing a custom HandlerMapping, configuring it in Spring Boot 2.6.12, and demonstrating usage with example code, while also covering related handler adapters and underlying principles.

Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
How to Create Custom HandlerMapping with Annotations in Spring Boot

Environment: Spring Boot 2.6.12

1. Introduction

In Spring MVC, custom annotations can simplify interface calls. This article introduces how to use custom annotations to implement interface calls, covering definition, usage on interface methods and controller classes, registration in Spring MVC configuration, and example code.

2. Implementation Process

Custom Annotation Class

<code>@Component
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface CustomEndpoint {
}
</code>

The @CustomEndpoint annotation marks a class as a custom Bean managed by the container.

PackMapping Annotation

<code>@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface PackMapping {
  // request URL
  String[] value() default {};
  // request method
  RequestMethod[] method() default {};
}
</code>

This annotation works like @RequestMapping to define request interfaces.

Custom HandlerMapping

<code>public class CustomPackRequestHandlerMapping extends RequestMappingHandlerMapping {

  @Override
  protected boolean isHandler(Class<?> beanType) {
    return AnnotatedElementUtils.hasAnnotation(beanType, CustomEndpoint.class);
  }

  @Override
  public void afterPropertiesSet() {
    super.setOrder(0); // ensure this mapping has highest priority
    super.afterPropertiesSet();
  }

  @Override
  protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
    RequestMappingInfo requestMappingInfo = createRequestMappingInfo(method);
    return requestMappingInfo;
  }

  private RequestMappingInfo createRequestMappingInfo(AnnotatedElement element) {
    PackMapping packMapping = AnnotatedElementUtils.findMergedAnnotation(element, PackMapping.class);
    return (packMapping != null ? builderMapping(packMapping) : null);
  }

  private RequestMappingInfo builderMapping(PackMapping packMapping) {
    RequestMappingInfo.Builder builder = RequestMappingInfo
      .paths(packMapping.value())
      .methods(packMapping.method())
      .params(new String[] {})
      .headers(new String[] {})
      .consumes(new String[] {})
      .produces(new String[] {})
      .mappingName("");
    return builder.build();
  }
}
</code>

The essential methods of the custom HandlerMapping are explained above.

Configuration

<code>@Configuration
public class CustomEndpointConfig {

  @Bean
  public CustomPackRequestHandlerMapping customPackRequestHandlerMapping() {
    CustomPackRequestHandlerMapping handlerMapping = new CustomPackRequestHandlerMapping();
    return handlerMapping;
  }
}
</code>

Test Controller

<code>@CustomEndpoint
@ResponseBody
public class CustomWebController {

  @PackMapping("/custom/index")
  public Object index(String key) {
    return key;
  }
}
</code>

At this point, a custom HandlerMapping is completed. The actual request handling is performed by a HandlerAdapter.

3. Underlying Principles

AbstractHandlerMethodAdapter defines abstract methods for subclasses to implement, such as:

<code>protected abstract boolean supportsInternal(HandlerMethod handlerMethod);
</code>

RequestMappingHandlerAdapter overrides this method to always return true.

<code>@Override
protected boolean supportsInternal(HandlerMethod handlerMethod) {
  return true;
}
</code>

All interceptors are also associated with HandlerMapping, and the RequestMappingHandlerMapping bean is configured with order, interceptors, content negotiation, CORS, etc.

<code>@Bean
@SuppressWarnings("deprecation")
public RequestMappingHandlerMapping requestMappingHandlerMapping(
      @Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager,
      @Qualifier("mvcConversionService") FormattingConversionService conversionService,
      @Qualifier("mvcResourceUrlProvider") ResourceUrlProvider resourceUrlProvider) {

    RequestMappingHandlerMapping mapping = createRequestMappingHandlerMapping();
    mapping.setOrder(0);
    mapping.setInterceptors(getInterceptors(conversionService, resourceUrlProvider));
    mapping.setContentNegotiationManager(contentNegotiationManager);
    mapping.setCorsConfigurations(getCorsConfigurations());
    // ... additional configuration omitted
    return mapping;
}
</code>

The RequestMappingHandlerAdapter is similarly configured with message converters, argument resolvers, return value handlers, async support, and other components.

<code>@Bean
public RequestMappingHandlerAdapter requestMappingHandlerAdapter(
      @Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager,
      @Qualifier("mvcConversionService") FormattingConversionService conversionService,
      @Qualifier("mvcValidator") Validator validator) {

    RequestMappingHandlerAdapter adapter = createRequestMappingHandlerAdapter();
    adapter.setContentNegotiationManager(contentNegotiationManager);
    adapter.setMessageConverters(getMessageConverters());
    adapter.setWebBindingInitializer(getConfigurableWebBindingInitializer(conversionService, validator));
    adapter.setCustomArgumentResolvers(getArgumentResolvers());
    adapter.setCustomReturnValueHandlers(getReturnValueHandlers());
    // additional configuration omitted for brevity
    return adapter;
}
</code>

Finished!

backend-developmentSpring BootCustom AnnotationSpring MVCHandlerMapping
Spring Full-Stack Practical Cases
Written by

Spring Full-Stack Practical Cases

Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.