JSF自定义ExceptionHandler遇响应已提交问题,求提前触发方案
解决方案:提前拦截无效会话ID,避免响应提交后无法重定向
针对特定角色下响应已提交、ExceptionHandler无法执行重定向的问题,以下是几个可行的处理方案:
1. 改用PhaseListener在JSF生命周期早期拦截
ExceptionHandler的触发时机偏晚(通常在渲染响应阶段前后),若部分CDI Bean在更早阶段就提交了响应,会导致重定向失败。可以通过PhaseListener在RESTORE_VIEW阶段(JSF生命周期第一个阶段)提前检查cid有效性:
public class ConversationValidationPhaseListener implements PhaseListener { private static final String CONVERSATION_PARAM = "cid"; private static final Logger log = LoggerFactory.getLogger(ConversationValidationPhaseListener.class); @Override public void beforePhase(PhaseEvent event) { FacesContext fc = event.getFacesContext(); Map<String, String> requestParams = fc.getExternalContext().getRequestParameterMap(); if (requestParams.containsKey(CONVERSATION_PARAM)) { String cid = requestParams.get(CONVERSATION_PARAM); Conversation conversation = null; try { ConversationContext conversationContext = CDI.current().select(ConversationContext.class).get(); conversation = conversationContext.getConversation(cid); } catch (NonexistentConversationException e) { handleInvalidCid(fc); return; } if (conversation == null || !conversation.isActive()) { handleInvalidCid(fc); } } } private void handleInvalidCid(FacesContext fc) { try { String requestedUrl = Faces.getRequestURLWithQueryString(); String updatedUrl = removeQueryParameter(requestedUrl, CONVERSATION_PARAM); log.debug("无效会话ID,重定向至: {}", updatedUrl); fc.getExternalContext().redirect(updatedUrl); fc.responseComplete(); // 终止后续JSF生命周期流程 } catch (Exception e) { throw new FacesException(e); } } // 复用原有参数移除方法 private String removeQueryParameter(String url, String parameterName) throws URISyntaxException { URIBuilder uriBuilder = new URIBuilder(url); List<NameValuePair> queryParameters = uriBuilder.getQueryParams(); for (Iterator<NameValuePair> queryParameterItr = queryParameters.iterator(); queryParameterItr.hasNext();) { NameValuePair queryParameter = queryParameterItr.next(); if (queryParameter.getName().equals(parameterName)) { queryParameterItr.remove(); } } uriBuilder.setParameters(queryParameters); return uriBuilder.build().toString(); } @Override public void afterPhase(PhaseEvent event) {} @Override public PhaseId getPhaseId() { return PhaseId.RESTORE_VIEW; } }
在faces-config.xml中注册监听器:
<lifecycle> <phase-listener>com.yourpackage.ConversationValidationPhaseListener</phase-listener> </lifecycle>
2. 排查特定角色的CDI Bean配置
结合你推测的CDI Bean差异,重点检查这类角色关联的Bean:
- 查看是否有Bean在
@PostConstruct方法中直接向响应输出内容(如写入HTML、空白字符等) - 检查是否有拦截器(Interceptor)在请求早期触发了响应提交操作
- 确认是否有非请求作用域的Bean,其初始化逻辑过早占用了响应输出流
3. 尝试重置响应(谨慎使用)
如果必须保留ExceptionHandler的处理逻辑,可尝试在响应已提交时重置响应(该操作依赖容器实现,存在兼容性风险):
// 修改原ExceptionHandler中的try代码块 try { if (fc.getExternalContext().isResponseCommitted()) { // 尝试重置响应,仅部分容器支持 fc.getExternalContext().resetResponse(); } if (!fc.getExternalContext().isResponseCommitted()) { if(Faces.getRequestQueryStringMap().containsKey(CONVERSATION_PARAM)) { String requestedUrl = Faces.getRequestURLWithQueryString(); String updatedUrl = this.removeQueryParameter(requestedUrl,CONVERSATION_PARAM); log.debug("会话不存在,重定向至: {} -> {}", requestedUrl, updatedUrl); fc.getExternalContext().redirect(updatedUrl); } } fc.renderResponse(); break; } catch (Exception ex) { throw new FacesException(ex); } finally { i.remove(); }
原理说明
JSF ExceptionHandler触发时,若部分CDI Bean已完成初始化并向输出流写入内容,响应会被标记为已提交。而PhaseListener在RESTORE_VIEW阶段触发,此时还未进入视图渲染或Bean初始化的响应提交环节,能确保重定向操作有效执行。
内容的提问来源于stack exchange,提问作者DaveB
相关产品推荐
相关产品推荐

