Backend Development 7 min read

Mastering Spring Boot Custom Parameter Resolvers: A Step‑by‑Step Guide

This guide explains how to create a Spring Boot custom HTTP message converter and argument resolver, register them, and use a @Pack annotation to parse a simple 'name:张三,age:20' request body into a Users object, enhancing backend flexibility.

Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Mastering Spring Boot Custom Parameter Resolvers: A Step‑by‑Step Guide

Environment: Spring Boot 2.7.12

Overview

Spring's custom argument resolver allows developers to define their own parameter parsing logic, handling non‑standard types, complex structures, or specific formats. In Spring MVC it is a separate component that Spring iterates over for each controller method invocation.

Custom Resolver

The example processes a request body formatted as name:张三,age:20 . A custom HttpMessageConverter parses the string, splits by commas and colons, and populates a Users object via reflection.

<code>public class CustomHttpMessageConverter extends AbstractHttpMessageConverter<Object> {
    private static Logger logger = LoggerFactory.getLogger(CustomHttpMessageConverter.class);
    @Override
    protected boolean supports(Class<?> clazz) {
        return Users.class == clazz;
    }
    @Override
    protected Object readInternal(Class<? extends Object> clazz, HttpInputMessage inputMessage)
            throws IOException, HttpMessageNotReadableException {
        String content = inToString(inputMessage.getBody());
        String[] keys = content.split(",");
        Users instance = null;
        try {
            instance = (Users) clazz.newInstance();
        } catch (Exception e1) {
            e1.printStackTrace();
        }
        for (String key : keys) {
            String[] vk = key.split(":");
            try {
                Field[] fields = clazz.getDeclaredFields();
                for (Field f : fields) {
                    if (f.getName().equals(vk[0])) {
                        f.setAccessible(true);
                        Class<?> type = f.getType();
                        if (String.class == type) {
                            f.set(instance, vk[1]);
                        } else if (Integer.class == type) {
                            f.set(instance, Integer.parseInt(vk[1]));
                        }
                        break;
                    }
                }
            } catch (Exception e) {
                logger.error("错误:{}", e);
            }
        }
        return instance;
    }
    // writeInternal and other methods omitted for brevity
    private String inToString(InputStream is) {
        byte[] buf = new byte[10 * 1024];
        int leng = -1;
        StringBuilder sb = new StringBuilder();
        try {
            while ((leng = is.read(buf)) != -1) {
                sb.append(new String(buf, 0, leng));
            }
            return sb.toString();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}</code>

The corresponding HandlerMethodArgumentResolver checks for a custom @Pack annotation and delegates the conversion to the message converter.

<code>public class CustomHandlerMethodParameterResolver implements HandlerMethodArgumentResolver {
    private CustomHttpMessageConverter messageConverter;
    public CustomHttpMessageConverter getMessageConverter() { return messageConverter; }
    public void setMessageConverter(CustomHttpMessageConverter messageConverter) { this.messageConverter = messageConverter; }
    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        return parameter.hasParameterAnnotation(Pack.class);
    }
    @Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
                                  NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
        HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
        ServletServerHttpRequest inputMessage = new ServletServerHttpRequest(request);
        return messageConverter.read((Class<?>)parameter.getNestedGenericParameterType(), inputMessage);
    }
}</code>

Configuration registers the custom converter (with media type application/fm ) and adds the resolver to Spring MVC.

<code>@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Bean
    public CustomHttpMessageConverter customHttpMessageConverter() {
        return new CustomHttpMessageConverter();
    }
    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        CustomHttpMessageConverter messageConvert = customHttpMessageConverter();
        List<MediaType> supportedMediaTypes = new ArrayList<>();
        supportedMediaTypes.add(new MediaType("application", "fm"));
        messageConvert.setSupportedMediaTypes(supportedMediaTypes);
        converters.add(messageConvert);
    }
    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
        CustomHandlerMethodArgumentResolver customResolver = new CustomHandlerMethodArgumentResolver();
        customResolver.setMessageConverter(customHttpMessageConverter());
        resolvers.add(customResolver);
    }
}</code>

The controller uses the @Pack annotation on a Users parameter to trigger the custom resolver.

<code>@PostMapping("/resolver")
public Object resolver(@Pack Users user) {
    System.out.println("自定义参数解析器处理结果:" + user);
    return user;
}</code>

A simple @Pack annotation is defined with runtime retention and parameter target.

<code>@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface Pack { }</code>

Testing shows that the request body name:张三,age:20 is correctly mapped to a Users object, demonstrating the flexibility and maintainability of custom parameter resolvers in Spring applications.

Javabackend developmentSpring BootCustom Argument ResolverHTTP message converter
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.