Объявление компонента Spring в родительском контексте против дочернего контекста

#security #spring #ioc-container #servlets #security-context

#Безопасность #spring #ioc-контейнер #сервлеты #безопасность-контекст

Вопрос:

У меня есть объект spring bean (dao), который я создаю в своем ServletContext с помощью следующего xml:

 <bean id="userDao" class="com.company.dao.impl.UserDaoImpl">
    <property name="sessionFactory" ref="sessionFactory" />
</bean>
  

Этот компонент объявлен внутри моего webapp-servlet.xml файл и используется моим приложением в ServletContext.

Я также использую SpringSecurity. Насколько я понимаю, это выполняется в другом контексте (SecurityContext).

Мое приложение имеет webapp-security.xml где я создаю экземпляр пользовательского поставщика аутентификации. Я хотел бы использовать свой dao, который используется в моем приложении, чтобы также выполнять поиск пользователя в моем контексте безопасности, но когда я запускаю:

 <bean id="userAuthenticationProvider" class="com.company.security.UserAuthenticationProvider">
    <property name="userDao" ref="userDao" />
</bean>
  

Я получаю ошибки, говорящие о том, что такого компонента «UserDao» не существует. Компонент автоматически подключается в beans, объявленных в моем другом контексте, но не в моем контексте безопасности. Согласно документам Spring, я считаю, что оба отдельных контекста необходимы в web.xml

 <listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener
    </listener-class>
</listener>

<listener>
    <listener-class>
        org.springframework.security.web.session.HttpSessionEventPublisher
    </listener-class>
</listener>
  

Итак, мой вопрос в том, как я могу получить доступ к моему DAO, который находится в моем ServletContext внутри моего SecurityContext? Есть ли модификатор области видимости для моего dao, или я могу каким-то образом получить ServletContext во время выполнения в моем поставщике аутентификации? Для справки, вот как я хочу использовать его внутри моего поставщика аутентификации:

 public class UserAuthenticationProvider extends
    AbstractUserDetailsAuthenticationProvider {

    @Override
protected UserDetails retrieveUser(String userName,
        UsernamePasswordAuthenticationToken authenticationToken)
        throws AuthenticationException {

    // use dao here
  

спасибо, что объяснили мне это

Обновить:

Продолжая мое расследование, кажется, что DispatcherServlet, в котором я использую свои dao, является дочерним контекстом, а контекст безопасности находится где-то выше. Следовательно, компоненты в моем DispatcherServlet не могут быть видны родительскими контекстами. Я думаю, что ответ заключается в том, чтобы каким-то образом переместить мои объявления компонентов в контекст родительского приложения, но я не уверен, как это сделать. Вот мой web.xml

 <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>WEB-INF/spring-*.xml
    </param-value>
</context-param>

<listener>
    <listener-class>
        org.springframework.security.web.session.HttpSessionEventPublisher
    </listener-class>
</listener>

<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener
    </listener-class>
</listener>

<filter>
    <filter-name>springSecurityFilterChain</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy
    </filter-class>
</filter>

<filter-mapping>
    <filter-name>springSecurityFilterChain</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

<servlet>
    <servlet-name>myapp</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet
    </servlet-class>
    <load-on-startup>1</load-on-startup>
    <init-param>
        <param-name>listings</param-name>
        <param-value>true</param-value>
    </init-param>
</servlet>

    ...
  

I moved all of my dao creation out to a spring-dao.xml, and in my spring-security.xml I am now doing a:

 <import resource="spring-dao.xml" />
  

The daos stil remain visible to the DispatcherServlet context and invisible to my SecurityContext though.

ANSWERED:

Alright, I figured it out. Here were some helpful links:

http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/beans.html#context-create

http://forum.springsource.org/showthread.php?115774-Spring-Security-Custom-UserDetailsService-to-use-User-Service-Dao

http://static.springsource.org/spring-security/site/faq.html#faq-method-security-in-web-context

So the problem was we need to make sure that the dao exists in the ApplicationContext (higher up spring container). To make sure this happened I changed my web.xml to be:

 <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>WEB-INF/spring-dao.xml WEB-INF/spring-security.xml
    </param-value>
</context-param>

<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener
    </listener-class>
</listener>

<listener>
    <listener-class>
        org.springframework.security.web.session.HttpSessionEventPublisher
    </listener-class>
</listener>

<filter>
    <filter-name>springSecurityFilterChain</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy
    </filter-class>
</filter>

<filter-mapping>
    <filter-name>springSecurityFilterChain</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

<servlet>
    <servlet-name>webapp</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet
    </servlet-class>
    <load-on-startup>1</load-on-startup>
    <init-param>
        <param-name>listings</param-name>
        <param-value>true</param-value>
    </init-param>
</servlet>
  

Я думал, что это гарантирует, что первый запускаемый загрузчик контекста прочитает мою конфигурацию dao (и создаст мои компоненты dao), а затем мою конфигурацию безопасности. Поскольку компоненты dao создаются таким образом, я удалил предыдущий «import resource =»spring-dao.xml «» заявление в security.xml потому что в нем больше не будет необходимости.

Сразу после этой настройки параметров контекста я создал ContextLoaderListener. Это контейнер spring более высокого уровня, чем DispatcherServlet, поэтому я решил, что если поставить это первым, то первым будет парень, который прочитает эти конфигурационные файлы, а затем создаст компоненты. Тогда любой дочерний контекст будет иметь к ним доступ. Возможно, это работает не так, поскольку DispatcherServlet может даже не прочитать contextConfigLocation , но даже если это произойдет, я полагал, что на данный момент компоненты уже будут объявлены, так что, к сожалению, они принадлежат родительскому контексту.

Теперь, для другого трюка … чтобы получить мой DAO, я не смог @Autowired его. Мне пришлось вручную вводить его через XML:

     <bean id="userAuthenticationProvider" class="com.company.app.security.UserAuthenticationProvider">
    <property name="userDao" ref="userDao" />
</bean>
  

Конечно, я создал методы getter и setter в своем dao, и вуаля! Я не знаю, почему @Autowired здесь не работает. Я предполагаю, что это сделано специально. Возможно, это относится к SecurityContext (он не будет извлекаться из других контекстов), или, возможно, @Autowired вообще извлекается только из текущего контекста, или, может быть, потому, что я создал компонент через XML, я должен также устанавливать любые свойства через xml, а не через аннотации? (аннотации включены и работают в моем пространстве имен приложений верхнего уровня).

В любом случае .. я все еще многого не понимаю, но важным моментом является то, что он наконец работает.

Ответ №1:

Если вы собираетесь использовать Spring MVC, вам обязательно нужно понимать иерархию ApplicationContext Spring MVC. Вам также следует кое-что узнать об основных компонентах и жизненных циклах в контейнере сервлетов, поскольку вы, похоже, тоже не понимаете, как работают слушатели и сервлеты.

Чтобы кратко объяснить вашу ситуацию:

  1. Вы создаете два ApplicationContexts: корневой контекст и контекст DispatcherServlet. Корневой контекст создается ContextLoaderListener на основе файлов, названных в contextConfigLocation . Этот контекст предназначен для содержания компонентов, составляющих основную логику вашего приложения. Контекст DispatcherServlet создается при запуске этого сервлета и основан на файле с именем «webapp-servlet.xml «. Этот контекст предназначен для содержания любых компонентов, которые поддерживают экземпляр DispatcherServlet, с которым он связан, и в нем должны быть только компоненты, связанные с представлением.
  2. Контекст DispatcherServlet становится дочерним элементом корневого контекста. Это позволяет вводить ваши основные компоненты из корневого контекста в компоненты уровня представления. Видимость односторонняя. Компоненты уровня представления недоступны для основных компонентов, что желательно. Вот почему ваш DAO не удалось внедрить в вашего поставщика аутентификации. DAO был в дочернем контексте.
  3. Службы на основе аннотаций применяются только в контексте, в котором они объявлены. Если @Autowired не работает для определенного компонента, это потому, что вы не объявили <context:component-scan/> или <context:annotation-config/> в контексте, где этот компонент существует.

Комментарии:

1. это интересно, что вы не можете @autowired в разных контекстах, но вы можете вводить xml в разных контекстах. спасибо за информацию

2. Нет, это неверно. Извините, если я был неясен. @Autowired отлично работает в разных контекстах. Однако «компонент обработки», который выполняет @Autowiring, существует внутри и работает только с одним контекстом. Таким образом, для заданных компонентов a и b в контекстах A и B , соответственно, где a есть @Autowired b поле, это поле будет установлено автоматически, только если 1) A содержит «компонент обработки» и 2) A является дочерним элементом B . Этот «компонент обработки» создается тегами, которые я связал в части 3 моего ответа. Наличие этого компонента обработки B не повлияет на компоненты A .

3. как насчет applicationContext-security.xml в моем заявлении? создает ли он компоненты в корневом контексте или контексте сервлета?

4. @anton1980 — это зависит от того, где applicationContext-security.xml загружается файл. Импортируется ли он в определение DispatcherServlet или в ContextLoaderListener

5. @RyanStewart, можно ли использовать @ComponentScan его в обоих контекстах? Когда я попытался использовать его в корневом контексте, каким-то образом все мои компоненты были созданы во второй раз и в веб-контексте, и в итоге я получил повторяющиеся вызовы некоторых моих служб.