在项目开发中,咱们一般经过参数的形式注入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