Exception Handling框架

项目开发过程中,经常会涉及到Exception的处理,我们项目使用JSF,所以在Unchecked Exception方面着实碰到了麻烦。

现在说一下我们项目的Exception处理框架,无论是checked exception or unchecked exception,我们都需要将其记录到LOG文件中,用log4j是个不错的选择。


Checked Excpetion Handling:

checked exception比较好处理,我的方案是,定义一两个用户自定义的exception, 比如ApplicationException or CustomException, 一般checked exception会分两种,一个是程序员感兴趣或者需要可以处理的exception, 比如有两个文件,一个加载不到就加载另一个,前者加载不到时,程序会抛出FileNotFoundException,那么程序员可以catch住,在catch块中加载另一个文件(假定这次不出问题)。另一种情况就是比如程序抛出SQLAccessException,那么可以不用理它,catch住后抛出自定义的exception。

我们的工程是这样:任何地方可能抛出exception的地方,只需要用Exception这一个catch clause抓住,然后在catch块中,记录它,并抛出我们自定义的exception,只在JSF的Backing Bean中做处理。

public String associateAppsAndModule(PageGroup pageGroup)
		throws MyFleetTPOException {
	String saveState = null;
	try {
		StringBuffer urlSb = new StringBuffer("http://localhost:8080/MyFleetTPO");
		urlSb.append(TpoConstants.WS_ROOT).append(TpoConstants.WS_BIND_APP_MODULE);
		
		saveState = ServiceUtil.getInstance().doPost(new URL(urlSb.toString()), pageGroup);
	} catch (Exception e) { // actually here throws MalformedURLException, but we only catch Exception
                // because in below line, log4j can show error stack with exact error type
                logger.error(e.getLocalizedMessage(), e);
		throw new MyFleetTPOException(e);
	} 
	
	return saveState;
}

然后在Backing Bean中抓住并处理:

public String bindAppsAndModule(){
	String bindPageApp = TpoConstants.SAVE_FAILED;
	try {
		bindPageApp = pageGroupingService.associateAppsAndModule(pageGroup);
	} catch (MyFleetTPOException e) {
		logger.error(e.getLocalizedMessage(), e);
	}
	return bindPageApp;
}

 

页面根据bindPageApp,显示消息,或干别的等等。


Unchecked Excpetion Handling:

对于unchecked exception,我们还需要把用户导航到一个公共的错误页面。在讲述unchecked exception handling内容之前,讲讲我的摸索经历吧。

  • 一开始我的确想到了使用AOP的方式去处理,由于我想覆盖大部分java类方法抛出的异常,所以Spring AOP(只能对Service层做切面)就不足以完成这个功能,那么AspectJ就是一个必须要选择的工具了,它可以做到Domain Object的切面。
  • 首先想到的是@AfterThrowing,但是这种方法只能将异常的信息输出到Log中,但是始终没办法实现页面的跳转。于是打算通过配置web.xml实现跳转:

<error-page>
  	<exception-type>java.lang.Throwable</exception-type>
  	<location>/error.jsp</location>
  </error-page>
            但是也失败了,我猜想啊:可能是JSF的生命周期导致的,具体原因没有考证过。。。。。。

  • 当时始终为拿不到当前请求request变量,为了跳过获得request,尝试了FacesContext.getCurrentInstance().getExternalContext.dispatch()方法,未果后,又希望通过AspectJ对FacesServlet.service() && args(request, response)做切面,希许可以拿到request,始终失败,后来才知道FacesServlet是一个final类,没办法对其proxy.
  • 经过一番google,发现可以利用JSF的NavigationHandler去处理,但是前提是通过继承现有的JSF的ActionListnerImpl并重写processAction()方法。
public class ThrowableActionListener extends ActionListenerImpl {
	private static final Logger logger = Logger.getLogger(ThrowableActionListener.class);
	
	@Override
	public void processAction(ActionEvent event) {
		try {
			super.processAction(event);
		} catch (Throwable e) {
			logger.error(e.getLocalizedMessage(), e);
			
			FacesContext fc = FacesContext.getCurrentInstance();									fc.getApplication().getNavigationHandler().handleNavigation(fc, null, "exceptionNavigation");					fc.renderResponse();
		}
	}
}


同时需要在faces-config.xml中配置下:

<application>
	<action-listener>
		com.ge.energy.pgs.myfleet.tpo.common.ThrowableActionListener
	</action-listener>
</application>

<navigation-rule>
	<from-view-id>/*</from-view-id>
	<navigation-case>
		<from-outcome>exceptionNavigation</from-outcome>
		<to-view-id>/pages/common/common_error.jsp</to-view-id>
	</navigation-case>
</navigation-rule>
总结一下:由于FacesServlet是个final类,所以没办法使用子类来复写service()方法,但是对于struts的DispactchServlet,我们完全可以使用类似于这次的ThrowableActionListener的方法,轻而易举地在catch块中实现页面转向,log



 更新于2016-11-20:

项目中使用SSM框架,DAL使用的是MyBatis自动生成的代码,**Mapper.xml里面写SQL,Service层组装数据,并做业务逻辑处理。任何地方都可以抓异常,无论是Checked 还是 Unchecked异常,都可以抓住后抛出RuntimeException, 这样Service 的方法签名就不需要throws Exception这样的声明了。

然后,所以的异常都会bubble up到Controller层,我们可以定义一个GlobalExceptionHandler统一处理,这样前端拿到的就是一个合规的JSON响应。(getTrace() 方法则是打印整个错误栈)

@ControllerAdvice
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler{
	private Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
	
	@ResponseBody
    @ExceptionHandler(value = { Exception.class, RuntimeException.class })
    public BaseResponse<String> defaultErrorHandler(HttpServletResponse response, Exception ex) throws Exception {
		BaseResponse<String> resp = new BaseResponse<>(RESPONSE_STATUS.FAIL);
		logger.warn("抓到了一个异常, 以下是完整的出错路径", ex.getMessage());
		logger.error(getTrace(ex));
		resp.setMsg(ex.getMessage());
		return resp;
    }
	
	private String getTrace(Exception e) {
        StringWriter stringWriter= new StringWriter();
        PrintWriter writer= new PrintWriter(stringWriter);
        e.printStackTrace(writer);
        StringBuffer buffer= stringWriter.getBuffer();
        return buffer.toString();
    }
}


对于多线程的处理,则分情况了,如果对于执行的是Runnable的方法,那么为了能抓住run()方法里面抛出的异常,就有几个注意点了:

1. 不能使用submit 方法提交线程到线程池,而只能使用execute()方法

2. 不能直接使用Executors.newFixedThreadPool(threadNum)创建线程池,因为这样就没办法抓到 run()方法抛出的异常,而是需要覆盖afterExecute方法

ThreadPoolExecutor exe = new ThreadPoolExecutor(2, 2,
                0L, TimeUnit.MILLISECONDS,
                new LinkedBlockingQueue<Runnable>()){

					@Override
					protected void afterExecute(Runnable r, Throwable t) {
						System.out.println(t);
						throw new RuntimeException(t);
					}
			
		};

如果执行的是Callable的方法,那么就没必要这么麻烦了,所有的异常都会在 future.get() 里面通过ExecutionException抛出来。

 

for (Future<List<String>> result : futureResults){				
				try {
					while(result.isDone()){
						List<String> pIdList = result.get();
						reducedPersonIdList.addAll(pIdList);
					}
				} catch (Exception e) {
					throw new RuntimeException("执行多线程匹配的时候,在获取Future.get()的时候发出异常:", e);
				}
			}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值