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重复执行两次(实例被构造两次)问题分析》下载。

posted on 2017-07-13 08:19  bijian1013  阅读(12996)  评论(0编辑  收藏  举报

导航