Pull to refresh

REST через Spring: Строгое отображение URL в ресурс

Reading time3 min
Views9.5K
Как вы знаете Spring MVC использует новую модель конфигурации на основе аннотаций начиная с версии 2.5. Чтобы получить эти плюшки, нужно использовать тег <mvc:annotation-driven /> в файле конфигурации. Этот тег регистрирует DefaultAnnotationHandlerMapping и AnnotationMethodHandlerAdaptor в контексте приложения.



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 на несуществующие, короче вам виднее.
Tags:
Hubs:
+3
Comments0

Articles