springboot2 - HandlerMethodArgumentResolver

场景 A

@RequestParam 大家都用过,设置 required = true,告诉前端这个参数是必传的,
但是用过的也基本了解,前端传个空字符串,校验就跳过了,实用性不佳。

class Controller{
    @ResponseBody
    @RequestMapping("/data")
    public Map<String, Object> data(@RequestParam(required = true) String b) {
        return new HashMap<>();
    }
}

场景 B

能不能通过一些注解,把一些缓存中的数据,优雅地注入进来,比如:当前登录的用户信息。

class Controller{
    @ResponseBody
    @RequestMapping("/data")
    public Map<String, Object> data(@Authority User user) {
        return new HashMap<>();
    }
}

基本功能介绍

此时你需要写一个 AOP 切面,HandlerMethodArgumentResolver,直译是:处理函数参数的分解器。

import org.springframework.core.MethodParameter;
import org.springframework.lang.Nullable;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;

/**
 * @since 3.1
 */
public interface HandlerMethodArgumentResolver {

	/**
     * 设置一些规则,返回 true 的时候,才执行 resolveArgument() 函数
	 */
	boolean supportsParameter(MethodParameter parameter);

	/**
     * 从给定的 request 中获取参数,返回值最终会通过参数,传递给 controller 的 method。
	 */
	@Nullable
	Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
			NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception;

}

spring 配置

<mvc:annotation-driven>
      <mvc:argument-resolvers>
        <bean class="xxxxx全类名"/>
      </mvc:argument-resolvers>
</mvc:annotation-driven>

springboot 配置

import org.springframework.context.annotation.Configuration;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

import java.util.List;

/**
 * Created by 12614 on 2018/5/11.
 */
@Configuration
public class ApplicationConfigurer extends WebMvcConfigurerAdapter {
    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
        super.addArgumentResolvers(argumentResolvers);
        argumentResolvers.add(new TestArgumentResolver());
    }
}

代码样例

针对上面的场景 A,设计一个注解,用于非空校验。

import org.springframework.core.annotation.AliasFor;

import java.lang.annotation.*;
/**
 * 客户端参数校验注解
 * <p>
 * 同类注解:{@link javax.validation.constraints.NotEmpty}
 *
 * @author Created by Mr.css on 2018/5/11.
 */
@Documented
@Target(value = ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface Params {
    /**
     * 参数名
     */
    @AliasFor("name")
    String value() default "";

    /**
     * 参数名
     */
    @AliasFor("value")
    String name() default "";

    /**
     * 提示用参数名(中文名称 Label)
     */
    String label() default "";

    /**
     * 不为空
     */
    boolean notEmpty() default true;

    /**
     * 最大长度
     */
    int length() default Integer.MAX_VALUE;

    /**
     * 默认值
     */
    String defaultValue() default "";
}

参数处理器

import cn.seaboot.commons.core.CommonUtils;
import cn.seaboot.commons.exception.BizException;
import cn.seaboot.commons.lang.Null;
import org.jetbrains.annotations.NotNull;
import org.springframework.core.MethodParameter;
import org.springframework.core.convert.ConversionException;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.TypeDescriptor;
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;

/**
 * 参数校验
 * <p>
 * 请求参数校验工具,主要用于补充 @RequestParam 的功能,增加 NotEmpty 校验
 * <p>
 * 同类注解:{@link javax.validation.constraints.NotEmpty}
 *
 * @author Mr.css on 2018-05-11.
 */
public class ArgumentResolver implements HandlerMethodArgumentResolver {

    /**
     * {@link org.springframework.boot.autoconfigure.web.format.WebConversionService}
     */
    private final ConversionService conversionService;

    /**
     * ConversionService is necessary, you can get ConversionService instance from spring-context
     *
     * @param conversionService form spring-context
     */
    public ArgumentResolver(@NotNull ConversionService conversionService) {
        this.conversionService = conversionService;
    }

    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        Params requestParam = parameter.getParameterAnnotation(Params.class);
        return requestParam != null;
    }

    @Override
    @SuppressWarnings("all")
    public Object resolveArgument(MethodParameter methodParameter,
                                  ModelAndViewContainer modelAndViewContainer,
                                  NativeWebRequest nativeWebRequest,
                                  WebDataBinderFactory webDataBinderFactory) {
        HttpServletRequest request = nativeWebRequest.getNativeRequest(HttpServletRequest.class);

        Params params = methodParameter.getParameterAnnotation(Params.class);
        // get parameter name
        String paramName = params.value();
        if ("".equals(paramName)) {
            paramName = methodParameter.getParameterName();
        }

        // get value by parameter name
        String clientParam = request.getParameter(paramName);
        if (clientParam == null) {
            clientParam = params.defaultValue();
        }

        if (params.notEmpty()) {
            if (CommonUtils.isEmpty(clientParam)) {
                // if value is empty, throw a exception
                throw new BizException(CommonUtils.nvl(params.label(), paramName) + " 不能为空!");
            } else if (clientParam.length() > params.length()) {
                throw new BizException(CommonUtils.nvl(params.label(), paramName) + " 超出最大长度限制:" + params.length() + "!");
            }
        }
        try {
            // 对象类型转换
            return conversionService.convert(clientParam, TypeDescriptor.forObject(clientParam), new TypeDescriptor(methodParameter));
        } catch (Exception e) {
            throw new BizException("客户端参数格式转换失败:" + paramName, e);
        }
    }
}

使用方式

class Controller{
    @ResponseBody
    @RequestMapping("/data")
    public Map<String, Object> data(@Params String b) {
        return new HashMap<>();
    }
}

posted on 2018-05-27 10:22  疯狂的妞妞  阅读(11425)  评论(0编辑  收藏  举报

导航