spring mvc 拓展 -- controller 方法不加註解自動接收json參數或者from表單參數

本文代碼已整理上傳 githubhtml

上篇文章中已經實現了同一個url不一樣視圖的返回, 能夠返回 json 數據, 也能夠返回 html,
可是對於 spring mvc 接收 request 參數來講, 
若是接收的是 json 數據, 須要在參數前加@RequestBody註解
接收 form 表單提交的數據則不需加註解
如何作到自動接收json參數或者from表單參數?

對於http request來講, 咱們能夠根據請求的 content-type 來判斷請求傳遞的參數是什麼格式的
content-type 是 application/json的 request 的 body 是 json 數據
content-type 是 application/x-www-form-urlencoded的 request 是表單提交java

對於spring mvc, 對接收參數對處理是 HandlerMethodArgumentResolver 實現處理的,
因此思路就是: 找處處理json的HandlerMethodArgumentResolver和處理表單的HandlerMethodArgumentResolver,
而後自定義 本身處理表單和json的HandlerMethodArgumentResolver, 根據 request 的content-type 在選擇對應的參數Resolvergit

自定義的 Resolver至關於 json 的Resolver 和 form表單的Resolver的組合

form 表單的 HandlerMethodArgumentResolver

先在GoodsController中寫一個方法, 參數是經過 表單提交接收, 而後斷點調試github

@Controller
@RequestMapping("/goods")
public class GoodsController {

    private static final List<Goods> GOODS_LIST = new ArrayList<>();

    static {
        GOODS_LIST.add(new Goods("998765", "哇哈哈礦泉水", 2.0));
        GOODS_LIST.add(new Goods("568925", "蒙牛真果粒", 4.7));
    }

    @RequestMapping("/list")
    public String list(GoodsCondition condition, Model model) {
        model.addAttribute("data", GOODS_LIST);
        return "goods";
    }

    // goods 參數 以表單形式接收
    @PostMapping
    public void add(Goods goods) {
        GOODS_LIST.add(goods);
    }

}

而後使用接口測試工具(可使用postman), 測試上面的接口, 表單方式web

經過DispatchServlet的doDispatch方發找處處理參數的地方,
處理參數的地方在org.springframework.web.method.support.InvocableHandlerMethod.getMethodArgumentValues() 方法spring

而後委託給HandlerMethodArgumentResolverComposite去處理參數, 這是個全部ArgumentResolver的集合, 自己不處理, 負責找到合適的ArgumentResolverjson

HandlerMethodArgumentResolverComposite#getArgumentResolver() 這個方法就是根據須要綁定的參數找到ArgumentResolver, 因此在這裏找到 form 表單的處理Resolver就能夠了mvc

clipboard.png

斷點走到這裏, 這個ServletModelAttributeMethodProcessor就是咱們要找的表單處理器,裏面的屬性annotationNotRequired的值是trueapp

處理 json 的 HandlerMethodArgumentResolver

將上面的add方法的參數中加上 @RequestBody註解ide

@PostMapping
    public void add(@RequestBody Goods goods) {
        GOODS_LIST.add(goods);
    }

再用接口測試工具, 以json數據提交

clipboard.png

再次斷點到HandlerMethodArgumentResolverComposite#getArgumentResolver()看最終的結果

clipboard.png

能夠看到: 處理json參數的是 RequestResponseBodyMethodProcessor, 裏面的屬性裏有不少HttpMessageConverter

接下來, 就是將這兩個 組合起來

自定義 HandlerMethodArgumentResolver

建立JsonAndFormArgumentResolver爲自定義的resolver
按照上面的分析, 須要在JsonAndFormArgumentResolver內獲取 當前的request來判斷再使用具體的resolver, 如何獲取當前request呢?須要在過濾器中加入 RequestContextFilter, 這是spring提供的, 將當前 request 保存起來

// 配置filters
    @Override
    protected Filter[] getServletFilters() {
        // 字符
        CharacterEncodingFilter encodingFilter = new CharacterEncodingFilter();
        encodingFilter.setEncoding(String.valueOf(StandardCharsets.UTF_8));
        encodingFilter.setForceEncoding(true);

        // 請求攔截器
        RequestContextFilter requestContextFilter = new RequestContextFilter();

        return new Filter[]{encodingFilter, requestContextFilter};
    }

使用 spring boot的同窗能夠過濾此步驟, 都已經自動配置了

而後, 貼出自定義 resolver的代碼

/**
 * User : liulu
 * Date : 2018/4/4 09:50
 * version $Id: JsonAndFormArgumentResolver.java, v 0.1 Exp $
 */
@NoArgsConstructor
public class JsonAndFormArgumentResolver implements HandlerMethodArgumentResolver {

    @Setter
    private RequestResponseBodyMethodProcessor requestResponseBodyMethodProcessor;

    @Setter
    private ModelAttributeMethodProcessor modelAttributeMethodProcessor;

    public JsonAndFormArgumentResolver(ModelAttributeMethodProcessor methodProcessor, RequestResponseBodyMethodProcessor bodyMethodProcessor){
        this.modelAttributeMethodProcessor = methodProcessor;
        this.requestResponseBodyMethodProcessor = bodyMethodProcessor;
    }


    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        return modelAttributeMethodProcessor.supportsParameter(parameter)
                || requestResponseBodyMethodProcessor.supportsParameter(parameter);
    }

    @Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        if (HttpMethod.GET.matches(request.getMethod().toUpperCase())) {
            return modelAttributeMethodProcessor.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
        }
        if (request.getContentType().contains(MediaType.APPLICATION_JSON_VALUE)) {
            return requestResponseBodyMethodProcessor.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
        }
        return modelAttributeMethodProcessor.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
    }
}

配置自定義 HandlerMethodArgumentResolver

類是寫好了, 可是總歸是要配置才能生效的,配置寫在 SpringMvcConfig

配置以下:

@Bean
    public WebMvcConfigurerAdapter webMvcConfigurerAdapter() {
        return new WebMvcConfigurerAdapter() {
            @Override
            public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
                argumentResolvers.add(new JsonAndFormArgumentResolver());
            }
        };
    }

    @Bean
    public BeanPostProcessor jsonAndFormArgumentResolverProcessor() {
        return new BeanPostProcessor() {
            @Override
            public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
                return bean;
            }

            @Override
            public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {

                if (bean instanceof RequestMappingHandlerAdapter) {
                    JsonAndFormArgumentResolver jsonAndFormArgumentResolver = null;
                    ModelAttributeMethodProcessor modelAttributeMethodProcessor = null;
                    RequestResponseBodyMethodProcessor requestResponseBodyMethodProcessor = null;

                    for (HandlerMethodArgumentResolver argumentResolver : ((RequestMappingHandlerAdapter) bean).getArgumentResolvers()) {
                        if (argumentResolver instanceof JsonAndFormArgumentResolver) {
                            jsonAndFormArgumentResolver = (JsonAndFormArgumentResolver) argumentResolver;
                            continue;
                        }
                        if (argumentResolver instanceof RequestResponseBodyMethodProcessor) {
                            requestResponseBodyMethodProcessor = (RequestResponseBodyMethodProcessor) argumentResolver;
                            continue;
                        }
                        if (argumentResolver instanceof ModelAttributeMethodProcessor) {
                            modelAttributeMethodProcessor = (ModelAttributeMethodProcessor) argumentResolver;
                        }
                    }
                    if (jsonAndFormArgumentResolver != null) {
                        jsonAndFormArgumentResolver.setModelAttributeMethodProcessor(modelAttributeMethodProcessor);
                        jsonAndFormArgumentResolver.setRequestResponseBodyMethodProcessor(requestResponseBodyMethodProcessor);
                    }
                }

                return bean;
            }
        };
    }

第一個bean是讓配置的自定義參數處理器生效,
第二個bean是初始化自定義參數處理器中的參數,

到此,自動接收json參數或者from表單參數已經完成, 將參數中的 @RequestBody 註解去掉再運行剛纔的表單提交和json提交, 均可以正常綁定參數

相關文章
相關標籤/搜索