SpringMvc自定义参数解析与返回值处理

版权声明:转载请注明出处 https://blog.csdn.net/jlh912008548/article/details/81814688

SpringMvc自定义参数解析与返回值处理

近日在做项目的时候,需要解析客户端传来的经过AES加密处理的实体信息,同时也需要向客户端返回经过AES加密的实体信息,在项目初期,都是在Controller方法中去调用某个工具类进行decode、encode操作比较繁琐,于是去寻求解决办法,在翻阅了SpringMvc解析参数的源码后,仿照@RequestBody的进行以下实现。本文基于SpringBoot 2.0SpringMvc 5.0.6

SpringMvc 参数绑定原理

ArgumentResolver与ReturnValueHandler

通过在maven在项目中引入SpringMvc依赖,你可以使用ide的快捷键(比如,idea是ctrl+n)查找类RequestBody,其类注释如下:

/**
 * Annotation indicating a method parameter should be bound to the body of the web request.
 * The body of the request is passed through an {@link HttpMessageConverter} to resolve the
 * method argument depending on the content type of the request. Optionally, automatic
 * validation can be applied by annotating the argument with {@code @Valid}.
 *
 * <p>Supported for annotated handler methods in Servlet environments.
 *
 * @author Arjen Poutsma
 * @since 3.0
 * @see RequestHeader
 * @see ResponseBody
 * @see org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter
 */

接着继续查找类注释中的类RequestMappingHandlerAdapter,查看其源码可以发现,其源码的注释中有这样两行代码:

 * @see HandlerMethodArgumentResolver
 * @see HandlerMethodReturnValueHandler

分别查看这两个类:

HandlerMethodArgumentResolver

在给定请求的上下文中,将方法参数解析为参数值的策略接口。

HandlerMethodArgumentResolver中有两个接口

//判断传入的参数是否被该方法所支持
boolean supportsParameter(MethodParameter parameter);
//参数解析方法
Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
            NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception;

其中resolveArgument方法的返回值,会被作为参数传入到Controller的方法参数中。

HandlerMethodReturnValueHandler

处理程序方法返回值的策略接口。

同样的,HandlerMethodReturnValueHandler中也有两个接口

//判断传入的参数是否被该方法所支持
boolean supportsReturnType(MethodParameter returnType);
//返回值解析
void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
            ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception;

在handleReturnValue我们可以直接构造返回给客户端的内容。

MethodParameter

按照官方的说明,这个类封装了方法参数的规范,记录了一个方法的类注解、方法注解、方法参数。

我们在supportsReturnType方法去判断这个类是否拥有指定注解(自定义注解),从而进行相应的处理逻辑,另外我们还可以通过这个类的对象去获取Controller方法的参数类型,比如:

parameter.getNestedGenericParameterType()

如果该注解在方法的参数上,即ElementType.PARAMETER,例子见下方,则getNestedGenericParameterType方法返回的为其制定参数的类型:

@Controller
public class TestApi extends BaseController {
    private static Logger logger = LoggerFactory.getLogger(TestApi.class);
    @PostMapping(value = "/encryptTest")
    public UserDo encryptTest(@EncryptBody UserDo user) {
        return user;
    }
}

如果将注解打在方法上,即ElementType.METHOD,例子见下方,则getNestedGenericParameterType方法返回的为方法返回值类型:

@EncryptBody
public UserDo encryptTest(){
}

NativeWebRequest

NativeWebRequest是WebRequest接口的扩展 ,是springmvc专门定义用来供框架内部使用,特别是通用参数解析代码。

在ArgumentResolver与ReturnValueHandler中,我们可以使用其获取HttpServletRequestHttpServletResponse,从而实现对参数的解析与返回值的构建。

HttpServletRequest servletRequest = nativeWebRequest.getNativeRequest(HttpServletRequest.class);
HttpServletResponse response = nativeWebRequest.getNativeResponse(HttpServletResponse.class);

RequestMappingHandlerAdapter

继续查看RequestMappingHandlerAdapter的源码,分别以下四个变量:

  • customArgumentResolvers:自定义的参数解析器
  • argumentResolvers:默认的参数解析器,通过getDefaultArgumentResolvers方法可查看其具体的初始换方式。
  • customReturnValueHandlers:自定义的返回值处理器
  • returnValueHandlers:默认的返回值处理器,通过getDefaultReturnValueHandlers方法可查看其具体的初始化方式

getDefaultArgumentResolvers源码如下:

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

        // Annotation-based argument resolution
        resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), false));
        resolvers.add(new RequestParamMapMethodArgumentResolver());
        resolvers.add(new PathVariableMethodArgumentResolver());
        resolvers.add(new PathVariableMapMethodArgumentResolver());
        resolvers.add(new MatrixVariableMethodArgumentResolver());
        resolvers.add(new MatrixVariableMapMethodArgumentResolver());
        resolvers.add(new ServletModelAttributeMethodProcessor(false));
        resolvers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice));
        resolvers.add(new RequestPartMethodArgumentResolver(getMessageConverters(), this.requestResponseBodyAdvice));
        resolvers.add(new RequestHeaderMethodArgumentResolver(getBeanFactory()));
        resolvers.add(new RequestHeaderMapMethodArgumentResolver());
        resolvers.add(new ServletCookieValueMethodArgumentResolver(getBeanFactory()));
        resolvers.add(new ExpressionValueMethodArgumentResolver(getBeanFactory()));
        resolvers.add(new SessionAttributeMethodArgumentResolver());
        resolvers.add(new RequestAttributeMethodArgumentResolver());

        // Type-based argument resolution
        resolvers.add(new ServletRequestMethodArgumentResolver());
        resolvers.add(new ServletResponseMethodArgumentResolver());
        resolvers.add(new HttpEntityMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice));
        resolvers.add(new RedirectAttributesMethodArgumentResolver());
        resolvers.add(new ModelMethodProcessor());
        resolvers.add(new MapMethodProcessor());
        resolvers.add(new ErrorsMethodArgumentResolver());
        resolvers.add(new SessionStatusMethodArgumentResolver());
        resolvers.add(new UriComponentsBuilderMethodArgumentResolver());

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

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

        return resolvers;
    }

getDefaultReturnValueHandlers源码如下:

private List<HandlerMethodReturnValueHandler> getDefaultReturnValueHandlers() {
        List<HandlerMethodReturnValueHandler> handlers = new ArrayList<>();

        // Single-purpose return value types
        handlers.add(new ModelAndViewMethodReturnValueHandler());
        handlers.add(new ModelMethodProcessor());
        handlers.add(new ViewMethodReturnValueHandler());
        handlers.add(new ResponseBodyEmitterReturnValueHandler(getMessageConverters(),
                this.reactiveAdapterRegistry, this.taskExecutor, this.contentNegotiationManager));
        handlers.add(new StreamingResponseBodyReturnValueHandler());
        handlers.add(new HttpEntityMethodProcessor(getMessageConverters(),
                this.contentNegotiationManager, this.requestResponseBodyAdvice));
        handlers.add(new HttpHeadersReturnValueHandler());
        handlers.add(new CallableMethodReturnValueHandler());
        handlers.add(new DeferredResultMethodReturnValueHandler());
        handlers.add(new AsyncTaskMethodReturnValueHandler(this.beanFactory));

        // Annotation-based return value types
        handlers.add(new ModelAttributeMethodProcessor(false));
        handlers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(),
                this.contentNegotiationManager, this.requestResponseBodyAdvice));

        // Multi-purpose return value types
        handlers.add(new ViewNameMethodReturnValueHandler());
        handlers.add(new MapMethodProcessor());

        // Custom return value types
        if (getCustomReturnValueHandlers() != null) {
            handlers.addAll(getCustomReturnValueHandlers());
        }

        // Catch-all
        if (!CollectionUtils.isEmpty(getModelAndViewResolvers())) {
            handlers.add(new ModelAndViewResolverMethodReturnValueHandler(getModelAndViewResolvers()));
        }
        else {
            handlers.add(new ModelAttributeMethodProcessor(true));
        }

        return handlers;
    }

自定义解析器与构造器

在自定义的过程中依赖了项目中的一些工具类,比如:AbstractEcryptMappingHadlerEncrypt等等,由于项目中预留了多种加密方式的接口,类稍有点过多,此处就不一一贴出,如有需要,请移驾github查看源码https://github.com/jiangliuhong/RedisWClient-server查看源码(包路径为:pers.jarome.redis.wclient.common.web.encrypt),当然,你也可以移除这些依赖,然后自定义逻辑。

自定义EncryptArgumentResolver

package pers.jarome.redis.wclient.common.web.encrypt.method.resolver;

import org.springframework.core.MethodParameter;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;
import pers.jarome.redis.wclient.common.web.encrypt.anno.EncryptBody;
import pers.jarome.redis.wclient.common.web.encrypt.constants.EncryptMethod;
import pers.jarome.redis.wclient.common.web.encrypt.entity.Encrypt;
import pers.jarome.redis.wclient.common.web.encrypt.exception.EncryptException;
import pers.jarome.redis.wclient.common.web.encrypt.method.AbstractEcryptMappingHadler;

import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

/**
 * EncryptArgumentResolver
 *
 * @author jiangliuhong
 * @description 加密解析器
 * @date 2018/8/17 9:35
 */
public class EncryptArgumentResolver extends AbstractEcryptMappingHadler implements HandlerMethodArgumentResolver {

    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        return hasEncryptAnnotaion(parameter);
    }

    @Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
        //请自定义你的处理逻辑。
        //你可以根据HttpServletRequest去获取你想要的
        //此处我是通过webRequest获取body中的内容,然后将其进行AES解密,然后转为controller方法中需要的对象
        String body = getRequestBody(webRequest);
        EncryptBody encryptBody = parameter.getAnnotatedElement().getAnnotation(EncryptBody.class);
        Encrypt encrypt = getEncrypt(encryptBody.method());
        if(encrypt == null){
            throw new EncryptException("Not Found Encrypt.");
        }
        return encrypt.decode(body, parameter.getNestedGenericParameterType());

    }

    private Boolean hasEncryptAnnotaion(MethodParameter parameter) {
        return parameter.hasParameterAnnotation(EncryptBody.class);
    }

    private String getRequestBody(NativeWebRequest webRequest) throws IOException {
        HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
        ServletInputStream inputStream = servletRequest.getInputStream();
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
        StringBuilder body = new StringBuilder();
        String str;
        while ((str = bufferedReader.readLine()) != null) {
            body.append(str);
        }
        return body.toString();
    }
}

自定义EncryptBodyRturnValueHandler

package pers.jarome.redis.wclient.common.web.encrypt.method.handler;

import com.alibaba.fastjson.JSON;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
import org.springframework.web.method.support.ModelAndViewContainer;
import pers.jarome.redis.wclient.common.web.encrypt.anno.EncryptBody;
import pers.jarome.redis.wclient.common.web.encrypt.entity.Encrypt;
import pers.jarome.redis.wclient.common.web.encrypt.method.AbstractEcryptMappingHadler;

import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;

/**
 * 
 * EncryptBodyRturnValueHandler
 * @description 加密实体返回值组装器
 * @author jiangliuhong
 * @date 2018/8/16 21:53
 */
public class EncryptBodyRturnValueHandler extends AbstractEcryptMappingHadler implements HandlerMethodReturnValueHandler {

    @Override
    public boolean supportsReturnType(MethodParameter returnType) {
        return returnType.hasMethodAnnotation(EncryptBody.class);
    }

    @Override
    public void handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
        //请自定义你的处理逻辑。
        //此处我是将返回值进行AES加密,然后返回给客户端
        EncryptBody encryptBody = returnType.getMethodAnnotation(EncryptBody.class);
        mavContainer.setRequestHandled(true);
        HttpServletResponse response = webRequest.getNativeResponse(HttpServletResponse.class);
        response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
        PrintWriter outWriter = response.getWriter();
        Encrypt encrypt = getEncrypt(encryptBody.method());
        Object encode = encrypt.encode(returnValue);
        String jsonString = "";
        if(encode!=null) {
            jsonString = JSON.toJSONString(encode);
        }
        outWriter.write(jsonString);
        outWriter.flush();
        outWriter.close();
    }
}

注册

继续调试跟踪代码,发现在WebMvcConfigurationSupport类(只有高版本的SpringBoot才具有该类,低版本的为WebMvcConfigurerAdapter该类的逻辑,由于没有深入查看,此处就不做赘述了)中有这样一个方法:

@Bean
    public RequestMappingHandlerAdapter requestMappingHandlerAdapter() {
        //初始化一个RequestMappingHandlerAdapter
        RequestMappingHandlerAdapter adapter = createRequestMappingHandlerAdapter();
        adapter.setContentNegotiationManager(mvcContentNegotiationManager());
        adapter.setMessageConverters(getMessageConverters());
        adapter.setWebBindingInitializer(getConfigurableWebBindingInitializer());
        adapter.setCustomArgumentResolvers(getArgumentResolvers());
        adapter.setCustomReturnValueHandlers(getReturnValueHandlers());

        if (jackson2Present) {
            adapter.setRequestBodyAdvice(Collections.singletonList(new JsonViewRequestBodyAdvice()));
            adapter.setResponseBodyAdvice(Collections.singletonList(new JsonViewResponseBodyAdvice()));
        }

        AsyncSupportConfigurer configurer = new AsyncSupportConfigurer();
        configureAsyncSupport(configurer);
        if (configurer.getTaskExecutor() != null) {
            adapter.setTaskExecutor(configurer.getTaskExecutor());
        }
        if (configurer.getTimeout() != null) {
            adapter.setAsyncRequestTimeout(configurer.getTimeout());
        }
        adapter.setCallableInterceptors(configurer.getCallableInterceptors());
        adapter.setDeferredResultInterceptors(configurer.getDeferredResultInterceptors());

        return adapter;
    }

该方法的作用为在系统初始化的时候,返回系统以及用户自定义的解析器等。

从上诉代码不难看出,在加载用户自定义的处理器的代码为:

adapter.setCustomArgumentResolvers(getArgumentResolvers());
adapter.setCustomReturnValueHandlers(getReturnValueHandlers());

刨根究底,发现getArgumentResolversgetReturnValueHandlers的数据源源为WebMvcConfigurer接口中的addArgumentResolversaddReturnValueHandlers。此时我们可以通过自定义类,实现这两个接口来实现注册了。

SpringBoot1.x注册方法

package pers.jarome.redis.wclient.app.config;

import org.springframework.stereotype.Component;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
import org.springframework.web.servlet.config.annotation.InterceptorRegistration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import pers.jarome.redis.wclient.common.web.encrypt.method.handler.EncryptBodyRturnValueHandler;
import pers.jarome.redis.wclient.common.web.interceptor.AuthenticationInterceptor;
import pers.jarome.redis.wclient.common.web.encrypt.method.resolver.EncryptArgumentResolver;

import java.util.List;

/**
 * Web环境配置
 *
 * @author jiangliuhong
 * @date 2017/12/29
 **/
@Component
public class WebMvcConfigAdapter extends WebMvcConfigurerAdapter {
    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
        argumentResolvers.add(new EncryptArgumentResolver());
    }
    @Override
    public void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> returnValueHandlers) {
        returnValueHandlers.add(new EncryptBodyRturnValueHandler());
    }
}

SpringBoot2.0注册方法

如果你的SpringBoot版本大于等于2.0,那么WebMvcConfigurerAdapter过期,此时应该使用新的WebMvcConfigurationSupport

package pers.jarome.redis.wclient.app.config;

import org.springframework.stereotype.Component;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
import org.springframework.web.servlet.config.annotation.InterceptorRegistration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
import pers.jarome.redis.wclient.common.web.encrypt.method.handler.EncryptBodyRturnValueHandler;
import pers.jarome.redis.wclient.common.web.encrypt.method.resolver.EncryptArgumentResolver;
import pers.jarome.redis.wclient.common.web.interceptor.AuthenticationInterceptor;

import java.util.List;

/**
 * 
 * WebMvcConfig
 * @description Web环境配置
 * @author jiangliuhong
 * @date 2018/8/19 0:46
 */
@Component
public class WebMvcConfig extends WebMvcConfigurationSupport {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        InterceptorRegistration ir = registry.addInterceptor(new AuthenticationInterceptor());
        ir.addPathPatterns("/**");
    }

    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
        argumentResolvers.add(new EncryptArgumentResolver());
    }

    @Override
    public void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> returnValueHandlers) {
        returnValueHandlers.add(new EncryptBodyRturnValueHandler());
    }

}

非SpringBoot注册方法

对于非SpringBoot项目的注册方式大同小异,也就是xml配置与类配置的区别。

对于非SpringBoot的项目,在其springmvc的配置文件中加入以下代码即可:

<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
        <!-- 自定义参数解析器 -->
        <property name="customArgumentResolvers">
            <list>
                <bean class="pers.jarome.redis.wclient.common.web.encrypt.method.resolver.EncryptArgumentResolver" />
            </list>
        </property>
    <property name="customReturnValueHandlers">
            <list>
                <bean class="pers.jarome.redis.wclient.common.web.encrypt.method.handler.EncryptBodyRturnValueHandler" />
            </list>
        </property>
    </bean>

结语

在自定义解析器时,我是基于RequestMappingHandlerAdapter进行封装实现的,在这个类中通过加入自定义的Resolver与Handler,从而达到我们期望的参数绑定与返回值处理效果,另外,我们还可以定义messageConverters等,当然,也可以自定义一个HandlerAdapter。最后再引入一个类WebMvcAutoConfiguration作为下一次Spring源码学习的主题吧。

猜你喜欢

转载自blog.csdn.net/jlh912008548/article/details/81814688
今日推荐