在项目开发中,我们通常通过参数的形式注入Model对象,如:

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

直接通过返回String来指定需要返回的View,然后在页面上直接可以访问Model对象的值了。

为了全局统对异常处理,通常我们还有个全局的异常处理中心:

需要实现HandlerExceptionResolver接口,具体配置的话这里就不赘述了。在异常处理中心,统一返回异常消息,这里假设返回的是json信息:

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就表示发生了并发错误,需要用户刷新页面重试。

发生错误后,当然要把用户之前的信息回显出来了,要不然用辛苦写了那么多信息被丢弃了,体验多不好。在页面上只要获取Model的信息显示出来就好了。

但是SpringMvc在异常情况下,并没有提供获取Model对象的方法

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对象的数据。

对于解决方法

1、AOP肯定是可以的,在Handler调用之前,把Model对象保存下来,当方法调用抛出异常后,将Model信息保存到Request对象上,这样在HandlerExceptionResolver中就可以获取到Model的信息了。

2、通过SpringMvc的HandlerMethodArgumentResolver来解决,当设置参数时,把Model对象保存到Request上,这样在HandlerExceptionResolver也可以移获取到了。

下面来说说第二中实现方式:

首先项目是集成了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

SpringMvc 你该知道如何在HandlerExceptionResolver中获取Model的更多相关文章

  1. 如何在Sql2008中获取表字段属性和注释?

    如何在Sql2008中获取表字段属性和注释? select b.[value] from sys.columns a left join sys.extended_properties b on a. ...

  2. 关于Activity的getReferrer():如何在Activity中获取调用者?

    http://blog.csdn.net/u013553529/article/details/53856800 关于Activity的getReferrer()之一:如何在Activity中获取调用 ...

  3. 如何在onCreate中获取View的高度和宽度

    如何在onCreate中获取View的高度和宽度 原文链接:http://mp.weixin.qq.com/s?__biz=MzAwODE1NTI2MQ==&mid=2247483676&am ...

  4. 如何在Job中获取 IOC applicationcontext

    如何在Job中获取 IOC applicationcontext https://segmentfault.com/q/1010000008002800 SpringBoot之整合Quartz调度框架 ...

  5. 如何在 WPF 中获取所有已经显式赋过值的依赖项属性

    原文:如何在 WPF 中获取所有已经显式赋过值的依赖项属性 获取 WPF 的依赖项属性的值时,会依照优先级去各个级别获取.这样,无论你什么时候去获取依赖项属性,都至少是有一个有效值的.有什么方法可以获 ...

  6. asp.net mvc 如何在View中获取Url参数的值

    如果url是 /home/index?id=3 直接Request就ok. 但是如果路由设定为:{controller}/{action}/{id} url是 /home/index/3   这时想在 ...

  7. jsp内置对象pageContext如何在Servlet中获取值

    pageContext javax.servlet.jsp.PageContext 的实例,该对象代表该JSP 页面上下文,使用该对象可以访问页面中的共享数据.常用的方法有getServletCont ...

  8. js技术之如何在JS中获取input的值

    在JavaScript中获取input元素value的值: 方法一:var variations_number = $("#input的id名").val(); 1 <!DO ...

  9. 如何在SqlServer中获取前端连接的IP地址,计算机名等信息

    在一些需求中,可能我们需要知道连接到SqlServer的前端程序的一些系统信息,比如前端连接的计算机名称,IP地址,什么时候开始请求连接,什么时候结束连接等信息. 如果你对SqlServer的系统函数 ...

随机推荐

  1. C#栈、堆的理解(2)

    接上一遍博文有关值类型和引用类型的相关概念. 所有值类型数据存放:栈(内存) 引用类型的数据存放:堆(内存) 栈:可以认为是一本书的目录部分称其为栈.栈可快速检索,运行速度比堆大,而且栈的空间小得多. ...

  2. 报错:require_once cannot allocate memory----php,以前自己弄的稍微有点特殊的开发环境

    最近出现过一个问题,值得记录 类似于这样的报错的问题: Warning: require_once(/www/app/somecomponent.php): failed to open stream ...

  3. JAVA企业级应用TOMCAT实战(二)

    使用普通用户运行 使用普通用户来部署服务是比较安全的做法 [root@tomcat application]# useradd -u tomcat [root@tomcat application]# ...

  4. KMP & AC自动机

    KMP void kmp(char t[],char p[]) { int n=strlen(t),m=strlen(p); int *f=new int[m]; f[0]=f[1]=0; for(i ...

  5. 突然地心血来潮,为 MaixPy( k210 micropython ) 添加看门狗(WDT) C 模块的开发过程记录,给后来的人做开发参考。

    事情是前几天群里有人说做个看门狗不难吧,5分钟的事情,然后我就怼了几句,后来才发现,原来真的没有看门狗模块鸭. 那好吧,那我就写一下好了,今天是(2020年4月30日)想着最后一天了,不如做点什么有价 ...

  6. F查询,Q查询,事物,only与defer

    F查询 之前的单表查询多表查询筛选条件都是一个固定的值,那么如何用字段来筛选呢? 比如 :           查询卖出数大于库存数的商品 这个时候我们就可以用到django里面的F查询了 查询示例表 ...

  7. 桌面上的Flutter:Electron又多了个对手

    从本质上看,Flutter是一个独立的二进制可执行文件.它不仅改变了移动设备的玩法,在桌面设备上也同样不可小觑.一次编写,可在Android.iOS.Windows.Mac和Linux上进行原生部署, ...

  8. Js6利用class创建类

    <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...

  9. CF1335E Three Blocks Palindrome

    就是我这个菜鸡,赛时写出了 E2 的做法,但是算错复杂度,导致以为自己的做法只能AC E1,就没交到 E2 上,然后赛后秒A..... 题意 定义一种字串为形如:\([\underbrace{a, a ...

  10. postman(动态数据获取)

    一:返回报文为 json 格式 示例:因为充值记录接口中需要用到登录接口返回报文中的信息如下 1.以获取token(JWT)和uid为例 2.在登录接口的tests中写入代码(因为登录接口报文信息中有 ...