gwt@jazoon08 - part 2/6 - attentive service
DESCRIPTION
A presentation about GWT which I presentaed at Jazoon '08Part 2/6 - Attentive ServiceTRANSCRIPT
LOGO SPEAKER‘S COMPANY
# 2
attentive service.
LOGO SPEAKER‘S COMPANY
the doorman hailing a cab ...
LOGO SPEAKER‘S COMPANY
the waiter refilling your glass ...
LOGO SPEAKER‘S COMPANY
opening a new line at the checkout ...
LOGO SPEAKER‘S COMPANY
annoying.
hit search button 10 times before match found
click add button for items that already exist
country dropdowns Belgium = b + 6x down arrow
LOGO SPEAKER‘S COMPANY
intuitive.act as a user not as a system
take all the nerdy and techy stuff out
user laziness is key
- auto complete
- smart tabs (members [0] vs members)
- smart country dropdown (your country on top)
LOGO SPEAKER‘S COMPANY
gwt rpc.[ for dummies ]
LOGO SPEAKER‘S COMPANY
overview.moves Java instances between client and server
uses Serializable + IsSerializable marker interface
polymorphism is supported but at a cost
generator creates the necessary marshaling code
supports Java 1.5 language constructs
built on top of RequestBuilder (XHR)
LOGO SPEAKER‘S COMPANY
DEFINE REMOTE SERVICE
@RemoteServiceRelativePath(“wishlistService.do”) // moduleBaseURL + value
public interface WishlistService extends RemoteService {
List<Wish> findMatches(String query);
}
IMPLEMENT REMOTE SERVICE
public class WishlistServiceImpl extends RemoteServiceServlet
implements WishlistService {
public List<Wish> findMatches(String query) { ... do something ... }
}
LOGO SPEAKER‘S COMPANY
DEFINE ASYNC SERVICE
public interface WishlistServiceAsync {
void findMatches(String query, AsyncCallback async);
}
CALL SERVICE
WishlistAsyncServiceAsync service = GWT.create(WishlistService.class);
service.findMatches(“abc”, new AsyncCallback<List<Wish>>() {
public void onSuccess(List<Wish> matches) { ... }
public void onFailure(Throwable caught) { ... }
});
LOGO SPEAKER‘S COMPANY
gwt rpc.[ best practices ]
LOGO SPEAKER‘S COMPANY
use stateless servers – better handling / scaling
keep conversational state on the client
balance your RPC calls, think about latency
only pull data that is really being used
paginate on the server
…
LOGO SPEAKER‘S COMPANY
PROBLEM: NESTED INNER CALLBACK SYNDROME
// async call #1
myService.doFirstRequest(“foo”, new AsyncCallback<String>() {
public void onSuccess(String result) {
// async call #2
myService.doSecondRequest(result, new AsyncCallback<String>() {
public void onSuccess(String result) {
// and more and more and moreeee async calls on the way
}
public void onFailure(Throwable caught) { ... }
});
}
public void onFailure(Throwable caught) { ... }
});
LOGO SPEAKER‘S COMPANY
BAD SOLUTION: NESTED CALLBACKS BECOME CLASSES
myService.doFirstRequest(“foo”, new FirstRequestHandler());
class FirstRequestHandler implements AsyncCallback {
public void onSuccess(String result) {
myService.doSecondRequest(result, new SecondRequestHandler());
public void onFailure(Throwable caught) { ... }
}
class SecondRequestHandler implements AsyncCallback {
…
}
LOGO SPEAKER‘S COMPANY
SOLUTION: REFACTOR NESTED CALLBACKS INTO METHODS
myService.doFirstRequest(“foo”, handleFirstRequest());
protected AsyncCallback handleFirstRequest() {
return new AsyncCallback<String>() {
public void onSuccess(String result) {
myService.doSecondRequest(result, handeSecondRequest());}
public void onFailure(Throwable caught) { ... }
}
}
protected AsyncCallback handleSecondRequest() { … }
LOGO SPEAKER‘S COMPANY
real world issues.
GWT + JPA = KABOOMMM !!!
back to the future with DTOs ???
SpringFramework integration ???
LOGO SPEAKER‘S COMPANY
gwt rpc.[ demystified ]
LOGO SPEAKER‘S COMPANY
WishlistServiceAsync service = (...) GWT.create(WishlistService.class);
service.findMatches(“abc”, new AsyncCallback<List<Wish>>() {
public void onSuccess(List<Wish> matches) { ... }
public void onFailure(Throwable caught) { ... }
});
WishlistServiceImpl extends RemoteServiceServlet
???
LOGO SPEAKER‘S COMPANY
RequestBuilderRemoteServiceProxy
WishlistServiceAsync service = (...) GWT.create(WishlistService.class);
service.findMatches(“abc”, new AsyncCallback<List<Wish>>() {
public void onSuccess(List<Wish> matches) { ... }
public void onFailure(Throwable caught) { ... }
});
WishlistServiceImpl RemoteServiceServlet
WishlistServiceAsync service
public void onSuccess(List<Wish> matches) { ... }
public void onFailure(Throwable caught) { ... }
RemoteServiceProxy
- configure RequestBuilder - method = POST - header = text/x-gwt-rpc - url - data- invoke RequestBuilder send()
RemoteServiceServlet
- invoke implementation- write response
WishlistService_ProxyWishlistService_Proxy [Generated]WishlistService_Proxy [Generated]
- prepare stream
XHR
RequestCallbackAdapterRequestCallbackAdapterRequestCallbackAdapter
- trigger client callback for: - onError - onResponseReceived
XHR
- response found- trigger callback
XHRXHR
- make request- listen to response
RequestBuilder
- init XHR callback- open XHR- send data
LOGO SPEAKER‘S COMPANY
WishlistService_Proxy.java [generated]
public class WishlistService_Proxy extends RemoteServiceProxy implements WishlistServiceAsync {
public void findMatches(String text, AsyncCallback async) { ... ClientSerializationStreamWriter w = createStreamWriter(); // _TypeSerializer w.writeString(“WishlistService”); // Remote service I/F name w.writeString("findMatches"); // MethodName w.writeInt(1); // Number of parameters w.writeString("java.lang.String"); // First parameter type w.writeString(text); // First parameter value
String payload = w.toString(); doInvoke(ResponseReader.OBJECT, “WishlistService_Proxy.findMatches", getRequestId(), payload, async); } }
LOGO SPEAKER‘S COMPANY
RemoteServiceProxy.java
public abstract class RemoteServiceProxy implements ... {
protected <T> Request doInvoke(ResponseReader reader, String methodName, int invocationCount, String requestData, AsyncCallback<T> async) {
RequestBuilder rb = new RequestBuilder(RequestBuilder.POST, “WishlistService.rpc”); rb.setHeader("Content-Type", "text/x-gwt-rpc; charset=utf-8"); rb.setCallback(responseHandler); // RequestCallbackAdapter rb.setRequestData(requestData);
try { rb.send(); } catch (RequestException re) { callback.onFailure(new InvovationException(re))
} }
LOGO SPEAKER‘S COMPANY
RequestCallbackAdapter.java
public class RequestCallbackAdapter<T> implements RequestCallback {
public void onError(Request request, Throwable exception) { callback.onFailure(exception); }
public void onResponseReceived(Request request, Response response) { String encodedResponse = response.getText(); int statusCode = response.getStatusCode();
if (statusCode != Response.SC_OK) { // + multiple other exception related test !!! caught = new StatusCodeException(statusCode, encodedResponse); } else { result =
(T) responseReader.read(streamFactory.createStreamReader(encodedResponse)); }
if (caught == null) { callback.onSuccess(result); } else { callback.onFailure(caught);}
LOGO SPEAKER‘S COMPANY
RemoteServiceServlet.java
public class RemoteServiceServlet extends HttpServlet implements ... {
public final void doPost(HttpServletRequest request, HttpServletResponse response) {
perThreadRequest.set(request); perThreadResponse.set(response); String requestPayload = RPCServletUtils.readContentAsUtf8(request); RPCRequest rpcRequest = RPC.decodeRequest(requestPayload, …);
String responsePayload = RPC.invokeAndEncodeResponse(...); // calls remote method on WishListServiceImpl class
RPCServletUtils.writeResponse(..., responsePayload, ...);
LOGO SPEAKER‘S COMPANY
gwt rpc.[ rocked science ]
LOGO SPEAKER‘S COMPANY
CUSTOMIZING RPC SERIALIZATION
PURPOSE
Null out lazy collections and load lazy proxy objects
EXAMPLE (subclass ServerSerializationStreamWriter)
public void serializableValue(Object value, Class type) ... {
else if (type == java.util.Set.class) {
Set hashSet = new HashSet();
if (value instanceof PersistentSet) {
PersistentSet persistentSet = (PersistentSet) value;
if (persistentSet.wasInitialized()) { hashSet.addAll(persistentSet);
LOGO SPEAKER‘S COMPANY
gwt rpc.[ new features ]
LOGO SPEAKER‘S COMPANY
ACCESSING THE REQUEST OBJECT
PURPOSE
useful for canceling the HTTP request used by RPC
EXAMPLE
public interface WishlistServiceAsync {
Request findMatches(String query, AsyncCallback async);
}
LOGO SPEAKER‘S COMPANY
ACCESSING THE REQUESTBUILDER OBJECT
PURPOSE
provides access to HTTP timeouts and headers
caller must manually call requestBuilder.send();
EXAMPLE
public interface WishlistServiceAsync {
RequestBuilder findMatches(String query, AsyncCallback async);
}
LOGO SPEAKER‘S COMPANY
INLINE PRESERIALIZED PAYLOADS (1/3)
PURPOSE
replace an RPC call that always takes place right after startup
one less call to make to the server (Turbo boost for startup)
LOGO SPEAKER‘S COMPANY
INLINE PRESERIALIZED PAYLOADS (2/3)
CREATE REMOTE SERVICE INTERFACE AS USUAL
public interface MyRemoteService extends RemoteService {
MyPayload aMethod(String var);
}
CREATE SERIALIZED VALUE ON THE SERVER-SIDE
String myPayload = RPC.encodeResponseForSuccess(…);
EMBED PAYLOAD INTO HTML PAGE (SiteMesh / Servlet / …)
<script>window.myPayload = “MyPayload generated String”</script>
LOGO SPEAKER‘S COMPANY
INLINE PRESERIALIZED PAYLOADS (3/3)
READ PRESERIALIZED STRING IN THE CLIENT
SerializationStreamFactory f = GWT.create(MyRemoteService.class);
MyPayload myPayload = f.createStreamReader(getPayload()).readObject();
native String getPayload() /*- {
return $wnd.myPayload;
} -*/
LOGO SPEAKER‘S COMPANY
events.
LOGO SPEAKER‘S COMPANY
ASYNC CALLBACK EVENTS
act upon success / failure of asynchronous callbacks
public interface AsyncCallback<T> { void onFailure(Throwable caught); void onSuccess(T result); }
LOGO SPEAKER‘S COMPANY
EVENT LISTENERS
listen upon events being triggered implement EventListener adapters available for some don’t forget to detach the listeners when destroying the widget
examples
History.addHistoryListener(historyListener); myButton.addClickListener(clickListener) myTextField.addKeyboardListener(keyboardListenerAdapter)
LOGO SPEAKER‘S COMPANY
NATIVE DOM EVENTS
public class MyWidget extends Widget {
public myWidget() {
setElement(DOM.createDiv());
sinkEvents(Event.ONCLICK()); // event bubbling
}
public void onBrowserEvent(Event event) {
switch (DOM.eventGetType(event)) {
case Event.ONCLICK: // do something…; break;
}
}
}
LOGO SPEAKER‘S COMPANY
MEMORY LEAK
in some browsers any reference cycle that involves a JavaScript object and a DOM element has a nasty tendency to never get garbage-collected
hybrid system
DOM objects are referenced through reference counting JS object is using garbage collection
example
obj = document.getElementById("DivElement"); document.getElementById("DivElement").propertyX = obj; JS object “obj” DOM object “DivElement” obj / propertyX
LOGO SPEAKER‘S COMPANY
MAARTENVOLDERS.comPASSIONATE ABOUT PEOPLE AND TECHNOLOGY