DWR and Spring
- Initial Considerations
- DWR and Spring step by step - Step 1 - Give DWR access to the Spring context
- DWR and Spring step by step - Step 2 - Configure DWR's remoting
- Use the DWR/Spring namespace (Spring 2.5 or greater, DWR 2.x or greater, dwr.xml not required or recommended)
- Use the DWR/Spring namespace with annotations (Spring 2.5 or greater, DWR 2.x or greater, dwr.xml not required or recommended)
- Use the Spring Creator (Will work for older versions of Spring and DWR - dwr.xml required. Not the recommended approach.)
- Solving Common Problems
Initial considerations
- Make sure you have the appropriate version of Spring. DWR 3 requires Spring version 2.5 or greater.
- Make sure you understand everything on the getting started with DWR page.
- Make sure your Spring beans work properly outside of DWR (unit test).
- Select your configuration style based on the version of Spring you are using, etc. (see below).
- Configure DWR to work with Spring (see below).
- Check the DWR generated test page:
http://localhost:[PORT]/[YOUR-WEBAPP]/dwr/index.htmlto make sure your spring beans appear.
Step 1 - Give DWR access to the Spring context
In order to integrate DWR with Spring, DWR needs to gain access to the Spring context. There are two options here:
Use Spring MVC
If you are using Spring MVC your web.xml should look something like this:
<servlet>
<servlet-name>springDispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value> classpath:yourSpringContext.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springDispatcher</servlet-name>
<url-pattern>*.html</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>springDispatcher</servlet-name>
<url-pattern>/dwr/*</url-pattern>
</servlet-mapping>
The only thing you need to add is the mapping of /dwr/* to the Spring dispatcher servlet. If you are mapping your dispatcher servlet to / or /*, you will still need the /dwr/* mapping and it must be defined before the global mapping. Please see our common problems section for more details.
A complete working example of this configuration can be found here.
Use the DWRSpringServlet
The DwrSpringServlet can be used if you are not using Spring MVC. This servlet gains access to the Spring context configured in your web.xml. Example web.xml:
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
classpath:yourSpringContext.xml
</param-value>
</context-param>
<servlet>
<servlet-name>dwr</servlet-name>
<servlet-class>org.directwebremoting.spring.DwrSpringServlet</servlet-class>
<init-param>
<param-name>debug</param-name>
<param-value>true</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>dwr</servlet-name>
<url-pattern>/dwr/*</url-pattern>
</servlet-mapping>
A complete working example of this configuration can be found here.
Step 2 - Configure DWR's remoting
The next and final step is to configure DWR's remoting - once again there are several options. In the past (3 below) this step was accomplished in the dwr.xml via Creators and Converters. However, Spring 2.x introduced a new feature named XML Namespace Handlers. This allows DWR and Spring MVC to remote Spring beans easily with a custom syntax and removes the need for dwr.xml. Configuration for the custom namespace is covered in 1 and 2 below.
- Use the DWR/Spring namespace (Spring 2.5 or greater, DWR 2.x or greater, dwr.xml not required or recommended)
- Use the DWR/Spring namespace with annotations (Spring 2.5 or greater, DWR 2.x or greater, dwr.xml not required or recommended)
- Use the Spring Creator (Will work for older versions of Spring or DWR - dwr.xml required. Not the recommended approach.)
The namespace
The first task you need to accomplish is adding the following lines (in bold, below) to any of your Spring XML files that includes at least one DWR specific tag. Add them inside the beans declaration (at the beginning of the file):
<beans
xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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-2.5.xsd
http://www.directwebremoting.org/schema/spring-dwr
http://www.directwebremoting.org/schema/spring-dwr-3.0.xsd">
Please continue reading for an overview of some of the critical features of the DWR/Spring schema. To view the complete capabilities you may want to view the schemas directly - For DWR 3.x users spring-dwr-3.0.xsd - For DWR 2.x users spring-dwr-2.0.xsd.
The controller tag
If you are not using Spring MVC - skip this section. The controller tag only applies to Spring MVC configurations.
If you are using Spring MVC you must declare one <dwr:controller id="dwrController" debug="true" /> tag.
<dwr:controller id="dwrController" debug="true"/>You may also specify configuration parameters for the dwrController:
<dwr:controller id="dwrController" debug="true">
<dwr:config-param name="activeReverseAjaxEnabled" value="true"/>
</dwr:controller
In Spring MVC, for each controller you have to map the URLs that it will handle. The simplest way to do this is by using the <dwr:url-mapping /> tag. If you use the url-mapping tag the DWR test page will be unavailable.
Alternatively, you may specify your own SimpleUrlHandlerMapping. DWR needs mappings for the following URLs: /engine.js, /interface.js, /call/**, /interface/**. An example of a SimpleUrlHandlerMapping that will allow the DWR test page to work can be found here (see step 2, numbered item 3). Spring mappings can be tricky. Please see our section on mappings if you are having problems.
The configuration tag
The <dwr:configuration/> is used to mimic the behavior of the configuration available in dwr.xml. This tag is optional and it may have nested tags (init, creator, signatures,..). These nested tags mimic the behavior of those available in dwr.xml. Example:
<dwr:configuration>
<dwr:convert type="bean" class="org.uk.ltd.dwr.dev.model.Address" />
</dwr:configuration>
The remote tag
Inside each bean you want to remote include a <dwr:remote javascript="Fred"> tag. There you can specify the methods that are going to be proxied and those that won't. For example:
<bean id="timeConvert" class="com.mycompany.ui.util.TimeConvert">
<dwr:remote javascript="AjaxTimeConvert">
<dwr:include method="convert" />
</dwr:remote>
</bean>
Exposing beans in other application contexts - the proxy-ref element
It is also possible to remote a bean already defined in a reachable application context indicating the reference to it by using the proxy-ref element.
Two complete (one with Spring MVC, one without) working examples of DWR and Spring using the namespace can be found here.
The namespace with Annotations
Before attempting to integrate DWR and Spring with annotations you should be familiar with the DWR/Spring namespace. Below is a listing of the most important DWR annotations and their functional namespace equivalents:
- @RemoteProxy - Functional namespace equivalent = dwr:remote
- @RemoteMethod - Functional namespace equivalent = dwr:include method="methodName"
- @DataTransferObject - Functional namespace equivalent = dwr:convert
If you would like to use annotations to remote your Spring beans DWR provides two tags that make it easy.
- annotation-scan - Enables DWR to scan the classpath and:
- Detect beans annotated with @RemoteProxy & @RemoteMethod, and register the Spring beans and DWR Creator proxies for them. Because DWR is registering the beans into the Spring context for you all Spring annotated beans (@Service, @Component, @Controller, etc.) will be ignored to avoid duplicate registration.
- Detect DWR converters annotated with @DataTransferObject.
- base-package - The base package to initiate scanning from - i.e. com.myApp.*.
- regex - A regular expression that will be used in the classpath scanner.
- scanRemoteProxy - Should DWR scan for remote proxies? Defaults to true.
- scanDataTransferObject - Should DWR scan for converters? Defaults to true.
- scanGlobalFilter - Defaults to true.
- annotation-config - Enables DWR to scan the Spring context, detect beans annotated with @RemoteProxy & @RemoteMethod and register the DWR Creator proxies for them.
A complete working example of a dwr:annotation-scan configuration can be found here.
Annotations - An example of the configuration required for a typical use-case
<dwr:annotation-config id="dwrAnnotationConfig" /> <dwr:annotation-scan base-package="com.yourpackage.whatever" scanDataTransferObject="true" scanRemoteProxy="false" />
In the example above:
- The dwr:annotation-config tag scans the Spring context and adds DWR creator proxies for beans annotated with @RemoteProxy.
- The dwr:annotation-scan tag scans the classpath and adds DWR converters for all beans annotated with @DataTransferObject. We have disabled the scanning for remote proxies - in this case it is assumed the DWR creator proxies are existing beans in the Spring context and the dwr:annotation-config tag will add them.
The Spring Creator and dwr.xml
If you feel comfortable using dwr.xml you may use the Spring creator. This creator will lookup beans in your Spring <beans>.xml file and rely on Spring to instantiate them. This creator will be useful to you if you already use Spring and totally useless if you don't.
You allow DWR to use the spring creator to create and remote your beans as follows:
<allow>
...
<create creator="spring" javascript="Fred">
<param name="beanName" value="Shiela"/>
</create>
...
</allow>
There are several ways to find your spring configuration files:
Spring MVC
Define a DispatcherServlet and declare the beans in <dispatcher>-servlet.xml. There are no specifics for DWR remoted beans.ContextLoaderListener
For other MVC frameworks.Using location* parameters
If you prefer to specify which beans.xml to use in your dwr.xml file then you can use a location* parameter. You can specify as many as you wish although the names must be unique and start 'location'. For example: location-1, location-2. These locations are used as parameters to a Spring ClassPathXmlApplicationContext:<allow> ... <create creator="spring" javascript="Fred"> <param name="beanName" value="Shiela"/> <param name=location value="beans.xml"/> </create> ... </allow>Setting the BeanFactory directly
The SpringCreator has a staticsetOverrideBeanFactory(BeanFactory)method that provides a way to programatically override any BeanFactories found by other means (if any).
Please, take into account that not all methods are equally easy in practice. Probably, unless you know what you're doing you're better served using one of the first two.
Common Problems
Scoped Beans
One of the common pitfalls when integrating Spring and DWR are scoped beans (session, request, ...). In practice is easy to get them going, just remember two basic rules.
- Always declare and implement an interface with the remoted methods.
- Remember to include
<aop:scoped-proxy proxy-target-class="false" />in the bean declaration
Here's an example:
<bean id="calc" class="...CalculatorImpl" scope=session>
<dwr:remote javascript="Calculator">
<dwr:include method="add"/>
</dwr:remote>
<aop:scoped-proxy proxy-target-class="false" />
</bean>
Aspects & DWR
If you're receiving the dreaded object is not an instance of declaring class error always check the following:
- You have an interface and an implementation
- You have declared
<aop:aspectj-autoproxy proxy-target-class="false" />in your Spring XML - You have decorated your remoted bean with
<aop:scoped-proxy />
Mappings! Mappings! Mappings!
It is important to note that the creation of the SimpleUrlHandlerMapping may cause your existing mappings to fail if you have not explicitly created a Handler Mapping in your Spring configuration. By default Spring creates a BeanNameUrlHandlerMapping if you have not explicitly created a Handler Mapping. So when the SimpleUrlHandlerMapping is created for DWR, Spring will no longer create the default BeanNameUrlHandlerMapping and existing mappings will not work. Spring allows you to have multiple Handler Mappings, to fix this you need to create a BeanNameUrlHandlerMapping explicitly in your spring.xml (in addition to the SimpleUrlHandlerMapping). See the Spring documentation section 13.4.1 for more information.
Id is required for element 'annotation-config' when used as a top-level tag
This appears to be happening with Spring 3.x and above. We use a Spring class to parse the annotation-config element and if you do not specify an id the parser is unhappy. For now a solution to this appears to be simply adding an id attribute.
/ or /* Spring MVC Dispatcher Mapping
If you have mapped Spring MVC's dispatcher servlet to / or /* you will run into issues with DWR. Example (don't do this):
<servlet>
<servlet-name>springDispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value> classpath:yourSpringContext.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springDispatcher</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
This mapping is problematic for DWR. The problem arises when Spring sends the request to the DWR Controller Servlet - the path info will be null due to the / or /* mapping. This situation will prevent DWR from functioning properly. To resolve this issue always include a /dwr/* mapping before any root mappings (/, /*, etc.). Example (do this):
<servlet>
<servlet-name>springDispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value> classpath:yourSpringContext.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springDispatcher</servlet-name>
<url-pattern>/dwr/*</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>springDispatcher</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>