Проблема Spring-MVC с использованием @Controller на контроллере, реализующем интерфейс

Я использую Spring 2.5 и аннотации для настройки моего веб-контекста spring-mvc. К сожалению, я не могу заставить работать следующее. Я не уверен, является ли это ошибкой (кажется, так) или существует базовое недоразумение относительно того, как работают подклассы аннотаций и реализации интерфейса.

Например,

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

работает отлично. Когда контекст запускается, обнаруживаются URL-адреса, с которыми имеет дело этот обработчик, и все работает отлично.

Однако это не так:

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

Когда я пытаюсь открыть URL-адрес, я получаю следующую неприятную трассировку стека:

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)

Однако, если я сделаю Bar абстрактным суперклассом и расширю его с помощью Foo, он снова заработает.

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

Это похоже на ошибку. Аннотации @Controller должно быть достаточно, чтобы пометить его как контроллер, и я должен иметь возможность реализовать один или несколько интерфейсов в своем контроллере без необходимости делать что-либо еще. Есть идеи?

Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
34
0
50 943
6

Ответы 6

Нет сомнений в том, что аннотации и наследование могут быть немного сложными, но я думаю, что это должно сработать. Попробуйте явно добавить AnnotationMethodHandlerAdapter в контекст сервлета.

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

Если это не сработает, будет полезно немного дополнительной информации. В частности, являются ли два аннотированных метода контроллера из интерфейса? Должен ли Foo быть RegistrationController?

Эд прав, добавляя

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

работает отлично

Мне нужно было заменить

 <tx:annotation-driven/>

с

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

Это заставляет аспектj использовать CGLIB для выполнения аспектов вместо динамических прокси - CGLIB не теряет аннотацию, поскольку он расширяет класс, тогда как динамические прокси просто открывают реализованный интерфейс.

Необходимо отметить, что CGLIB скорее устарел.

Ondrej Bozek 28.07.2015 18:44

Истинная причина, по которой вам нужно использовать 'proxy-target-class = "true"', заключается в методе DefaultAnnotationHandlerMapping#determineUrlsForHandler(): хотя он использует ListableBeanFactory#findAnnotationOnBean для поиска аннотации @RequestMapping (и это решает любые проблемы с прокси), дополнительный поиск аннотации @Controller выполняется с использованием AnnotationUtils#findAnnotation (который не обрабатывает проблемы с прокси)

Вы имеете в виду ошибку?

Ruslan 19.07.2017 14:04

Если вы хотите использовать интерфейсы для своих контроллеров Spring MVC, вам нужно немного переместить аннотации, как указано в документации Spring: http://static.springsource.org/spring/docs/3.1.x/spring-framework-reference/html/mvc.html#mvc-ann-requestmapping

Using @RequestMapping On Interface Methods A common pitfall when working with annotated controller classes happens when applying functionality that requires creating a proxy for the controller object (e.g. @Transactional methods). Usually you will introduce an interface for the controller in order to use JDK dynamic proxies. To make this work you must move the @RequestMapping annotations to the interface as well as the mapping mechanism can only "see" the interface exposed by the proxy. Alternatively, you could activate proxy-target-class = "true" in the configuration for the functionality applied to the controller (in our transaction scenario in ). Doing so indicates that CGLIB-based subclass proxies should be used instead of interface-based JDK proxies. For more information on various proxying mechanisms see Section 8.6, “Proxying mechanisms”.

К сожалению, здесь нет конкретного примера. Я обнаружил, что работает такая установка:

@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) {

    }
}

Итак, у вас есть интерфейс, который объявляет аннотации @Controller, @PathVariable и @RequestMapping (аннотации Spring MVC), а затем вы можете поместить свои аннотации @Transactional или @Secured, например, в конкретный класс. В интерфейсе необходимо добавить только аннотации типа @Controller, поскольку Spring выполняет свои сопоставления.

Обратите внимание, что вам нужно сделать это только в том случае, если вы используете интерфейс. Вам не обязательно делать это, если вам нравятся прокси CGLib, но если по какой-то причине вы хотите использовать динамические прокси JDK, это может быть подходящим вариантом.

Я знаю, что уже слишком поздно, но я пишу это для тех, у кого есть эта проблема если вы используете конфигурацию на основе аннотаций ... решение может быть таким:

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

Другие вопросы по теме