如何防止多个bean实例?

时间:2012-10-05 16:12:34

标签: java spring glassfish

我有一个基于Spring的Web应用程序,有两个servlet - 一个用于MVC,另一个用于spring-ws。应用程序中使用了几个bean,它们使用注释进行自动装配。每次应用程序启动时,它都会创建每个bean类型的3个实例 - 即使它们是单例范围的。 @PostConstruct方法也为每个方法调用三次。

据我所知,有3个应用程序上下文= 1个常用+ 2个servlet,但每个bean,控制器,端点等都创建了三次。至少在父应用程序上下文中加载的公共bean应该只实例一次。

组件扫描的base-package属性指向不相交的包。

我使用了一个类来转储上下文信息(https://gist.github.com/1347171),看起来有三个不同的上下文具有相同的结构(相同的bean)。他们的id是“/ project /”,“/ project / rest”,“/ project / soap”。

我尝试注释掉ContextLoaderListener,删除soap servlet及其相关的XML文件(applicationContext& soap-servlet),并将常用内容移动到其余的servlet中(这样只有一个配置xml,只有一个组件 - 扫描),我仍然得到每个bean的3个实例。在这种情况下,应用程序上下文ID是“/ Project /”(确切的大小写),“/ project /”和“/ project /”。

的web.xml

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

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

<servlet>
    <servlet-name>rest</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
    <servlet-name>rest</servlet-name>
    <url-pattern>/rest/*</url-pattern>
</servlet-mapping>

<servlet>
    <servlet-name>soap</servlet-name>
    <servlet-class>org.springframework.ws.transport.http.MessageDispatcherServlet</servlet-class>
    <init-param>
        <param-name>transformWsdlLocations</param-name>
        <param-value>true</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
    <servlet-name>soap</servlet-name>
    <url-pattern>/soap/*</url-pattern>
</servlet-mapping>

的applicationContext.xml

<context:annotation-config/>
<context:component-scan base-package="test.common"/>

<task:annotation-driven/>

rest-servlet.xml

<mvc:annotation-driven/>
<context:component-scan base-package="test.rest"/>

<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
    <property name="messageConverters">
        <util:list id="beanList">
            <ref bean="formHttpMessageConverter"/>
        </util:list>
    </property>
</bean>

<bean id="formHttpMessageConverter"
      class="org.springframework.http.converter.FormHttpMessageConverter"/>

<mvc:interceptors>
    <bean class="test.rest.Interceptor"/>
</mvc:interceptors>

皂servlet.xml中

<sws:annotation-driven/>
<context:component-scan base-package="test.soap"/>

<sws:dynamic-wsdl
        id="service"
        portTypeName="service"
        locationUri="/soap/service"
        targetNamespace="http://server/soap">

    <sws:xsd location="/WEB-INF/SoapService.xsd"/>
</sws:dynamic-wsdl>

<sws:interceptors>
    <bean id="validatingInterceptor"
          class="org.springframework.ws.soap.server.endpoint.interceptor.PayloadValidatingInterceptor">
        <property name="schema" value="/WEB-INF/SoapService.xsd"/>
        <property name="validateRequest" value="true"/>
        <property name="validateResponse" value="true"/>
    </bean>
</sws:interceptors>

2 个答案:

答案 0 :(得分:0)

在bean上使用javax.ejb.Singleton注释。

答案 1 :(得分:0)

原因是Spring mvc上下文初始化和这些神奇的注释默认值有点令人困惑的文档:(。

由于以下原因,您可能有三份副本:

  1. 您的contextConfigLocation定义创建了一个在所有servlet之间共享的根webapp上下文加载(每个应用程序只有一个)。每个-servlet-config.xml文件都可以访问这些bean,但反之亦然。 Difference between applicationContext.xml and spring-servlet.xml in Spring Framework
  2. 所以第二个实例来自你的-servlet应用程序上下文,因为你再次明确定义了注释驱动。
  3. 不确定您使用的是哪个Spring框架版本,但是通过定义注释驱动和自定义请求映射适配器,您几乎可以创建两个。 https://jira.spring.io/browse/SPR-8648
  4. 事实上,<mvc:annotation-driven>拥有自定义适配器的所有标签,请检查您想要的架构。然而,另一种解决这个问题的方法是在跟踪模式下进行痛苦的调试,并查看哪个bean实际上正在创建适配器。在您的适配器构造函数中放置一个断点,然后在DispatcherServlet-&gt; mapperHandler-&gt;拦截器 - &gt; mapping-&gt; context-&gt; configFileLocation中查看堆栈以查看正在创建此bean的文件

    如果您确实想要自定义消息转换器,那么您应该这样做:

    <mvc:annotation-driven
            content-negotiation-manager="contentNegotiationManager">
                <mvc:message-converters register-defaults="false">
                    <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
                        <property name="objectMapper" ref="afterBurnerObjectMapper"/>
                    </bean>
                    <bean class="org.springframework.http.converter.ByteArrayHttpMessageConverter"/>
                    <bean class="org.springframework.http.converter.xml.SourceHttpMessageConverter"/>
                    <bean class="org.springframework.http.converter.FormHttpMessageConverter"/>
                    <bean class="org.springframework.http.converter.StringHttpMessageConverter"/>
                </mvc:message-converters>
        </mvc:annotation-driven>