What is DWR?
DWR is a Java library that enables Java on the server and JavaScript in a browser to interact and call each other as simply as possible.
DWR is Easy Ajax for Java
Source: http://directwebremoting.org/dwr/index.html
What is AJAX?
Ajax is a group of interrelated web development methods used on the client-side to create interactive web applications. With Ajax, web applications can retrieve data from the server asynchronously in the background without interfering with the display and behavior of the existing page. Data is usually retrieved using the XMLHttpRequest object. Despite the name, the use of XML is not needed, and the requests need not be asynchronous.
Like DHTML and LAMP, Ajax is not one technology, but a group of technologies. Ajax uses a combination of HTML and CSS to mark up and style information. The DOM is accessed with JavaScript to dynamically display, and to allow the user to interact with the information presented. JavaScript and the XMLHttpRequest object provide a method for exchanging data asynchronously between browser and server to avoid full page reloads.
Source: http://en.wikipedia.org/wiki/Ajax_(programming)
Our application is a simple arithmetic operation that adds two numbers and displays the sum. Here's a screenshot of the non-AJAX version:
Here's a screenshot of the AJAX version:
Notice nothing much is different, except that the non-AJAX version will display the result on another page, while the AJAX version will display on the same page. Actually, this is the main difference! With AJAX we have a responsive, desktop-like application. No page refresh.
Non-AJAX Version
Let's develop first our non-AJAX Spring MVC application.We need a controller to handle the user's requests. Let's call it NonAjaxController
NonAjaxController
package org.krams.tutorial.controller; import javax.annotation.Resource; import org.apache.log4j.Logger; import org.krams.tutorial.service.ArithmeticService; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; /** * Handles and retrieves the main requests */ @Controller @RequestMapping("/main/nonajax") public class NonAjaxController { protected static Logger logger = Logger.getLogger("controller"); @Resource(name="springService") private ArithmeticService springService; /** * Handles and retrieves the non-AJAX, ordinary Add page */ @RequestMapping(value="/add", method = RequestMethod.GET) public String getNonAjaxAddPage() { logger.debug("Received request to show non-AJAX, ordinary add page"); // This will resolve to /WEB-INF/jsp/nonajax-add-page.jsp return "nonajax-add-page"; } /** * Handles request for adding two numbers */ @RequestMapping(value = "/add", method = RequestMethod.POST) public String add(@RequestParam(value="inputNumber1", required=true) Integer inputNumber1, @RequestParam(value="inputNumber2", required=true) Integer inputNumber2, Model model) { logger.debug("Received request to add two numbers"); // Delegate to service to do the actual adding // This service is the same service that DWR uses! Integer sum = springService.add(inputNumber1, inputNumber2); // Add to model model.addAttribute("sum", sum); // This will resolve to /WEB-INF/jsp/nonajax-add-result-page.jsp return "nonajax-add-result-page"; } }This controller declares the following mappings:
/main/nonajax/add (GET) - retrieves the add page /main/nonajax/add POST) - computes the sum and retrieves the result pageThe first mapping receives a request to display the add page. The second mapping receives two numbers and delegates the computation to the ArithmeticService. When the ArithmeticService is done processing, the controller then forwards the result to another JSP page which displays the result.
Here are the JSP pages:
nonajax-add-page.jsp
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Spring MVC - DWR Integration Tutorial</title> </head> <body> <h3>Spring MVC - DWR Integration Tutorial</h3> <h4>Non-AJAX version</h4> <c:url var="addUrl" value="/krams/main/nonajax/add" /> <form method="POST" action="${addUrl}"> Demo 1 <div style="border: 1px solid #ccc; width: 250px;"> Add Two Numbers: <br/> <input id="inputNumber1" name="inputNumber1" type="text" size="5"> + <input id="inputNumber2" name="inputNumber2" type="text" size="5"> <input type="submit" value="Add" /> <br/> Sum: (Result will be shown on another page) </div> </form> </body> </html>
nonajax-add-result-page.jsp
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Spring MVC - DWR Integration Tutorial</title> </head> <body> <h3>Spring MVC - DWR Integration Tutorial</h3> <h4>Non-AJAX version</h4> <c:url var="addUrl" value="/krams/main/nonajax/add" /> <form method="POST" action="${addUrl}"> Demo 1 <div style="border: 1px solid #ccc; width: 250px;"> Add Two Numbers: <br/> <input id="inputNumber1" name="inputNumber1" type="text" size="5"> + <input id="inputNumber2" name="inputNumber2" type="text" size="5"> <input type="submit" value="Add" /> <br/> Sum: (Result will be shown on another page) </div> </form> </body> </html>
Let's run the application. We'll be adding two numbers: 5 and 10, and we expect 10 as the result.
To access the Add page, enter the following URL in your browser:
http://localhost:8080/spring-mvc-dwr/krams/main/nonajax/add
Here's the result:
Now let's examine the service that performs the actual processing:
ArithmeticService
package org.krams.tutorial.service; import org.apache.log4j.Logger; import org.directwebremoting.annotations.RemoteMethod; import org.directwebremoting.annotations.RemoteProxy; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; /** * @Service enables the class to be used as a Spring service * @RemoteProxy enables the class to be used as a DWR service * @Transactional enables transaction support for this clas */ @Service("springService") @RemoteProxy(name="dwrService") @Transactional public class ArithmeticService { protected static Logger logger = Logger.getLogger("service"); /** * @RemoteMethod exposes this method to DWR. * Your JSP pages can access this method as Javascript * Your Spring beans can still access this method. */ @RemoteMethod public Integer add(Integer operand1, Integer operand2) { logger.debug("Adding two numbers"); // A simple arithmetic addition return operand1 + operand2; } }This is a very simple POJO service that contains a simple arithmetic function. To make this POJO available as a Spring service bean, we just add the @Service annotation.
Notice we've also annotated the class with @RemoteProxy. This enables the same POJO to be utilized by DWR! This means to integrate DWR in our existing application, we don't need to duplicate existing code.
AJAX Version
Let us now convert our non-AJAX application to an AJAX-powered version.We have already enabled our existing ArithmeticService to be utilized by DWR. That means we just need to create our JSP pages to display the AJAX version of our application. Actually we just need to create one JSP page because both the request and result will be shown on the same page! This is the primary benefit of AJAX. We also need another controller to handle the page request for the AJAX version of our application.
Here is the controller.
AjaxController
package org.krams.tutorial.controller; import javax.annotation.Resource; import org.apache.log4j.Logger; import org.krams.tutorial.service.ArithmeticService; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; /** * Handles and retrieves the main requests */ @Controller @RequestMapping("/main/ajax") public class AjaxController { protected static Logger logger = Logger.getLogger("controller"); @Resource(name="springService") private ArithmeticService springService; /** * Handles and retrieves the AJAX Add page */ @RequestMapping(value = "/add", method = RequestMethod.GET) public String getAjaxAddPage() { logger.debug("Received request to show AJAX, add page"); // This will resolve to /WEB-INF/jsp/ajax-add-page.jsp return "ajax-add-page"; } }
Here is the JSP page.
ajax-add-page.jsp
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <script type='text/javascript' src="/spring-mvc-dwr/krams/dwr/engine.js"></script> <script type='text/javascript' src="/spring-mvc-dwr/krams/dwr/util.js"></script> <script type="text/javascript" src="/spring-mvc-dwr/krams/dwr/interface/dwrService.js"></script> <title>Spring MVC - DWR Integration Tutorial</title> </head> <body> <h3>Spring MVC - DWR Integration Tutorial</h3> <h4>AJAX version</h4> Demo 1 <div style="border: 1px solid #ccc; width: 250px;"> Add Two Numbers: <br/> <input id="inputNumber1" type="text" size="5"> + <input id="inputNumber2" type="text" size="5"> <input type="submit" value="Add" onclick="add()" /> <br/> Sum: <span id="sum">(Result will be shown here)</span> </div> <script type="text/javascript"> // Retrieves the matching value // Delegates to the dwrService function add() { // Retrieve value of text inputs var operand1 = dwr.util.getValue("inputNumber1"); var operand2 = dwr.util.getValue("inputNumber2"); // Pass two numbers, a callback function, and error function dwrService.add(operand1, operand2, { callback : handleAddSuccess, errorHandler : handleAddError }); } // data contains the returned value function handleAddSuccess(data) { // Assigns data to result id dwr.util.setValue("sum", data); } function handleAddError() { // Show a popup message alert("We can't add those values!"); } </script> </body> </html>Notice in the head section, we have included three JavaScript libraries:
<script type='text/javascript' src="/spring-mvc-dwr/krams/dwr/engine.js"></script> <script type='text/javascript' src="/spring-mvc-dwr/krams/dwr/util.js"></script> <script type="text/javascript" src="/spring-mvc-dwr/krams/dwr/interface/dwrService.js"></script>
What are these libraries?
DWR comes with some small JavaScript libraries to help you:
Source: http://directwebremoting.org/dwr/documentation/index.html
- engine.js: Handles all server communication
- util.js: Helps you alter web pages with the data you got from the server (and a few neat extras too)
The dwrService.js is our ArithmeticService itself! Let's examine how our Java class has been converted to a JavaScript:
dwrService.js
// Provide a default path to dwr.engine if (typeof this['dwr'] == 'undefined') this.dwr = {}; if (typeof dwr['engine'] == 'undefined') dwr.engine = {}; if (typeof dwr.engine['_mappedClasses'] == 'undefined') dwr.engine._mappedClasses = {}; if (window['dojo']) dojo.provide('dwr.interface.dwrService'); if (typeof this['dwrService'] == 'undefined') dwrService = {}; dwrService._path = '/spring-mvc-dwr/krams'; /** * @param {class java.lang.Integer} p0 a param * @param {class java.lang.Integer} p1 a param * @param {function|Object} callback callback function or options object */ dwrService.add = function(p0, p1, callback) { return dwr.engine._execute(dwrService._path, 'dwrService', 'add', arguments); };
Let's examine the JSP page again. Notice it contains two parts: the HTML part that shows the input, and the JavaScript part that handles the AJAX.
HTML part
Demo 1 <div style="border: 1px solid #ccc; width: 250px;"> Add Two Numbers: <br/> <input id="inputNumber1" type="text" size="5"> + <input id="inputNumber2" type="text" size="5"> <input type="submit" value="Add" onclick="add()" /> <br/> Sum: <span id="sum">(Result will be shown here)</span> </div>This is a simple form that takes two numbers. When the Add button is clicked, the JavaScript add() function is called.
JavaScript part
<script type="text/javascript"> // Retrieves the matching value // Delegates to the dwrService function add() { // Retrieve value of text inputs var operand1 = dwr.util.getValue("inputNumber1"); var operand2 = dwr.util.getValue("inputNumber2"); // Pass two numbers, a callback function, and error function dwrService.add(operand1, operand2, { callback : handleAddSuccess, errorHandler : handleAddError }); } // data contains the returned value function handleAddSuccess(data) { // Assigns data to result id dwr.util.setValue("sum", data); } function handleAddError() { // Show a popup message alert("We can't add those values!"); } </script>The add() function is a wrapper to DWR's service named dwrService. Where did we get this name? This is the name we assigned in the ArithmeticService inside the @RemoteProxy
@RemoteProxy(name="dwrService") @Transactional public class ArithmeticService { ... }
The add() function retrieves the value of the form input fields using DWR's utility class dwr.util.getValue():
// Retrieve value of text inputs var operand1 = dwr.util.getValue("inputNumber1"); var operand2 = dwr.util.getValue("inputNumber2");
Then it calls the actual dwrService.add(), passing the two inputs, a callback function to handle the result if successful, a error function to handle error messages:
// Pass two numbers, a callback function, and error function dwrService.add(operand1, operand2, { callback : handleAddSuccess, errorHandler : handleAddError });
The success callback function retrieves the result named data. Then we used DWR' utility class again to update an HTML's value with the result.
// data contains the returned value function handleAddSuccess(data) { // Assigns data to result id dwr.util.setValue("sum", data); }Here the sum is the named of the HTML element we assigned earlier in the HTML part:
Sum: <span id="sum">(Result will be shown here)</span>
Let's run our application and check the result.
To access the Add page, enter the following URL in your browser:
http://localhost:8080/spring-mvc-dwr/krams/main/ajax/add
Here's the result:
The result is displayed on the same page. There's no need to create another page just to view the result.
We're almost done with our tutorial. Now we need to add the required XML configuration to enable both Spring MVC and DWR.
To enable Spring MVC and DWR we need to add both in the web.xml
web.xml
<servlet> <servlet-name>spring</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <!-- Mapping for Spring MVC --> <servlet-mapping> <servlet-name>spring</servlet-name> <url-pattern>/krams/*</url-pattern> </servlet-mapping> <!-- Mapping for DWR --> <servlet-mapping> <servlet-name>spring</servlet-name> <url-pattern>/krams/dwr/*</url-pattern> </servlet-mapping> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener>Take note of the URL patterns. We've separated the URL patterns for pure Spring MVC and DWR calls.
In the web.xml we declared a servlet-name spring. By convention, we must declare a spring-servlet.xml as well.
spring-servlet.xml
<!-- Declare a view resolver --> <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver" p:prefix="/WEB-INF/jsp/" p:suffix=".jsp" />
By convention, we must declare an applicationContext.xml as well.
applicationContext.xml
<!-- Activates various annotations to be detected in bean classes --> <context:annotation-config /> <!-- Scans the classpath for annotated components that will be auto-registered as Spring beans. For example @Controller and @Service. Make sure to set the correct base-package--> <context:component-scan base-package="org.krams.tutorial" /> <!-- Configures the annotation-driven Spring MVC Controller programming model. Note that, with Spring 3.0, this tag works in Servlet MVC only! See Spring Reference 15.12 Configuring Spring MVC @ http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/mvc.html#mvc-annotation-driven--> <mvc:annotation-driven /> <!-- Without the following adapter, we'll get a "Does your handler implement a supported interface like Controller?" This is because mvc:annotation-driven element doesn't declare a SimpleControllerHandlerAdapter For more info See http://stackoverflow.com/questions/3896013/no-adapter-for-handler-exception See http://forum.springsource.org/showthread.php?t=48372&highlight=UrlFilenameViewController --> <bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter" /> <!-- Loads DWR-related configuration --> <import resource="dwr-context.xml" />We've referenced a separate config file dwr-context.xml that contains DWR-related beans.
dwr-context.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p" xmlns:dwr="http://www.directwebremoting.org/schema/spring-dwr" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.directwebremoting.org/schema/spring-dwr http://www.directwebremoting.org/schema/spring-dwr-3.0.xsd"> <!-- DWR will scan all Spring managed beans containing @RemoteProxy or @RemoteMethod annotations and register Creator proxies for them. This will NOT scan any classes not managed by Spring. --> <dwr:annotation-config /> <!-- DWR will scan the classpath and search classes containing @RemoteProxy or @RemoteMethod annotations. This will register the beans and Creator proxies for these classes.--> <dwr:annotation-scan base-package="org.krams.tutorial" scanDataTransferObject="true" scanRemoteProxy="true" /> <!-- DWR will map util.js and engine.js files to the dwrController. You can then include this files as external Javascript references from your JSP --> <dwr:url-mapping /> <!-- Defines the dwrController. During production, set the debug property to false --> <dwr:controller id="dwrController" debug="true" /> <!-- This is required if you want to configure any beans not managed by Spring. Leaving it enabled doesn't do any negative effects. Here's a sample config: <dwr:configuration> <dwr:convert type="bean" class="org.krams.tutorial.CustomClass" /> </dwr:configuration> --> <dwr:configuration /> <!-- Some articles DWR 2.0.x, Spring 2.x, with Spring MVC http://www.butterdev.com/dwr/2008/02/dwr-20x-spring-2x-with-spring-mvc/ --> </beans>
Conclusion
That's it. We've completed our application. We've managed to build a simple Spring MVC 3 application with AJAX capabilities using DWR. We've reused the same service bean both for MVC and DWR. We've also used some of DWR's utility JavaScript libraries.The best way to learn further is to try the actual application.
Download the project
You can access the project site at Google's Project Hosting at http://code.google.com/p/spring-mvc-dwr/
You can download the project as a Maven build. Look for the spring-mvc-dwr.zip in the Download sections.
You can run the project directly using an embedded server via Maven.
For Tomcat: mvn tomcat:run
For Jetty: mvn jetty:run
Updates
March 19, 2010
I have updated the Maven project which now includes the following DWR library in the pom.xml file:
<dependency> <groupId>org.directwebremoting</groupId> <artifactId>dwr</artifactId> <version>3.0.M1</version> <type>jar</type> <scope>compile</scope> </dependency>
The problem with the original Maven project is it uses the latest DWR 3.0.RC1, but it is not available in the Maven repository. So I had to add it manually under the WEB-INF/lib folder. The project works when it's deployed via Eclipse's m2 (Maven) plugin.
However, when it's deployed via the standalone Maven, it complains that some DWR classes are missing. There are a couple of workarounds like adding the library to your local repository. See here: http://stackoverflow.com/questions/2479046/maven-how-to-add-additional-libs-not-available-in-repo. But this would require the user to setup a repo and makes the project not portable.
An alternative approach is to use an older DWR jar. We have to add a Maven reference to an older DWR jar, but we keep the new library under the WEB-INF/lib so that our project will still use the latest jar. For the purpose of this tutorial, you can use this workaround. But for actual development, please use the latest DWR jar (which I use as well for production). To run the application, you still run the same Maven commands:
For Tomcat: mvn tomcat:run
For Jetty: mvn jetty:run
If you want to learn more about Spring MVC and integration with other technologies, feel free to read my other tutorials in the Tutorials section.
Share the joy:
|
Subscribe by reader Subscribe by email Share