如何记录JSF ajax请求的方法表达式



我已经弄清楚了如何记录当一个请求是一个ajax请求,它是从哪个页面,在一个过滤器。

我真正想做的是记录ajax请求的实际目的。例如ajax调用的方法的名称(例如在此调用中的"findAddress":<p:ajax process="contactDetails" update="@form" listener="#{aboutYouController.findAddress}" ....)

我该怎么做?我的应用程序有许多ajax请求,我想记录正在触发的日志。

public class TrackingFilter implements Filter {
private static Logger LOG = Logger.getLogger(TrackingFilter.class);
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException {    
    HttpServletRequest req = (HttpServletRequest) request;
    String pageHit = req.getRequestURI().substring(req.getContextPath().length()+1).replace(".xhtml", "");
    if(!pageHit.contains("javax.faces.resource")){ // if is a url we want to log
        if ("partial/ajax".equals(req.getHeader("Faces-Request"))) {
            LOG.trace("ajax on URI: " + req.getRequestURI());
        }

我真正想做的是记录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()这样的方法。只有添加和删除它们的方法,而不是获取它们的列表。因此,必须使用一些令人讨厌的反射技巧,才能使用名称特定于JSF实现的侦听器访问private字段。在Mojarra中是listeners,在MyFaces中是_behaviorListeners。幸运的是,这两个字段都可以从List中赋值,而且这是唯一一个这种类型的字段,所以我们可以检查一下。一旦获得了BehaviorListener实例,那么仍然需要执行另一个反射技巧来获取该实例的MethodExpression字段。恶心。

总而言之,这是PhaseListenerRESTORE_VIEWafterPhase上听的骗局的样子:

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兼容。


Update:如果您正在使用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.
    }
}

相关内容

  • 没有找到相关文章

最新更新