Spring MVC сложная модель популяции из нескольких источников - PullRequest
10 голосов
/ 21 марта 2011

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

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

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

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

Ответы [ 5 ]

4 голосов
/ 23 марта 2011

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>
4 голосов
/ 23 марта 2011

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

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

Плюсы: нет

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

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 использует шаблонную модель «push» вместо «pull». В данном случае это означает, что при вызове любого из методов @RequestMapping, определенных в этом классе, вызываются все методы @ModelAttribute этого класса. Нет никакой разницы, если шаблон действительно нуждается в visitorName и действительно ли шаблон существует для определенного действия. Это включает в себя запросы 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

}

Затем выставьте такие бины 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 в рамках одного фреймворка (нужно больше думать об этом), никакой поддержки Spring MVC в классах реализации контекста (я имею в виду эти неприятные заранее заполненные аргументы в методах обработчика контроллера), просто бобы весенние.

2 голосов
/ 24 марта 2011

Наконец, я решил придерживаться подхода @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}. Это позволяет мне прозрачно переключаться на реализацию bean-компонента Spring (см. № 3 в моем ответе выше, @Component ("headerContext")) в случае любых будущих проблем с @ ModelAttributes.

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

1 голос
/ 22 марта 2011

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

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

это всего лишь некоторые идеи, чтобы начать обсуждение

0 голосов
/ 10 ноября 2012
Перехватчик

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...