Springパラメーターパーサーを理解する

記事のソースshenyifengtk.github.ioの再版を示してください

Spring MVCで開発されたものは、@ RequstBodyを使用してjsonパラメーターを受け取り、それらをpojoobjects.objectに変換する必要があります。

フロントエンド値 バックエンド受信 結果
{"id":3、 "name": "xxx"} User(id、name) 注射の成功
{"id":3、 "name": "xxx"} (文字列名、int id) このメソッドをサポートしていません

インターフェースにアップロード用のパラメーターが2つしかない場合があり、このために個別のオブジェクトを作成したくない場合があります。これにより、各hangdlerメソッドにpojoオブジェクトが作成され、ほとんどのオブジェクトが他のハンドラーによって共有されない場合があります。むしろ、1つまたは2つのパラメーターを使用して受信されることが期待されます。まず、このアノテーションがjsonからpojoオブジェクトへの変換をどのように実現するかを見てみましょう@RequestBody 。これはSpring HandlerMethodArgumentResolverパラメーターパーサーを介して実現され、インターフェイスを理解します。

public interface HandlerMethodArgumentResolver {

	/**
	 *   是否支持方法上参数的处理,只有返回ture,才会执行下面方法
	 * @param parameter the method parameter to check
	 * @return {@code true} if this resolver supports the supplied parameter;
	 * {@code false} otherwise
	 */
	boolean supportsParameter(MethodParameter parameter);

	/**
	 * 将方法参数解析为给定请求的参数值
	 * 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.
	 * @param parameter the method parameter to resolve. This parameter must
	 * have previously been passed to {@link #supportsParameter} which must
	 * have returned {@code true}.
	 * @param mavContainer the ModelAndViewContainer for the current request
	 * @param webRequest the current request
	 * @param binderFactory a factory for creating {@link WebDataBinder} instances
	 * @return the resolved argument value, or {@code null} if not resolvable
	 * @throws Exception in case of errors with the preparation of argument values
	 */
	@Nullable
	Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
			NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception;

}
复制代码

RequestMappingHandlerAdapterデフォルトのパラメーターパーサーは、以下

	private List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() {
		List<HandlerMethodArgumentResolver> resolvers = new ArrayList<>(30);

		// Annotation-based argument resolution
		resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), false));  //@RequestPart  文件注入
		resolvers.add(new RequestParamMapMethodArgumentResolver()); //@RequestParam 
		resolvers.add(new PathVariableMethodArgumentResolver()); //@PathVariable
		resolvers.add(new PathVariableMapMethodArgumentResolver()); //@PathVariable 会返回一个Map对象
		resolvers.add(new MatrixVariableMethodArgumentResolver()); //@MatrixVariable
		resolvers.add(new MatrixVariableMapMethodArgumentResolver()); //MatrixVariable 会返回Map 对象
		resolvers.add(new ServletModelAttributeMethodProcessor(false)); //属性板顶
		resolvers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice)); //@RequestBody 后面重点讲解
		resolvers.add(new RequestPartMethodArgumentResolver(getMessageConverters(), this.requestResponseBodyAdvice)); //@RequestPart
		resolvers.add(new RequestHeaderMethodArgumentResolver(getBeanFactory())); // @RequestHeader
		resolvers.add(new RequestHeaderMapMethodArgumentResolver()); //@RequestHeader Map对象
		resolvers.add(new ServletCookieValueMethodArgumentResolver(getBeanFactory())); //Cookie 值 注入
		resolvers.add(new ExpressionValueMethodArgumentResolver(getBeanFactory())); //@Value
		resolvers.add(new SessionAttributeMethodArgumentResolver()); //@SessionAttribute
		resolvers.add(new RequestAttributeMethodArgumentResolver()); //@RequestAttribute

		// Type-based argument resolution
		resolvers.add(new ServletRequestMethodArgumentResolver());  //servlet api对象 HttpServletRequest  这类
		resolvers.add(new ServletResponseMethodArgumentResolver()); //ServletResponse 对象注入
		resolvers.add(new HttpEntityMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice)); //RequestEntity、HttpEntity
		resolvers.add(new RedirectAttributesMethodArgumentResolver()); //重定向
		resolvers.add(new ModelMethodProcessor()); //返回Model 对象
		resolvers.add(new MapMethodProcessor()); // 处理方法参数返回一个Map
		resolvers.add(new ErrorsMethodArgumentResolver()); //处理错误方法参数,返回最后一个对象
		resolvers.add(new SessionStatusMethodArgumentResolver()); //SessionStatus
		resolvers.add(new UriComponentsBuilderMethodArgumentResolver());  //UriComponentsBuilder
		if (KotlinDetector.isKotlinPresent()) {
			resolvers.add(new ContinuationHandlerMethodArgumentResolver());
		}

		// Custom arguments 自定义
		if (getCustomArgumentResolvers() != null) {
			resolvers.addAll(getCustomArgumentResolvers());
		}

		// Catch-all
		resolvers.add(new PrincipalMethodArgumentResolver()); 
		resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), true));
		resolvers.add(new ServletModelAttributeMethodProcessor(true));

		return resolvers;
	}
复制代码

RequestResponseBodyMethodProcessorがjsonを注入する方法に焦点を当てて、ほとんどのメソッドパラメーターが上記のプロセッサーによって実装されていることがわかります。主に2つのメソッドの実装方法に依存します。

	@Override
	public boolean supportsParameter(MethodParameter parameter) {
                //只有使用了@RequestBody 注解默认就开启处理
		return parameter.hasParameterAnnotation(RequestBody.class); 
	}

	@Override
	public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
			NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {

		parameter = parameter.nestedIfOptional();
              //转换成对象了
		Object arg = readWithMessageConverters(webRequest, parameter, parameter.getNestedGenericParameterType());
               //获取参数变量名称
		String name = Conventions.getVariableNameForParameter(parameter);

		if (binderFactory != null) { //使用WebDatabinder 对已经序列化对象属性绑定处理
			WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name);
			if (arg != null) {
				validateIfApplicable(binder, parameter);
				if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
					throw new MethodArgumentNotValidException(parameter, binder.getBindingResult());
				}
			}
			if (mavContainer != null) {
				mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult());
			}
		}

		return adaptArgumentIfNecessary(arg, parameter);
	}

复制代码

オブジェクトへのjsonシリアル番号の実現は、readWithMessageConvertersメソッドにあります

	protected <T> Object readWithMessageConverters(HttpInputMessage inputMessage, MethodParameter parameter,
			Type targetType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {

		MediaType contentType;
		boolean noContentType = false;
		try {
			contentType = inputMessage.getHeaders().getContentType();
		}
		catch (InvalidMediaTypeException ex) {
			throw new HttpMediaTypeNotSupportedException(ex.getMessage());
		}
		if (contentType == null) {
			noContentType = true;
			contentType = MediaType.APPLICATION_OCTET_STREAM;
		}

		Class<?> contextClass = parameter.getContainingClass();
		Class<T> targetClass = (targetType instanceof Class ? (Class<T>) targetType : null);
		if (targetClass == null) {
			ResolvableType resolvableType = ResolvableType.forMethodParameter(parameter);
			targetClass = (Class<T>) resolvableType.resolve();
		}

		HttpMethod httpMethod = (inputMessage instanceof HttpRequest ? ((HttpRequest) inputMessage).getMethod() : null);
		Object body = NO_VALUE;

		EmptyBodyCheckingHttpInputMessage message;
		try {
                        //创建重复读写流
			message = new EmptyBodyCheckingHttpInputMessage(inputMessage); 
                       //通过HttpMessageConverter 来对String json 转换成对象
			for (HttpMessageConverter<?> converter : this.messageConverters) {
				Class<HttpMessageConverter<?>> converterType = (Class<HttpMessageConverter<?>>) converter.getClass();
				GenericHttpMessageConverter<?> genericConverter =
						(converter instanceof GenericHttpMessageConverter ? (GenericHttpMessageConverter<?>) converter : null);
				if (genericConverter != null ? genericConverter.canRead(targetType, contextClass, contentType) :
						(targetClass != null && converter.canRead(targetClass, contentType))) {
					if (message.hasBody()) {
						HttpInputMessage msgToUse =
								getAdvice().beforeBodyRead(message, parameter, targetType, converterType); //前置处理
						body = (genericConverter != null ? genericConverter.read(targetType, contextClass, msgToUse) :
								((HttpMessageConverter<T>) converter).read(targetClass, msgToUse));
						body = getAdvice().afterBodyRead(body, msgToUse, parameter, targetType, converterType); //后置处理 类型通知
					}
					else {
						body = getAdvice().handleEmptyBody(null, message, parameter, targetType, converterType);
					}
					break;
				}
			}
		}
		catch (IOException ex) {
			throw new HttpMessageNotReadableException("I/O error while reading input message", ex, inputMessage);
		}
                //缩减部分代码
		return body;
	}
复制代码

GenericHttpMessageConverter読み取りメソッドを呼び出すことにより、httpリクエストのコンテンツがオブジェクトに変換されます。このとき、呼び出しは次のようになります。AbstractJackson2HttpMessageConverter

	private Object readJavaType(JavaType javaType, HttpInputMessage inputMessage) throws IOException {
		MediaType contentType = inputMessage.getHeaders().getContentType();
		Charset charset = getCharset(contentType);

		ObjectMapper objectMapper = selectObjectMapper(javaType.getRawClass(), contentType);
		Assert.state(objectMapper != null, "No ObjectMapper for " + javaType);

		boolean isUnicode = ENCODINGS.containsKey(charset.name()) ||
				"UTF-16".equals(charset.name()) ||
				"UTF-32".equals(charset.name());
		try {
			if (inputMessage instanceof MappingJacksonInputMessage) { //这个是使用了JsonView 
				Class<?> deserializationView = ((MappingJacksonInputMessage) inputMessage).getDeserializationView();
				if (deserializationView != null) {
					ObjectReader objectReader = objectMapper.readerWithView(deserializationView).forType(javaType);
					if (isUnicode) {
						return objectReader.readValue(inputMessage.getBody());
					}
					else {
						Reader reader = new InputStreamReader(inputMessage.getBody(), charset);
						return objectReader.readValue(reader);  //反序列化成Java 对象
					}
				}
			}
			if (isUnicode) {
				return objectMapper.readValue(inputMessage.getBody(), javaType);
			}
			else {
				Reader reader = new InputStreamReader(inputMessage.getBody(), charset);
				return objectMapper.readValue(reader, javaType);
			}
		}
		catch (InvalidDefinitionException ex) {
			throw new HttpMessageConversionException("Type definition error: " + ex.getType(), ex);
		}
		catch (JsonProcessingException ex) {
			throw new HttpMessageNotReadableException("JSON parse error: " + ex.getOriginalMessage(), ex, inputMessage);
		}
	}
复制代码

@RequestBodyは、GenericHttpMessageConverterクラスを介してObjectMapper readValueを呼び出すことで変換されることがわかります。これは、jsonをpojoオブジェクトに変換することしかできず、String、Integer、Longなどの自然な欠陥がある単純な型の変換をサポートしていません。jsonキーをパラメーターに挿入する場合は、パラメーターパーサーを手動で実装する必要があります。簡単な実装コードを以下に示します。

ハンズオン

目標:@RequestBodyに似たアノテーションを作成し、jsonキーの直接注入をサポートするように現在のパラメーターをマークします。このアノテーションは、メソッドで直接変更することもできます。もちろん、メソッドパラメーター全体にアノテーションが付けられることを示します。また、クラス全体をサポートし、すべてのメソッドがパラメーターであることがサポートされていることを示します。アノテーションに対応するパラメータパーサーを実装します。

アノテーションクラスを作成する

@Target({ElementType.PARAMETER,ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface JsonKeyValue {

    /**
     *  json key 如何没有则使用 类型变量名
     * @return
     */
    @AliasFor("name")
    String value() default "";

    @AliasFor("value")
    String name() default "";
}

复制代码

パラメータパーサーを実装する

public class RequestJsonKeyValueMethodProcessor  implements HandlerMethodArgumentResolver {

    private ObjectMapper objectMapper ;

    public RequestJsonKeyValueMethodProcessor(ObjectMapper objectMapper){
        this.objectMapper = objectMapper;
    }

    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        boolean b = parameter.hasParameterAnnotation(JsonKeyValue.class);
        if (!b) {
            JsonKeyValue value = parameter.getMethodAnnotation(JsonKeyValue.class);  //从方法上找注解
            b = value != null;
            if (!b){
                value = parameter.getContainingClass().getAnnotation(JsonKeyValue.class); //从类上找注解
                b = value != null;
            }
        }
        return b;
    }

    @Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
                                  NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
        parameter = parameter.nestedIfOptional();
        HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
        Assert.state(servletRequest != null, "No HttpServletRequest");
        ServletServerHttpRequest inputMessage = new ServletServerHttpRequest(servletRequest);
        MediaType contentType = inputMessage.getHeaders().getContentType();
        Charset charset = getCharset(contentType);
        Reader reader = new InputStreamReader(inputMessage.getBody(), charset);
        JsonNode jsonNode = objectMapper.readTree(reader);
        String parameterName = parameterName(parameter);
        JsonNode path = jsonNode.path(parameterName);
        Object o = objectMapper.convertValue(path, parameter.getNestedParameterType());
        return o;
    }


    private String parameterName(MethodParameter parameter){
        JsonKeyValue annotation = parameter.getParameterAnnotation(JsonKeyValue.class);
        if (annotation != null){
            String name = annotation.name();
            if (StringUtils.hasText(name))
                return annotation.name();
        }
        return parameter.getParameterName();
    }

    //抄袭  AbstractMessageConverterMethodArgumentResolver  主要是防止将流读入后,controller 方法不能在读了
    private static class EmptyBodyCheckingHttpInputMessage implements HttpInputMessage {

        private final HttpHeaders headers;

        @Nullable
        private final InputStream body;

        public EmptyBodyCheckingHttpInputMessage(HttpInputMessage inputMessage) throws IOException {
            this.headers = inputMessage.getHeaders();
            InputStream inputStream = inputMessage.getBody();
            if (inputStream.markSupported()) {
                inputStream.mark(1);
                this.body = (inputStream.read() != -1 ? inputStream : null);
                inputStream.reset();
            }
            else {
                PushbackInputStream pushbackInputStream = new PushbackInputStream(inputStream);
                int b = pushbackInputStream.read();
                if (b == -1) {
                    this.body = null;
                }
                else {
                    this.body = pushbackInputStream;
                    pushbackInputStream.unread(b);
                }
            }
        }

        @Override
        public HttpHeaders getHeaders() {
            return this.headers;
        }

        @Override
        public InputStream getBody() {
            return (this.body != null ? this.body : StreamUtils.emptyInput());
        }

        public boolean hasBody() {
            return (this.body != null);
        }
    }

    private Charset getCharset(@Nullable MediaType contentType) {
        if (contentType != null && contentType.getCharset() != null) {
            return contentType.getCharset();
        }
        else {
            return StandardCharsets.UTF_8;
        }
    }
}
复制代码

カスタムパラメータパーサーを追加する

@EnableWebMvc
@Configuration
public class WebMvcConfig implements WebMvcConfigurer{

    private Logger logger = LoggerFactory.getLogger(WebMvcConfig.class);

    @Autowired
    private ObjectMapper objectMapper;

    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
        resolvers.add(new RequestJsonKeyValueMethodProcessor(objectMapper));
    }
}
复制代码

カスタムパラメータリゾルバがスプリングネイティブリゾルバをオーバーライドすることを心配する必要はありません。ここで、addArgumentResolvers追加リゾルバは自動的にCustomArgumentResolversに配置されます。通常のクラスのStringやIntegerなどのパラメーターの解析をサポートする単純なカスタムパラメーターパーサーが完成しました。このパラメーターパーサーは空のパラメーターの処理を考慮していないことに注意してください。オプションなどの場合は、一部の単純で迅速な開発シナリオにのみ適しています。同僚がこの知識を日常の開発に利用したかどうかはわかりませんが、当社の基本的な枠組みでは、これらの技術を駆使して迅速な開発を行い、開発効率を大幅に向上させています。

おすすめ

転載: juejin.im/post/7085607229587456031