views:

981

answers:

1

I'm trying to expose a REST-full service (hosted by Tomcat) and can't figure out what is the required configuration for Spring 3 (M3).

This is how (example) the service looks like:

@Controller
@Scope("prototype")
public class UsersController
{
    @RequestMapping(value="/users/hello", method=RequestMethod.GET)
    public String hello()
    {
     return "hello, user!";
    }
}

The Spring configuration that I have looks like this (I omit the full class names for simplicity):

<beans ...>
    <context:annotation-config />
    <bean class="...AutowiredAnnotationBeanPostProcessor"/>
    <bean class="...DefaultAnnotationHandlerMapping">
    <context:component-scan base-package="com.mycompany.myserver"/>
</beans>

This is how I've plugged-in my Spring configuration into web.xml:

    <listener>
            <listener-class>...RequestContextListener</listener-class>
    </listener>
    <!-- Servlets -->
    <servlet>
     <servlet-name>dispatcher</servlet-name>
     <servlet-class>...DispatcherServlet</servlet-class>
     <init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>classpath:*:dispatcher-servlet.xml</param-value>  
     </init-param>
     <load-on-startup>2</load-on-startup>
    </servlet>
    <servlet-mapping>
     <servlet-name>dispatcher</servlet-name>
     <url-pattern>/services/*</url-pattern>
    </servlet-mapping>

Note that I'm trying to create a minimal configuration (no extra servlet-config.xml files). This is why I'm pointing the Dispatcher to a built-in configuration.

Is this correct?

After I start Tomcat and all the beans are loaded I'm navigating to the following URL:

http://localhost:8080/myserver/services/users/hello

And, instead of the "hello, user!" reply, to my dismay, I see the following errors in the log file:

09:54:45,140 DEBUG RequestContextListener:69 - Bound request context to thread: org.apache.catalina.connector.RequestFacade@19299f5
09:54:45,140 DEBUG DispatcherServlet:834 - DispatcherServlet with name 'dispatcher' determining Last-Modified value for [/myserver/services/users/hello]
09:54:45,156 DEBUG DefaultAnnotationHandlerMapping:178 - Mapping [/users/hello] to handler 'com.symantec.repserver.myserver.UsersController@21447f'
09:54:45,171 DEBUG DispatcherServlet:850 - Last-Modified value for [/myserver/services/users/hello] is: -1
09:54:45,171 DEBUG DispatcherServlet:683 - DispatcherServlet with name 'dispatcher' processing GET request for [/myserver/services/users/hello]
09:54:45,218 DEBUG HandlerMethodInvoker:148 - Invoking request handler method: public java.lang.String com.symantec.repserver.myserver.UsersController.hello()
09:54:45,218 DEBUG DefaultListableBeanFactory:1366 - Invoking afterPropertiesSet() on bean with name 'hello, user!'
09:54:45,218 DEBUG DispatcherServlet:1060 - Rendering view [org.springframework.web.servlet.view.InternalResourceView: name 'hello, user!'; URL [hello, user!]] in DispatcherServlet with name 'dispatcher'
09:54:45,234 DEBUG InternalResourceView:237 - Forwarding to resource [hello, user!] in InternalResourceView 'hello, user!'
09:54:45,250 DEBUG DispatcherServlet:834 - DispatcherServlet with name 'dispatcher' determining Last-Modified value for [/myserver/services/users/hello, user!]
09:54:45,250 DEBUG DispatcherServlet:842 - No handler found in getLastModified
09:54:45,250 DEBUG DispatcherServlet:683 - DispatcherServlet with name 'dispatcher' processing GET request for [/myserver/services/users/hello, user!]
09:54:45,250  WARN PageNotFound:959 - No mapping found for HTTP request with URI [/myserver/services/users/hello, user!] in DispatcherServlet with name 'dispatcher'
09:54:45,250 DEBUG DispatcherServlet:643 - Successfully completed request
09:54:45,250 DEBUG DispatcherServlet:643 - Successfully completed request
09:54:45,250 DEBUG RequestContextListener:89 - Cleared thread-bound request context: org.apache.catalina.connector.RequestFacade@19299f5

As you notice, my current configuration is trying to reroute this request as a new request:

/myserver/services/users/hello, user!

Any idea what is wrong? What am I missing?

+1  A: 

The issue is your hello() method is annotated as a handlerMethod to Spring and per the @RequestMapping documentation, handler methods may return any number of objects but if it returns a String then :

@Request Documentation

A String value which is interpreted as view name, with the model implicitly determined through command objects and ModelAttribute annotated reference data accessor methods. The handler method may also programmatically enrich the model by declaring a ModelMap argument (see above)

So it's basically looking for the mapping for that String you return 'hello, user' - which doesn't exist. You may have your method return void and inside the method write out to the HttpServletResponse yourself.

Further answer:

@RequestMapping(value="/users/hello", method=RequestMethod.GET) 
public void hello(Writer w) { 
   w.write("hello, user!"); 
   w.flush(); 
   return; 
}

You may want to get the entire HttpServletResponse rather then just the Writer, that way you can set the appropriate Http Headers.

Sorry my comment below is jumbled, I'm new to this site.

Gandalf
Hmm... But then it is breaking the whole JAX-RS idea, which claims that the return value of the method is sent to the requesting party.Is there a way to annotate my methods so the above requirement will hold true?
IgorM
Per the javadoc is your method takes an argument of OutputStream or Writer the RequestMapper will pass in the ServletOutputStream/Writer for you to write to. I'm not sure if this is really the best way to go about this, but it will work. @RequestMapping(value="/users/hello", method=RequestMethod.GET) public void hello(Writer w) { w.write("hello, user!"); w.flush(); return; }You may want to get the entire HttpServletResponse rather then just the Writer, that way you can set the appropriate Http Headers.
Gandalf
You can either write the response directly to the stream as suggested above, or you could return a ModelAndView object which does it. The effect is much the same.It would nice if Spring 3 used the new HttpMessageConverter pattern to handle method return values, but ssdly they only apply to @RequestBody. Method return values are still interpreted as they were in Spring 2.5Remember that Spring REST MVC is *not* JAX-RS compliant, that was an explicit choise they made. JAX-RS just didn't fit nicely with Spring MVC.
skaffman
Looks like this has been addressed to some degree in http://jira.springsource.org/browse/SPR-5426. You can implement the ModelAndViewResolver interface and add the bean to your context, and it will be able to resolve the String "view name" and jusst write it to your response.
skaffman
Wow. So, apparently, Spring 3 is not ready to be called a JAX-RS compatible framework.The creation of ModelAndView or access to the body writers makes the development process complicated and requires a lot of ceremony coding. The goal of the WS frameworks is to reduce that and make the framework to do the rest.Sad. Sad. I've had a lot of hopes for this framework. Luckily I've made a decision a week ago to move with Jersey, but will be still monitoring the progress of Spring.
IgorM
It's not ready to be called JAX-RS compatible because it was never intended to be. It's an extension of Spring MVC. SpringSource have said that there are already at least 3 JAX-RS implementations out there, and they have no desire to add a 4th.
skaffman
On the other hand, don't forget, the idea of following a specification is the compatibility - an important ability to allow an easy swap of one library implementation with another. Very important when you have tens of different open source libraries in the Java landscape.
IgorM