开发者

Spring MVC annotated controller in groovy

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 et开发者_如何学运维c.), Just that I can not get the url mapped in GroovyController

Can anyone explain what needs to be done in order to get Controllers 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.


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


Unfortunately, if you want to get this running in Groovy you'll have to create an interface for your Controller class and annotate the method definitions as well. Spring creates a proxy for your class using Cglib. However, without creating a custom interface for your controller Spring is proxying on groovy.lang.GroovyObject because all Groovy objects implement that interface by default.

interface GroovyControllerInterface {
    @RequestMapping("/status_groovy")
    @ResponseBody String getStatus()
}

@Controller
class GroovyController implements GroovyControllerInterface {
    @RequestMapping("/status_groovy")
    public @ResponseBody String getStatus() {
        return "Hello World from groovy!";
    }
}
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜