Как вы знаете Spring MVC использует новую модель конфигурации на основе аннотаций начиная с версии 2.5. Чтобы получить эти плюшки, нужно использовать тег <mvc:annotation-driven /> в файле конфигурации. Этот тег регистрирует DefaultAnnotationHandlerMapping и AnnotationMethodHandlerAdaptor в контексте приложения.
DefaultAnnotationHandlerMapping делает поиск аннотаций @RequestMapping в классах и создает отображение на обработчик для каждого плюс два отображения с суффиксамии '.*' и '/' на тот же обработчик. Задача AnnotationMethodHandlerAdaptor'а — ответственное делегирование обработки HTTP запросов правильному методу, у которого аннотация замечена.
Поэтому для следующего контроллера
вы получите три отображения для запросов к /service/hotels, /service/hotels/ и /service/hotels.*.
Цель первых двух явно быть более дружественным к пользователю приложением, а последний используется при определении наилучшего представления ресурса в ContentNegotiatingViewResolver.
Все бы хорошо…
Проблема возникает, когда вы пытаетесь применить RESTful подход к веб-службам с использованием аннотаций для отображения запросов в обработчики подобным образом. Так как URL в REST — это ресурс, то различные URL-адреса теперь указывают на различные ресурсы и ваше приложение не должно беззаботно с ними обращаться и применять подобные неявные обработчики для несуществующих ресурсов. Проблема усугубляется желанием использовать слеш или звездочку в качестве маркера коллекций, то есть вместо /service/hotels в данном случае кто то мог бы использовать, к примеру /service/hotel/ или /service/hotel/* как URL для списка всех отелей, хоть это и не очень интуитивно и еще менее расширяемо.
Грубо говоря — нужно вернуть 404, а для этого нужно отключить генерацию подобных неявных маппингов.
Другое дело, что сама весна относится к обработке этих подразумеваемых адресов, скажем так, небрежно. Очевидный выход — настроить DefaultAnnotationHandlerMapping для приложения и установить его свойство defaultSuffixPattern в false, не так прост как можно подумать.
На первый взгляд получается:
Теперь давайте вместе подумаем, что порисходит при создании контекста. Как только весна видит <mvc:annotation-driven />, она создает один DefaultAnnotationHandlerMapping и помещает его в контекст. Как только она видит выше определенную фасоль, она создает другой экземпляр DefaultAnnotationHandlerMapping, и также помещает его в контекст. Так что наше крутое приложение будет иметь два экземпляра DefaultAnnotationHandlerMapping, один с настройками по умолчанию, а другой настроен так, как нужно. Который HandlerMapping будет жевать HTTP-запрос первым зависит от их внутреннего порядка, и вне вашего контроля (ну, почти… можно применить упорядочивание, но это в данном случае костыль).
Хотя для /service/hotels нет никакой разницы, для /service/hotels/ и /service/hotels.* разница есть. Скорее всего, вы будете использовать ContentNegotiatingViewResolver и вести переговоры с клиентом о лучшем для него представлением ресурсов, и в этом случае вы фактически потеряли контроль над этим занятным и важным в REST процессом для неявно созданных маппингов. Результатом запроса к ним может быть как правильное представление, так и неправильное, в некоторых ситуациях результатом будет исключение и 500. В детали вдаваться не буду, но слону понятно, что такого поведения надо избегать.
Чтобы избежать этого, необходимо удалить один из HandlerMapping'ов из контекста. Таким образом, мы должны удалить <mvc:annotation-driven /> и делать тяжелый труд регистрации AnnotationMethodHandlerAdaptor'а руками:
Это должно сделать то, что требуется, и вы получите гарантируемый и желаемый 404 к неявным URL на существующие ресурсы, то есть, я хотел сказать, к явным URL на несуществующие, короче вам виднее.
DefaultAnnotationHandlerMapping делает поиск аннотаций @RequestMapping в классах и создает отображение на обработчик для каждого плюс два отображения с суффиксамии '.*' и '/' на тот же обработчик. Задача AnnotationMethodHandlerAdaptor'а — ответственное делегирование обработки HTTP запросов правильному методу, у которого аннотация замечена.
Поэтому для следующего контроллера
@Controller
@RequestMapping("/service/hotels")
public class HotelsCollectionController {
@Autowired
private HotelService hotelService;
@RequestMapping(method = RequestMethod.GET)
public String getHotelList(Model model) {
List<Hotel> list = hotelService .getHotelList();
model.addAttribute("hotels", list);
return "service/hotels/read";
}
public void setHotelService(HotelService hotelService) {
this.hotelService = hotelService;
}
}
вы получите три отображения для запросов к /service/hotels, /service/hotels/ и /service/hotels.*.
Цель первых двух явно быть более дружественным к пользователю приложением, а последний используется при определении наилучшего представления ресурса в ContentNegotiatingViewResolver.
Все бы хорошо…
Проблема возникает, когда вы пытаетесь применить RESTful подход к веб-службам с использованием аннотаций для отображения запросов в обработчики подобным образом. Так как URL в REST — это ресурс, то различные URL-адреса теперь указывают на различные ресурсы и ваше приложение не должно беззаботно с ними обращаться и применять подобные неявные обработчики для несуществующих ресурсов. Проблема усугубляется желанием использовать слеш или звездочку в качестве маркера коллекций, то есть вместо /service/hotels в данном случае кто то мог бы использовать, к примеру /service/hotel/ или /service/hotel/* как URL для списка всех отелей, хоть это и не очень интуитивно и еще менее расширяемо.
Грубо говоря — нужно вернуть 404, а для этого нужно отключить генерацию подобных неявных маппингов.
Другое дело, что сама весна относится к обработке этих подразумеваемых адресов, скажем так, небрежно. Очевидный выход — настроить DefaultAnnotationHandlerMapping для приложения и установить его свойство defaultSuffixPattern в false, не так прост как можно подумать.
На первый взгляд получается:
<mvc:annotation-driven />
<bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping">
<property name="useDefaultSuffixPattern" value="false" />
</bean>
Теперь давайте вместе подумаем, что порисходит при создании контекста. Как только весна видит <mvc:annotation-driven />, она создает один DefaultAnnotationHandlerMapping и помещает его в контекст. Как только она видит выше определенную фасоль, она создает другой экземпляр DefaultAnnotationHandlerMapping, и также помещает его в контекст. Так что наше крутое приложение будет иметь два экземпляра DefaultAnnotationHandlerMapping, один с настройками по умолчанию, а другой настроен так, как нужно. Который HandlerMapping будет жевать HTTP-запрос первым зависит от их внутреннего порядка, и вне вашего контроля (ну, почти… можно применить упорядочивание, но это в данном случае костыль).
Хотя для /service/hotels нет никакой разницы, для /service/hotels/ и /service/hotels.* разница есть. Скорее всего, вы будете использовать ContentNegotiatingViewResolver и вести переговоры с клиентом о лучшем для него представлением ресурсов, и в этом случае вы фактически потеряли контроль над этим занятным и важным в REST процессом для неявно созданных маппингов. Результатом запроса к ним может быть как правильное представление, так и неправильное, в некоторых ситуациях результатом будет исключение и 500. В детали вдаваться не буду, но слону понятно, что такого поведения надо избегать.
Чтобы избежать этого, необходимо удалить один из HandlerMapping'ов из контекста. Таким образом, мы должны удалить <mvc:annotation-driven /> и делать тяжелый труд регистрации AnnotationMethodHandlerAdaptor'а руками:
<bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping">
<property name="useDefaultSuffixPattern" value="false" />
</bean>
<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter" />
Это должно сделать то, что требуется, и вы получите гарантируемый и желаемый 404 к неявным URL на существующие ресурсы, то есть, я хотел сказать, к явным URL на несуществующие, короче вам виднее.