Polishing

This commit is contained in:
rstoyanchev
2023-03-09 13:04:29 +00:00
parent 287dab03e7
commit f23784234a
4 changed files with 104 additions and 96 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -27,9 +27,8 @@ import org.springframework.core.MethodParameter;
import org.springframework.lang.Nullable;
/**
* Resolves method parameters by delegating to a list of registered
* {@link HandlerMethodArgumentResolver HandlerMethodArgumentResolver}'s.
* Previously resolved method parameters are cached for faster lookups.
* Container for a list of resolvers that looks for one that supports a given
* method parameter type, and delegates to it.
*
* @author Rossen Stoyanchev
* @since 1.0.0
@@ -63,7 +62,7 @@ public class HandlerMethodArgumentResolverComposite implements HandlerMethodArgu
*/
@Override
public boolean supportsParameter(MethodParameter parameter) {
return getArgumentResolver(parameter) != null;
return (getArgumentResolver(parameter) != null);
}
/**
@@ -77,8 +76,7 @@ public class HandlerMethodArgumentResolverComposite implements HandlerMethodArgu
public Object resolveArgument(MethodParameter parameter, DataFetchingEnvironment environment) throws Exception {
HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);
if (resolver == null) {
throw new IllegalArgumentException("Unsupported parameter type [" +
parameter.getParameterType().getName() + "]. supportsParameter should be called first.");
throw new IllegalArgumentException("Unsupported parameter [" + parameter + "].");
}
return resolver.resolveArgument(parameter, environment);
}
@@ -89,17 +87,14 @@ public class HandlerMethodArgumentResolverComposite implements HandlerMethodArgu
*/
@Nullable
public HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);
if (result == null) {
return this.argumentResolverCache.computeIfAbsent(parameter, p -> {
for (HandlerMethodArgumentResolver resolver : this.argumentResolvers) {
if (resolver.supportsParameter(parameter)) {
result = resolver;
this.argumentResolverCache.put(parameter, result);
break;
return resolver;
}
}
}
return result;
return null;
});
}
}

View File

@@ -121,12 +121,6 @@ public class AnnotatedControllerConfigurer
private final FormattingConversionService conversionService = new DefaultFormattingConversionService();
@Nullable
private Executor executor;
@Nullable
private ApplicationContext applicationContext;
@Nullable
private HandlerMethodArgumentResolverComposite argumentResolvers;
@@ -136,6 +130,12 @@ public class AnnotatedControllerConfigurer
@Nullable
private AnnotatedControllerExceptionResolver exceptionResolver;
@Nullable
private Executor executor;
@Nullable
private ApplicationContext applicationContext;
/**
* Add a {@code FormatterRegistrar} to customize the {@link ConversionService}
@@ -147,31 +147,10 @@ public class AnnotatedControllerConfigurer
registrar.registerFormatters(this.conversionService);
}
/**
* Configure an {@link Executor} to use for asynchronous handling of
* {@link Callable} return values from controller methods.
* <p>By default, this is not set in which case controller methods with a
* {@code Callable} return value cannot be registered.
* @param executor the executor to use
*/
public void setExecutor(Executor executor) {
this.executor = executor;
}
/**
* Configure an initializer that configures the {@link DataBinder} before the binding process.
* @param consumer the data binder initializer
* @since 1.0.1
* @deprecated this property is deprecated, ignored, and should not be
* necessary as a {@link DataBinder} is no longer used to bind arguments
*/
@Deprecated(since = "1.1.0", forRemoval = true)
public void setDataBinderInitializer(@Nullable Consumer<DataBinder> consumer) {
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
HandlerMethodArgumentResolverComposite getArgumentResolvers() {
Assert.notNull(this.argumentResolvers,
"HandlerMethodArgumentResolverComposite is not yet initialized, was afterPropertiesSet called?");
return this.argumentResolvers;
}
/**
@@ -189,15 +168,39 @@ public class AnnotatedControllerConfigurer
* @since 1.2
*/
public DataFetcherExceptionResolver getExceptionResolver() {
Assert.notNull(this.exceptionResolver, "ExceptionResolver is not initialized, was afterPropertiesSet called?");
Assert.notNull(this.exceptionResolver,
"DataFetcherExceptionResolver is not yet initialized, was afterPropertiesSet called?");
return (ex, env) -> this.exceptionResolver.resolveException(ex, env, null);
}
@Nullable
HandlerMethodArgumentResolverComposite getArgumentResolvers() {
return this.argumentResolvers;
/**
* Configure an initializer that configures the {@link DataBinder} before the binding process.
* @param consumer the data binder initializer
* @since 1.0.1
* @deprecated this property is deprecated, ignored, and should not be
* necessary as a {@link DataBinder} is no longer used to bind arguments
*/
@Deprecated(since = "1.1.0", forRemoval = true)
public void setDataBinderInitializer(@Nullable Consumer<DataBinder> consumer) {
}
/**
* Configure an {@link Executor} to use for asynchronous handling of
* {@link Callable} return values from controller methods.
* <p>By default, this is not set in which case controller methods with a
* {@code Callable} return value cannot be registered.
* @param executor the executor to use
*/
public void setExecutor(Executor executor) {
this.executor = executor;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
@Override
public void afterPropertiesSet() {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2020-2022 the original author or authors.
* Copyright 2020-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -47,7 +47,6 @@ import org.springframework.graphql.data.method.HandlerMethodArgumentResolverComp
import org.springframework.graphql.data.method.annotation.BatchMapping;
import org.springframework.graphql.data.method.annotation.SchemaMapping;
import org.springframework.stereotype.Controller;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;
@@ -57,17 +56,21 @@ import static org.springframework.core.annotation.MergedAnnotations.SearchStrate
* {@link BeanFactoryInitializationAotProcessor} implementation for registering
* runtime hints discoverable through GraphQL controllers, such as:
* <ul>
* <li>invocation reflection on {@code @SchemaMapping} and {@code @BatchMapping} annotated controllers methods
* <li>binding reflection on controller method arguments, needed for binding or by the GraphQL Java engine itself
* <li>reflection for SpEL support and JDK proxy creation for {@code @ProjectedPayload} projections,
* if Spring Data Commons is present on the classpath.
* <li>invocation reflection on {@code @SchemaMapping} and {@code @BatchMapping}
* annotated controllers methods
* <li>binding reflection on controller method arguments, needed for binding or
* by the GraphQL Java engine itself
* <li>reflection for SpEL support and JDK proxy creation for
* {@code @ProjectedPayload} projections, if Spring Data Commons is present on
* the classpath.
* </ul>
* <p>This processor is using a {@link HandlerMethodArgumentResolver} resolution mechanism similar
* to the one used in {@link AnnotatedControllerConfigurer}. The type of runtime hints registered
* for each method argument depends on the {@link HandlerMethodArgumentResolver} resolved.
* <p>Manual registration of {@link graphql.schema.DataFetcher} cannot be detected by this
* processor; developers will need to declare bound types with {@link RegisterReflectionForBinding}
* annotations on their configuration class.
* <p>This processor is using a {@link HandlerMethodArgumentResolver} resolution
* mechanism similar to the one used in {@link AnnotatedControllerConfigurer}.
* The type of runtime hints registered for each method argument depends on the
* {@link HandlerMethodArgumentResolver} resolved.
* <p>Manual registration of {@link graphql.schema.DataFetcher} cannot be detected
* by this processor; developers will need to declare bound types with
* {@link RegisterReflectionForBinding} annotations on their configuration class.
*
* @author Brian Clozel
* @see org.springframework.graphql.data.method.HandlerMethodArgumentResolver
@@ -93,7 +96,9 @@ class SchemaMappingBeanFactoryInitializationAotProcessor implements BeanFactoryI
return MergedAnnotations.from(element, TYPE_HIERARCHY).isPresent(Controller.class);
}
private static class SchemaMappingBeanFactoryInitializationAotContribution implements BeanFactoryInitializationAotContribution {
private static class SchemaMappingBeanFactoryInitializationAotContribution
implements BeanFactoryInitializationAotContribution {
private final Class<?>[] controllers;
@@ -105,21 +110,21 @@ class SchemaMappingBeanFactoryInitializationAotProcessor implements BeanFactoryI
}
private HandlerMethodArgumentResolverComposite createArgumentResolvers() {
AnnotatedControllerConfigurer controllerConfigurer = new AnnotatedControllerConfigurer();
controllerConfigurer.setApplicationContext(new StaticApplicationContext());
controllerConfigurer.afterPropertiesSet();
HandlerMethodArgumentResolverComposite argumentResolverComposite = controllerConfigurer.getArgumentResolvers();
Assert.notNull(argumentResolverComposite, "argument resolvers should not be null");
return argumentResolverComposite;
AnnotatedControllerConfigurer configurer = new AnnotatedControllerConfigurer();
configurer.setApplicationContext(new StaticApplicationContext());
configurer.afterPropertiesSet();
return configurer.getArgumentResolvers();
}
@Override
public void applyTo(GenerationContext generationContext, BeanFactoryInitializationCode beanFactoryInitializationCode) {
RuntimeHints runtimeHints = generationContext.getRuntimeHints();
public void applyTo(GenerationContext context, BeanFactoryInitializationCode initializationCode) {
RuntimeHints runtimeHints = context.getRuntimeHints();
registerSpringDataSpelSupport(runtimeHints);
Arrays.stream(this.controllers).forEach(controller -> {
runtimeHints.reflection().registerType(controller);
ReflectionUtils.doWithMethods(controller, method -> processSchemaMappingMethod(runtimeHints, method), this::isGraphQlHandlerMethod);
ReflectionUtils.doWithMethods(controller,
method -> processSchemaMappingMethod(runtimeHints, method),
this::isGraphQlHandlerMethod);
});
}
@@ -134,9 +139,8 @@ class SchemaMappingBeanFactoryInitializationAotProcessor implements BeanFactoryI
}
private boolean isGraphQlHandlerMethod(AnnotatedElement element) {
MergedAnnotations mergedAnnotations = MergedAnnotations.from(element, TYPE_HIERARCHY);
return mergedAnnotations.isPresent(SchemaMapping.class)
|| mergedAnnotations.isPresent(BatchMapping.class);
MergedAnnotations annotations = MergedAnnotations.from(element, TYPE_HIERARCHY);
return annotations.isPresent(SchemaMapping.class) || annotations.isPresent(BatchMapping.class);
}
private void processSchemaMappingMethod(RuntimeHints runtimeHints, Method method) {
@@ -147,17 +151,17 @@ class SchemaMappingBeanFactoryInitializationAotProcessor implements BeanFactoryI
processReturnType(runtimeHints, MethodParameter.forExecutable(method, -1));
}
private void processMethodParameter(RuntimeHints runtimeHints, MethodParameter methodParameter) {
MethodParameterRuntimeHintsRegistrar.fromMethodParameter(this.argumentResolvers, methodParameter)
.apply(runtimeHints);
private void processMethodParameter(RuntimeHints hints, MethodParameter parameter) {
MethodParameterRuntimeHintsRegistrar.fromMethodParameter(this.argumentResolvers, parameter).apply(hints);
}
private void processReturnType(RuntimeHints runtimeHints, MethodParameter methodParameter) {
new ArgumentBindingHints(methodParameter).apply(runtimeHints);
private void processReturnType(RuntimeHints hints, MethodParameter parameter) {
new ArgumentBindingHints(parameter).apply(hints);
}
}
@FunctionalInterface
private interface MethodParameterRuntimeHintsRegistrar {
@@ -165,18 +169,20 @@ class SchemaMappingBeanFactoryInitializationAotProcessor implements BeanFactoryI
void apply(RuntimeHints runtimeHints);
static MethodParameterRuntimeHintsRegistrar fromMethodParameter(HandlerMethodArgumentResolverComposite argumentResolvers, MethodParameter methodParameter) {
HandlerMethodArgumentResolver argumentResolver = argumentResolvers.getArgumentResolver(methodParameter);
if (argumentResolver instanceof ArgumentMethodArgumentResolver
|| argumentResolver instanceof ArgumentsMethodArgumentResolver) {
return new ArgumentBindingHints(methodParameter);
static MethodParameterRuntimeHintsRegistrar fromMethodParameter(
HandlerMethodArgumentResolverComposite resolvers, MethodParameter parameter) {
HandlerMethodArgumentResolver resolver = resolvers.getArgumentResolver(parameter);
if (resolver instanceof ArgumentMethodArgumentResolver
|| resolver instanceof ArgumentsMethodArgumentResolver) {
return new ArgumentBindingHints(parameter);
}
if (argumentResolver instanceof DataLoaderMethodArgumentResolver) {
return new DataLoaderHints(methodParameter);
if (resolver instanceof DataLoaderMethodArgumentResolver) {
return new DataLoaderHints(parameter);
}
if (springDataPresent) {
if (argumentResolver instanceof ProjectedPayloadMethodArgumentResolver) {
return new ProjectedPayloadHints(methodParameter);
if (resolver instanceof ProjectedPayloadMethodArgumentResolver) {
return new ProjectedPayloadHints(parameter);
}
}
return new NoHintsRequired();
@@ -184,6 +190,7 @@ class SchemaMappingBeanFactoryInitializationAotProcessor implements BeanFactoryI
}
private static class NoHintsRequired implements MethodParameterRuntimeHintsRegistrar {
@Override
@@ -192,6 +199,7 @@ class SchemaMappingBeanFactoryInitializationAotProcessor implements BeanFactoryI
}
}
private static class ArgumentBindingHints implements MethodParameterRuntimeHintsRegistrar {
private final MethodParameter methodParameter;
@@ -210,6 +218,7 @@ class SchemaMappingBeanFactoryInitializationAotProcessor implements BeanFactoryI
}
}
private static class DataLoaderHints implements MethodParameterRuntimeHintsRegistrar {
private final MethodParameter methodParameter;
@@ -219,12 +228,13 @@ class SchemaMappingBeanFactoryInitializationAotProcessor implements BeanFactoryI
}
@Override
public void apply(RuntimeHints runtimeHints) {
bindingRegistrar.registerReflectionHints(runtimeHints.reflection(),
this.methodParameter.nested().getNestedGenericParameterType());
public void apply(RuntimeHints hints) {
bindingRegistrar.registerReflectionHints(
hints.reflection(), this.methodParameter.nested().getNestedGenericParameterType());
}
}
private static class ProjectedPayloadHints implements MethodParameterRuntimeHintsRegistrar {
private final MethodParameter methodParameter;
@@ -234,10 +244,10 @@ class SchemaMappingBeanFactoryInitializationAotProcessor implements BeanFactoryI
}
@Override
public void apply(RuntimeHints runtimeHints) {
public void apply(RuntimeHints hints) {
Class<?> parameterType = this.methodParameter.nestedIfOptional().getNestedParameterType();
runtimeHints.reflection().registerType(parameterType);
runtimeHints.proxies().registerJdkProxy(parameterType, TargetAware.class, SpringProxy.class, DecoratingProxy.class);
hints.reflection().registerType(parameterType);
hints.proxies().registerJdkProxy(parameterType, TargetAware.class, SpringProxy.class, DecoratingProxy.class);
}
}

View File

@@ -148,8 +148,8 @@ public class AnnotatedControllerExceptionResolverTests {
configurer.setApplicationContext(applicationContext);
configurer.afterPropertiesSet();
HandlerMethodArgumentResolverComposite argumentResolvers = configurer.getArgumentResolvers();
AnnotatedControllerExceptionResolver resolver = new AnnotatedControllerExceptionResolver(argumentResolvers);
HandlerMethodArgumentResolverComposite resolvers = configurer.getArgumentResolvers();
AnnotatedControllerExceptionResolver resolver = new AnnotatedControllerExceptionResolver(resolvers);
resolver.registerControllerAdvice(applicationContext);
return resolver;
}