SpringMvc 你該知道如何在HandlerExceptionResolver中獲取Model

在項目開發中,咱們一般經過參數的形式注入Model對象,如:css

@RequestMapping("/demo")
    public String demo(Model model) {
        model.addAttribute("message", "我是你的message!!!");
        // HandlerMethodArgumentResolver
        throw new IllegalArgumentException("你就錯了!!");
    }

直接經過返回String來指定須要返回的View,而後在頁面上直接能夠訪問Model對象的值了。java

爲了全局統對異常處理,一般咱們還有個全局的異常處理中心:
須要實現HandlerExceptionResolver接口,具體配置的話這裏就不贅述了。在異常處理中心,統一返回異常消息,這裏假設返回的是json信息:git

BaseResponse responseBean = new BaseResponse(Configuration.Status.STATUS_FAIL, filterErrorMsg ? defaultErrorMsg : ex.getMessage());
        try {
            String message = mapper.writeValueAsString(responseBean);
            response.reset();
            response.setContentType(contentType);
            response.getOutputStream().write(message.getBytes());
            response.getOutputStream().flush();
        } catch (Exception e) {
            log.error(e);
        }
        return new ModelAndView();

若是頁面上只須要返回統一的錯誤信息,那麼這個方式很是適合,沒毛病。可是若是想在頁面上回顯消息呢?好比管理員修改用戶信息,此時用戶也在修改用戶信息。管理員提交修改後,用戶再提交修改就發生了併發錯誤,目前咱們的系統是採用統一的異常拋出處理。只要拋出ConcurrentException就表示發生了併發錯誤,須要用戶刷新頁面重試。github

發生錯誤後,固然要把用戶以前的信息回顯出來了,要否則用辛苦寫了那麼多信息被丟棄了,體驗多很差。在頁面上只要獲取Model的信息顯示出來就行了。web

可是SpringMvc在異常狀況下,並無提供獲取Model對象的方法spring

public interface HandlerExceptionResolver {

    /**
     * Try to resolve the given exception that got thrown during handler execution,
     * returning a {@link ModelAndView} that represents a specific error page if appropriate.
     * <p>The returned {@code ModelAndView} may be {@linkplain ModelAndView#isEmpty() empty}
     * to indicate that the exception has been resolved successfully but that no view
     * should be rendered, for instance by setting a status code.
     * @param request current HTTP request
     * @param response current HTTP response
     * @param handler the executed handler, or {@code null} if none chosen at the
     * time of the exception (for example, if multipart resolution failed)
     * @param ex the exception that got thrown during handler execution
     * @return a corresponding {@code ModelAndView} to forward to, or {@code null}
     * for default processing
     */
    ModelAndView resolveException(
            HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex);

}

由於數據是經過參數對象傳入的,因此有handler也拿不到model對象的數據。json

對於解決方法
一、AOP確定是能夠的,在Handler調用以前,把Model對象保存下來,當方法調用拋出異常後,將Model信息保存到Request對象上,這樣在HandlerExceptionResolver中就能夠獲取到Model的信息了。springboot

二、經過SpringMvc的HandlerMethodArgumentResolver來解決,當設置參數時,把Model對象保存到Request上,這樣在HandlerExceptionResolver也能夠移獲取到了。markdown

下面來講說第二中實現方式:
首先項目是集成了SpringBoot的,在WebMvcConfigurerAdapter中,有addArgumentResolvers方法併發

@Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
        // argumentResolvers.add(new MyArgumentsResolver());
        // argumentResolvers.add(0, new CacheableModelMethodProcessor());
    }

經過此方法能夠自定義參數處理,可是僅限於自定義的參數,好比你本身添加了Student類型的參數。從註釋中能夠得知,對於SpringMvc內置提供的參數是沒法覆蓋的。

Add resolvers to support custom controller method argument types. This does not override the built-in support for resolving handler method arguments. To customize the built-in support for argument resolution, configure RequestMappingHandlerAdapter directly. This implementation is empty. 

要覆蓋默認的參數處理,須要經過RequestMappingHandlerAdapter進行處理。

首先,自定義一個HandlerMethodArgumentResolver,專門對Model對象進行處理:

public class CacheableModelMethodProcessor implements HandlerMethodArgumentResolver {

    public static final String KEY_MODEL = "com.cml.springboot.framework.argument.CacheableModelMethodProcessor.KEY_MODEL";

    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        return Model.class.isAssignableFrom(parameter.getParameterType());
    }

    @Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest,
            WebDataBinderFactory binderFactory) throws Exception {
        ModelMap map = mavContainer.getModel();
        webRequest.setAttribute(KEY_MODEL, map, NativeWebRequest.SCOPE_REQUEST);
        return map;
    }

}

每次處理後,將Model對象保存到Request中。

其次,註冊此HandlerMethodArgumentResolver,在Bean初始化完畢後,進行註冊:

@Component
public class CustomModelArgumentResolverConfiguration {

    @Autowired
    private RequestMappingHandlerAdapter requestMappingHandlerAdapter;

    /** * 覆蓋系統默認的處理器 */
    @PostConstruct
    public void afterProperties() {
        List<HandlerMethodArgumentResolver> argumentResolvers = new ArrayList<>(requestMappingHandlerAdapter.getArgumentResolvers());
        argumentResolvers.add(0, new CacheableModelMethodProcessor());
        requestMappingHandlerAdapter.setArgumentResolvers(argumentResolvers);
    }

}

@Configuration
public class CustomModelArgumentResolverConfiguration {


    @Bean
    public RequestMappingHandlerAdapter adapter(RequestMappingHandlerAdapter adapter) {
        List<HandlerMethodArgumentResolver> argumentResolvers = new ArrayList<>(adapter.getArgumentResolvers());
        argumentResolvers.add(0, new CacheableModelMethodProcessor());
        adapter.setArgumentResolvers(argumentResolvers);
        return adapter;
    }
}

以上都能實現覆蓋默認的HandlerMethodArgumentResolver,推薦使用第一種方法。

注入自定義的HandlerMethodArgumentResolver後,只要在Controller中添加Model對象的參數,這樣在HandlerExceptionResolver中就能夠從Request中獲取Model對象了:

request.getAttribute(CacheableModelMethodProcessor.KEY_MODEL)

至於ModelAndView參數,也是同理。

以上集成後就能夠在HandlerExceptionResolver中獲取到Model,在返回ModelAndView中設置此model,這樣在頁面上就能夠獲取到要回顯的信息了。

固然可能有疑問了,既然能獲取到Request對象,我直接從Request中獲取參數不就行了嗎?固然,這樣是能夠的,可是若是在Controller中你有自定義消息呢?好比我就單純的添加了處理消息:

@RequestMapping("/demo")
    public String demo(Model model) {
        model.addAttribute("message", "我是你的message!!!");
        // HandlerMethodArgumentResolver
        throw new IllegalArgumentException("你就錯了!!");
    }

這中狀況沒法從Request中獲取到了。

固然,數據不必定要存到Model對象中,能夠放到Request,或ThreadLocal中…各類方法都有,只要能實現。都是能夠的。這裏只是提供了一種以爲簡便的方法。

至於在Spring項目中而不是SpringBoot使用,原理是相同的,只要根據步驟實現便可。


SpringBootLean 是對springboot學習與研究項目,是根據實際項目的形式對進行配置與處理,歡迎star與fork。
[oschina 地址]
http://git.oschina.net/cmlbeliever/SpringBootLearning
[github 地址]
https://github.com/cmlbeliever/SpringBootLearning

相關文章
相關標籤/搜索