参数解析

参数解析指将从请求中解析出Controller参数值的过程(包含反序列化)

典型的有

  • @RequestParam
  • @RequestHeader
  • @RequestBody
  • @PathVariable
  • @CookieValue
  • @MatrixVariable
  • @QueryBean
  • AsyncRequest
  • AsyncResponse

接口定义

public interface ArgumentResolver {

    /**
     * 从AsyncRequest中解析出对应参数的值
     * 解析后的Object必须能和Param类型匹配
     */
    Object resolve(AsyncRequest request, AsyncResponse response) throws Exception;
    
}

框架会在启动的初始化阶段试图为每一个Controller中的每一个参数都找到一个与之匹配的ArgumentResolver用于请求的参数解析。

框架如何确定ArgumentResolver

ArgumentResolverAdapter


public interface ArgumentResolverPredicate {

    /**
     * 判断当前ArgumentResolver是否支持给定Param解析
     * Controller中的每个参数都对应一个Param实例, 
     * 可以通过Param获取注解等各类反射相关的元数据信息
     */
    boolean supports(Param param);

}

public interface ArgumentResolverAdapter extends ArgumentResolverPredicate, ArgumentResolver, Ordered {
    @Override
    default int getOrder() {
        return HIGHEST_PRECEDENCE;
    }

}

初始化逻辑:

  1. 按照getOrder()方法的返回将Spring容器中所有的ArgumentResolver进行排序
  2. 按照排序后的顺序依次调用supports(Param param)方法, 返回true则将其作为该参数的ArgumentResolver, 运行时每次请求都将调用resolve(AsyncRequest request, AsyncResponse response)方法进行参数解析, 并且运行时不会改变。
  3. 未找到则启动报错。

细心的人可能会发现该设计可能并不能覆盖到以下场景

  • 因为resolve(AsyncRequest request, AsyncResponse response)方法参数中并没有传递Param参数, 虽然初始化阶段能根据supports(Param param)方法获取参数元数据信息(获取某个注解, 获取参数类型等等)判断是否支持, 但是如果运行时也需要获取参数的元数据信息(某个注解的值等)的话,此接口则无法满足需求。
  • 假如ArgumentResolver实现中需要做序列化操作, 因此期望获取到Spring容器中的序列化器时,则该接口无法支持(例如@RequestBody的场景)。

针对以上问题, 答案是确实无法支持。因为Restlight的设计理念是

  • 能在初始化阶段解决的问题就在初始化阶段解决

因此不期望用户以及Restlight的开发人员大量的在运行时去频繁获取一些JVM启动后就不会变动的内容(如: 注解的值), 甚至针对某些元数据信息使用ConcurrentHashMap进行缓存(看似是为了提高性能的缓存, 实际上初始化就固定了的内容反而增加了并发性能的损耗)。

基于以上原因我们提供了另一个ArgumentResolver的实现方式

ArgumentResolverFactory

public interface ArgumentResolverFactory extends ArgumentResolverPredicate, Ordered {

    ArgumentResolver createResolver(Param param,
                                    List<? extends HttpRequestSerializer> serializers);

    @Override
    default int getOrder() {
        return HIGHEST_PRECEDENCE;
    }
}

与上面的ArgumentResolver类似

初始化逻辑:

  1. 按照getOrder()方法的返回将所有的ArgumentResolverFactory进行排序
  2. 按照排序后的顺序依次调用supports(Param param)方法, 返回true则将其作为该参数的ArgumentResolverFactory, 同时调用createResolver(Param param, List<? extends HttpRequestSerializer> serializers)方法创建出对应的ArgumentResolver
  3. 未找到则启动报错。

由于初始化时通过createResolver(Param param, List<? extends HttpRequestSerializer> serializers)方法传入了Param以及序列化器, 因此能满足上面的要求。

两种模式的定位

  • ArgumentResolver: 适用于参数解析器不依赖方法元数据信息以及序列化的场景。例如: 如果参数上使用了@XXX注解则返回某个固定的值。
  • ArgumentResolverFactory: 适用于参数解析器依赖方法元数据信息以及序列化的场景。例如: @RequestBody@RequestParameter(name = "foo")

自定义参数解析器

将自定义实现的ArgumentResolverAdapter或者ArgumentResolverFactory注入到Spring容器即可。

  • ArgumentResolverAdapter案例

场景: 当参数上有@AppId注解时, 使用固定的AppId

// 自定义注解
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface AppId {
}
@Bean
public ArgumentResolverAdapter resolver() {
    return new ArgumentResolverAdapter() {
        @Override
        public boolean supports(Param param) {
            // 当方法上有此注解时生效
            return param.hasParameterAnnotation(AppId.class);
        }
 
        @Override
        public Object resolve(AsyncRequest request, AsyncResponse response) {
            return "your appid";
        }
    };
}

controller使用


@GetMapping("/foo")
public String foo(@AppId String appId) {
    return appId;
}

上面的代码自定义实现了依据自定义注解获取固定appId的功能

  • ArgumentResolverFactory

场景: 通过自定义注解获取固定前缀x-custom的Header

// 自定义注解
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CustomHeader {
    String value();
}
@Bean
public ArgumentResolverFactory resolver() {
    return new ArgumentResolverFactory() {
        @Override
        public boolean supports(Param param) {
            // 当方法上有此注解时生效
            return param.hasParameterAnnotation(CustomHeader.class);
        }

        @Override
        ArgumentResolver createResolver(Param param,
                                        List<? extends HttpRequestSerializer> serializers) {
            return new Resolver(param);
        }
    };
}

/**
 * 实际ArgumentResolver实现
 */
private static class Resolver implements ArgumentResolver {

    private final String headerName;

    private Resolver(Param param) {
        CustomHeader anno = param.getParameterAnnotation(CustomHeader.class);
        if (anno.value().length() == 0) {
            throw new IllegalArgumentException("Name of header must not be empty.");
        }
        // 初始化时组装好需要的参数
        this.headerName = "x-custom" + anno.value();
    }

    @Override
    public Object resolve(AsyncRequest request, AsyncResponse response) {
        // 运行时直接获取Header
        return request.getHeader(headerName);
    }
}

controller使用


@GetMapping("/foo")
public String foo(@CustomHeader("foo") String foo) {
    return foo;
}