środa, 15 kwietnia 2009

GWT oraz Session Beans

Uwaga do tego artykułu.
Kod zawarty w tym wpisie w części nie jest kodem napisanym przezemnie. Sam pomysł został zaczerpnięty z grup googlowych, ja postanowiłem, że pomysł jest na tyle ciekawy, że warto go przedstawić.

Standardowo GWT nie jest zawarte w transakcji. Ten stan raczej nie jest do końca wygodny, szczególnie jeżeli odbywa się jakieś przetwarzanie po stronie serwera. Standardowa struktura aplikacji jest następująca

  • Klient (przeglądarka)
  • Serwer (GWT)
  • Serwer (EJB) (logika biznesowa)
  • DB
Oczywiście ta struktura może być różna, w zależności od aplikacji, ale generalnie wygląda podobnie. Problem polega na tym, że na poziomie Server(GWT) dane nie są objęte transakcjami. Okazuje się jednak, że przy pomocy stosunkowo prostego zabiegu da się tą warstwę całkowicie pominąć.

Standardowo aplikacja GWT wymaga zdefiniowania 2 interfejsów oraz ich implementacji. Interfejsy są zrobione standardowo

Service:

public interface SmapleService extends RemoteService {
// Sample interface method of remote interface
String getMessage(String msg);

/**
* Utility/Convenience class.
* Use SmapleService.App.getInstance () to access static instance of SmapleServiceAsync
*/
public static class App {
private static SmapleServiceAsync app = null;

public static synchronized SmapleServiceAsync getInstance() {
if (app == null) {
app = (SmapleServiceAsync) GWT.create(SmapleService.class);
((ServiceDefTarget) app).setServiceEntryPoint(GWT.getModuleBaseURL() +
"smaple.Smaple/SmapleService");
}
return app;
}
}
}


ServiceAsync:

public interface SmapleServiceAsync {
void getMessage(String msg, AsyncCallback async);
}


Standardowa implementacja wygląda tak:
 
public class SmapleServiceImpl
extends RemoteServiceServlet
implements SmapleService {
// Implementation of sample interface method
public String getMessage(String msg) {
return "Client said: \"" + msg + "\"
Server answered: \"Hi!\"";
}
}


Ten kod niestety nie pozwala na transakcyjność. Wystarczy jednak zrobić kilka niewielkich zmian. Po pierwsze klasa SampleServiceImpl nie powinna implementować interfejsu SampleService. Po drugie, należy przeciążyć metodę processCall odpowiedzialną za obsługę przychodzących wywołąń tak, aby wywoływała metody z SSB.


public class SampleServiceImpl
extends RemoteServiceServlet {
private static final Logger log = Logger.getLogger(SampleServiceImpl.class);

public String processCall(String payload) throws SerializationException {
try {
Object bean = getBean();
synchronized (bean) {
RPCRequest rpcRequest = RPC.decodeRequest(payload, bean.getClass());
log.debug("RPC METHOD CALL: " +
rpcRequest.getMethod() +
" PARAMETERS: " +
Arrays.toString(rpcRequest.getParameters()));
return RPC.invokeAndEncodeResponse(bean, rpcRequest.getMethod(),
rpcRequest.getParameters());
}

} catch (IncompatibleRemoteServiceException ex) {
ex.printStackTrace();
return RPC.encodeResponseForFailure(null, ex);
} catch (NamingException ne) {
ne.printStackTrace();
return RPC.encodeResponseForFailure(null, ne);
}
}

public ISampleBeanService getBean() throws NamingException {
final HttpSession httpSession = getThreadLocalRequest().getSession();
ISampleService bean = (ISampleBeanService) httpSession.getAttribute("SampleBeanService");

if (bean != null) try {
// Call a method to test whether the bean is still live;
// if it's dead, we'll re-create it
synchronized (bean) {
bean.isAlive();
log.debug("Bean alive");
}
} catch (NoSuchEJBException ex) {
log.debug("Bean dead");
bean = null;
httpSession.removeAttribute("SampleBeanService");
}

if (bean == null) {
InitialContext ic = new InitialContext();
Object objRef = ic.lookup("sample/SampleBeanService/local");
bean = (ISampleBeanService) PortableRemoteObject.narrow(objRef,
ISampleBeanService.class);
httpSession.setAttribute("SampleBeanService", bean);
}

return bean;
}


Warto zwrocic uwage, ze nie zgadzaja sie nazwy interfejsow. Bean korzysta z interfejsu ISampleBeanService podczas gdy GWT korzysta z SampleService. To nie jest błąd.


public interface ISampleBeanService extends SampleService {
public boolean isAlive();
}


Ten interfejs jest pomocniczy implementuje tylko metodę isAlive(), wykorzystywaną do sprawdzenia czy SSB zostało stworzone, oraz czy jest nadal dostępne.

Aby całość działała brakuje już tylko samego SSB:


@Stateless(name = "SampleBeanService")
@Local(ISampleBeanService.class)
public class SampleService implements ISampleBeanService {
// Implementation of sample interface method
public String getMessage(String msg) {
return "Client said: \"" + msg + "\"
Server answered: \"Hi!\"";
}
public boolean isAlive() {
return true;
}

}


Bean jak widać jest standardowym beanem. Nie wymaga niczego nadzwyczajnego.

Napotkałem na dwa problemy przy implementacji tego rozwiązania.
  • Problem z sesją. Na dobrą sprawę, jeżeli użyjemy tego rozwiązania pojawia się problem z przechowywaniem danych. Standardowo w GWT dane wrzucane są do sesji HTTP. Niestety w przypadku Statless bean z założenia dane nie są w nim trzymane, a z poziomu SSB nie ma dostępu do Sesji HTTP. Rozwiązaniem jakie się w sumie samo nasuwa są Statefull Session Bean, które w zasadzie właśnie do tego celu były przewidziane.
  • Problem z kompilacją modułów (w Idei). Chodzi o zależności między modułami. W tym przypadku SSB musi być w module WEB, a to nie jest dobre rozwiązanie. Na szczęście znalazłem inne rozwiązanie, które zostało opisane w osobnym wpisie w tym blogu.

Brak komentarzy:

Prześlij komentarz