В данной заметке расскажу о проблеме с навигацией в JSF, когда после перехода в адресной строке браузера отображается URL предыдущей страницы, а не той на которую перешли.
История была такая: попросили посмотреть и решить проблему. Открываем страницу и видим, что url не соответствует содержимому. Находимся мы на page2.xhtml, а url page1.xthml. Пробуем перейти на page3.xhtml через меню, навигация происходит, но в url теперь page2.xhtml.
Окей. Идём в код смотреть содержимое xhtml страницы.
<p:menubar class="main-menu" model="#{someBean.model}"/>
Навигация реализована через некий элемент menuBar из библиотеки тегов Primefaces. Имеет смысл отправиться в код бина.
public MenuModel getModel() {
MenuModel model = new DefaultMenuModel();
int i = 0;
for (Page page : currentModule.pages()) {
DefaultMenuItem item = new DefaultMenuItem(i18nMap.get(getMessageId(page)));
item.setId(String.valueOf(i));
if(page.equals(currentPage)) {
item.setStyleClass("selected");
}
item.setAjax(false);
item.setCommand("#{some.redirectListener}");
item.setParam("page", page);
model.addElement(item);
i++;
}
return model;
}
Если немного почитать исходный код Primefaces и углубиться в реализацию можно увидеть, что setCommand ожидает получить listener, который будет вызван при нажатии на элемент в меню. Данный listener принимает ActionEvent и возвращает строку. Используя строку, которую вернёт ему listener он совершит переход на другую страницу. Логику работы можно посмотреть в сорцах библиотеки. Вот интересующий нас класс. Приведу небольшой участок кода этого класса.
if (command != null) {
String actionExpressionString = menuItem.getCommand();
MethodExpression noArgExpr = facesContext.getApplication().getExpressionFactory().
createMethodExpression(eLContext, actionExpressionString,
String.class, new Class[0]);
Object invokeResult = null;
try {
invokeResult = noArgExpr.invoke(eLContext, null);
}
catch (MethodNotFoundException methodNotFoundException) {
try {
MethodExpression argExpr = facesContext.getApplication().getExpressionFactory().
createMethodExpression(eLContext, actionExpressionString,
String.class, new Class[]{ActionEvent.class});
invokeResult = argExpr.invoke(eLContext, new Object[]{event});
}
catch (MethodNotFoundException methodNotFoundException2) {
MethodExpression argExpr = facesContext.getApplication().getExpressionFactory().
createMethodExpression(eLContext, actionExpressionString,
String.class, new Class[]{MenuActionEvent.class});
invokeResult = argExpr.invoke(eLContext, new Object[]{event});
}
}
finally {
String outcome = (invokeResult != null) ? invokeResult.toString() : null;
facesContext.getApplication().getNavigationHandler().handleNavigation(facesContext, actionExpressionString, outcome);
}
}
Если в MenuItem был задан атрибут команда, то из контекста приложений извлекается метод бина указанный в команде. После этого происходит вызов данного метода и запись результата в переменную outcome. После этого outcome передаётся в NagivationHandler приложения и jsf обрабатывает действие навигации.

Здесь нужно вспомнить одну простую вещь. Если в outcome попадает просто URL, то JSF рассматривает данное действие не как redirect, а как forward. Из-за этого url браузера не меняется, меняется лишь содержимое страницы. Для того чтобы произвести redirect, необходимо в URL добавить соответствующий параметр запрос faces_redirect равный true. Давайте теперь взгляем как формируется url.
public String redirectListener(ActionEvent event) {
MenuItem menuItem = ((MenuActionEvent) event).getMenuItem();
return goToPage(Page.valueOf(menuItem.getParams().get("page").get(0)));
}
private String goToPage(Page page) {
currentPage = page;
return currentPage.url();
}
Как видно из кода, url - атрибут сущности Page. В коде попавшемся мне, Page реализовали через enum. у каждого элемента enum'а был задан атрибут url. Поэтому, чтобы решить проблему необходимо внести небольшую правку в getter этого атрибута. В результате должны получить следующее:
public String url() {
return url + "?faces-redirect=true";
}
С этим параметром теперь всё должно работать корректно и JSF по клику будет осуществлять не forwarding, а redirect.
На сегодня, пожалуй, откланяюсь.