I must admit – I’m neither very experienced in the field of user interfaces nor really familiarised with all that
JavaScript, jQuery stuff. I was always thinking of myself more as a service layer guy.
Nevertheless, I wanted to improve my knowledge, so I decided to learn more about something called DWR (Direct Web Remoting).
I’ve heard about it few years ago but never actually passed the “yeah, I’ve heard about it” stage.
This will be very basic introduction, so I’m afraid that for those of you who know what DWR is and used it, this article won’t introduce nothing new.
XMLHttpRequest
Up to know I’ve always thought that if the JavaScript have its XMLHttpRequest
object, it can send asynchronous requests
to the server – so this is basically the AJAX. Who needs anything more?
Well, it seems that the XMLHttpRequest
is quite low-level and as usual, more high-level tools and frameworks
arose. DWR is one of such frameworks, but it goes even further. Not only you don’t have to deal with those low-level
XMLHttpRequest
objects, but you can use your Java objects directly in the JavaScript code.
DWR creates a JavaScript’s proxy for your Java methods and sends the request to invoke particular method
asynchronously – expecting the result in the callback method. You can easily define what Java methods should be
accessible from the JS using the dwr.xml
configuration file you’ll see in a moment.
Server side
Take a look at this Java code – it’s just a regular class with two methods. Let’s pretend it’s a Dice Roller service,
so it allows you to roll a dice (rollDice()
) and to scream at the dealer if you’re not happy with the resulting
pips (scream(String)
.)
package com.piotrnowicki.dwr;
import java.util.Random;
import java.util.logging.Level;
import java.util.logging.Logger;
/** Simulates a 6-sided dice roll. Also allows to scream if you're not
* satisfied with the result.
*
* @author PiotrNowicki
**/
public class DiceRollService {
private Logger log = Logger.getLogger(DiceRollService.class.getName());
/**
* Just for inspection when the object is created.
*/
public DiceRollService() {
log.info("Constructing an object");
}
/**
* Roll a 6-sided dice.
*
* @return rolled pips.
*/
public Integer rollDice() {
return new Random().nextInt(6) + 1;
}
/**
* Scream (e.g. at the dealer) given sentence. Shows how you can
* deal with parameters in DWR.
*
* @param msg
* what to be screamed.
*/
public void scream(String msg) {
log.log(Level.INFO, "Screaming: {0}", msg);
}
}
This is a class which methods we want to be accessible directly from the JS (embedded on our user-interface HTML page).
To achieve this, we need to inform the DWR about the existence of the DiceRollService
class. We do this by
creating a dwr.xml
file in webapp/WEB-INF/
(just next to the web.xml
):
<!DOCTYPE dwr
PUBLIC "-//GetAhead Limited//DTD Direct Web Remoting 3.0//EN"
"http://getahead.org/dwr/dwr30.dtd">
<dwr>
<allow>
<create creator="new"
javascript="JSDiceRollService"
scope="application">
<param name="class"
value="com.piotrnowicki.dwr.DiceRollService" />
</create>
</allow>
</dwr>
The above excerpt tells DWR to create an instance of DiceRollService
using the new
Java operator (you can as well
use static method to obtain your class instance, access bean registered in the Spring Framework and so on – read
this page for more details.)
This newly created object will be named JSDiceRollService
and it’ll be bound to the application scope (similarly as
in Servlets, we have: application, session, page and request scope.)
The last thing we need is to create a DWR Servlet which will be responsible for registering our classes / methods. It
will make them accessible for the DWR client or RESTful client. We do that by creating a web.xml
that looks somewhat
like this:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0">
<servlet>
<servlet-name>dwr-invoker</servlet-name>
<servlet-class>
org.directwebremoting.servlet.DwrServlet
</servlet-class>
<init-param>
<param-name>jsonpEnabled</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<param-name>debug</param-name>
<param-value>true</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>dwr-invoker</servlet-name>
<url-pattern>/dwr/*</url-pattern>
</servlet-mapping>
</web-app>
Notice the /dwr/*
url-pattern has been bound to the DWR Servlet – this is the entry-point to the server-side DWR.
You don’t really need the jsonpEnabled
init-param, but in a moment I’ll show you why it can be useful.
The debug
param allows you to see the test page when you enter the DWR Servlet URL, e.g.:
http://localhost:8080/DWRTest/dwr
(DWRTest
is an application context.)
I’m not aware if this can be configured using the Servlets 3.0 @WebServlet
annotation, but as I don’t see much
added value using it here, I won’t even try digging it.
And that’s pretty much all there is – you’ve created your business logic class in pure Java, instructed DWR that it should create a JS proxy for your class and, finally, you’ve defined and configured the server-side DWR Servlet.
Now it’s time to go to the client layer.
Client Side
First thing you can check after you run your application, is to enter the already mentioned DWR test page (e.g.
http://localhost:8080/DWRTest/dwr
) and see if you can access your Java methods from it. You can observe that your
Java object will be created as many times as appropriate, depending on your scope
attribute value in dwr.xml
.
Every object instantiation is logged, so you can inspect your server logs if you want to check it.
However, let’s go to some more interesting stuff – accessing your Java objects from an arbitrary JavaScript code.
Take a look at the code below (it’s called index.jsp
and it’s located directly in the webapp/
directory):
<!DOCTYPE html
PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
<title>DWR Test</title>
</head>
<body>
<script type="text/javascript"
src="dwr/engine.js"></script>
<script type="text/javascript"
src="dwr/interface/JSDiceRollService.js"></script>
<input type="button" value="Roll a dice" onclick="clicked()" />
<div id="diceRes"></div>
<script type="text/javascript">
function clicked() {
JSDiceRollService.rollDice({ callback : function(str) {
document.getElementById("diceRes").innerHTML = str;
} });
}
// Taken from kdenney's post:
// http://stackoverflow.com/a/155265/920607
function keyPressed(e) {
if (typeof e == 'undefined' && window.event) {
e = window.event;
}
if (e.keyCode == 13) {
JSDiceRollService.scream(
document.getElementById("shoutField").value, {
callback : function(str) {
alert("Check your Java server logs");
}
}
);
}
}
</script>
<br /> <br />
Type any text to shout in Java System.out.println
(ENTER sends the request):
<input id="shoutField"
type="text"
value="You bastard!"
onkeypress="keyPressed(event)" />
</body>
</html>
It’s not the most elegant and prettiest HTML + JavaScript code in the world, but for this exemplary purpose – it does it work. I’m not sure if it will even pass the W3C validator, but that’s not the point now.
Take a look at the <script>
imports at the beginning of the <body>
. It’s value refers to our DWR Servlet URL, so
those scripts are generated by DWR for you. Notice that you’ve also included a JavaScript code for DWR generated
proxy – dwr/interface/JSDiceRollService.js
.
Also, be very careful with the order of imports. The engine.js
script import must occur before your JS proxy
import.
This simple page shows you:
- a button which will invoke
DiceRollService#rollDice()
method and print the result in div with ID =diceRes
, - an input field that after you hit ENTER, will invoke
DiceRollService#scream(msg)
where themsg
is text typed into the field.
That’s basically it – you should be able to successfully send an asynchronous HTTP request which will invoke one of
your Java methods using XMLHttpRequest
under the hood. Pretty neat, huh?
REST
One of the additional features of the DWR is the support for REST’style requests / responses. If you access the
following URL: http://localhost:8080/DWRTest/dwr/jsonp/JSDiceRollService/rollDice
(of course, substituting the
appropriate parts like protocol, host, port and application context) you can access the DiceRollService#rollDice
method as well. The response will be in the JSONP format and it should look something like this:
{ "reply":5 }
Summary
I didn’t test any reverse Ajax support, as I think I’ll write a separate post about it – just when I’ll try to do some tests with Server Sent Events and Web Sockets – so stay tuned!
For the first time I think I understood what the DWR really does and how it can improve your development.
However, I’m curious what will happen if the boundary of the service layer is an EJB. The EJB container is responsible
for creation of the EJB’s and the EJB support for DWR doesn’t seem to be stable yet.
What about the CDI and its scopes? Perhaps the @Produces
CDI method and scope = static
in DWR will do the work?
What is the place of DWR in JSF and managed beans world? What about the JSF managed beans scopes and DWR scopes – how do they comply?
There are a lot of questions I don’t know answer at this point. Nevertheless, I think it’s just a regular matter of picking the right tool to do the job.