Почему Spring @Value несовместим с @Controller?

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

Я использую Spring 3.1.0.RELEASE с Spring STS и сервером vFabric tc. Реализован базовый небольшой REST-сервер с использованием класса @Controller. Это все здорово (правда, так оно и есть), но @Controller также является @Transactional, и между этим и переплетением времени загрузки и tc-сервером vFabric он ломает @Value.

@Controller
@RequestMapping("/hello")
public class MyAPI {

    @Value("${my.property}")
    private String prop;
    ...

    @Transactional
    handleRequest(...) ...


}

И файл свойств app.properties:

my.property = SUCCESS

Это отлично работает в JUnit, когда тест получает объект MyAPI, для которого свойство установлено на «УСПЕХ». Но когда приложение загружается в vFabric, я предполагаю, что оно получает время загрузки и проксирование. Что бы ни случилось, создано два экземпляра MyAPI, один из которых имеет prop == "SUCCESS", а другой (который, к сожалению, обрабатывает http-запрос) имеет prop == "${my.prop}".

Так что в целом я называю это провалом магии, что больше всего беспокоит меня при использовании таких вещей, как АОП. Даже с STS я не знаю, как отследить причину проблемы или выяснить, является ли это серьезной ошибкой. Если это ошибка, то я не знаю, является ли это ошибкой Spring, AspectJ, ткача во время загрузки или vFabric, поэтому я даже не знаю, куда подать отчет об ошибке.

Поэтому любая помощь в понимании этого будет оценена по достоинству. Спасибо.


person Old Pro    schedule 11.04.2012    source источник
comment
Вы уверены, что это вызвано АОП? Что если удалить @Transactional?   -  person axtavt    schedule 11.04.2012
comment
@axtavt вы указали мне на решение здесь. См. мой ответ полностью ниже, но краткий ответ заключается в том, что только Controller создаются дважды в моей (неправильной) конфигурации. С @Transactional проблем нет, так как он использует AspectJ, а не прокси.   -  person Old Pro    schedule 12.04.2012


Ответы (4)


Я понял. Это было действительно слишком много магии.

Я использовал Spring Roo в STS для создания базовой структуры приложения, а затем исключил Roo с помощью STS, так как мы не хотели придерживаться его.

Одна вещь, которую Ру делает в качестве «лучшей практики», — это создание двух контекстов Spring, одного для всего приложения и одного, ограниченного сервлетом диспетчера. Точно почему, я до сих пор не понял, но я предполагаю, что они хотят, чтобы вещи уровня представления, такие как контроллеры, не проникали в общий сервисный уровень. Это было прекрасно объяснено axtavt здесь. Все это было скрыто от меня СТС.

В STS с Roo источник WEB-INF находится не там, где я ожидал, в /src/main/resources (где находится каталог META-INF), а в /src/main/webapp, который не является Java исходный каталог и, таким образом, показан совершенно отдельно, чуть выше каталога /target, поэтому я принял его за выходную папку.

В applicationContext.xml Roo вставил фильтр, чтобы предотвратить создание контроллеров контекстом приложения, как описано в сообщении axtavt, но также добавил еще один фильтр, чтобы исключить сканирование сгенерированных Roo классов. Я вынул оба фильтра одновременно, не совсем понимая, для чего они нужны, но думая, что это просто остатки Ру.

Итак, теперь у меня проблема с Контроллеры создаются дважды, как объяснялось ранее . А тот, что в контексте приложения, получает назначенное свойство, потому что он использует applicationContext.xml и находит файл свойств. Но почему они оба не установили свойства?

Что возвращает меня к скрытой папке веб-приложений. Внутри папки WEB-INF Ру поместил файл web.xml (естественно) и папку spring, содержащую файл webmvc-config.xml. Этот файл конфигурации был настроен для сканирования, создания и настройки только контроллеров. Файл web.xml настраивает веб-приложение на использование applicationContext.xml и диспетчерский сервер на использование webmvc-config.xml, поэтому мне следовало оставить фильтр в applicationContext.xml, чтобы избежать двойного создания.

Последней частью головоломки был файл webmvc-config.xml. Поскольку это контекст, в котором настраиваются контроллеры, в этом файле также должен быть настроен ‹context:property-placeholder/›, чтобы он мог найти файл свойств.

person Old Pro    schedule 12.04.2012

Во-первых, запись с использованием $ верна, а не #

Теперь, что касается исходного плаката, я думаю, что у вас конфликтующее поведение между двумя аннотациями (@Controller и @Transactional).

Использование аннотации @Transactional проксирует вашу реализацию (думаю, чтобы обнаружить ошибку и запустить откат).

Теперь вы говорите, что видите 2 экземпляра вашего контроллера. Этого не должно быть. Обычно в памяти должен быть загружен только один экземпляр контроллера.

Может быть связано с вашими конфигурационными файлами или из-за наличия @Transactional и его прокси?

В качестве примечания: я никогда не использую @Transactional в самих контроллерах, а либо в методе службы, либо в dao. Поскольку фактический процесс, который может дать сбой и который необходимо откатить, существует, и к нему может быть доступ с разных контроллеров/источников.

С Уважением.

person Farid    schedule 11.04.2012
comment
Я понимаю желание переместить @Transactional из Controller на сервисный уровень. Тем не менее, я верю в бережливое программирование, при котором вы не выполняете дополнительную работу, которая вам никогда не понадобится. В этом случае при реализации API-интерфейса службы REST контроллеру практически ничего не остается делать, кроме как реализовать службу, и никто никогда не будет вызывать службу каким-либо другим способом. Если когда-нибудь они это сделают, тогда будет легко провести рефакторинг, но заранее я сэкономил на создании целого слоя. А благодаря использованию AspectJ для @Transactional мне не нужно беспокоиться о прокси. - person Old Pro; 12.04.2012
comment
бережливое программирование — это хорошо, но может принимать различное представление в зависимости от контекста. В случае Rest Services вы должны учитывать, что 1. они часто развиваются (новые требования/требования) 2. вы хотите, чтобы контроллеры были легко читаемы для разработчиков и не загромождали их бизнес-правилами. это требует немного больше работы в начале (создание службы), но дает вам реальную свободу в долгосрочной перспективе. - person Farid; 12.04.2012
comment
Фарид, мое видение бережливого производства заключается в том, чтобы избежать дополнительной работы на начальном этапе, если это не усложнит будущий рост. Логика контроллера в основном такова: прочитать запрос, выполнить запрошенное действие, записать ответ с запрошенным действием внутри блока try/catch. Позже, если потребуется отдельный объект службы, вы можете просто вырезать внутреннюю часть блока try/catch из контроллера и вставить его в метод службы. На самом деле нет ограничений на долгосрочную свободу, но определенно быстрее получить работающий продукт. - person Old Pro; 12.04.2012

У меня была та же проблема, и это было то, что мне нужны ОБА

Чтобы заставить эти два сенариуса работать

  1. внедрить атрибут (через @Value) в контроллер
  2. Внедрить управляемый компонент (через @Inject), который имеет атрибут (через @Value) в контроллер

Надеюсь, это поможет.

Вот файл web.xml.

    <servlet>
    <servlet-name>spring</servlet-name>
    <servlet-class>
        org.springframework.web.servlet.DispatcherServlet
    </servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:/spring-config/spring-servlet.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
    <servlet-name>spring</servlet-name>
    <url-pattern>/</url-pattern>
</servlet-mapping>

Вот spring-servlet.xml: ключ должен был иметь ОБА util:properties и context:property-placeholder. Хотя они находят один и тот же файл, они служат разным целям.

<util:properties id="propSource" location="classpath:/properties/prop1.properties" />
<context:property-placeholder location="classpath:/properties/prop1.properties" />

<context:component-scan base-package="com.concurrent.controller" />

<context:annotation-config/>
<mvc:annotation-driven/> 

Вот фрагмент моего класса контроллера:

@Controller
@RequestMapping("/fooController")
public class MyController {

@Value("${message1}")
public String message1;

@Inject
public ConfigProperties configProperties;

Наконец, вот мой класс, в который я добавляю свойство:

@Service
public class ConfigProperties {

@Value("${message1}")
public String message1;
}

Это сработало для меня и сработает для вас. Удачи!

person CKuharski    schedule 30.10.2013

В моем случае я решаю это так: в spring-servlet.xml перед <context:component-scan ... /> я помещаю это:

<context:property-placeholder location="classpath:strings.properties"/>

Пока strings.properties файл я положил в src/main/resources/.

Примечание: тег context:property-placeholder имеет ограниченную видимость, поэтому я где-то читал рекомендацию дублировать его в каждом файле контекста, где вы используете строки из него.

person msangel    schedule 11.09.2013