簡單做了一個定時器,發現它的構造方法被執行了兩次,且是不同的對象。配置如下所示:
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重復執行兩次(實例被構造兩次)問題分析》下載。