Spring MVC自定义类型转换器Converter、参数解析器HandlerMethodArgumentResolver

一、前言

Spring MVC源码分析相关文章已出:

  1. Spring MVC <Form>表单中支持REST风格DELETE、PUT类型方法的方式和原理
  2. Spring MVC请求执行流程
  3. Spring MVC如何将请求映射到Controller
  4. 使用FastJsonHttpMessageConverter解析@RequestBody参数
  5. Spring MVC多种请求入参处理方式都在这了

更多Spring系列源码分析文章见SpringBoot专栏:

二、类型转换器Converter

Spring官方文档

在这里插入图片描述

Spring 3.0 引入了一个 core.convert,并提供通用类型转换系统的包。系统定义了一个 SPI 来实现类型转换逻辑,并定义一个 API 来在运行时执行类型转换。

这套类型转换系统的顶层接口为:Converter

@FunctionalInterface
public interface Converter<S, T> {
    
    

	/**
	 * Convert the source object of type {@code S} to target type {@code T}.
	 * @param source the source object to convert, which must be an instance of {@code S} (never {@code null})
	 * @return the converted object, which must be an instance of {@code T} (potentially {@code null})
	 * @throws IllegalArgumentException if the source cannot be converted to the desired target type
	 */
	@Nullable
	T convert(S source);

}

Converter的作用是将类型 S 转换为 T,在参数解析器中使用该接口的实现类 将前端请求参数 转换成 控制器方法(Controller#Method) 需要的参数类型。

此外,还有ConverterFactory<S, R>(将类型S 转换为 R及其子类型)、ConversionService(用来在运行时执行类型转换);

Spring 提供的所有支持的类型转换器实现类都在 org.springframework.core.convert.support 包下,大多数转换器的convert()方法都很简单,感兴趣可以自己看一下源码。

在这里插入图片描述

比如:

  1. StringToCollectionConverter将String转换为集合;例如:ids -> 1,2,3 能够用 List<Long> 接收;
  2. StringToBooleanConverter将String转换为Boolean,例如:enable -> no 能够用 Boolean 接收;

1、自定义类型转换器

要实现的效果:

  • 请求中传递JavaModel数据,多个属性之间以英文逗号,分隔;
    比如:POST请求 http://127.0.0.1:38888/person/other?person=saint,15,true,otherInfo&other=hahaha
  • 后端Controller的方法中使用JavaModel接收;

1> Java Model类:

public class Person {
    
    
    private String name;

    private Integer age;

    private Boolean sex;

    private String otherInfo;
}

2> 自定义Converter:

逻辑很简单,就是将Spring字符串用英文逗号,分隔,按Person类的属性顺序,将请求中的参数填充到Person对象中。

@Configuration
public class WebMvcConfig {
    
    

    /**
     * 自定义参数类型转换器
     * WebMvcConfigurer#addFormatters()方法用来添加自定义的参数类型转换器;
     */
    @Bean
    public WebMvcConfigurer webMvcConfigurer() {
    
    
        return new WebMvcConfigurer() {
    
    
            @Override
            public void addFormatters(FormatterRegistry registry) {
    
    
                registry.addConverter(new Converter<String, Person>() {
    
    
                    @Override
                    public Person convert(String source) {
    
    
                        if (StringUtils.isEmpty(source)) {
    
    
                            return null;
                        }
                        final String[] sourceArgs = source.split(",");
                        Person person = new Person();
                        person.setName(sourceArgs[0]);
                        person.setAge(Integer.valueOf(sourceArgs[1]));
                        person.setSex(Boolean.valueOf(sourceArgs[2]));
                        person.setOtherInfo(String.valueOf(sourceArgs[3]));
                        return person;
                    }
                });
            }
        };
    }
}

3> controller方法:

@PostMapping("/person/other")
public String otherPerson(Person person, String other) {
    
    
    return person.toString() + other;
}

4> 效果:

在这里插入图片描述

三、参数解析器

Spring中参数解析器的最上层接口为HandlerMethodArgumentResolver,其中有两个方法:

  • supportsParameter() 判断当前参数解析器是否支持解析的方法参数;
  • resolveArgument() 从请求数据中解析出当前方法参数对应的参数值。
public interface HandlerMethodArgumentResolver {
    
    

	/**
	 * Whether the given {@linkplain MethodParameter method parameter} is
	 * supported by this resolver.
	 */
	boolean supportsParameter(MethodParameter parameter);

	/**
	 * Resolves a method parameter into an argument value from a given request.
	 * A {@link ModelAndViewContainer} provides access to the model for the
	 * request. A {@link WebDataBinderFactory} provides a way to create
	 * a {@link WebDataBinder} instance when needed for data binding and
	 * type conversion purposes.
	 */
	@Nullable
	Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
			NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception;

}

1、自定义分页参数解析器

前端请求传递两个分页参数 pageNum、pageSize,后端用一个IPage模型接收;

参数解析器策略要支持的是 IPage.class,核心是 HandlerMethodArgumentResolver 的两个方法实现。

这里基于spring-data-common 包下的PageableHandlerMethodArgumentResolver 做一个扩展。

1> maven依赖:

<dependency>
    <groupId>org.springframework.data</groupId>
    <artifactId>spring-data-commons</artifactId>
</dependency>

2> 自定义参数解析器PageHandlerMethodArgumentResolver

package com.saint.config;

import com.saint.model.IPage;
import org.springframework.core.MethodParameter;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.web.PageableHandlerMethodArgumentResolver;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;

/**
 * 自定义分页参数解析器
 *
 * @author Saint
 */
public class PageHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver {
    
    

    private static final int DEFAULT_PAGE = 0;
    private static final int DEFAULT_SIZE = 10;
    private static final String DEFAULT_PAGE_PARAM = "pageNum";
    private static final String DEFAULT_SIZE_PARAM = "pageSize";

    /**
     * 组合 `spring-data-commons` 包下的PageableHandlerMethodArgumentResolver,实现分页参数解析功能
     */
    private final PageableHandlerMethodArgumentResolver pageableArgumentResolver;

    public PageHandlerMethodArgumentResolver() {
    
    
        PageableHandlerMethodArgumentResolver resolver = new PageableHandlerMethodArgumentResolver();
        resolver.setFallbackPageable(PageRequest.of(DEFAULT_PAGE, DEFAULT_SIZE));
        resolver.setSizeParameterName(DEFAULT_SIZE_PARAM);
        resolver.setPageParameterName(DEFAULT_PAGE_PARAM);
        resolver.setOneIndexedParameters(true);
        this.pageableArgumentResolver = resolver;
    }

    @Override
    public boolean supportsParameter(MethodParameter parameter) {
    
    
        return IPage.class.equals(parameter.getParameterType());
    }

    @Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
    
    
        Pageable pageable =
                pageableArgumentResolver.resolveArgument(
                        parameter, mavContainer, webRequest, binderFactory);
        return new IPage(pageable.getPageNumber() + 1, pageable.getPageSize());
    }
}

3> 将自定义的参数解析器添加到Spring MVC 的参数解析器集合中:

@Configuration
public class WebMvcConfig {
    
    

    @Bean
    public WebMvcConfigurer webMvcConfigurer() {
    
    
        return new WebMvcConfigurer() {
    
    

            @Override
            public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
    
    
                resolvers.add(new PageHandlerMethodArgumentResolver());
            }
        };
    }
}

4> Controller方法:

/**
 * http://localhost:38888/page?pageNum=9&pageSize=20
 */
@GetMapping("/page")
public String page(IPage page) {
    
    
    return page.toString();
}

5> 效果:

  • GET /xxx?pageNum=1&pageSize=10请求的分页参数被解析成了IPage对象

在这里插入图片描述

2、自定义注解参数解析器

自定义一个注解用于标注某个JavaModel,JavaModel中的信息是根据请求中的某些数据间接得到。

1> 自定义的注解@UserParam:

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface UserParam {
    
    
}

2> 自定义的JavaModel:

@Data
@NoArgsConstructor
@AllArgsConstructor
public class UserInfo {
    
    

    private String userCode;

    private String userName;

    private String address;
}

3> 自定义的参数解析器:

package com.saint.config;

import com.saint.annotation.UserParam;
import com.saint.model.UserInfo;
import com.saint.service.UserInfoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.MethodParameter;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;

import javax.servlet.http.HttpServletRequest;

/**
 * @author Saint
 */
public class UserInfoArgumentResolver implements HandlerMethodArgumentResolver {
    
    

    @Autowired
    private UserInfoService userInfoService;

    @Override
    public boolean supportsParameter(MethodParameter methodParameter) {
    
    
        return methodParameter.getParameterType() == UserInfo.class
                && methodParameter.hasParameterAnnotation(UserParam.class);
    }

    @Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
    
    
        HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
        String token = request.getHeader("token");
        UserInfo userInfo = userInfoService.getUserInfoByToken(token);
        return userInfo;
    }
}

4> UserInfoService:

package com.saint.service;

import com.saint.model.UserInfo;
import org.springframework.stereotype.Service;

/**
 * @author Saint
 */
@Service
public class UserInfoService {
    
    

    public UserInfo getUserInfoByToken(String token) {
    
    
        // todo 调用用户中心返回用户的信息
        UserInfo user = new UserInfo("code01", "saint", "你心里 " + token);
        return user;
    }
}

5> 将自定义的参数解析器添加到Spring MVC 的参数解析器集合中:

  • 一定要先将自定义的参数解析器实例化一个Bean到Spring容器中,否则其中无法调用其他SpringBean。
package com.saint.config;

import com.saint.model.Person;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.converter.Converter;
import org.springframework.format.FormatterRegistry;
import org.springframework.util.StringUtils;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import java.util.List;

/**
 * @author Saint
 */
@Configuration
public class WebMvcConfig {
    
    

    @Bean
    public UserInfoArgumentResolver getUserInfoArgumentResolver() {
    
    
        return new UserInfoArgumentResolver();
    }

    @Bean
    public WebMvcConfigurer webMvcConfigurer() {
    
    
        return new WebMvcConfigurer() {
    
    
            @Override
            public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
    
    
                resolvers.add(getUserInfoArgumentResolver());
            }
        };
    }
}

6> 效果:

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/Saintmm/article/details/129470532