View Full Version : Method level RequestMapping annotations for more REST style actions
joeslow
Jun 9th, 2008, 11:48 AM
Hi,
I'm toying with annotation driven controller configuration and I really, really like it.
Now I just have one issue: I'd like to be able to have more (g)rails like URLs with the pattern /controller/action[/id] but that doesn't seem to be possible as I don't seem to be able to specify a pattern like "action/*" in the method level RequestMapping annotation. If I do, that method never gets mapped. Is there a simple solution to that short of writing my own variant of org.springframework.web.servlet.mvc.annotation.Ann otationMethodHandlerAdapter (which somehow seems like a bad idea) and isn't that something that would be more generally useful?
Any suggestions would be greatly appreciated.
Jörg
David Winterfeldt
Jun 9th, 2008, 03:22 PM
I'm able to do this and it works fine mapping all html requests (below). Have you tried putting two asterisks instead of just one? I also jus tried "/delete/*" and "/delete/**" and they both intercepted a "/delete/person.html?id=1" request.
Have you tried "/**/action/*"?
@RequestMapping(value="/**/*.html")
David Winterfeldt
Jun 9th, 2008, 03:31 PM
I actually just tried "/**/action/*" in a webapp I have setup already and seemed like it was working ok. It intercepted '/action/person.html', '/foo/bar/action/person.html', '/foo/bar/action/12', etc.
joeslow
Jun 10th, 2008, 03:57 AM
Thanks David! That's good news - but I actually think now that the controller mapping is the problem. I've always tried with relative paths at the method level RequestMapping and mapping the controller at the type level and it seems that then it doesn't work. Will need to further test that.
Jörg
joeslow
Jun 10th, 2008, 06:05 AM
OK, it was my fault. The mappings didn't work becuase I didn't explicity add org.springframework.web.servlet.mvc.annotation.Def aultAnnotationHandlerMapping as a bean in my context but inadvertently added a org.springframework.web.servlet.mvc.annotation.Ann otationMethodHandlerAdapter bean instead which kind of made it work half.
Now if only I wouldn't need to specify '/**/action/*' at th emethod level but 'action/*' I would be in heaven ...
joeslow
Jun 10th, 2008, 08:14 AM
Just if anybody else cares: I've created a little interceptor to extract ids from the URL in REST style URLs following the pattern /controller/action/id. It's not tested production quality code but maybe somebody else can draw some inspiration from it.
I have a dispatcher servlet context that looks something like this
<?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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd">
<bean id="handlerMappingTemplate" abstract="true">
<property name="interceptors">
<list>
<bean class="some.package.IdFromUrlInterceptor" />
</list>
</property>
</bean>
<bean class="org.springframework.web.servlet.mvc.support.Contro llerClassNameHandlerMapping" parent="handlerMappingTemplate" />
<bean class="org.springframework.web.servlet.mvc.annotation.Def aultAnnotationHandlerMapping" parent="handlerMappingTemplate" />
<context:component-scan base-package="some.package.controllers" />
...
</beans>
and the interceptor looks like this
package some.package;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.core.CollectionFactory;
import org.springframework.web.servlet.handler.HandlerInt erceptorAdapter;
/**
* Interceptor that tries to read an id value from URLs of the pattern /controller/action/id and tries to then set
* the read value on the passed in handler (generally a Controller class) by calling a method with the signature setId(String id) if existent.
*
* @author joerg
*
*/
public class IdFromUrlInterceptor extends HandlerInterceptorAdapter
{
private final Map<String, Method> handlerMethodCache = CollectionFactory.createConcurrentMapIfPossible(16 );
private final static Pattern idPattern = Pattern.compile("^/(\\S*)/(\\S*)/(\\S*)");
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception
{
Matcher matcher = idPattern.matcher(request.getPathInfo());
if (matcher.matches())
{
try
{
Method idSetter;
if (handlerMethodCache.containsKey(handler.getClass() .getName()))
idSetter = handlerMethodCache.get(handler.getClass().getName( ));
else
{
idSetter = handler.getClass().getMethod("setId", new Class[]{String.class});
handlerMethodCache.put(handler.getClass().getName( ), idSetter);
}
if (idSetter != null)
idSetter.invoke(handler, new Object[]{matcher.group(3)});
}
catch (Exception e)
{
//noop
}
}
return true;
}
}
You can then write a controller like this
@Controller
@Scope("prototype")
public class TestController
{
private String id;
public String getId()
{
return id;
}
public void setId(String id)
{
this.id = id;
}
@RequestMapping("/**/hello/*")
public ModelAndView hello(HttpServletRequest request, HttpServletResponse response) throws Exception
{
System.out.println("id: " + getId());
return new ModelAndView("hello");
}
}
So as long as your controller has a method with the signature void setId(String id) you then should be able to get the extracted id by the time the action is called. Hope this all makes sense.
If somebody knows of a better way to achieve this please let me know.
vBulletin® v3.7.3, Copyright ©2000-2008, Jelsoft Enterprises Ltd.