Статьи

Интеграция Spring и JSF: навигация

Это первая из серии блогов о моих усилиях по обеспечению глубокой интеграции между Spring и JavaServer Faces. Все упомянутое здесь — «работа в процессе», поэтому, если вы извлекаете код, имейте в виду, что это движущаяся цель; ожидайте некоторые грубые края и не удивляйтесь, если это иногда сломано.

Вы уже можете использовать Spring с JSF довольно счастливо из коробки: Spring управляет вашими компонентами, а JSF обрабатывает ваши экраны. Также есть отличная поддержка JSF в Spring Web Flow, если вы создаете какое-либо потоковое приложение, вам действительно следует использовать Web Flow. Веб-поток также предоставляет org.springframework.faces.mvc. Класс JsfView, который позволит вам визуализировать страницу JSF из Spring MVC. К сожалению, JsfView отображает только переходные (без сохранения состояния) представления, если вы хотите обрабатывать обратные передачи, вам не повезло.

Разрешение Spring MVC отображать представления JSF, которые могут обрабатывать обратную передачу, было моим основным драйвером для запуска этого проекта. Благодаря гибкости как MVC, так и JSF, можно полностью интегрировать эти технологии (хотя точные детали того, как, вероятно, лучше всего сохранить для другого поста). Я хочу провести остаток этой статьи, рассказывая о том, как мы можем создать действительно хорошую навигацию JSF.

Если вы использовали стандартную навигацию JSF, вы, вероятно, привыкли к следующему типу вещей в вашем faces-config.xml :

1
2
3
4
5
6
7
8
<navigation-rule>
    <from-view-id>/pages/list.xhtml</from-view-id>
    <navigation-case>
        <from-outcome>select</from-outcome>
        <to-view-id>/pages/details.xhtml</to-view-id>
        <redirect/>
    </navigation-case>
</navigation-rule>

Хотя это довольно легко понять, у стандартного подхода есть некоторые очевидные недостатки, для начала он довольно многословен. Большую часть времени я хочу перенаправить своих пользователей, а не путать их с тем, почему URL-адрес показывает что-то отличное от их текущей страницы. Потребность в том, что <redirect/> практически для каждого элемента действительно раздражает. Количество XML явно расстроило самих разработчиков JSF, и, к счастью, в JSF 2.0 появилась концепция неявной навигации. Это то, что мы будем использовать позже. Если вы хотите прочитать действительно хорошую статью о навигации JSF, ознакомьтесь с Fluent Navigation in JSF 2 от Dan Allen.

Навигация на самом деле все о пунктах назначения, нет особого смысла перенаправлять кого-то на 404 page not found Создание удобных для чтения URL-адресов всегда было проблемой для JSF. Прямо сейчас, без разработки собственного кода, лучшим вариантом для создания читаемых URL-адресов, вероятно, является использование PrettyFaces . Конечно, с JSF и Spring хорошо интегрированными, вам не нужно использовать ничего, кроме аннотации @RequestMapping для создания читаемых URL. В приведенном ниже примере показано, как можно сопоставить удобный для чтения URL-адрес, чтобы отобразить информацию об отеле по идентификатору.

1
2
3
4
5
6
7
8
@Controller
public class HotelsController {
  @RequestMapping(value = "/hotels/{id}", method = RequestMethod.GET)
  public String show(@PathVariable Long id, Model model) {
    model.addAttribute(bookingService.findHotelById(id));
    return "hotels/show";
  }
}

С аннотациями @RequestMapping мы снова можем думать о навигации. Обычно <h:commandButton> , <h:button> , <h:commandLink> или <h:link> используются для запуска навигации, например:

1
<h:commandButton value="Go" action="select">

Здесь, когда пользователь нажимает кнопку "Go" , включается действие "select" и правила навигации используются для поиска пункта назначения. Поскольку мы хотим отойти от определения навигационного XML, нам нужен альтернативный подход для поиска мест назначения MVC. Немного подорванная поддержка JSF для неявной навигации дает нам довольно хороший способ сделать это. С помощью небольшого кода интеграции мы можем поддерживать специальный префикс "spring:" который указывает JSF разрешать места назначения с помощью Spring MVC.

1
<h:commandButton value="Go" action="spring:redirect:/spring/hotels/123"/>

В приведенном выше примере будет разрешено "redirect:/spring/hotel/123" с использованием ViewResolver зарегистрированного в Spring MVC. В этом случае UrlBasedViewResolver выберет "redirect:" и будет использоваться RedirectView .
Это довольно хорошо, но жесткое кодирование идентификатора отеля "123" в названии вида не так уж и практично. К счастью, есть ответ:

1
2
3
<h:commandButton value="Go" action="spring:redirect:/spring/hotels/{id}">
    <f:param name="id" value="#{resultItem.id}/>
</h:commandButton>

Все дочерние теги <f:param> commandButton будут использоваться для построения модели для представления MVC. В этом случае мы получаем модель, содержащую « id=#{resultItem.id} «. Выражение значения EL #{resultItem.id} будет разрешено до отображения представления. Класс RedirectView в Spring 3.1 будет работать с переменными шаблона URL, поэтому « /spring/hotels/{id} » выберет « id » для отображения полного URL.

Небольшое раздражение в связи с описанным выше методом заключается в том, что вам необходимо определить свои URL-адреса внутри файлов XHTML, а также в аннотациях @RequestMapping . В качестве альтернативы этому вы можете использовать специальную @bean.method « @bean.method », чтобы указать, что вы хотите перейти к значению @RequestMapping для указанного метода bean-компонента контроллера:

1
2
3
<h:commandButton value="Go" action="spring:@hotelsController.show">
    <f:param name="id" value="#{resultItem.id}/>
</h:commandButton>

Если у вас есть более одного метода @RequestMapping в вашем компоненте контроллера, вы можете перемещаться между ними, используя еще более короткий синтаксис « @method » (здесь предполагается, что компонент является текущим обработчиком). Конечно, не каждый тип @RequestMapping может быть преобразован в URL, например, если вы используете подстановочные знаки, это не будет работать. Совет состоит в том, чтобы сделать ваши отображения максимально простыми.
Последнее преимущество этого метода заключается в том, что мы также можем обратить вспять процесс DataBinder . Например:

1
2
3
4
5
public class SearchCriteria implements Serializable {
  private String searchString;
  private int page;
  // ... getters / setters
}
1
2
3
4
@RequestMapping(value = "/hotels")
public String list(SearchCriteria criteria, Model model) {
  // ...
}
1
2
3
<h:link outcome="spring:@list">
    <f:param name="sc" value="#{searchCriteria}"/>
</h:link>

Предполагая, что выражение EL #{searchCriteria} в объект SearchCriteria содержащий строку "California" и целое число 10 создан URL-адрес: "/spring/hotels?searchString=California&page=10" .

Если вы хотите взглянуть на код этого проекта, он в настоящее время доступен по адресу http://github.com/philwebb/springfaces . Как уже упоминалось в верхней части поста, этот код находится в стадии разработки, поэтому, пожалуйста, ожидайте некоторых проблем. Моя следующая задача в планах — поддержка аннотации @NavigationMapping , которая обеспечит программную навигацию.

Ссылка: Интеграция Spring & JavaServer Faces: навигация от нашего партнера JCG Филиппа Вебба в блоге Фила Вебба .