Spring Bean重復執行兩次(實例被構造兩次)問題分析


  簡單做了一個定時器,發現它的構造方法被執行了兩次,且是不同的對象。配置如下所示:

springMVC-servlet.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p"
    xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:task="http://www.springframework.org/schema/task"
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
        http://www.springframework.org/schema/beans/spring-beans-4.1.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context-4.1.xsd
        http://www.springframework.org/schema/mvc
        http://www.springframework.org/schema/mvc/spring-mvc-4.1.xsd
        http://www.springframework.org/schema/task  
        http://www.springframework.org/schema/task/spring-task-4.1.xsd">
    
    <context:component-scan base-package="com.bijian.study">
    </context:component-scan>
    
    <context:annotation-config />
    
    <mvc:annotation-driven></mvc:annotation-driven>
    
    <bean id="viewResolver" class="org.springframework.web.servlet.view.UrlBasedViewResolver">   
        <property name="viewClass"  
            value="org.springframework.web.servlet.view.JstlView" />   
        <property name="prefix" value="/WEB-INF/views" />
        <property name="suffix" value=".jsp" />   
    </bean>
    
    <task:scheduled-tasks>
        <task:scheduled ref="timerTask" method="doTask" fixed-delay="1000" initial-delay="2000"/>
    </task:scheduled-tasks>
</beans>

web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee    
   http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
   
    <display-name>SpringMVC</display-name>
    
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/springMVC-servlet.xml</param-value>
    </context-param>
    
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    
    <filter>
        <filter-name>encodingFilter</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
        <init-param>
            <param-name>forceEncoding</param-name>
            <param-value>true</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>encodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

    <servlet>
        <servlet-name>springMVC</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>springMVC</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
</web-app>  

TimerTask.java

package com.bijian.study.task;

import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Locale;

import org.springframework.stereotype.Component;

@Component("timerTask")
public class TimerTask {
    
    public void printTimeStamp() {
        
        Calendar ca = Calendar.getInstance();
        ca.setTimeInMillis(System.currentTimeMillis());
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS ", Locale.CHINA);
        //顯示當前時間 精確到毫秒  
        System.out.print(sdf.format(ca.getTime()));
    }

    public TimerTask() {
        
        this.printTimeStamp();
        System.out.println("計划任務被初始化了");
    }

    public void doTask() {
        
        this.printTimeStamp();
        System.out.print("計划任務被執行,線程id:");
        System.out.println(Thread.currentThread().getId());
    }
}

  啟動Tomcat服務器,運行結果如下:

  TimerTask.java中的構造方法被執行了兩次,創建了兩個TimerTask對象。為什么呢?

  讓我們來看下web.xml中的配置:

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

  因為該節點指派的applicationContext*.xml是用於實例化除servlet之外的所有對象的,可以說項目中絕大多數的service和dao層操作都由ContextLoaderListener傳遞給Spring來進行實例化。

  而如下配置:

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

  這個是用來處理所有servlet的,沒有它就無法通過請求地址來調用相應的Controller。如果不指定,則會按照注釋中所描述地那樣自動加載"工程名-servlet.xml"配置文件。無論是servletContext.xml還是applicationContext*.xml都可以按照<beans>...<bean id="XXX" class="XXX" />...</beans>這樣的形式來配置。

  問題來了,有時候不注重對象初始化的分類,尤其是使用<context:component-scan base-package="controller" />這樣的包掃描形式統一初始化,很容易造成滿足條件的對象被初始化兩次,那么在計划任務的時候被執行兩次也就不奇怪了。其實說來說去,還是要提醒大家,不同的配置文件其作用是不一樣的,不要將所有的初始化操作都放到一個配置文件中,更不要重復配置。不僅浪費資源,還很容易導致莫名其妙的故障。

  從另外一個角度來看:使用springMVC有兩個配置文件需要配置,一個是applicationContext.xml、另一個是web.xml,在applicationContext.xml里面配置事務管理器以及屬性注入等。web.xml里面要添加一個springMVC的servlet的注冊和映射(DispatcherServlet),這個servlet是springMVC的核心控制器,專門處理各個請求的,然后根據相應的參數分發給相應的業務控制器處理,業務控制器處理完之后就會返回一字符串給核心控制器,核心控制器再根據該字符串重定向或者轉發到相應的頁面。還必須給該核心控制器建一個配置文件,其形式為:核心控制器servlet名-servlet.xml,如springMVC-servlet.xml,該配置文件放在WEB-INF下面。

  修改思路:拆成兩個配置文件,分開配置。

web.xml,Controller在springMVC-servlet.xml中配置,Service、Dao等在applicationContext.xml中配置,為什么分開配置就能達到這樣的效果呢?其實還是靠springMVC-servlet.xml、applicationContext.xml中的exclude-filter、include-filter中的配置起作用的。

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee    
   http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
   
    <display-name>SpringMVC</display-name>
    
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/applicationContext.xml;/WEB-INF/task.xml</param-value>
    </context-param>
    
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    
    <filter>
        <filter-name>encodingFilter</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
        <init-param>
            <param-name>forceEncoding</param-name>
            <param-value>true</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>encodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

    <servlet>
        <servlet-name>springMVC</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>  
            <param-name>contextConfigLocation</param-name>  
            <param-value>/WEB-INF/springMVC-servlet.xml</param-value>  
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>springMVC</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
</web-app>  

applicationContext.xml,且根據《關於Spring中的<context:annotation-config/>配置》一文可知,在有context:component-scan配置時,<context:annotation-config />配置可刪除。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p"
    xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:task="http://www.springframework.org/schema/task"
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
        http://www.springframework.org/schema/beans/spring-beans-4.1.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context-4.1.xsd
        http://www.springframework.org/schema/mvc
        http://www.springframework.org/schema/mvc/spring-mvc-4.1.xsd
        http://www.springframework.org/schema/task  
        http://www.springframework.org/schema/task/spring-task-4.1.xsd">
    
    <context:component-scan base-package="com.bijian.study">
         <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller" />
    </context:component-scan>
</beans>

springMVC-servlet.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p"
    xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:task="http://www.springframework.org/schema/task"
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
        http://www.springframework.org/schema/beans/spring-beans-4.1.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context-4.1.xsd
        http://www.springframework.org/schema/mvc
        http://www.springframework.org/schema/mvc/spring-mvc-4.1.xsd
        http://www.springframework.org/schema/task  
        http://www.springframework.org/schema/task/spring-task-4.1.xsd">
    
    <context:component-scan base-package="com.bijian.study" use-default-filters="false">
         <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller" />
         <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Service" />
    </context:component-scan>
    
    <mvc:annotation-driven></mvc:annotation-driven>
    
    <bean id="viewResolver" class="org.springframework.web.servlet.view.UrlBasedViewResolver">   
        <property name="viewClass"  
            value="org.springframework.web.servlet.view.JstlView" />   
        <property name="prefix" value="/WEB-INF/views" />
        <property name="suffix" value=".jsp" />   
    </bean>
</beans>

task.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p"
    xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:task="http://www.springframework.org/schema/task"
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
        http://www.springframework.org/schema/beans/spring-beans-4.1.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context-4.1.xsd
        http://www.springframework.org/schema/mvc
        http://www.springframework.org/schema/mvc/spring-mvc-4.1.xsd
        http://www.springframework.org/schema/task  
        http://www.springframework.org/schema/task/spring-task-4.1.xsd">
    
    <task:scheduled-tasks>
        <task:scheduled ref="timerTask" method="doTask" fixed-delay="1000" initial-delay="2000"/>
    </task:scheduled-tasks>
</beans>

   啟動Tomcat服務器,運行結果如下,定時任務只初始化了一次。

  其實在大型的Web工程中,在部署是會分成兩塊部署,一塊部署純Web服務器,即WAR包,另一塊部署App服務器,即JAR包,Web服務通過http之類的請求到App服務器,且在Web服務器設置檢測,檢測哪些App服務是正常的,做負載均衡。

 

PS:在《JAVA項目監聽文件是否發生變化》一文最后,onApplicationEvent方法被執行兩次以上的問題,因為在web 項目中(spring mvc),系統會存在兩個容器,一個是root application context ,另一個就是我們自己的 projectName-servlet context(作為root application context的子容器)。在一個類里面實現了ApplicationListener接口,用於在初始化完成后做一些事情,但是Debug或輸出日志,發現它執行了3次,其中一次是Spring 框架初始化時執行,另外兩次是在項目啟動成功后,加載Spring-MVC時執行的。我們按上面的配置修改了,配置監聽器是否還會執行多次呢?

ContextFileListener.java

package com.bijian.study.listener;

import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.web.context.support.XmlWebApplicationContext;

public class ContextFileListener implements ApplicationListener<ContextRefreshedEvent> {

    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        //if(event.getSource() instanceof XmlWebApplicationContext){
            System.out.println(((XmlWebApplicationContext)event.getSource()).getDisplayName());
            //if(((XmlWebApplicationContext)event.getSource()).getDisplayName().equals("Root WebApplicationContext")){
                try {
                    System.out.println("init -----------------------------------------------");
                } catch (Exception e) {
                    e.printStackTrace();
                }
            //}
        //}
    }
}

1.將監聽器配置在applicationContext.xml中

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p"
    xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:task="http://www.springframework.org/schema/task"
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
        http://www.springframework.org/schema/beans/spring-beans-4.1.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context-4.1.xsd
        http://www.springframework.org/schema/mvc
        http://www.springframework.org/schema/mvc/spring-mvc-4.1.xsd
        http://www.springframework.org/schema/task  
        http://www.springframework.org/schema/task/spring-task-4.1.xsd">
    
    <context:component-scan base-package="com.bijian.study">
         <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller" />
    </context:component-scan>
    
    <bean class="com.bijian.study.listener.ContextFileListener"/>
</beans>

  運行結果:

  配置在applicationContext.xml中,還是會執行兩次,一次是Spring 框架初始化時執行,另外一次是在項目啟動成功后,加載Spring-MVC時執行的。

2.將監聽器配置在springMVC-servlet.xml中

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p"
    xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:task="http://www.springframework.org/schema/task"
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
        http://www.springframework.org/schema/beans/spring-beans-4.1.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context-4.1.xsd
        http://www.springframework.org/schema/mvc
        http://www.springframework.org/schema/mvc/spring-mvc-4.1.xsd
        http://www.springframework.org/schema/task  
        http://www.springframework.org/schema/task/spring-task-4.1.xsd">
    
    <context:component-scan base-package="com.bijian.study" use-default-filters="false">
         <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller" />
          <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Service" />
    </context:component-scan>
    
    <mvc:annotation-driven></mvc:annotation-driven>
    
    <bean id="viewResolver" class="org.springframework.web.servlet.view.UrlBasedViewResolver">   
        <property name="viewClass"  
            value="org.springframework.web.servlet.view.JstlView" />   
        <property name="prefix" value="/WEB-INF/views" />
        <property name="suffix" value=".jsp" />   
    </bean>
    
    <bean class="com.bijian.study.listener.ContextFileListener"/>
</beans>

  運行結果:

   配置在springMVC-servlet.xml中,只會執行一次,即在項目啟動成功后,加載Spring-MVC時執行的。

 

  工程代碼請在《Spring Bean重復執行兩次(實例被構造兩次)問題分析》下載。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM