如何记录 JSF ajax 请求的方法表达式
How can I log method expressions of JSF ajax requests
我已经弄清楚如何在过滤器中记录请求是 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());
}
What I would really like to do is log what the ajax request is actually for. Such as the name of the method being called by the ajax (eg "findAddress" in this call:<p:ajax process="contactDetails" update="@form" listener="#{aboutYouController.findAddress}" ....
)
此信息仅在 JSF 组件树中可用。 JSF 组件树仅在视图构建时间之后可用。仅当请求已由 FacesServlet
提供服务时才会构建视图。因此,servlet 过滤器太早了,因为它 运行s 在任何 servlet 之前。
您最好 运行 回发的恢复视图阶段之后的代码。 JSF 组件树保证在那一刻可用。您可以使用 FacesContext#isPostback()
to check if the current request is a postback. You can use PartialViewContext#isAjaxRequest()
来检查当前请求是否为 ajax 请求。您可以使用预定义的 javax.faces.source
请求参数来获取 ajax 请求的源组件的客户端 ID。您可以使用预定义的 javax.faces.behavior.event
请求参数来获取 ajax 事件名称(例如 change
、click
、action
等)。
获取关联的行为侦听器又是另外一回事。这在 ActionSource2
components (e.g. <h|p:commandButton action="#{...}">
) as the MethodExpression
is just available by ActionSource2#getActionExpression()
. However, this isn't easy on BehaviorBase
taghandlers (e.g. <f|p:ajax listener="#{...}">
) as this API doesn't have any method like getBehaviorListeners()
. There are only methods to add and remove them, but not to obtain a list of them. So some nasty reflection trickery is necessary to access the private
field with those listeners whose name is JSF implementation specific. In Mojarra it's listeners
and in MyFaces it's _behaviorListeners
. Both are fortunately assignable from List
and it's the only field of that type, so we could just check for that. Once having hand of the BehaviorListener
实例上很容易,然后你 仍然 需要做另一个反射技巧来获得该实例的 MethodExpression
字段。呸
总而言之,这就是 PhaseListener
监听 RESTORE_VIEW
的 afterPhase
的诡计:
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, or are open to, since version 2.4 you can use the new Components#getCurrentActionSource()
utility method to find out the current action source component and 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.
}
}
我已经弄清楚如何在过滤器中记录请求是 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());
}
What I would really like to do is log what the ajax request is actually for. Such as the name of the method being called by the ajax (eg "findAddress" in this call:
<p:ajax process="contactDetails" update="@form" listener="#{aboutYouController.findAddress}" ....
)
此信息仅在 JSF 组件树中可用。 JSF 组件树仅在视图构建时间之后可用。仅当请求已由 FacesServlet
提供服务时才会构建视图。因此,servlet 过滤器太早了,因为它 运行s 在任何 servlet 之前。
您最好 运行 回发的恢复视图阶段之后的代码。 JSF 组件树保证在那一刻可用。您可以使用 FacesContext#isPostback()
to check if the current request is a postback. You can use PartialViewContext#isAjaxRequest()
来检查当前请求是否为 ajax 请求。您可以使用预定义的 javax.faces.source
请求参数来获取 ajax 请求的源组件的客户端 ID。您可以使用预定义的 javax.faces.behavior.event
请求参数来获取 ajax 事件名称(例如 change
、click
、action
等)。
获取关联的行为侦听器又是另外一回事。这在 ActionSource2
components (e.g. <h|p:commandButton action="#{...}">
) as the MethodExpression
is just available by ActionSource2#getActionExpression()
. However, this isn't easy on BehaviorBase
taghandlers (e.g. <f|p:ajax listener="#{...}">
) as this API doesn't have any method like getBehaviorListeners()
. There are only methods to add and remove them, but not to obtain a list of them. So some nasty reflection trickery is necessary to access the private
field with those listeners whose name is JSF implementation specific. In Mojarra it's listeners
and in MyFaces it's _behaviorListeners
. Both are fortunately assignable from List
and it's the only field of that type, so we could just check for that. Once having hand of the BehaviorListener
实例上很容易,然后你 仍然 需要做另一个反射技巧来获得该实例的 MethodExpression
字段。呸
总而言之,这就是 PhaseListener
监听 RESTORE_VIEW
的 afterPhase
的诡计:
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, or are open to, since version 2.4 you can use the new Components#getCurrentActionSource()
utility method to find out the current action source component and 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.
}
}