poniedziałek, 26 marca 2012

JSF - Managed Bean

Każdy kto pracuje z technologią JSF musiał spotkać się z Managed Beanami, czyli klasami Javy, za pomocą których możemy manipulować treścią na stronach xhtml. W niniejszym poście przyjrzymy się jak racjonalnie wykorzystywać MB do zarządzania treścią i nie popaść w skrajne patologie.

Logika biznesowa, skomplikowane operacje, dostęp do bazy, itp.
Należy pamiętać, że JSF to technologia za pomocą której tworzymy wygląd naszej aplikacji, czyli nie powinniśmy zaszywać w niej skomplikowanych obliczeń, dostępu do bazy danych, itd. Managed Beand jest częścią JSF i obowiązują go takie same reguły. 99% MB powinno być tylko pośrednikiem między tym co jest na stronach xhtml, a tym co jest w kodzie Java. Pytanie co z tym 1%?, zawsze znajdą się jakieś wyjątki :), ale jeśli twoja aplikacja składa się z samych wyjątków, to znaczy że coś jest nie tak.

Racjonalne wykorzystanie zmiennych
JSF i technologie pośrednie, mają kilka zasięgów do naszej dyspozycji:
  • @RequestScoped czyli standardowy request aplikacji. Wszystko co żyje w tym zasięgu jest dostępne tylko podczas jednego requestu.
  • @ViewScoped zasięg idealny dla AJAX'owych komponentów. Wszystkie komponenty w tym zasięgu są aktywne, do momentu kiedy nie przejdziemy na inną stronę. Zasięg nie jest dostępny dla JSF 1.2
  • @SessionScoped czyli zasięg sesyjny, idealny do trzymania wszelkich informacji do których często się odwołujemy (np. o zalogowanym użytkowniku)
  • @ApplicationScoped czyli zasięg aplikacyjny. Wszystko co w nim żyje jest dostępne przez cały okres życia naszej aplikacji.
Podczas tworzenia MB musimy określić jego zasięg. Przy wyborze któregoś z zasięgów powinniśmy kierować się jedną z najważniejszej zasad, czyli: "Nie używamy większego zasięgu niż jest nam potrzebny". Jeśli tworzymy listę osób/dokumentów/itp, nie musimy trzymać jej w sesji, wystarczy request (ewentualnie view, jeśli chcemy ją później modyfikować ajaxem). Innym przykładem mogą być informacje o zalogowanym użytkowniu / lub o koszyku z produktami, które ze względu na częste odwołania trzymamy w zasięgu sesji. Reqest w tym wypadku byłby niewydajny.

Gettery ...
Jedna z ważniejszych zasad jaka dotyczy JSF, to nie umieszczanie w metodach get, referencji do innych metod/obiektów realizujących jakąkolwiek logikę, ponieważ metody get wywoływane są przez silnik JSF kilkakrotnie podczas jednego requesta. Jeśli zapomnimy o tej zasadzie to nagle nasze skomplikowane metody przetwarzające dane (czyli najczęściej jest to jakiś select do bazy danych i/lub obróbka danych w javie) wywoływane są kilkakrotnie. Efektem takiego postępowania jest znaczne wydłużenie się ładowania strony. Strona, która powinna wczytać się w 3-4s, nagle wczytuje się w ponad 10 sekund, itd.

Oto krótki przykład- jak taki kod wygląda.
package pl.blogspot.mkorwel.myapp.view.faces;

import java.io.Serializable;
import java.util.List;

import javax.faces.bean.ManagedBean;
import javax.faces.bean.RequestScoped;

import pl.blogspot.mkorwel.myapp.model.Person;
import pl.blogspot.mkorwel.myapp.service.PersonService;

@ManagedBean
@RequestScoped
public class PersonBrowser implements Serializable {

 //jakieś wstrzyknięcie
 private PersonService personService;

 public List<Person> getPersons() {
  return this.personService.getPersons();
 }

}

Jeśli ten kod przypomina wam wasze MB to znaczy, że trzeba zakasać rękawy i trochę popracować. Pierwszym pomysł na jaki wpadamy to przypisanie naszej listy osób do zmiennej i wywoływanie servicu tylko kiedy jest ona null'em. Po wprowadzeniu naszego hackerskiego tricku w życie, kod wygląda mniej więcej tak:

package pl.blogspot.mkorwel.myapp.view.faces;

import java.io.Serializable;
import java.util.List;

import javax.faces.bean.ManagedBean;
import javax.faces.bean.RequestScoped;

import pl.blogspot.mkorwel.myapp.model.Person;
import pl.blogspot.mkorwel.myapp.service.PersonService;

@ManagedBean
@RequestScoped
public class PersonBrowser implements Serializable {

 // jakieś wstrzyknięcie
 private PersonService personService;

 private List<Person> persons;

 public List<Person> getPersons() {

  if (this.persons == null) {
   this.persons = this.personService.getPersons();
  }

  return this.persons;
 }
}
Tak przerobiony kod działa już po naszej myśli, tz. metoda z klasy servicowej wywołuje się tylko raz. Nasza strona znacznie przyspieszyła i wydaje się, że zadanie wykonane. Niestety po dłuższym namyśle nasz instynkt programisty podpowiada nam, że coś jest nie tak. Do rozwiązania tego problemu musimy sięgnąć trochę głębiej, a mianowicie do adnotacji @PostConstruct. Taką adnotacją wskazujemy metodę, która będzie wywołana na początku tworzenia się Managed Beanów (coś a'la konstruktor) i to właśnie w tej metodzie, powinniśmy zainicjalizować listę naszych osób. Po wprowadzeniu poprawek całość powinna prezentować się tak jak poniżej.

package pl.blogspot.mkorwel.myapp.view.faces;

import java.io.Serializable;
import java.util.List;

import javax.annotation.PostConstruct;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.RequestScoped;

import pl.blogspot.mkorwel.myapp.model.Person;
import pl.blogspot.mkorwel.myapp.service.PersonService;

@ManagedBean
@RequestScoped
public class PersonBrowser implements Serializable {

 // jakieś wstrzyknięcie
 private PersonService personService;

 private List<Person> persons;

 public List<Person> getPersons() {
  return this.persons;
 }

 @PostConstruct
 public void init() {
  this.persons = this.personService.getPersons();
 }
}

Jeśli macie jakieś inne ciekawe przygody związane z JSF, to zapraszam do dyskusji w komentarzach.