ResponseBodyAdvice如何使用

其他教程   发布日期:2023年08月24日   浏览次数:175

本篇内容介绍了“ResponseBodyAdvice如何使用”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!

    前言

    ResponseBodyAdvice接口可以在将handler方法的返回值写入response前对返回值进行处理,例如将返回值封装成一个与客户端约定好的对象以便于客户端处理响应数据。

    SpringBoot版本:2.4.1

    一. ResponseBodyAdvice的使用

    假如已经存在一个Controller,如下所示。

    @RestController
    public class DemoController {
        @RequestMapping(value = "/api/v1/demo1/getdefault", method = RequestMethod.GET)
        public ResponseEntity<Demo1> getDefaultDemo1() {
            return new ResponseEntity<>(Demo1.defaultDemo1, HttpStatus.INTERNAL_SERVER_ERROR);
        }
        @RequestMapping(value = "/api/v1/demo2/getdefault", method = RequestMethod.GET)
        public Demo2 getDefaultDemo2() {
            return Demo2.defaultDemo2;
        }
    }
    public class Demo1 {
        private int id;
        private String name;
        public static Demo1 defaultDemo1 = new Demo1(1, "Admin");
        public Demo1() {}
        public Demo1(int id, String name) {		this.id = id;
    		this.name = name;
        }
        // 省略getter和setter
    }
    public class Demo2 {
        private int id;
        private String desc;
        public static Demo2 defaultDemo2 = new Demo2(1, "Root");
        public Demo2() {}
        public Demo2(int id, String desc) {
    		this.id = id;
            this.desc = desc;
        }
        // 省略getter和setter
    }

    上述Controller中有两个方法,并且返回值分别为ResponseEntity<Demo1>和Demo2。此时客户端收到响应之后,针对响应体的处理变得十分不方便,如果增加更多的方法,并且返回值都不相同,那么客户端将需要根据不同的请求来特定的处理响应体。因此为了方便客户端处理响应数据,服务器端专门创建了一个返回结果类ReturnResult,并且规定服务器端的所有handler方法执行后往response中写入的响应体都必须为ReturnResult。在这种情况下,使用ResponseBodyAdvice可以在不修改已有业务代码的情况下轻松实现上述需求。假设自定义的返回结果类ReturnResult如下所示。

    public class ReturnResult<T> {
        private int statusCode;
        private T body;
        public ReturnResult() {}
        public ReturnResult(T body) {
            this.body = body;
        }
        // 省略getter和setter
    }

    ReturnResult的body就是原本需要写入response的响应内容,现在整个ReturnResult为需要写入response的响应内容,相当于ReturnResult对handler方法的返回值进行了一层封装。

    现在创建一个ReturnResultAdvice类并实现ResponseBodyAdvice接口,如下所示。

    @ControllerAdvice
    public class ReturnResultAdvice implements ResponseBodyAdvice<Object> {
        @Override
        public boolean supports(@Nullable MethodParameter returnType, @Nullable Class converterType) {
            return true;
        }
        @Override
        public Object beforeBodyWrite(Object body, @Nullable MethodParameter returnType,
                                      @Nullable MediaType selectedContentType, @Nullable Class selectedConverterType,
                                      @Nullable ServerHttpRequest request, @Nullable ServerHttpResponse response) {
            if (body == null) {
                return null;
            }
            if (body instanceof ReturnResult) {
                return body;
            }
            return new ReturnResult<>(body);
        }
    }

    ReturnResultAdvice的beforeBodyWrite() 方法会在handler方法返回值写入response前被调用。

    此时调用DemoController的接口,会发现响应数据结构统一为ReturnResult。

    小节:由

    @ControllerAdvice
    注解修饰并实现
    ResponseBodyAdvice
    接口的类所实现的
    beforeBodyWrite()
    方法会在handler方法返回值写入response前被调用,并且handler方法返回值会作为入参传入
    beforeBodyWrite()
    ,从而可以在返回值写入response前对返回值进行一些定制操作,例如对返回值进行一层封装。

    二. ResponseBodyAdvice的原理

    首先说明一下为什么第一小节中DemoController的getDefaultDemo1() 方法的返回值类型为ResponseEntity<Demo1>,但是实际往response写的响应体内容为ResponseEntity中的body。首先所有ResponseBodyAdvice接口的调用是发生在AbstractMessageConverterMethodProcessor的writeWithMessageConverters() 方法中,这个方法的声明如下所示。

    protected <T> void writeWithMessageConverters(@Nullable T value, MethodParameter returnType,
                ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)
                throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException;

    其中value就是需要写入响应体的值,同时也是ResponseBodyAdvice要处理的值。然后如果handler方法的返回值是非ResponseEntity对象且handler方法由@ResponseBody注解修饰,那么writeWithMessageConverters() 方法的调用发生在RequestResponseBodyMethodProcessor#handleReturnValue方法中;

    如果handler方法的返回值是ResponseEntity对象,那么writeWithMessageConverters() 方法的调用发生在HttpEntityMethodProcessor#handleReturnValue中,分别看一下在这两个方法中调用writeWithMessageConverters() 时传入的参数,就可以解释之前的疑问了。

    RequestResponseBodyMethodProcessor#handleReturnValue
    public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
            ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
            throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
        ......
        writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
    }

    HttpEntityMethodProcessor#handleReturnValue()
    public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
            ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
        ......
        HttpEntity<?> responseEntity = (HttpEntity<?>) returnValue;
        ......
        writeWithMessageConverters(responseEntity.getBody(), returnType, inputMessage, outputMessage);
        ......
    }

    现在正式开始对ResponseBodyAdvice的原理进行分析。

    已知所有ResponseBodyAdvice接口的调用是发生在AbstractMessageConverterMethodProcessor的writeWithMessageConverters() 方法中,其部分源码如下所示。

    protected <T> void writeWithMessageConverters(@Nullable T value, MethodParameter returnType,
            ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)
            throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
        ......
        if (selectedMediaType != null) {
            selectedMediaType = selectedMediaType.removeQualityValue();
            for (HttpMessageConverter<?> converter : this.messageConverters) {
                GenericHttpMessageConverter genericConverter = (converter instanceof GenericHttpMessageConverter ?
                        (GenericHttpMessageConverter<?>) converter : null);
                if (genericConverter != null ?
                        ((GenericHttpMessageConverter) converter).canWrite(targetType, valueType, selectedMediaType) :
                        converter.canWrite(valueType, selectedMediaType)) {
                    // ResponseBodyAdvice的调用发生在这里
                    body = getAdvice().beforeBodyWrite(body, returnType, selectedMediaType,
                            (Class<? extends HttpMessageConverter<?>>) converter.getClass(),
                            inputMessage, outputMessage);
                    if (body != null) {
                        Object theBody = body;
                        LogFormatUtils.traceDebug(logger, traceOn ->
                                "Writing [" + LogFormatUtils.formatValue(theBody, !traceOn) + "]");
                        addContentDispositionHeader(inputMessage, outputMessage);
                        if (genericConverter != null) {
                            genericConverter.write(body, targetType, selectedMediaType, outputMessage);
                        }
                        else {
                            ((HttpMessageConverter) converter).write(body, selectedMediaType, outputMessage);
                        }
                    }
                    else {
                        if (logger.isDebugEnabled()) {
                            logger.debug("Nothing to write: null body");
                        }
                    }
                    return;
                }
            }
        }
        ......
    }

    AbstractMessageConverterMethodProcessor的getAdvice() 方法会返回其在构造函数中加载好的RequestResponseBodyAdviceChain对象,下面看一下RequestResponseBodyAdviceChain的beforeBodyWrite() 方法。

    public Object beforeBodyWrite(@Nullable Object body, MethodParameter returnType, MediaType contentType,
            Class<? extends HttpMessageConverter<?>> converterType,
            ServerHttpRequest request, ServerHttpResponse response) {
        return processBody(body, returnType, contentType, converterType, request, response);
    }
    private <T> Object processBody(@Nullable Object body, MethodParameter returnType, MediaType contentType,
            Class<? extends HttpMessageConverter<?>> converterType,
            ServerHttpRequest request, ServerHttpResponse response) {
        // 从加载好的ResponseBodyAdvice中获取适用于当前handler的ResponseBodyAdvice
        for (ResponseBodyAdvice<?> advice : getMatchingAdvice(returnType, ResponseBodyAdvice.class)) {
            if (advice.supports(returnType, converterType)) {
                // 执行ResponseBodyAdvice的beforeBodyWrite()方法以处理handler方法返回值
                body = ((ResponseBodyAdvice<T>) advice).beforeBodyWrite((T) body, returnType,
                        contentType, converterType, request, response);
            }
        }
        return body;
    }
    private <A> List<A> getMatchingAdvice(MethodParameter parameter, Class<? extends A> adviceType) {
        // 获取ResponseBodyAdvice集合
        List<Object> availableAdvice = getAdvice(adviceType);
        if (CollectionUtils.isEmpty(availableAdvice)) {
            return Collections.emptyList();
        }
        List<A> result = new ArrayList<>(availableAdvice.size());
        for (Object advice : availableAdvice) {
            // 判断ResponseBodyAdvice是否由@ControllerAdvice注解修饰
            if (advice instanceof ControllerAdviceBean) {
                ControllerAdviceBean adviceBean = (ControllerAdviceBean) advice;
                // 判断ResponseBodyAdvice是否适用于当前handler
                if (!adviceBean.isApplicableToBeanType(parameter.getContainingClass())) {
                    continue;
                }
                advice = adviceBean.resolveBean();
            }
            if (adviceType.isAssignableFrom(advice.getClass())) {
                result.add((A) advice);
            }
        }
        return result;
    }

    在RequestResponseBodyAdviceChain中,beforeBodyWrite() 方法调用了processBody() 方法,processBody() 方法会遍历所有加载好并且适用于当前handler的ResponseBodyAdvice并执行,至此,所有由@ControllerAdvice注解修饰的ResponseBodyAdvice接口会在这里执行。

    小节:由

    @ControllerAdvice
    注解修饰的
    ResponseBodyAdvice
    接口会被SpringMVC框架加载到
    RequestResponseBodyMethodProcessor
    HttpEntityMethodProcessor
    这两个返回值处理器中,当这两个返回值处理器将返回值写入response前,适用于当前handler的
    ResponseBodyAdvice
    接口会被调用,从而可以完成对返回值的定制化改造。

    三. ResponseBodyAdvice的加载

    由第二小节可知,正是因为RequestResponseBodyMethodProcessor和HttpEntityMethodProcessor这两个返回值处理器会将由@ControllerAdvice注解修饰的ResponseBodyAdvice接口加载,才能够实现将返回值写入response前调用这些ResponseBodyAdvice接口对返回值进行一些操作。那么本小节将对esponseBodyAdvice接口的加载进行学习。

    首先给出结论:

    ResponseBodyAdvice
    的加载发生在
    RequestMappingHandlerAdapter
    afterPropertiesSet()
    方法中。

    已知,RequestMappingHandlerAdapter实现了InitializingBean接口,因此RequestMappingHandlerAdapter实现了afterPropertiesSet() 方法。该方法实现如下。

    public void afterPropertiesSet() {
        // 加载ControllerAdviceBean相关内容(同时就会将由@ControllerAdvice注解修饰的ResponseBodyAdvice接口加载)
        initControllerAdviceCache();
        if (this.argumentResolvers == null) {
            List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();
            this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
        }
        if (this.initBinderArgumentResolvers == null) {
            List<HandlerMethodArgumentResolver> resolvers = getDefaultInitBinderArgumentResolvers();
            this.initBinderArgumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
        }
        if (this.returnValueHandlers == null) {
            // 获取返回值处理器,在这里就会完成RequestResponseBodyMethodProcessor和HttpEntityMethodProcessor的初始化,初始化的同时就会完成ResponseBodyAdvice接口的加载
            List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();
            this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);
        }
    }

    上述实现中,initControllerAdviceCache() 方法会加载ControllerAdviceBean相关内容到RequestMappingHandlerAdapter中,这其中就包含由@ControllerAdvice注解修饰的ResponseBodyAdvice接口。然后在getDefaultReturnValueHandlers() 方法中会创建返回值处理器,在创建RequestResponseBodyMethodProcessor和HttpEntityMethodProcessor时会使用加载好的ResponseBodyAdvice接口完成这两个返回值处理器的初始化。上述两个方法的部分源码如下所示。

    initControllerAdviceCache()
    private void initControllerAdviceCache() {
        if (getApplicationContext() == null) {
            return;
        }
        // 获取由@ControllerAdvice注解修饰的bean
        List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());
        List<Object> requestResponseBodyAdviceBeans = new ArrayList<>();
        for (ControllerAdviceBean adviceBean : adviceBeans) {
            Class<?> beanType = adviceBean.getBeanType();
            if (beanType == null) {
                throw new IllegalStateException("Unresolvable type for ControllerAdviceBean: " + adviceBean);
            }
            Set<Method> attrMethods = MethodIntrospector.selectMethods(beanType, MODEL_ATTRIBUTE_METHODS);
            if (!attrMethods.isEmpty()) {
                this.modelAttributeAdviceCache.put(adviceBean, attrMethods);
            }
            Set<Method> binderMethods = MethodIntrospector.selectMethods(beanType, INIT_BINDER_METHODS);
            if (!binderMethods.isEmpty()) {
                this.initBinderAdviceCache.put(adviceBean, binderMethods);
            }
            // 如果ControllerAdviceBean实现了ResponseBodyAdvice接口,那么这个ControllerAdviceBean需要加载到requestResponseBodyAdvice中
            if (RequestBodyAdvice.class.isAssignableFrom(beanType) || ResponseBodyAdvice.class.isAssignableFrom(beanType)) {
                requestResponseBodyAdviceBeans.add(adviceBean);
            }
        }
        if (!requestResponseBodyAdviceBeans.isEmpty()) {
            this.requestResponseBodyAdvice.addAll(0, requestResponseBodyAdviceBeans);
        }
        ......
    }

    getDefaultReturnValueHandlers()
    private List<HandlerMethodReturnValueHandler> getDefaultReturnValueHandlers() {
        List<HandlerMethodReturnValueHandler> handlers = new ArrayList<>(20);
        ......
        // 创建并加载HttpEntityMethodProcessor
        handlers.add(new HttpEntityMethodProcessor(getMessageConverters(),
                this.contentNegotiationManager, this.requestResponseBodyAdvice));
        ...
        // 创建并加载RequestResponseBodyMethodProcessor
        handlers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(),
                this.contentNegotiationManager, this.requestResponseBodyAdvice));
        ......
        return handlers;
    }

    根据getDefaultReturnValueHandlers() 方法可知,在创建HttpEntityMethodProcessor或者RequestResponseBodyMethodProcessor时,会将RequestMappingHandlerAdapter加载好的ResponseBodyAdvice传入构造函数,并且,无论是HttpEntityMethodProcessor还是RequestResponseBodyMethodProcessor,其构造函数最终都会调用到父类AbstractMessageConverterMethodArgumentResolver的构造函数,并在其中初始化一个RequestResponseBodyAdviceChain以完成ResponseBodyAdvice的加载。构造函数源码如下所示。

    HttpEntityMethodProcessor#HttpEntityMethodProcessor
    public HttpEntityMethodProcessor(List<HttpMessageConverter<?>> converters,
            @Nullable ContentNegotiationManager manager, List<Object> requestResponseBodyAdvice) {
        super(converters, manager, requestResponseBodyAdvice);
    }

    AbstractMessageConverterMethodProcessor#AbstractMessageConverterMethodProcessor
    protected AbstractMessageConverterMethodProcessor(List&lt;HttpMessageConverter&lt;?&gt;&gt; converters,
            @Nullable ContentNegotiationManager manager, @Nullable List&lt;Object&gt; requestResponseBodyAdvice) {
        super(converters, requestResponseBodyAdvice);
        this.contentNegotiationManager = (manager != null ? manager : new ContentNegotiationManager());
        this.safeExtensions.addAll(this.contentNegotiationManager.getAllFileExtensions());
        this.safeExtensions.addAll(SAFE_EXTENSIONS);
    }

    AbstractMessageConverterMethodArgumentResolver#AbstractMessageConverterMethodArgumentResolver
    public AbstractMessageConverterMethodArgumentResolver(List<HttpMessageConverter<?>> converters,
            @Nullable List<Object> requestResponseBodyAdvice) {
        Assert.notEmpty(converters, "'messageConverters' must not be empty");
        this.messageConverters = converters;
        this.allSupportedMediaTypes = getAllSupportedMediaTypes(converters);
        this.advice = new RequestResponseBodyAdviceChain(requestResponseBodyAdvice);
    }

    小节:

    RequestMappingHandlerAdapter
    会在其实现的
    afterPropertiesSet()
    方法中加载由
    @ControllerAdvice
    注解修饰的
    ResponseBodyAdvice
    接口,然后会创建并加载返回值处理器,在创建
    RequestResponseBodyMethodProcessor
    HttpEntityMethodProcessor
    这两个返回值处理器时会传入加载好的
    ResponseBodyAdvice
    ,从而完成了ResponseBodyAdvice的加载。

    以上就是ResponseBodyAdvice如何使用的详细内容,更多关于ResponseBodyAdvice如何使用的资料请关注九品源码其它相关文章!