`

基于注解的spring mvc 异常封装

阅读更多

在使用基于注解的spring mvc时,使用情况如下:

 

@Controller
@RequestMapping("/test")
public class TestController {

    @RequestMapping(value="/index/{userId}", params="username")
    public ModelAndView index(@PathVariable("userId") String userId,
                        @RequestParam("password") String password,
                        @RequestParam("id") int id, Map<String, Object> map) throws Exception {

        System.out.println(userId);
        System.out.println(password);
        System.out.println(id);
        JSONObject obj = new JSONObject();
        obj.put("data", "");
        obj.put("status", "");
        return new ModelAndView("index");
    }

}

 出现异常会有一下几种情况:

 

 1 参数不匹配, spring mvc有自己的异常处理,会调到一个也没,HttpStatus 400

 2 用户在调用的service中,自己抛出异常或者代码异常,这个最好统一捕捉,不要在每个Controller方法里进行捕捉

 

  看spring mvc的源码,可以知道spring mvc处理步骤如下:

  1 spring mvc的入口为:

 

    <servlet>
        <servlet-name>spring-mvc-servlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath*:/spring-mvc-servlet.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>spring-mvc-servlet</servlet-name>
        <url-pattern>*.do</url-pattern>
    </servlet-mapping>

 2 DispatcherServlet 是一个Servlet,DispatcherServlet首先处理请求时会调用doService方法:

 

 

	@Override
	protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
		if (logger.isDebugEnabled()) {
			String requestUri = urlPathHelper.getRequestUri(request);
			String resumed = WebAsyncUtils.getAsyncManager(request).hasConcurrentResult() ? " resumed" : "";
			logger.debug("DispatcherServlet with name '" + getServletName() + "'" + resumed +
					" processing " + request.getMethod() + " request for [" + requestUri + "]");
		}

		// Keep a snapshot of the request attributes in case of an include,
		// to be able to restore the original attributes after the include.
		Map<String, Object> attributesSnapshot = null;
		if (WebUtils.isIncludeRequest(request)) {
			logger.debug("Taking snapshot of request attributes before include");
			attributesSnapshot = new HashMap<String, Object>();
			Enumeration<?> attrNames = request.getAttributeNames();
			while (attrNames.hasMoreElements()) {
				String attrName = (String) attrNames.nextElement();
				if (this.cleanupAfterInclude || attrName.startsWith("org.springframework.web.servlet")) {
					attributesSnapshot.put(attrName, request.getAttribute(attrName));
				}
			}
		}

		// Make framework objects available to handlers and view objects.
		request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
		request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
		request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
		request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());

		FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
		if (inputFlashMap != null) {
			request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));
		}
		request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
		request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);

		try {
                        //上面做了一系列的操作是想获取必要的资源,在这里分发了请求
			doDispatch(request, response);
		}
		finally {
			if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
				return;
			}
			// Restore the original attribute snapshot, in case of an include.
			if (attributesSnapshot != null) {
				restoreAttributesAfterInclude(request, attributesSnapshot);
			}
		}
	}

  doDispatch方法代码如下:

 

 

	protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
		HttpServletRequest processedRequest = request;
		HandlerExecutionChain mappedHandler = null;
		boolean multipartRequestParsed = false;

		WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

		try {
			ModelAndView mv = null;
			Exception dispatchException = null;

			try {
				processedRequest = checkMultipart(request);
				multipartRequestParsed = processedRequest != request;

				// Determine handler for the current request.
				mappedHandler = getHandler(processedRequest, false);
				if (mappedHandler == null || mappedHandler.getHandler() == null) {
					noHandlerFound(processedRequest, response);
					return;
				}

				// Determine handler adapter for the current request.
				HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

				// Process last-modified header, if supported by the handler.
				String method = request.getMethod();
				boolean isGet = "GET".equals(method);
				if (isGet || "HEAD".equals(method)) {
					long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
					if (logger.isDebugEnabled()) {
						String requestUri = urlPathHelper.getRequestUri(request);
						logger.debug("Last-Modified value for [" + requestUri + "] is: " + lastModified);
					}
					if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
						return;
					}
				}

				if (!mappedHandler.applyPreHandle(processedRequest, response)) {
					return;
				}

				try {
					// Actually invoke the handler.在此处真实的调用了我们的Controller
					mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
				}
				finally {
					if (asyncManager.isConcurrentHandlingStarted()) {
						return;
					}
				}

				applyDefaultViewName(request, mv);
				mappedHandler.applyPostHandle(processedRequest, response, mv);
			}
			catch (Exception ex) {//当出现错误的时候,捕捉到请求在下面的processDispatchResult处理异常信息
				dispatchException = ex;
			}
			processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);//处理异常信息
		}
		catch (Exception ex) {
			triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
		}
		catch (Error err) {
			triggerAfterCompletionWithError(processedRequest, response, mappedHandler, err);
		}
		finally {
			if (asyncManager.isConcurrentHandlingStarted()) {
				// Instead of postHandle and afterCompletion
				mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
				return;
			}
			// Clean up any resources used by a multipart request.
			if (multipartRequestParsed) {
				cleanupMultipart(processedRequest);
			}
		}
	}

   processDispatchResult方法的代码为:

 

 

	private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
			HandlerExecutionChain mappedHandler, ModelAndView mv, Exception exception) throws Exception {

		boolean errorView = false;

		if (exception != null) {
			if (exception instanceof ModelAndViewDefiningException) {
				logger.debug("ModelAndViewDefiningException encountered", exception);
				mv = ((ModelAndViewDefiningException) exception).getModelAndView();
			}
			else {//此处处理真正的异常
				Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
				mv = processHandlerException(request, response, handler, exception);
				errorView = (mv != null);
			}
		}

		// Did the handler return a view to render?
		if (mv != null && !mv.wasCleared()) {
			render(mv, request, response);
			if (errorView) {
				WebUtils.clearErrorRequestAttributes(request);
			}
		}
		else {
			if (logger.isDebugEnabled()) {
				logger.debug("Null ModelAndView returned to DispatcherServlet with name '" + getServletName() +
						"': assuming HandlerAdapter completed request handling");
			}
		}

		if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
			// Concurrent handling started during a forward
			return;
		}

		if (mappedHandler != null) {
			mappedHandler.triggerAfterCompletion(request, response, null);
		}
	}


   processHandlerException方法如下,这里会选取HandlerExceptionResolver来处理对应的异常

 

	protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,
			Object handler, Exception ex) throws Exception {

		// Check registered HandlerExceptionResolvers...
		ModelAndView exMv = null;
                //遍历HandlerExceptionResolver,获取合适来处理异常
		for (HandlerExceptionResolver handlerExceptionResolver : this.handlerExceptionResolvers) {
			exMv = handlerExceptionResolver.resolveException(request, response, handler, ex);
			if (exMv != null) {
				break;
			}
		}
		if (exMv != null) {
			if (exMv.isEmpty()) {
				return null;
			}
			// We might still need view name translation for a plain error model...
			if (!exMv.hasView()) {
				exMv.setViewName(getDefaultViewName(request));
			}
			if (logger.isDebugEnabled()) {
				logger.debug("Handler execution resulted in exception - forwarding to resolved error view: " + exMv, ex);
			}
			WebUtils.exposeErrorRequestAttributes(request, ex, getServletName());
			return exMv;
		}

		throw ex;
	}

    spring mvc 在Dispatcher里容器onRefresh()方法的时候加载了HandlerExceptionResolver:

	@Override
	protected void onRefresh(ApplicationContext context) {
		initStrategies(context);
	}

	protected void initStrategies(ApplicationContext context) {
		initMultipartResolver(context);
		initLocaleResolver(context);
		initThemeResolver(context);
		initHandlerMappings(context);
		initHandlerAdapters(context);
                //初始化HandlerExceptionResolvers
		initHandlerExceptionResolvers(context);
		initRequestToViewNameTranslator(context);
		initViewResolvers(context);
		initFlashMapManager(context);
	}

	private void initHandlerExceptionResolvers(ApplicationContext context) {
		this.handlerExceptionResolvers = null;

		if (this.detectAllHandlerExceptionResolvers) {
			// Find all HandlerExceptionResolvers in the ApplicationContext, including ancestor contexts.
                        //从spring mvc的容器中获取类型为HandlerExceptionResolver的bean
			Map<String, HandlerExceptionResolver> matchingBeans = BeanFactoryUtils
					.beansOfTypeIncludingAncestors(context, HandlerExceptionResolver.class, true, false);
			if (!matchingBeans.isEmpty()) {
				this.handlerExceptionResolvers = new ArrayList<HandlerExceptionResolver>(matchingBeans.values());
				// We keep HandlerExceptionResolvers in sorted order.
				OrderComparator.sort(this.handlerExceptionResolvers);
			}
		}
		else {
			try {
				HandlerExceptionResolver her =
						context.getBean(HANDLER_EXCEPTION_RESOLVER_BEAN_NAME, HandlerExceptionResolver.class);
				this.handlerExceptionResolvers = Collections.singletonList(her);
			}
			catch (NoSuchBeanDefinitionException ex) {
				// Ignore, no HandlerExceptionResolver is fine too.
			}
		}

		// Ensure we have at least some HandlerExceptionResolvers, by registering
		// default HandlerExceptionResolvers if no other resolvers are found.
		if (this.handlerExceptionResolvers == null) {
			this.handlerExceptionResolvers = getDefaultStrategies(context, HandlerExceptionResolver.class);
			if (logger.isDebugEnabled()) {
				logger.debug("No HandlerExceptionResolvers found in servlet '" + getServletName() + "': using default");
			}
		}
	}

   由上面可知:spring mvc 从容器中获取了处理异常的HandlerExceptionResolver,spring mvc提供三种异常处理器:

DefaultHandlerExceptionResolver, ResponseStatusExceptionResolver, ExceptionHandlerExceptionResolver,用户可以在spring mvc的配置文件中配置自己的异常处理类:

    <bean id="diyExceptionHandler" class="com.malone.handler.MyExceptionHandler"/>

    <bean id="myDefaultHandlerExceptionResolver" class="com.malone.handler.MyDefaultHandlerExceptionResolver"/>

  为了统一异常处理,我们可以自定义一个HandlerExceptionResolver,所有的异常均由自定义的异常处理, 由此我们可以重写DispatcherServlet类的

processHandlerException()方法:

public class MyDispatcherServlet extends DispatcherServlet {

    private Map<String, HandlerExceptionResolver> matchingBeans = new HashMap<String, HandlerExceptionResolver>();

    private Map<String, HandlerExceptionResolver> getMatchingBeans () {
        if (matchingBeans.isEmpty()) {
            System.out.println(SpringUtils.getContext());
            synchronized (this) {
                matchingBeans = BeanFactoryUtils
                        .beansOfTypeIncludingAncestors(SpringUtils.getContext(), HandlerExceptionResolver.class, true, false);
            }
        }
        return matchingBeans;
    }

    @Override
    protected void onRefresh(ApplicationContext context) {
        initStrategies(context);
        //把spring mvc容器缓存起来
        SpringUtils.setContext(context);
    }

    @Override
    protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,
                                                   Object handler, Exception ex) throws Exception {
        //获取HandlerExceptionResolver
        Map<String, HandlerExceptionResolver> matchingBeans = this.getMatchingBeans();
        if (matchingBeans == null || matchingBeans.size() == 0) {//没有配置处理异常的Controller
            return new ModelAndView();
        }
        Map<String, HandlerExceptionResolver> newMatchingBeans = new HashMap<String, HandlerExceptionResolver>();
        for (Iterator<String> iterator = matchingBeans.keySet().iterator(); iterator.hasNext();) {//使用指定handlerResolver
            String key = iterator.next();
            if (Objects.equals(key, "diyExceptionHandler") || Objects.equals(key, "myDefaultHandlerExceptionResolver")) {
                newMatchingBeans.put(key, matchingBeans.get(key));
            }
        }
        // Check registered HandlerExceptionResolvers...
        ModelAndView exMv = null;
        for (HandlerExceptionResolver handlerExceptionResolver : new ArrayList<HandlerExceptionResolver>(newMatchingBeans.values())) {
            exMv = handlerExceptionResolver.resolveException(request, response, handler, ex);
            if (exMv != null) {
                break;
            }
        }
        if (exMv != null) {
            if (exMv.isEmpty()) {
                return null;
            }
            // We might still need view name translation for a plain error model...
            if (!exMv.hasView()) {
                exMv.setViewName(getDefaultViewName(request));
            }
            if (logger.isDebugEnabled()) {
                logger.debug("Handler execution resulted in exception - forwarding to resolved error view: " + exMv, ex);
            }
            WebUtils.exposeErrorRequestAttributes(request, ex, getServletName());
            return exMv;
        }

        throw ex;
    }

}

   自定义了MyDispatcherServlet类,此类继承了DispatcherServlet,重写了一下方法:

  onRefresh():重写此方法的目的是在原来的代码基础上增加缓存spring mvc容器的方法

  processHandlerException ():重写此方法是为了修改获取HandlerExceptionResolver的逻辑,由上面代码可知,我们使用onRefresh()方法中缓存的spring mvc容器,

获取到自己注册的HandlerExceptionResolver,然后使用自己的注册HandlerExceptionResolver来处理所有异常

 

附完整的测试代码:

分享到:
评论

相关推荐

    Spring+Spring mvc+Hibernate+Bootstrap、企业级员工信息管理系统

    Spring mvc 返回数据格式采用统一的对象(JSONReturn)进行封装 09. 通过自定义处理器 ExceptionIntercept 实现 Spring mvc的全局异常捕获 10. 系统中包含了企业中采用的开发工具类的集合 11. AbstractDao 父类...

    springmvc:Spring MVC教程

    Spring MVC教程 教程学习: 本教程完整地讲解了Spring MVC的每个知识点,包括独立运行的Demo示例,欢迎一起交流学习。...Spring MVC请求参数封装 Spring MVC 基本类型封装 Spring MVC Post中文乱码 @RequestPara

    spring MVC 简单登陆例子

    一个简单的基于Spring MVC的简单登陆的例子,在这个例子中,把表单提交的用户名和密码封装到实体类中传递到controller中,项目代码使用注解模式。表单中的用户名和密码完全相同才能登陆成功。

    基于Springboot+Mybatis+ SpringMvc+springsecrity+Redis完整网站后台管理系统

    mybatis未进行二次封装,原滋原味,简单sql采用注解,复杂sql采用Mapper.xml配置 使用了layui的弹出层、菜单、文件上传、富文本编辑、日历、选项卡、数据表格等 表单数据采用bootstrapValidator校验,简单快捷方便...

    Spring MVC注解与数据绑定(含源码及结果图)

    (1) 创建一个Order类来封装上述订单信息,其中各个属性的名称和数据类型是:产品编号(ProductID,Integer),用户编号(UserID,Integer),交易日期(TransactionDate,Date),价格(Price,Double),数量(Quantity,...

    基于SSM框架的( spring、 spring mvc、 mybatis)社区论坛系统

    采用springMVC+mybatis+mysql,使用myeclipse10开发,注解,无封装,方便二次开发,初学者也能读懂的代码,每个类都有对应的注释,功能完善

    Spring 3.0MVC JDBC 单表操作示例.rar

    eclipse 直接导出的项目 Spring MVC 的一个DEMO jiaolongzhi作品 采用 Spring 3.0 MVC 框架 JSTL+EL 语句输出界面 ...一种是 Spring 封装的JDBC和 操作 (teacher表) 一种是 proxool 提供的方式(student表)

    Spring+3.x企业应用开发实战光盘源码(全)

     第8章:介绍了Spring所提供的DAO封装层,这包括Spring DAO的异常体系、数据访问模板等内容。  第9章:介绍了Spring事务管理的工作机制,通过XML、注解等方式进行事务管理配置,同时还讲解了JTA事务配置知识。  ...

    Spring Boot与Feign:微服务架构下的优雅通信

    本文将详细介绍在Spring Boot框架中如何...Spring Cloud对Feign进行了封装,使其支持了Spring MVC标准注解和HttpMessageConverters。Feign可以与Eureka和Consul等服务发现组件配合使用,以实现动态服务路由和负载均衡。

    struts2demo全注解

    struts2将请求在Action中封装为Map并将配置文件放到web-info中还可以自定义配置文件位置就是不将struts.xml放到src下但还是不如spring mvc 的封装来得方便

    陈开雄 Spring+3.x企业应用开发实战光盘源码.zip

     第8章:介绍了Spring所提供的DAO封装层,这包括Spring DAO的异常体系、数据访问模板等内容。  第9章:介绍了Spring事务管理的工作机制,通过XML、注解等方式进行事务管理配置,同时还讲解了JTA事务配置知识。 ...

    mybatis完整的理论知识(.md版本)用Typora查看

    MyBatis 是一个优秀的基于 java 的持久层框架, 内部封装了 jdbc,开发者只需要关注 sql 语句 本身, 而不需要处理加载驱动、创建连接、创建 statement 、关闭连接, 资源等繁杂 的过程。 MyBatis 通过 xml 或注解两...

    maven+springmvc+spring+hibernate+freemarker

    其中注解格式清晰,hibernate进行了代码封装,对开发效率有了提高,对异常进行了封装。freemarker也有优化,参考common包下。对日期工具类有各种情况的代码说明。参考utils下的DateUtils.java类。 slf4j日志文件有...

    百度地图开发java源码-project:springmvc+mybatis

    MVC属于SpringFrameWork的后续产品,已经融合在Spring Web Flow里面。Spring 框架提供了构建 Web 应用程序的全功能 MVC 模块。 ###mybatis MyBatis是一个支持普通SQL查询,存储过程和高级映射的优秀持久层框架。...

    基于maven项目的SSM框架与layu前端框架的代码

    Spring MVC属于Spring Framework的后续产品,已经融合在Spring Web Flow里面,它原生支持的Spring特性,让开发变得非常简单规范。Spring MVC 分离了控制器、模型对象、分派器以及处理程序对象的角色,这种分离让...

    java微信公众号MVC开发框架

    jwx是开源的java公众号开发MVC框架,基于spring配置文件和微信消息或事件注解,通过微信上下文处理一个或多个微信公众号服务请求。目的主要有两个,其一生封装微信请求xml消息为java实体对象,将返回对象转换为xml...

    springmybatis

    mybatis实战教程mybatis in action之六与Spring MVC 的集成 mybatis实战教程mybatis in action之七实现mybatis分页源码下载 mybatis实战教程mybatis in action之八mybatis 动态sql语句 mybatis实战教程mybatis in ...

    往届师兄师姐面试真题收集(Java相关岗位)

    在spring mvc中如何实现 前后端数据的互通是通过Ajax+Json技术实现的。springmvc框架封装了原生mvc,视图层向业务层传递数据时需经过控制层。 4、单线程和多线程的区别 多线程即同时存在多个线程运行,比只有一个...

    spbt-myb-plus是一款基于SpringBoot+MyBatis-Plus的快速开发脚手架,拥有完整的权限管理功能

    spbt-myb-plus是一款基于SpringBoot+MyBatis-Plus的快速开发脚手架,拥有完整的权限管理功能,可对接Vue前端,开箱即用。 项目演示 技术选型 技术 版本 说明 SpringBoot 2.7.5 容器+MVC框架 sa-token 1.32.0 认证和...

Global site tag (gtag.js) - Google Analytics