I have this in src/main/groovy/...
package com.mycompany.web;
// imports....
@Controller
class GroovyController {
@RequestMapping("/status_groovy")
public @ResponseBody String getStatus() {
return "Hello World from groovy!";
}
}
Using maven 3 and spring 3.1 (Milestone). Spring MVC works perfectly well for java controllers and everything is set up fine. The groovy class compiles fine and can be found in the classes
directory along with the java controller classes.
I have similar controller written in java (JavaController) in same package but under src/main/java and its getting picked up properly by spring and mapped and I can see the response on screen when I hit the url.
package com.mycompany.web;
// imports....
@Controller
class JavaController {
@RequestMapping("/status")
public @ResponseBody String getStatus() {
return "Hello World!";
}
}
Jetty starts normally with no error in log but in I dont see groovy url getting mapped whereas i can see the java one.
2011-09-23 16:05:50,412 [main] INFO org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping - Mapped "{[/status],methods=[],params=[],headers=[],consumes=[],produces=[]}" onto public java.lang.String com.mycompany.web.JavaController.getStatus()
All the setting are fine as other parts of app are working just fine with annotations (component-scan etc.), Just that I can not get the url mapped in GroovyController
Can anyone explain what needs to be done in order to get Controller
s written in groovy working?
PS: I am avoiding GroovyServlet to run the scripts because it has major downside when it comes to bean injection and url path mappings.
The @Controller annotation indicates that a particular class serves the role of a controller. Spring Controller annotation is typically used in combination with annotated handler methods based on the @RequestMapping annotation. It can be applied to classes only. It's used to mark a class as a web request handler.
@Controller annotation indicates that the class is a “controller” like a web controller. @RestController annotation indicates that class is a controller where @RequestMapping methods assume @ResponseBody semantics by default. In @Controller, we need to use @ResponseBody on every handler method.
RequestMapping annotation is used to map web requests onto specific handler classes and/or handler methods. @RequestMapping can be applied to the controller class as well as methods. Today we will look into various usage of this annotation with example and other annotations @PathVariable and @RequestParam .
Simply put, the @RequestBody annotation maps the HttpRequest body to a transfer or domain object, enabling automatic deserialization of the inbound HttpRequest body onto a Java object. Spring automatically deserializes the JSON into a Java type, assuming an appropriate one is specified.
With all due respect to Ben (whom I work with), the problem here isn't that Spring is creating a cglib proxy. Rather, it's creating a dynamic JDK (or interface-based) proxy. This method of creating proxies can only implement methods declared in the target's implemented interfaces. You actually want Spring to create a cglib proxy, which creates a proxy that is a subclass of the target object and can therefore recreate all of its public methods. Unless you specify otherwise, Spring will create a cglib proxy if the target object doesn't implement any interfaces, and an interface-based proxy otherwise. Since all Groovy objects implement GroovyObject, you're getting an interface-based proxy, even though you didn't explicitly implement any interfaces in your Groovy controller. Ben's solution is correct in that if you create an interface with all your controller methods, you'll get the expected behavior. An alternative is to create a BeanFactoryPostProcessor which instructs Spring to create cglib proxies for classes that implement GroovyObject and only GroovyObject. Here's the code:
/**
* Finds all objects in the bean factory that implement GroovyObject and only GroovyObject, and sets the
* AutoProxyUtils.PRESERVE_TARGET_CLASS_ATTRIBUTE value to true. This will, in the case when a proxy
* is necessary, force the creation of a CGLIB subclass proxy, rather than a dynamic JDK proxy, which
* would create a useless proxy that only implements the methods of GroovyObject.
*
* @author caleb
*/
public class GroovyObjectTargetClassPreservingBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
private static final Logger logger = LoggerFactory.getLogger(GroovyObjectTargetClassPreservingBeanFactoryPostProcessor.class);
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
for (String beanDefName : beanFactory.getBeanDefinitionNames()) {
BeanDefinition bd = beanFactory.getBeanDefinition(beanDefName);
//ignore abstract definitions (parent beans)
if (bd.isAbstract())
continue;
String className = bd.getBeanClassName();
//ignore definitions with null class names
if (className == null)
continue;
Class<?> beanClass;
try {
beanClass = ClassUtils.forName(className, beanFactory.getBeanClassLoader());
}
catch (ClassNotFoundException e) {
throw new CannotLoadBeanClassException(bd.getResourceDescription(), beanDefName, bd.getBeanClassName(), e);
}
catch (LinkageError e) {
throw new CannotLoadBeanClassException(bd.getResourceDescription(), beanDefName, bd.getBeanClassName(), e);
}
Class<?>[] interfaces = beanClass.getInterfaces();
if (interfaces.length == 1 && interfaces[0] == GroovyObject.class) {
logger.debug("Setting attribute {} to true for bean {}", AutoProxyUtils.PRESERVE_TARGET_CLASS_ATTRIBUTE, beanDefName);
bd.setAttribute(AutoProxyUtils.PRESERVE_TARGET_CLASS_ATTRIBUTE, true);
}
}
}
}
Just include a bean of this type in your context, and voila! You can have Groovy controllers without needing to define interfaces.
I beg to differ. There is no need to implement an interface. The problem here is that the default AnnotationMethodHandlerAdapter
does not read annotations from proxies. Hence we would have to create this proxy aware AnnotationMethodHandlerAdapter which extends the default AnnotationMethodHandlerAdapter
of spring. We also need to instantiate a bean for this ProxyAwareAnnotationMethodHandlerAdapter
in the Spring Configuration xml file.
Note: This feature is not available in Spring 3.x but since spring 4.0 would support groovy beans, this feature should be covered.
//ProxyAwareAnnotationMethodHandlerAdapter.java
package name.assafberg.spring;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.aop.TargetSource;
import org.springframework.aop.framework.Advised;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter;
/**
* Add proxy awareness to <code>AnnotationMethodHandlerAdapter</code>.
*
* @author assaf
*/
public class ProxyAwareAnnotationMethodHandlerAdapter extends AnnotationMethodHandlerAdapter {
/**
* @param request
* @param response
* @param handler
* @return
* @throws Exception
* @see org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter#handle(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, java.lang.Object)
*/
@Override
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
handler = unwrapHandler(handler);
return super.handle(request, response, handler);
}
/**
* @param handler
* @return
* @see org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter#supports(java.lang.Object)
*/
@Override
public boolean supports(Object handler) {
handler = unwrapHandler(handler);
return super.supports(handler);
}
/**
* Attempt to unwrap the given handler in case it is an AOP proxy
*
* @param handler
* @return Object
*/
private Object unwrapHandler(Object handler) {
if (handler instanceof Advised) {
try {
TargetSource targetSource = ((Advised) handler).getTargetSource();
return targetSource.getTarget();
} catch (Exception x) {
throw new RuntimeException(x);
}
} else {
return handler;
}
}
}
The spring configuration XML file must have the following. Instead of creating a bean of AnnotationMethodHandlerAdapter we must create a ProxyAwareAnnotationMethodHandlerAdapter bean.
<beans .........
...
...
<bean class="full.qualified.name.of.ProxyAwareAnnotationMethodHandlerAdapter" />
...
...
<lang:groovy script-source="classpath:com/example/mysample.groovy refresh-check-delay="1000" />
</beans>
Also Spring parses the configuration XML file using a SAX parser (based on event occurence). So, in order for spring to understand the annotations within the groovy scripts, the groovy beans (using tag) must be created after the ProxyAwareAnnotationMethodHandlerAdapter.
Hope than helps
Reference: http://forum.springsource.org/showthread.php?47271-Groovy-Controller
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With