View Javadoc
1   package cn.home1.oss.lib.webmvc.internal;
2   
3   import static org.springframework.web.HttpMessageConverterUtils.defaultContentNegotiationManager;
4   import static org.springframework.web.HttpMessageConverterUtils.defaultHttpMessageConverters;
5   
6   import lombok.Getter;
7   import lombok.NonNull;
8   import lombok.Setter;
9   import lombok.extern.slf4j.Slf4j;
10  
11  import org.springframework.beans.factory.InitializingBean;
12  import org.springframework.core.MethodParameter;
13  import org.springframework.core.env.PropertyResolver;
14  import org.springframework.http.MediaType;
15  import org.springframework.http.converter.HttpMessageConverter;
16  import org.springframework.web.HttpMediaTypeNotAcceptableException;
17  import org.springframework.web.HttpMessageConverterUtils;
18  import org.springframework.web.accept.ContentNegotiationManager;
19  import org.springframework.web.accept.FixedContentNegotiationStrategy;
20  import org.springframework.web.context.request.NativeWebRequest;
21  import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
22  import org.springframework.web.method.support.ModelAndViewContainer;
23  import org.springframework.web.servlet.mvc.method.annotation.HttpEntityMethodProcessor;
24  
25  import java.util.List;
26  
27  /**
28   * Created by zhanghaolun on 16/8/22.
29   */
30  @Slf4j
31  public class DefaultHttpEntityMethodProcessor implements HandlerMethodReturnValueHandler, InitializingBean {
32  
33    /**
34     * The {@link ContentNegotiationManager} to use to resolve acceptable media types. If not
35     * provided, the default instance of {@code ContentNegotiationManager} with
36     * {@link org.springframework.web.accept.HeaderContentNegotiationStrategy
37     * HeaderContentNegotiationStrategy} and
38     * {@link org.springframework.web.accept.FixedContentNegotiationStrategy
39     * FixedContentNegotiationStrategy} (with {@link #setDefaultContentType(MediaType)
40     * defaultContentType}) will be used.
41     */
42    @Getter
43    private ContentNegotiationManager contentNegotiationManager;
44    /**
45     * The default content type that will be used as a fallback when the requested content type is not
46     * supported.
47     */
48    @Getter
49    @Setter
50    private MediaType defaultContentType;
51    /**
52     * The message body converters to use for converting an error message into HTTP response body. If
53     * not provided, the default converters will be used (see
54     * {@link HttpMessageConverterUtils#defaultHttpMessageConverters(Object)}
55     * getDefaultHttpMessageConverters()}).
56     */
57    @NonNull
58    private List<HttpMessageConverter<?>> messageConverters;
59  
60    // package visibility for tests
61    private HandlerMethodReturnValueHandler responseProcessor;
62    // package visibility for tests
63    private HandlerMethodReturnValueHandler fallbackResponseProcessor;
64  
65    @Override
66    public boolean supportsReturnType(final MethodParameter returnType) {
67      return this.responseProcessor.supportsReturnType(returnType) || //
68        this.fallbackResponseProcessor.supportsReturnType(returnType);
69    }
70  
71    /**
72     * handleReturnValue.
73     *
74     * @param returnValue  must pass a HttpEntity here
75     * @param returnType   returnType
76     * @param mavContainer mavContainer
77     * @param webRequest   webRequest
78     * @throws Exception exception
79     */
80    @Override
81    public void handleReturnValue( //
82      final Object returnValue, final MethodParameter returnType, //
83      final ModelAndViewContainer mavContainer, final NativeWebRequest webRequest //
84    ) throws Exception {
85      try {
86        this.responseProcessor.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
87      } catch (final HttpMediaTypeNotAcceptableException ex) {
88        if (log.isDebugEnabled()) {
89          log.debug("Requested media type is not supported, falling back to default one", ex);
90        }
91        this.fallbackResponseProcessor.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
92      }
93    }
94  
95    @Override
96    public void afterPropertiesSet() throws Exception {
97      this.responseProcessor = new HttpEntityMethodProcessor( //
98        this.messageConverters, //
99        this.contentNegotiationManager //
100     );
101     this.fallbackResponseProcessor = new HttpEntityMethodProcessor( //
102       this.messageConverters, //
103       new ContentNegotiationManager( //
104         new FixedContentNegotiationStrategy(this.defaultContentType) //
105       ) //
106     );
107   }
108 
109   public void setContentNegotiationManager(final ContentNegotiationManager contentNegotiationManager) {
110     this.contentNegotiationManager = contentNegotiationManager;
111   }
112 
113   public List<HttpMessageConverter<?>> getMessageConverters() {
114     return this.messageConverters;
115   }
116 
117   public void setMessageConverters(final List<HttpMessageConverter<?>> messageConverters) {
118     this.messageConverters = messageConverters;
119   }
120 
121   public static DefaultHttpEntityMethodProcessor defaultHttpEntityMethodProcessor( //
122     final PropertyResolver propertyResolver, //
123     final Object objectMapper //
124   ) {
125     // before WebMvcAutoConfiguration, cant autowire contentNegotiationManager and httpMessageConverters
126     // Requested bean is currently in creation: Is there an unresolvable circular reference
127 
128     final ContentNegotiationManager defaultContentNegotiationManager = defaultContentNegotiationManager();
129     final List<HttpMessageConverter<?>> defaultHttpMessageConverters = defaultHttpMessageConverters( //
130       propertyResolver, objectMapper);
131 
132     final DefaultHttpEntityMethodProcessor processor = new DefaultHttpEntityMethodProcessor();
133     processor.setContentNegotiationManager(defaultContentNegotiationManager);
134     processor.setMessageConverters(defaultHttpMessageConverters);
135     return processor;
136   }
137 }