import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.lang.Nullable;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.service.annotation.HttpExchange;
import org.springframework.web.servlet.mvc.condition.RequestCondition;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
import java.util.Set;
public class CustomRequestMappingHandlerMapping extends RequestMappingHandlerMapping {
@Override
public void afterPropertiesSet() {
super.setOrder(1);
super.afterPropertiesSet();
}
@Nullable
private RequestMappingInfo createRequestMappingInfo(AnnotatedElement element) {
RequestCondition<?> customCondition = (element instanceof Class<?> clazz ?
getCustomTypeCondition(clazz) : getCustomMethodCondition((Method) element));
RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(element, RequestMapping.class);
if (requestMapping != null) {
return createRequestMappingInfo(requestMapping, customCondition);
}
HttpExchange httpExchange = AnnotatedElementUtils.findMergedAnnotation(element, HttpExchange.class);
if (httpExchange != null) {
return createRequestMappingInfo(httpExchange, customCondition);
}
return null;
}
@Override
protected boolean isHandler(Class<?> beanType) {
return AnnotatedElementUtils.hasAnnotation(beanType, FeignClient.class);
}
@Override
protected void handleMatch(RequestMappingInfo info, String lookupPath, HttpServletRequest request) {
}
@Override
protected HandlerMethod handleNoMatch(Set<RequestMappingInfo> infos, String lookupPath, HttpServletRequest request) throws ServletException {
return null;
}
protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
FeignClient annotation = AnnotatedElementUtils.findMergedAnnotation(handlerType, FeignClient.class);
RequestMappingInfo info = null;
if (annotation != null) {
RequestCondition<?> customMethodCondition = getCustomTypeCondition(handlerType);
Class<?>[] interfaces = handlerType.getInterfaces();
info = createRequestMappingInfo(annotation, customMethodCondition, method, interfaces[0]);
}
return info;
}
protected RequestMappingInfo createRequestMappingInfo(FeignClient requestMapping, RequestCondition<?> customCondition, Method method, Class<?> superInterface) {
RequestMapping mergedAnnotation = AnnotatedElementUtils.findMergedAnnotation(method, RequestMapping.class);
RequestMethod[] requestMethods = null;
if (mergedAnnotation != null) {
requestMethods = mergedAnnotation.method();
} else {
requestMethods = new RequestMethod[]{};
}
String substring = superInterface.getSimpleName().substring(0, 1).toLowerCase() + superInterface.getSimpleName().substring(1);
String path = StringUtils.hasText(requestMapping.path()) ? requestMapping.path() : StringUtils.hasText(requestMapping.contextId()) ? requestMapping.contextId() :
StringUtils.hasText(requestMapping.name()) ? requestMapping.name() : substring;
RequestMappingInfo.Builder builder = RequestMappingInfo
.paths(resolveEmbeddedValuesInPatterns(new String[]{path + "/" + method.getName()}))
.methods(requestMethods)
.params(new String[]{})
.headers(new String[]{})
.consumes(new String[]{})
.produces(new String[]{})
.mappingName(requestMapping.contextId());
return builder.options(getBuilderConfiguration()).build();
}
}
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.core.Conventions;
import org.springframework.core.MethodParameter;
import org.springframework.core.ResolvableType;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.http.HttpStatusCode;
import org.springframework.http.ProblemDetail;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.http.server.ServletServerHttpResponse;
import org.springframework.lang.Nullable;
import org.springframework.validation.BindingResult;
import org.springframework.web.HttpMediaTypeNotAcceptableException;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.ModelAndViewContainer;
import org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor;
import java.io.IOException;
import java.lang.reflect.Type;
import java.net.URI;
import java.util.List;
public class CustomMessageConverterMethodProcessor extends AbstractMessageConverterMethodProcessor {
protected CustomMessageConverterMethodProcessor(List<HttpMessageConverter<?>> converters) {
super(converters);
}
@Override
public boolean supportsReturnType(MethodParameter returnType) {
return AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), FeignClient.class);
}
@Override
public boolean supportsParameter(MethodParameter parameter) {
Class<?>[] interfaces = parameter.getContainingClass().getInterfaces();
for (Class<?> anInterface : interfaces) {
FeignClient annotation = anInterface.getAnnotation(FeignClient.class);
if (annotation != null) {
return true;
}
}
return false;
}
@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());
if (binderFactory != null) {
String name = Conventions.getVariableNameForParameter(parameter);
ResolvableType type = ResolvableType.forMethodParameter(parameter);
WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name, type);
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);
}
@Override
protected <T> Object readWithMessageConverters(NativeWebRequest webRequest, MethodParameter parameter,
Type paramType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {
ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
Object arg = readWithMessageConverters(inputMessage, parameter, paramType);
if (arg == null && checkRequired(parameter)) {
throw new HttpMessageNotReadableException("Required request body is missing: " +
parameter.getExecutable().toGenericString(), inputMessage);
}
return arg;
}
protected boolean checkRequired(MethodParameter parameter) {
RequestBody requestBody = parameter.getParameterAnnotation(RequestBody.class);
return (requestBody != null && requestBody.required() && !parameter.isOptional());
}
@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
mavContainer.setRequestHandled(true);
ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);
if (returnValue instanceof ProblemDetail detail) {
outputMessage.setStatusCode(HttpStatusCode.valueOf(detail.getStatus()));
if (detail.getInstance() == null) {
URI path = URI.create(inputMessage.getServletRequest().getRequestURI());
detail.setInstance(path);
}
}
// Try even with null return value. ResponseBodyAdvice could get involved.
writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
}
}
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
import java.util.ArrayList;
import java.util.List;
@Configuration
public class CustomBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof RequestMappingHandlerAdapter requestMappingHandlerAdapter) {
List<HttpMessageConverter<?>> messageConverters = requestMappingHandlerAdapter.getMessageConverters();
List<HandlerMethodReturnValueHandler> returnValueHandlers = requestMappingHandlerAdapter.getReturnValueHandlers();
ArrayList<HandlerMethodReturnValueHandler> handlerMethodReturnValueHandlers = new ArrayList<>(returnValueHandlers);
CustomMessageConverterMethodProcessor customMessageConverterMethodProcessor = new CustomMessageConverterMethodProcessor(messageConverters);
handlerMethodReturnValueHandlers.addFirst(customMessageConverterMethodProcessor);
requestMappingHandlerAdapter.setReturnValueHandlers(handlerMethodReturnValueHandlers);
List<HandlerMethodArgumentResolver> argumentResolvers = requestMappingHandlerAdapter.getArgumentResolvers();
ArrayList<HandlerMethodArgumentResolver> handlerMethodArgumentResolvers = new ArrayList<>(argumentResolvers);
handlerMethodArgumentResolvers.addFirst(customMessageConverterMethodProcessor);
requestMappingHandlerAdapter.setArgumentResolvers(handlerMethodArgumentResolvers);
}
return BeanPostProcessor.super.postProcessAfterInitialization(bean, beanName);
}
}
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.format.support.FormattingConversionService;
import org.springframework.web.accept.ContentNegotiationManager;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import org.springframework.web.servlet.resource.ResourceUrlProvider;
@Configuration
public class CustomWebMvcConfigurer {
@Bean
@ConditionalOnClass(WebMvcConfigurationSupport.class)
@SuppressWarnings("deprecation")
public RequestMappingHandlerMapping customRequestMappingHandlerMapping(
@Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager,
@Qualifier("mvcConversionService") FormattingConversionService conversionService,
@Qualifier("mvcResourceUrlProvider") ResourceUrlProvider resourceUrlProvider
) {
RequestMappingHandlerMapping mapping = new CustomRequestMappingHandlerMapping();
mapping.setOrder(1);
mapping.setContentNegotiationManager(contentNegotiationManager);
return mapping;
}
}