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

Mar*_*k W 8 ajax jsf logging methodexpression

我已经弄清楚如何在过滤器中记录请求是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());
        }
Run Code Online (Sandbox Code Playgroud)

Bal*_*usC 10

我真正想做的是记录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上听afterPhaseRESTORE_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;
    }

}
Run Code Online (Sandbox Code Playgroud)

要使其运行,请按以下方式注册faces-config.xml:

<lifecycle>
    <phase-listener>com.example.AjaxActionLoggerPhaseListener</phase-listener>
</lifecycle>
Run Code Online (Sandbox Code Playgroud)

以上测试与Mojarra和PrimeFaces兼容,理论上也与MyFaces兼容.


更新:如果您正在使用JSF实用程序库OmniFaces,或者从2.4版开始使用它,您可以使用新的Components#getCurrentActionSource()实用程序方法找出当前的操作源组件并Components#getActionExpressionsAndListeners()获取在a上注册的所有操作方法和侦听器的列表给定的组件.这也适用于常规(非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.
    }

}
Run Code Online (Sandbox Code Playgroud)

  • 别客气.至于赏金,你也可以让系统自动化它.赏金问题留在列表中的时间越长,人们就会越多地看到并提升问答,特别是在赏金的最后一天.通过这种方式,您可以获得一些代表您花费的回报. (2认同)