web-dev-qa-db-fra.com

Problème Spring-MVC lors de l'utilisation de @Controller sur le contrôleur implémentant une interface

J'utilise Spring 2.5 et des annotations pour configurer mon contexte Web Spring-MVC. Malheureusement, je ne parviens pas à faire fonctionner les éléments suivants. Je ne sais pas s'il s'agit d'un bogue (il en a l'air) ou s'il y a un malentendu de base sur le fonctionnement des annotations et du sous-classement de l'implémentation de l'interface.

Par exemple,

@Controller
@RequestMapping("url-mapping-here")
public class Foo {
  @RequestMapping(method=RequestMethod.GET)
  public void showForm() {
    ...
  }
  @RequestMapping(method=RequestMethod.POST)
  public String processForm() {
  ...
  }
}

fonctionne bien. Lorsque le contexte démarre, les URL traitées par ce gestionnaire sont découvertes et tout fonctionne parfaitement.

Cela ne signifie cependant pas:

@Controller
@RequestMapping("url-mapping-here")
public class Foo implements Bar {
  @RequestMapping(method=RequestMethod.GET)
  public void showForm() {
    ...
  }
  @RequestMapping(method=RequestMethod.POST)
  public String processForm() {
  ...
  }
}

Lorsque j'essaie de récupérer l'URL, j'obtiens la trace de pile désagréable suivante:

javax.servlet.ServletException: No adapter for handler [com.shaneleopard.web.controller.RegistrationController@e973e3]: Does your handler implement a supported interface like Controller?
    org.springframework.web.servlet.DispatcherServlet.getHandlerAdapter(DispatcherServlet.Java:1091)
    org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.Java:874)
    org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.Java:809)
    org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.Java:571)
    org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.Java:501)
    javax.servlet.http.HttpServlet.service(HttpServlet.Java:627)

Cependant, si je change Bar pour être une superclasse abstraite et que Foo l'étende, cela fonctionne à nouveau.

@Controller
@RequestMapping("url-mapping-here")
public class Foo extends Bar {
  @RequestMapping(method=RequestMethod.GET)
  public void showForm() {
    ...
  }
  @RequestMapping(method=RequestMethod.POST)
  public String processForm() {
  ...
  }
}

Cela semble être un bug. L'annotation @Controller devrait être suffisante pour marquer cela comme un contrôleur, et je devrais être capable d'implémenter une ou plusieurs interfaces dans mon contrôleur sans avoir à faire autre chose. Des idées?

34
layne

Ce que je devais faire était de remplacer

 <tx:annotation-driven/>

avec

 <tx:annotation-driven  proxy-target-class="true"/>

Cela force aspectj à utiliser CGLIB pour faire des aspects au lieu des proxys dynamiques - CGLIB ne perd pas l'annotation car il étend la classe, tandis que les proxies dynamiques exposent simplement l'interface implémentée.

13
James Kingsbery

Ed a raison, ajoutant

<bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping"/>
<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter"/>

fonctionne bien

12
Michal Bachman

Si vous souhaitez utiliser des interfaces pour vos contrôleurs Spring MVC, vous devez déplacer les annotations un peu, comme mentionné dans les documents Spring: http://static.springsource.org/spring/docs/3.1.x /spring-framework-reference/html/mvc.html#mvc-ann-requestmapping

Utilisation de @RequestMapping sur les méthodes d'interface Un écueil courant lorsque vous travaillez avec des classes de contrôleur annotées se produit lors de l'application de fonctionnalités qui nécessitent la création d'un proxy pour l'objet contrôleur (par exemple, les méthodes @Transactional). Habituellement, vous introduirez une interface pour le contrôleur afin d'utiliser les proxys dynamiques JDK. Pour que cela fonctionne, vous devez déplacer les annotations @RequestMapping vers l'interface ainsi que le mécanisme de mappage ne peut que "voir" l'interface exposée par le proxy. Alternativement, vous pouvez activer proxy-target-class = "true" dans la configuration pour la fonctionnalité appliquée au contrôleur (dans notre scénario de transaction en). Cela indique que les proxys de sous-classe basés sur CGLIB doivent être utilisés à la place des proxys JDK basés sur l'interface. Pour plus d'informations sur les différents mécanismes de proxy, reportez-vous à la Section 8.6, "Mécanismes de proxy".

Malheureusement, cela n'en donne pas un exemple concret. J'ai trouvé une configuration comme celle-ci qui fonctionne:

@Controller
@RequestMapping(value = "/secure/exhibitor")
public interface ExhibitorController {

    @RequestMapping(value = "/{id}")
    void exhibitor(@PathVariable("id") Long id);
}

@Controller
public class ExhibitorControllerImpl implements ExhibitorController {

    @Secured({"ROLE_EXHIBITOR"})
    @Transactional(readOnly = true)
    @Override
    public void exhibitor(final Long id) {

    }
}

Donc, ce que vous avez ici est une interface qui déclare les annotations @Controller, @PathVariable et @RequestMapping (les annotations Spring MVC) et vous pouvez ensuite placer vos annotations @Transactional ou @Secured par exemple sur la classe concrète. Ce ne sont que les annotations de type @Controller que vous devez mettre sur l'interface en raison de la façon dont Spring effectue ses mappages.

Notez que vous ne devez le faire que si vous utilisez une interface. Vous n'avez pas nécessairement besoin de le faire si vous êtes satisfait des proxys CGLib, mais si pour une raison quelconque vous souhaitez utiliser des proxys dynamiques JDK, cela pourrait être la voie à suivre.

10
Kieran

Il ne fait aucun doute que les annotations et l'héritage peuvent devenir un peu délicats, mais je pense que cela devrait fonctionner. Essayez d'ajouter explicitement AnnotationMethodHandlerAdapter à votre contexte de servlet.

http://static.springframework.org/spring/docs/2.5.x/reference/mvc.html#mvc-ann-setup

Si cela ne fonctionne pas, un peu plus d'informations seraient utiles. Plus précisément, les deux méthodes de contrôleur annotées proviennent-elles de l'interface? Foo est-il censé être RegistrationController?

5
Ed Thomas

Je sais qu'il est trop tard mais j'écris ceci pour quiconque a ce problème si vous utilisez une configuration basée sur des annotations ... la solution pourrait être comme ceci:

@Configuration
@ComponentScan("org.foo.controller.*")
@EnableAspectJAutoProxy(proxyTargetClass=true)
public class AppConfig { ...}
3
Amir

La vraie raison pour laquelle vous devez utiliser 'proxy-target-class = "true"' est dans la méthode DefaultAnnotationHandlerMapping#determineUrlsForHandler(): bien qu'elle utilise ListableBeanFactory#findAnnotationOnBean pour rechercher un @RequestMapping annotation (et cela prend en charge tous les problèmes de proxy), la recherche supplémentaire pour @Controller l'annotation se fait à l'aide de AnnotationUtils#findAnnotation (qui ne gère pas les problèmes de proxy)

0
Boris Kirzner