Dorado 7 : Http请求的异常处理(草稿) (SEUG)

Dorado7应用中的请求大致可分为两类:普通请求和Ajax类请求。对于这两种请求的异常处理方法存在着相当大的差异,因此以下将分开讨论这两种场景。

普通请求的异常处理

普通请求通常指那些直接访问某个视图的请求,例如那些通过.d结尾的URI直接映射某个.view.xml的请求。除此之外也包括那些装载Dorado7的脚本库、CSS、以及通过/resources/*装载各种资源文件的请求。

Dorado7通过SpringMVC中的SimpleMappingExceptionResolver来处理在这类请求中发生的异常,该类在Dorado缺省的上下文中是这样配置的:

<bean id="dorado.mappingExceptionResolver" class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
	<property name="defaultErrorView" value="/dorado/ErrorPage" />
</bean>

在上面的配置中,Dorado只定义了默认的错误页面,即/dorado/ErrorPage。

如果要自定义此处的异常处理,可以在dorado-home下的servlet-context.xml中添加一个同名的Bean,以此来覆盖Dorado中的缺省配置。例如在下面的配置为某种特定异常定义了专用的错误处理页面。

<bean id="dorado.mappingExceptionResolver" class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
	<property name="exceptionMappings">
		<map>
			<entry key="xxx.xxx.SecurityException" value="/access-denied.jsp" />
		</map>
	</property>
	<property name="defaultErrorView" value="/dorado/ErrorPage" />
</bean>

当然,Dorado7并不局限只能使用SimpleMappingExceptionResolver,如果有需要也完全可以使用SpringMVC中提供的其他异常处理器,例如DefaultHandlerExceptionResolver,具体请查阅Spring的文档。

在ErrorPage的处理逻辑中,通过request.getAttribute("exception")或doradoContext.getAttribute(DoradoContext.REQUEST, "exception")可以获得实际发生的异常。

Ajax类请求的异常处理

在Dorado7的运行过程中动态装载数据、提交数据、执行AjaxAction、执行远程数据校验等动作都会发起Ajax类请求。由于此类请求对于返回数据的格式有着严格的要求,无论Server的逻辑成否都必须确保向Client返回特定格式的数据。因此通常不会使用ErrorPage机制。

要统一的处理Ajax类请求中的异常应利用Dorado7提供的全局拦截器机制,见 定义各种全局拦截器(草稿) (SEUG)

很容易想象的是我们应该如何在这些拦截器中记录日志、改变传向客户端的异常信息。可是当我们一旦需要实现类似于普通请求中的出错重定向功能时应该怎么做呢?必须一旦业务系统抛出用户未登录或Session超时的异常时,即使在Ajax类请求中发生了这样的错误,开发者也仍然可能希望界面能够自动跳转到登录页面。

为解决这一问题,Dorado7提供了一种特别的异常类com.bstek.dorado.view.resolver.ClientRunnableException。该异常类的作用是向Client端返回一段可执行的JavaScript脚本,当Dorado7的客户端接收到这段脚本后会自动的执行它。

public class AjaxServiceInterceptor extends PatternMethodInterceptor {
	public Object invoke(MethodInvocation methodInvocation) throws Throwable {
		try {
			return methodInvocation.proceed();
		}
		catch (AuthenticationException e) {
			StringBuffer script = new StringBuffer();
			script.append("dorado.MessageBox.prompt('User not logged in or session expired!\\n")
				.append("Do you wanna go to the login page?', function() {")
				.append("open($url('>Login.d'), '_self');")
				.append("});");
			throw new ClientRunnableException(script.toString());
		}
	}
}

根据Dorado7的AjaxEngine实现的规范,当其接收到content-type为text/runnable的Response时,会自动将Response的内容识别为JavaScript并立即执行它。

不过,由于Dorado7中的Ajax请求支持请求自动合并的功能(一种自动将连续发出的Ajax请求合并为一次HTTP请求的优化功能),在这种合并请求中问题会变得比较复杂。因此,通常我们不建议你不要直接设置Response的content-type或者直接向Response的outputStream中进行输出。而是利用上面提到的ClientRunnableException这类经过包装方法。

由于返回一段可执行的JavaScript会打破AjaxAction等Ajax操作在客户端的正常后续处理,因此Dorado7将此操作视为失败的Ajax调用,在这种情况下AjaxAction的onSuccess事件不会被触发,相反onFailure事件会在执行的JavaScript被真正的执行前触发。