Spring 定时任务重复执行的问题分析

Spring 定时任务重复执行的问题分析

背景:使用quartz时客户现场不知道为什么跑着跑着就停了,后来决定换成spring定时任务。

当使用spring定时任务时莫名奇妙的就是同一时间重复执行多次任务(通常情况下执行两次,有时候会达到3次)。下面记录过程并逐一分析说明,供以后参考;

1、分析原因后发现是由ClassPathXmlApplicationContext导致
通过n多次试验发现:
    当任务方法中不含任何业务逻辑时(如:只打印一些标志性的信息),任务能够正常执行,没有重复执行的现象;
    当任务方法中包含业务逻辑,或应用程序执行别的请求任务时,均可能导致重复执行;
最终定位到了,只要执行ServletUtil.getBean("xxxxxxxx");程序获取对象,就会导致重复执行。

1.1 在执行任务的程序中,使用到的对象加载方式如下:
ColumnService columnService = (ColumnService) ServletUtil.getBean("columnServiceImpl");

1.2 ServletUtil 的getBean 方法如下:
public static Object getBean(String beanId)
    {
        if(factory == null)
            factory = new ClassPathXmlApplicationContext("applicationContext.xml");
        Object  o = null ;
        try{
            o = factory.getBean(beanId);
        }catch(Exception e){
            System.out.println("获取bean失败");
        }
        
        return o;
    }

分析发现,在Tomcat容器启动时会扫描bean初始化加载spring上下文环境;然而当显式的这样调用的时候,会再次加载bean,初始化spring上下文环境。相当于容器里除了刚启动时的一套之外又产生了一条spring上下文,当然定时任务也有了两套。 
这里还说明另一个问题,单实例的bean并不是“单例模式”,只是保证一套spring上下文里只有一个bean实例。

2、应对策略

总算是找到原因了,于是考虑换一个对象获取方式,在伟大的网络中找到了答案,使用 ApplicationContextAware 接口可有效避免此问题。

2.1 重写bean获取工具类:

public class SpringContextUtil implements ApplicationContextAware {  
  
    // Spring应用上下文环境  
    private static ApplicationContext applicationContext;  
  
    /** 
     * 实现ApplicationContextAware接口的回调方法,设置上下文环境 
     *  
     * @param applicationContext 
     */  
    public void setApplicationContext(ApplicationContext applicationContext) {  
        SpringContextUtil.applicationContext = applicationContext;  
    }  
  
    /** 
     * @return ApplicationContext 
     */  
    public static ApplicationContext getApplicationContext() {  
        return applicationContext;  
    }  
  
    /** 
     * 获取对象 
     * 这里重写了bean方法,起主要作用 
     * @param name 
     * @return Object 一个以所给名字注册的bean的实例 
     * @throws BeansException 
     */  
    public static Object getBean(String name) throws BeansException {  
        return applicationContext.getBean(name);  
    }  

分析:加载Spring配置文件时,如果Spring配置文件中所定义的Bean类实现了ApplicationContextAware 接口,那么在加载Spring配置文件时,会自动调用ApplicationContextAware 接口中的
public void setApplicationContext(ApplicationContext context) throws BeansException 方法,获得ApplicationContext对象。 前提必须在Spring配置文件中指定该类

2.2 配置spring 文件 applicationContext.xml,如下

    <bean id="SpringContextUtil" class="com.carnation.utils.SpringContextUtil" lazy-init="false"></bean>

    如上配置一定要注意当spring配置default-lazy-init="true" 时,此类的配置一定要指定 lazy-init="false",否则无法通过此类获取到bean(会报null异常)

2.3 改变bean的获取方式

    将原始使用 ServletUtil.getBean("xxxxxxxx"); 获取bean的全部修改为 SpringContextUtil.getBean("xxxxxxxx"); 如下:
    
    UserService userService= (UserService)SpringContextUtil.getBean("userServiceImpl");
    DepartmentService departmentService = (DepartmentService)SpringContextUtil.getBean("departmentServiceImpl");
    UserRoleService userRoleService = (UserRoleService)SpringContextUtil.getBean("userRoleServiceImpl");
    SyncRoService syncRoService = (SyncRoService) SpringContextUtil.getBean("syncRoServiceImpl");

    到此,定时任务重复执行的问题得到解决(这里只分析了我在实际的场景下遇到的问题,但并不一定适合所有)

其它参考网址 http://zoroeye.iteye.com/blog/2186822

转载于:https://my.oschina.net/u/3479110/blog/897763

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值