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

Posted

技术标签:

【中文标题】如何记录 JSF ajax 请求的方法表达式【英文标题】:How can I log method expressions of JSF ajax requests 【发布时间】:2015-10-29 10:47:47 【问题描述】:

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

【问题讨论】:

具体监听方法名,请看这里:***.com/questions/11860550/… @tt_dev 有趣。我可能会使用它,但是 facesContext 在我的过滤器中不可用,因为它不是 bean。我确实相信我可以从标准请求/会话中的某个地方获取 facesContext,但我不记得在哪里。我有我的应用程序在调试中搜索它无济于事。 没有理由否决这个问题。 action(***.com/questions/4788454/…)、actionListener(***.com/questions/17261195/…)提取MethodExpression相关问题。 【参考方案1】:

我真正想做的是记录 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事件名称(例如changeclickaction等)。

获取相关的行为监听器又是另外一回事。这在ActionSource2 组件(例如<h|p:commandButton action="#...">)上很容易,因为MethodExpression 仅由ActionSource2#getActionExpression() 提供。然而,这在BehaviorBase taghandlers(例如<f|p:ajax listener="#...">)上并不容易,因为这个API没有像getBehaviorListeners()这样的方法。只有添加和删除它们的方法,但不能获取它们的列表。因此,需要一些令人讨厌的反射技巧来访问private 字段,这些侦听器的名称是特定于JSF 实现的。在 Mojarra 中是 listeners,在 MyFaces 中是 _behaviorListeners。幸运的是,两者都可以从List 分配,并且它是该类型的唯一字段,因此我们可以检查一下。一旦掌握了BehaviorListener 实例,那么您仍然需要执行另一个反射技巧来获取该实例的MethodExpression 字段。呸。

总而言之,这就是 PhaseListenerafterPhaseRESTORE_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>

Above 经过测试并与 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.
    


【讨论】:

我知道100分赏金会引起你的注意。再次感谢@BalusC,由于您对 SO 的回答,我对 JSF 有了更多的了解。该解决方案听起来可行——而且很可能是可行的。当我开始实施您的建议时,我会尽快奖励赏金。 不客气。至于赏金,你也可以让系统自动奖励。悬赏问题在列表中停留的时间越长,就会有越多的人看到并支持问答,尤其是在悬赏的最后一天。通过这种方式,您可以赚取一些用于赏金的声望。

以上是关于如何记录 JSF ajax 请求的方法表达式的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Ajax 请求期间在 JSF2 中动态添加组件

发出 JSF Ajax 请求时显示加载进度

JSF2.0 - 具有可选方法表达式的复合组件

如何阻止 EL 表达式在未呈现的 JSF 组件中进行评估?

在 JSF 表达式语言中如何获取列表的长度?

JSF 复合组件支持 bean EL 表达式作为必需属性的默认值失败,方法未知