Spring Сложная модель MVC из нескольких источников

Ну, мой вопрос может звучать немного нечетко, но здесь это так или иначе. Я создаю веб-приложение с помощью Spring MVC 3.1.M1, JSP 2.1 (без Tiles я использую простые файлы тегов JSP для составления моих макетов).

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

JSP не имеет понятия "компонент", поэтому я не могу определить часть моего шаблона и его код поддержки в Java в одном месте, вместе взятые. В моем @Controllers я должен полностью заполнить мою модель, включая данные для заголовка, нижнего колонтитула, меню и других вещей. Я действительно хочу сделать это, чтобы избежать дублирования кода. Абстрактный класс BaseController с некоторыми общими методами популяции модели тоже не очень хорош.

JSP и Spring MVC очень часто используются вместе, поэтому я ожидаю, что на эту тему будут существовать лучшие практики. Давайте обсудим это.

Ответ 1

Springframework содержит обработчики обработчика как часть механизма отображения обработчика.
Внутри перехватчика вы можете использовать метод postHandle до выполнения фактического обработчика.

Такой перехватчик должен реализовать org.springframework.web.servlet.HandlerInterceptor или org.springframework.web.servlet.handler.HandlerInterceptorAdapter для упрощенной реализации.

public class MyHandlerInterceptor extends HandlerInterceptorAdapter {

    public void postHandle(HttpServletRequest request,
            HttpServletResponse response, Object handler,ModelAndView modelAndView) throws Exception {

        //populate header, menu, footer, ... model
    }
}

и конфигурацию для отображения обработчика.

<bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping">
<property name="interceptors">
    <list>
        <bean id="myInterceptor" class="...MyHandlerInterceptor"/>
    </list>
</property>

Ответ 2

Хорошо, поэтому я провел некоторое время с Spring справочными и примерными приложениями MVC и нашел несколько дополнительных способов для выполнения моей миссии. Вот они:

1) Путь номер один, плохой и непригодный для использования, просто чтобы упомянуть здесь. Абстрактный BaseController с такими методами, как populateHeaderData (модель модели), populateFooterData (модель модели) и так далее. Все методы @RequestMapping во всех классах контроллеров, которые расширяют BaseController, вызывают эти методы для заполнения данных модели для конкретной модели.

Плюсы: none

Минусы: дублирование кода остается неизменным, уменьшается количество дублированного кода

2) @ModelAttribute, т.е. неявное моделирование. Похож на

@Controller
@RequestMapping(value="/account")
public class AccountController {

    @ModelAttribute("visitorName")
    private String putVisitor() {
        return visitorService.getVisitorName();
    }

    // handler methods
}

И в JSP,

<span id="username">Welcome, ${visitorName}!</span>

Плюсы: не нужно явно вызывать методы обогащения модели - он просто работает

Минусы: здесь сложная штука. Spring В MVC вместо "pull" используется модель шаблонов "push". В этом случае это означает, что при вызове любого из методов @RequestMapping, определенных в этом классе, вызывается все методы @ModelAttribute этого класса. Нет никакой разницы, если шаблону действительно требуется имя посетителя и если шаблон действительно существует для конкретных действий. Это включает в себя запросы POST для отправки форм и т.д. На самом деле, это заставляет нас менять разделение контроллеров. Например, все формы должны быть в отдельных классах контроллеров, а методы обработчика должны быть каким-то образом сгруппированы по макетам. Мне нужно больше об этом думать, может быть, это не так уж плохо, как кажется на первый взгляд.

Больше минусов: предположим, что у нас есть макеты A и B с тем же нестатическим заголовком, а B и C с тем же нестационарным нижним колонтитулом (все остальные части разные). Мы не можем реализовать базовый класс для макета B, поскольку на Java нет множественного наследования.

Вопрос аудитории: Spring Состояние ссылки MVC: для методов обработчика поддерживаются следующие типы возвращаемых данных: объект ModelAndView с моделью, неявно обогащенной объектами команд, и результатами методов аннотированных ссылочных данных @ModelAttribute... ". Какого черта эти объекты команды?

3) Мой собственный подобный метод. Мы можем создавать пользовательские контексты в форме

@Component("headerContext")
public class HeaderContext {

    @Autowired
    private VisitorService visitorService;

    public String getVisitorName() {
        return visitorService.getVisitorName();
    }

    // more getters here

}

Затем выведите такой beans в JSP EL через

<!-- Resolves view names to protected .jsp resources within the /WEB-INF/views directory -->
<beans:bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <beans:property name="prefix" value="/WEB-INF/views/"/>
    <beans:property name="suffix" value=".jsp"/>
    <beans:property name="exposedContextBeanNames" value="headerContext,footerContext"/>
</beans:bean>

И в header.tag(файл тега JSP для повторно используемого заголовка)

<span id="username">Welcome, ${headerContext.visitorName}!</span>

Плюсы: стратегия "тянуть" (никто не спрашивает - ничего не выйдет), легко сделать контексты @Scope ( "запрос" ) и включить кэширование по всему запросу, никаких проблем с множественным наследованием. Просто закодирован в одном месте, настроен в одном месте и может использоваться в любом JSP или файле тега как обычное выражение.

Минусы: сочетание push и pull в рамках одного фреймворка (нужно больше об этом думать), no Spring Поддержка MVC в классах реализации контекста (я имею в виду эти неприятные предварительные аргументы в методах обработчика контроллера), просто Spring beans.

Ответ 3

Наконец, я решил придерживаться подхода @ModelAttribute, несмотря на его ограничения.

/**
 * Base class for all page controllers (i.e., not form submits)
 * @author malexejev
 * 23.03.2011
 */
public abstract class AbstractPageController {

    @Autowired
    private VisitorService visitorService;

    @Autowired
    private I18nSupport i18nSupport;

    @Value("${xxx.env}")
    private String environment;

    /**
     * Implicit model enrichment with reference data.
     * No heavy operations allowed here, since it is executed before any handler method of 
     * all extending controllers
     */
    @ModelAttribute("appContext")
    public Map<String, Object> populateReferenceData(HttpServletRequest request) {
        Map<String, Object> dataMap = new HashMap<String, Object>();

        // FIXME some data is app-wide and constant, no need to re-create such map entries
        // I should take care about it when more reference data is added
        dataMap.put("visitorName", visitorService.getVisitorName());
        dataMap.put("env", environment);
        dataMap.put("availableLanguages", i18nSupport.getAvailableLanguages());
        dataMap.put("currentPath", request.getPathInfo() != null ? request.getPathInfo() : request.getServletPath());

        return Collections.unmodifiableMap(dataMap);
    }

}

Таким образом, я могу получить данные в представлениях через ${appContext.visitorName}. Это позволяет мне прозрачно переключаться на реализацию Spring bean (см. № 3 в моем ответе выше, @Component ( "headerContext" )) в случае любых будущих проблем с @ModelAttributes.

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

Ответ 4

У вас есть несколько вариантов, хотя они и не идеальны.

  • абстрактный контроллер, как вы упомянули
  • создайте службу, которая вернет вам данные модели. Теперь вы переместили проблему на сервисный уровень, где он, вероятно, не принадлежит, но по крайней мере ваши контроллеры могут просто сделать один служебный вызов во время каждого метода контроллера.
  • создать фильтр и заполнить общие части модели в фильтре.
  • вы, возможно, можете создать какого-нибудь монстра с аннотациями, например, аннотировать методы контроллера, а затем отправлять процессы объектам контроллера для ввода данных (это я не уверен, как это сделать точно, но должен быть способ)
  • spring AOP может помочь вам сделать # 4 более изящно

Это всего лишь некоторые идеи, чтобы обсудить некоторые вопросы.

Ответ 5

Перехватчик обработчика

отлично подходит для общих данных, которые используются на каждой странице.

Если вам нужны мелкозернистые "компоненты", вы действительно должны пересмотреть использование плиток apache. оттуда вы можете использовать "контроллер" (ViewPreparer) для каждой плитки, как указано здесь:

http://richardbarabe.wordpress.com/2009/02/19/apache-tiles-2-viewpreparer-example/