lunes, 14 de mayo de 2007

Integrating DWR & Spring

As of lately, the DWR mailing list is full of messages from people suffering several problems when trying to tie DWR and Spring. The fact that they try it, it's an unambiguous sign that both technologies can and do play nice together. In fact, it's not unusual to see people from Interface21 (when will you start hiring in Spain!) answering questions there. Bram Smeets has even blogged about all this (several times indeed). But, even with their support, it's not unusual that some topics rise time and again because both technologies deal with complex tasks and have some subtle configuration possibilities. That's the reason that pushed me to make this little guide, offer some clues on how to determine the cause of some of the most common errors anyone can expect to see and, if possible, some workarounds.

Let's start with the basics. The essence of Spring is about dependency injection of plain objects (although it does much more). . The essence of DWR is to expose Java, objects running at the server, in remote (client) contexts. In the end, both have to deal with two concepts: beans and scopes. The most times, our effort is spent configuring both sides and then trying to mix them together. Fortunately, DWR includes the tools to make the task easier.

In the beginning (and it still can be used today), Spring beans were made available to the DWR context using the dwr.xml file. Here's a complete example of doing it. Basically, the <allow> tag includes a creator for each of the beans that should be remoted. Some extra configuration is needed to tell DWR where and how the Spring context can be reached. Several options are possible:
  • Expose the context with a listener (ContextLoaderListener)
  • Indicate the location of the configuration XML
  • Wrapping the servlet
Probably the first option is easier to read and understand for the begginer.

Since version of DWR (and Spring), the developers can opt for using the DWR namespace directly in Spring. This has several advantages:
  • Less files as dwr.xml is not needed anymore
  • Don't need to touch the web.xml file either (if using Spring MVC)
  • More readable and easier to maintain
  • One, unique, repository for configuration (avoid mess)
  • Tightly integrates Spring & DWR
Here's how to use it. First declare the XML 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.xsd
       http://www.directwebremoting.org/schema/spring-dwr
       http://www.directwebremoting.org/schema/spring-dwr-2.0.xsd“>

Then add the needed DWR stuff:

<dwr:controller id="dwrController" debug="true" />

<bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
   <property name="mappings">
      <props>
         <prop key="/engine.js">dwrController</prop>
         <prop key="/util.js">dwrController</prop>
         <prop key="/interface/**">dwrController</prop>
         <prop key="/call/**">dwrController</prop>
      </props>
   </property>
</bean>

The declare the beans that will be exposed adding a simple tag:

<bean id="aBean" class="package.BeanClass">
   <dwr:remote javascript="RemoteName">
      <dwr:include method="remoteMethod" />
   </dwr:remote>

</bean>

If the bean(s) happen to return objects (unneeded with primitive types or strings) DWR needs to be informed on how to transform them to JSON:

<dwr:configuration>
   <dwr:convert type="bean" class="package.ReturnedBeanClass" />
</dwr:configuration>

This is enough for 90% of the cases a user would need. I haven't looked at signatures (unneded for Java5+) or other advanced functionality (init for example) but everything is available looking at the schema. Take into account that some parts are not targeted to the end user but to developers.

There's one last thing that I would like to consider because it's the origin of 90% of the errors that pop up from time to time: scopes. In DWR, objects can be created with a scope, very similar to the servlet spec (application, session, request and page). In Spring beans are defined as singleton, prototype, request, session, global session or even creating our custom scopes. It's easy to see the cause of the confusion. The problem is aggravated by some circumstances:
  • DWR scope defaults to page, Spring to singleton
  • The schema doesn't have any reference to scopes
  • Spring uses AOP and proxies to implement scoped beans
  • A DWR request is not directly bindded to the Spring context
Let's try to review case by case:
  • Default scopes: The page scope works correctly when backed by singletons or session scoped beans. It does not work for prototype or request scoped beans! Take into account that DWR won't really distinguish prototype and request scoped beans.
  • Schema & scopes: Right now the schema is incomplete and no mappings can be made. That is, if you use this way of configuring, all the Spring remoted beans inherit the page scope (this is an educated guess, the documentation is scarce).
  • Singleton beans: As is well known, Spring shines when working with singletons. As a plus, no proxies are created when working with singleton beans. Whenever DWR ask the application context for the bean, Spring always returns the same instance. A developer should try hard to make all his beans singletons (at least those exposed to DWR). To optimize performance, a singleton bean should actually be mapped to application scope in DWR (so DWR saves trips to Spring).
  • AOP proxies: The trick here is done understanding why AOP proxies are needed and when does Spring create a proxy. It's more or less clear that proxy is created when injecting a scoped bean as a dependency. What people tend to forget is that the beans are in fact injected in DWR, so a proxy is needed even if the bean has no dependencies in the Spring context. So, as a rule of thumb, everytime that a scoped bean needs to be remoted <aop:scoped-proxy/> has to be declared (add <aop:scoped-proxy proxy-target-class="false" /> to ensure JDK proxies are constructed). A subtle error happens when omitting the above declaration because the bean is being already proxied (ie. because of transactions or other interceptors). Don't be fooled, a scoped proxy also has to be created!
  • Prototype beans: There are two possible scenarios. The first one is composed by beans explicitly declared as prototype, the second by lookup-methods. This second case deserves special treatment because CGLIB proxies are a requisite (if using ProxyFactoryBean remember to establish autodetectInterfaces to false). Both types should be mapped to request scope in DWR to ensure a new object is created for each petition.
  • Web scopes in Spring: The most common error is not being able to access the request/session with this type of scoped beans. This usually caused by an incorrect binding of the request to the thread. If using Spring MVC the DispatcherServlet does it automaticall, otherwise a RequestContextListener must be declared (in addition to the DwrSpringServlet). Finally remains the question of the mapping to DWR. For session beans, the default page scope is a good enough candidate, although not optimal performance wise (that's what the session scope would stand for). Request scoped beans should map to DWR's request scope and the request should be bounded. If possible it's probably easier (and will avoid common pitfalls) to use a singleton bean and get the request as a parameter.

16 comentarios:

DJ dijo...

Hi Jose,

If you are architecting a solution using DWR and Spring where you will using DWR, i mean whether DWR will be used only for READ ( like autocomplete or filling a data table) or will it be used for writing to a database ?
If you are using DWR to write to a database what will be strategy adopted? Will you use pop ups (dialog windows) to fill the data?

Regards,
Deepak

Jose Noheda dijo...

DWR, if correctly implemented, leverages your Spring services quite well. My advice would be to create beans flexible enough to answer AJAX calls in addition to common HTTP requests without the need of extra coding.

CTRL Space dijo...

So in the dwr clock example (http://www.directwebremoting.org/demo/clock/index.html) if I configure my clock like:

<dwr:configuration>
<dwr:create javascript="Clock" type="new" class="com.pizza73.service.dwr.impl.Clock" />
</dwr:configuration >
[/code]
This means that a new Clock object will be intantiated with each request? Is it possible to only have one Clock thread running?

Jose Noheda dijo...

Don't use the create but the remote tag. Declare the bean as singleton in Spring (it's so by default) and you have it.

ads dijo...

Thanks for the guide. I forgot to mention it in my own post - http://forum.springframework.org/showthread.php?t=40841.

I wrote about trying to put dwr under a subpath of the servlet. It is not so easy, yet it is what I and probably many others expected. Perhaps you could put a note in your guide to say 'use these paths, as dwr needs to access the servlet root'.

Also, I went straight in and configured dwr for my app. I felt I was missing out on the debugging side until I found the paths for them too. Perhaps you could add these to the handler - or just leave them here for readers to pick up :)

- test shows info on a service, use a url like /test/MyAjaxService -
/test/**/*">dwrController

- really basic about page! -
/about">dwrController

- index shows the classes which are dwr-enabled, this will no doubt conflict with your /servlet/index.html file -
/index.html">dwrController

david dijo...

Forgive me for my ignorance but I am new to Spring. I have followed your example and I appear to be missing an important piece. You mention to add the needed DWR stuff but it does not appear to be everything you need.

dwr:controller id="dwrController" debug="true"

How do I configure this!! I am getting a Spring error complaining about dwrController and it doesn't seem to be all the DWR stuff that is needed. How do I define the dwrController bean?

Eliot dijo...

Here is a clean way of handling the URL mappings.

In the SimpleUrlHandlerMapping bean you have setup for handling dwr requests, set the properties:

alwaysUseFullPath=true
order=1 (if you have another SimpleUrlHandlerMapping in your app context).

And give it this mapping:
prop
key="/dwr/**" dwrController
/prop

And in your web.xml you need as usual to map /dwr/* to your spring dispatcher servlet in a servlet-mapping element.

Now you will be able to find all kinds of useful DWR information in your app if you go to the URL:

http://host/servletcontext/dwr/

jon dijo...

there does appear to be a conflict with the dwr:convert... using Spring 2.0.2.

I rolled back to 2.0.1 and no problems. also using dwr 2.0rc1

Paul Vitic dijo...

On the server side I am using Spring MVC architechture. The application responds to periodic AJAX requests from the client to display changing business data. This imposes a communication and server processing overhead.
I would like to use DWR's ScriptProxy to push data to the client instead.
How do I implement this within the current Spring MVC architechure?
Can I continue to use the Spring DispatcherServlet and implement DWR's ScriptProxy within this container or will I have to use DwrSpringServlet alongside Spring's DispatcherServlet?

Rik Blankestijn dijo...

"Web scopes in Spring: The most common error is not being able to access the request/session with this type of scoped beans."

You can easily access the request object by doing this:

import org.directwebremoting.WebContext;
import org.directwebremoting.WebContextFactory;
...
WebContext ctx = WebContextFactory.get();
HttpServletRequest request = ctx.getHttpServletRequest();

Raphael dijo...

hi,

can anyone tell me if i can use this with spring portlet mvc?
i tried, but my js-function wasn'e mapped to my java-funtion.
is simpleurlhandlermapper applicable to portletmvc?

thanks, raphael

(Maesse The OWL) Carlos Adolfo Ortiz Q - TheOWLO dijo...

Well Maybe this is not the place or something but as I don't have a direct way for writing to you then here it goes.

I am using Spring V2.5 and DWR 2.0, and using Eclipse 3.3. Then I got a problem after following the configuration.

I cannot find a XSD at http://www.directwebremoting.org/schema/spring-dwr-2.0.xsd.

Can you tell where is this file actually located?

What can I do to use the file from JAR instead.

Doug dijo...

Carlos, remove "www" from the URL's.

http://directwebremoting.org/schema/spring-dwr

http://directwebremoting.org/schema/spring-dwr-2.0.xsd

The urls have changed. But, hehe, now my problem is that even though Eclipse recognizes the dwr tags, it complains that they are out of order [shrug].

Fabricio dijo...

buenisimo el tutorial, te felicito.

Te mando saludos desde Buenos Aires, Argentina!

Fabricio.

boristech dijo...

Thank you for writing this post. It really helped me understand how to integrate DWR and Spring.

Sergey Udaltsov dijo...

Thank you very much for you text, it is really useful.

What I could not understand from it - is there any way to pass the file (as an InputStream or smth) from html input form to the bean?