在项目开发中,我们通常通过参数的形式注入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. Spring Boot将Mybatis返回结果转为驼峰的三种实现方式

    本文不再更新,可能存在内容过时的情况,实时更新请访问原地址:Spring Boot将Mybatis返回结果转为驼峰的三种实现方式: 我们通常获取Mybatis返回的数据结果时想要将字段以驼峰的形式返回 ...

  2. 单图像三维重建、2D到3D风格迁移和3D DeepDream

    作者:Longway Date:2020-04-25 来源:单图像三维重建.2D到3D风格迁移和3D DeepDream 项目网址:http://hiroharu-kato.com/projects_ ...

  3. python 进阶篇 函数装饰器和类装饰器

    函数装饰器 简单装饰器 def my_decorator(func): def wrapper(): print('wrapper of decorator') func() return wrapp ...

  4. Ansible Facts 变量详解

    Ansible Facts 变量详解与使用案例 主机规划 添加用户账号 说明: 1. 运维人员使用的登录账号: 2. 所有的业务都放在 /app/ 下「yun用户的家目录」,避免业务数据乱放: 3. ...

  5. java 递归及其经典应用--求阶乘、打印文件信息、计算斐波那契数列

    什么是递归 我先看下百度百科的解释: 一种计算过程,如果其中每一步都要用到前一步或前几步的结果,称为递归的.用递归过程定义的函数,称为递归函数,例如连加.连乘及阶乘等.凡是递归的函数,都是可计算的,即 ...

  6. input type file onchange上传文件的过程中,同一个文件二次上传无效的问题。

    不要采用删除当前input[type=file]这个节点,然后再重新创建dom这种方案,这样是不合理的.解释如下:input[type=file]使用的是onchange去做,onchange监听的为 ...

  7. Qt 与 .Net 为何不兼容

    哪怕是非Qt的静态库里用了 .Net 也不行.

  8. MySQL join的7种理论及SQL写法

    转载于    https://www.cnblogs.com/dinglinyong/p/6656315.html 建表 在这里呢我们先来建立两张有外键关联的张表. CREATE DATABASE d ...

  9. 多线程并行请求问题及SplashActivity预加载问题解决方案

    1. 问题描述(一): 现有3个线程thread1, thread2, thread3.这3个线程是并发执行的,当着3个线程都执行完成以后,需要执行一个finish()事件. 1.1 实现方法: /* ...

  10. 实现QQ内打开链接跳转至浏览器

    经常遇到域名拦截的问题,不管是QQ还是微信在移动端如果打开被拦截是件很麻烦的事情. 那么,你怎样才能有效地避免这个问题呢?很多站长说域名可以抵御拦截?但是你有没有想过域名拦截的机制是什么? <? ...