在父上下文和子上下文中声明Spring Bean的区别

24

我有一个Spring Bean (dao) 对象,我通过以下xml在我的ServletContext中实例化它:

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

这个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"的bean"的错误提示。在我的其他上下文中声明的bean可以自动装配,但在安全上下文中却不行。根据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中? 我的dao有一个作用域修饰符吗?或者我能否在身份验证提供程序内部获取运行时的ServletContext?作为参考,这是我想在身份验证提供程序内使用它的方式:

public class UserAuthenticationProvider extends
    AbstractUserDetailsAuthenticationProvider {

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

    // use dao here

感谢您向我解释这个问题。

更新:

在继续调查中,我发现我正在使用 DAO 的 DispatcherServlet 是一个子上下文,而安全上下文则位于更高的位置。因此,DispatcherServlet 中的 bean 无法被父级上下文看到。我认为答案是以某种方式将我的 bean 声明移到父应用程序上下文中,但我不确定如何做到这一点。以下是我的 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>

    ...

我将所有的dao创建移到了一个spring-dao.xml中,在我的spring-security.xml文件中,我现在进行以下操作:

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

这些 DAO 对于 DispatcherServlet 上下文可见,但对于我的 SecurityContext 是不可见的。

回答:

好的,我明白了。下面是一些有用的链接:

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

问题出在我们需要确保 DAO 存在于应用程序上下文(更高层次的 Spring 容器)中。为了确保这一点,我改变了我的 web.xml 文件如下:

<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 bean),然后再读取安全性配置文件。由于通过这种方式创建了dao bean,因此我在security.xml中删除了之前的"import resource="spring-dao.xml""语句,因为它已不再需要。
紧接着context-param配置,我创建了ContextLoaderListener。这是一个高级spring容器,比DispatcherServlet更高级,我想把它放在最前面来读取这些配置文件,然后“他”将创建这些bean。然后,任何子上下文都可以访问它们。这可能并不是DispatcherServlet的工作方式,因为它甚至可能没有读取contextConfigLocation,但即使它这样做了,我也认为此时bean已经被声明,那么太糟糕了,父上下文拥有它们。
现在,另一个技巧是......为了获取我的DAO,我不能使用@Autowired自动装配。我必须通过XML手动注入它:
    <bean id="userAuthenticationProvider" class="com.company.app.security.UserAuthenticationProvider">
    <property name="userDao" ref="userDao" />
</bean>

当然,我在我的dao中创建了getter和setter方法,然后就完成了!我不知道为什么@ Autowired在这里不能工作。我认为这是有意为之的。也许这对于SecurityContext(它不会从其他上下文中拉取)是特定的,或者@ Autowired通常仅从当前上下文中获取,或者因为我通过XML创建了bean,所以我也必须通过xml而不是注释设置任何属性? (注释在我的顶级应用程序命名空间中启用并正常工作)。总之,还有很多我不理解的地方,但重要的是它终于运行起来了。
1个回答

61

如果你要使用Spring MVC,你一定需要了解Spring MVC的ApplicationContext层次结构。你还应该学习一些关于servlet容器中基本组件和生命周期的知识,因为你似乎对监听器和servlet的工作方式感到困惑。

简要说明你的情况:

  1. 你正在创建两个ApplicationContext: 根上下文和DispatcherServlet上下文。根上下文由ContextLoaderListener基于contextConfigLocation中命名的文件创建。该上下文旨在包含组成应用程序核心逻辑的bean。当servlet启动并基于名为"webapp-servlet.xml"的文件创建时,将创建DispatcherServlet上下文。该上下文旨在包含支持与之相关联的DispatcherServlet实例的任何bean,并且应仅在其中包含与视图相关的bean。
  2. DispatcherServlet上下文成为根上下文的子级。这使得根上下文中的核心bean可以注入到视图层bean中。可见性是单向的。视图层bean对核心bean不可用,这是期望的。这就是为什么DAO无法注入到身份验证提供者中的原因。 DAO在子上下文中。
  3. 基于注释的服务仅适用于声明它们的上下文中。如果@Autowired对于特定的bean不起作用,则是因为您没有在该bean存在的上下文中声明<context:component-scan/><context:annotation-config/>

有趣的是,您不能在不同上下文中使用 @autowired,但您可以在不同上下文中进行 XML 注入。感谢提供这些信息。 - dev
1
不,那不正确。如果我表达不清楚,我很抱歉。@Autowired 在不同的上下文中都可以正常工作。然而,执行 @Autowiring 的“处理 bean”仅存在于一个上下文中并且只对该上下文中的 bean 进行操作。因此,假设在上下文 A 和 B 中有 bean ab,其中 a 具有一个 @Autowired b 字段,那么只有在 1) A 包含一个“处理 bean”并且 2) AB 的子级时,该字段才会自动设置。这个“处理 bean”是由我在答案第三部分中链接的标签创建的。将该处理 bean 放在 B 中对 A 中的 bean 没有影响。 - Ryan Stewart
我的应用程序中的applicationContext-security.xml文件是什么?它会在根上下文或servlet上下文中创建bean吗? - anton1980
@anton1980 - 这取决于 applicationContext-security.xml 加载的位置。它是在 DispatcherServlet 定义中导入还是在 ContextLoaderListener 中导入? - kapad
@RyanStewart,两个上下文中都使用@ComponentScan可以吗?当我尝试在根上下文中使用它时,所有的bean都在Web上下文中创建了第二次,最终导致我的某些服务被重复调用。 - lanoxx
@Ianoxx 我刚遇到了这个问题。修复方法似乎是要小心,只给子组件@ComponentScan包树,而不是在父级中也扫描它们。 - Mark Wood

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接