简体   繁体   中英

How to prevent multiple bean instances?

I have a Spring-based web application, with two servlets - one for MVC and one for spring-ws. There are several beans used in the application, and they are autowired using annotations. Each time the application starts, it creates 3 instances of each bean type - even though they are singleton-scoped. The @PostConstruct methods are also called three times for each of them.

I understand that there are 3 application contexts = 1 common + 2 servlets, but each bean, controller, endpoint, etc. is created three times. At least the common beans, loaded in the parent application context should be instanced only once.

The base-package attribute of component-scan points to disjoint packages.

I've used a class to dump the context information (https://gist.github.com/1347171) and it appears there are three different contexts with identical structure (same beans). Their id's are "/project/", "/project/rest", "/project/soap".

I tried commenting out the ContextLoaderListener, removing the soap servlet and their associated XML files (applicationContext & soap-servlet) and moving the common stuff into the rest servlet (so that there is only one config xml and only one component-scan), and I still get 3 instances of each bean. In this case the application context id's are "/Project/" (exact casing), "/project/" and "/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>

soap-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>

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

Well the reason is the slightly confusing documentation on spring mvc context initialization and these magical annotation defaults :(.

You probably have three copies because of the following:

  1. your contextConfigLocation definition creates a root webapp context loading (only one per application) that is shared across all servlets. Each of your -servlet-config.xml files can access these beans but not vice versa. Difference between applicationContext.xml and spring-servlet.xml in Spring Framework
  2. so the second instance is coming from your -servlet application context because you defined the annotation-driven explicitly again.
  3. Not sure which spring framework version you are using, but by defining both annotation-driven and custom request mapping adapter, you are pretty much creating two. https://jira.spring.io/browse/SPR-8648

In fact, <mvc:annotation-driven> has all the tags to customize your adapter, check the schema which is what you want. Yet anther way to figure this out is by painful debugging in trace mode and looking at which bean is actually creating your adapter. Put a breakpoint in your adapter constructor and then look at the stack in DispatcherServlet->mapperHandler->interceptor->mapping->context->configFileLocation to see which file is creating this bean

If you do want to customize the message converters, you should be doing this:

<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>  

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM