spring自定义RequestMappingHandlerMapping

imp

ort 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;
    }

}