我真正想做的是记录 ajax 请求的实际用途。比如被 ajax 调用的方法的名字(例如这个调用中的“findAddress” <p:ajax process="contactDetails" update="@form" listener="#{aboutYouController.findAddress}" ....
:)
此信息仅在 JSF 组件树中可用。JSF 组件树仅在视图构建时间之后可用。仅当请求已由FacesServlet
. 因此,servlet 过滤器太早了,因为它在任何 servlet 之前运行。
您最好在回发的恢复视图阶段之后运行代码。JSF 组件树保证在那一刻可用。您可以使用FacesContext#isPostback()
来检查当前请求是否是回发。您可以使用PartialViewContext#isAjaxRequest()
来检查当前请求是否为 ajax 请求。您可以使用预定义的javax.faces.source
请求参数来获取 ajax 请求的源组件的客户端 ID。您可以使用预定义的javax.faces.behavior.event
请求参数来获取 ajax 事件名称(例如change
、click
、action
等)。
获得相关的行为听众反过来又是一个故事。ActionSource2
这在组件(例如<h|p:commandButton action="#{...}">
)上很容易,因为MethodExpression
仅可通过ActionSource2#getActionExpression()
. 但是,这在BehaviorBase
标记处理程序(例如<f|p:ajax listener="#{...}">
)上并不容易,因为此 API 没有任何方法,例如getBehaviorListeners()
. 只有添加和删除它们的方法,但不能获取它们的列表。因此,需要一些令人讨厌的反射技巧来访问private
那些名称是特定于 JSF 实现的侦听器的字段。在 Mojarra 中它是listeners
,在 MyFaces 中它是_behaviorListeners
. 幸运的是,两者都是可分配的,List
并且它是该类型的唯一字段,因此我们可以检查一下。一旦掌握了BehaviorListener
实例,那么你仍然需要做另一个反射技巧来获取该MethodExpression
实例的字段。呸。
PhaseListener
总而言之,这就是听听的诡计afterPhase
的样子RESTORE_VIEW
:
public class AjaxActionLoggerPhaseListener implements PhaseListener {
@Override
public PhaseId getPhaseId() {
return PhaseId.RESTORE_VIEW;
}
@Override
public void beforePhase(PhaseEvent event) {
// NOOP.
}
@Override
public void afterPhase(PhaseEvent event) {
FacesContext context = event.getFacesContext();
if (!(context.isPostback() && context.getPartialViewContext().isAjaxRequest())) {
return; // Not an ajax postback.
}
Map<String, String> params = context.getExternalContext().getRequestParameterMap();
String sourceClientId = params.get("javax.faces.source");
String behaviorEvent = params.get("javax.faces.behavior.event");
UIComponent source = context.getViewRoot().findComponent(sourceClientId);
List<String> methodExpressions = new ArrayList<>();
if (source instanceof ClientBehaviorHolder && behaviorEvent != null) {
for (ClientBehavior behavior : ((ClientBehaviorHolder) source).getClientBehaviors().get(behaviorEvent)) {
List<BehaviorListener> listeners = getField(BehaviorBase.class, List.class, behavior);
if (listeners != null) {
for (BehaviorListener listener : listeners) {
MethodExpression methodExpression = getField(listener.getClass(), MethodExpression.class, listener);
if (methodExpression != null) {
methodExpressions.add(methodExpression.getExpressionString());
}
}
}
}
}
if (source instanceof ActionSource2) {
MethodExpression methodExpression = ((ActionSource2) source).getActionExpression();
if (methodExpression != null) {
methodExpressions.add(methodExpression.getExpressionString());
}
}
System.out.println(methodExpressions); // Do your thing with it.
}
private static <C, F> F getField(Class<? extends C> classType, Class<F> fieldType, C instance) {
try {
for (Field field : classType.getDeclaredFields()) {
if (field.getType().isAssignableFrom(fieldType)) {
field.setAccessible(true);
return (F) field.get(instance);
}
}
} catch (Exception e) {
// Handle?
}
return null;
}
}
为了让它运行,注册如下faces-config.xml
:
<lifecycle>
<phase-listener>com.example.AjaxActionLoggerPhaseListener</phase-listener>
</lifecycle>
以上经过测试,与 Mojarra 和 PrimeFaces 兼容,理论上也与 MyFaces 兼容。
更新:如果您正在使用 JSF 实用程序库OmniFaces,或者从 2.4 版开始,您可以使用新的Components#getCurrentActionSource()
实用程序方法来找出当前的操作源组件并Components#getActionExpressionsAndListeners()
获取所有操作方法和注册的侦听器的列表给定的组件。这也可用于常规(非 ajax)请求。有了这个,上面的PhaseListener
例子可以简化如下:
public class FacesActionLoggerPhaseListener implements PhaseListener {
@Override
public PhaseId getPhaseId() {
return PhaseId.PROCESS_VALIDATIONS;
}
@Override
public void beforePhase(PhaseEvent event) {
// NOOP.
}
@Override
public void afterPhase(PhaseEvent event) {
if (!event.getFacesContext().isPostback())) {
return;
}
UIComponent source = Components.getCurrentActionSource();
List<String> methodExpressions = Components.getActionExpressionsAndListeners(source);
System.out.println(methodExpressions); // Do your thing with it.
}
}