spring懒加载lazy-init踩坑记

221 阅读1分钟

问题描述

最近接手的一个较老的项目,在其上进行开发时遇到了一个spring懒加载导致的问题,下面进行简单的总结。

为了方便查看,下面是经过简化处理过新增的几个类,主要的实现是业务处理类SomeService在使用B的实例时,会根据不同的事件类型去选择具体的执行器AA的实现类去做具体的执行。

@Service
public class SomeService {
    @Autowired
    private B b;
    
    public void doSomething() {
        // 做一些业务逻辑....
        Event event = new Event();
        event.setType("xxx");
        event.setXxx(..);
        
        b.process(event);
    }
}
@Component
public class B {
    private Map<String, AA>> listenersMap = new ConcurrentHashMap<>();
    
    public void registerXxx(String name, AA a) {
        listenersMap.put(name, a);
    }
     
    public void process(Event event) {
        AA a = listenersMap.get(event.getType());
        if (null != a) {
            a.doProcess();
        }
    }
}
public interface AA {
    void doProcess(Event event);
}


@Service
public class A implements AA{
    
    @Autowired
    private B b;
   
    @PostConstruct
    public void init() {
        b.registerXxx(A.class.getName(), this);
    }
    
    @Override
    public void doProcess(Event event) {
        
    }  
}

如果是在启动项目时去加载并实例化所有的bean(不延迟加载),那么上面的实现是没有问题。但是,这个项目使用的是spring xml的配置方式,且有如下配置:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:util="http://www.springframework.org/schema/util" 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/tx http://www.springframework.org/schema/tx/spring-tx-4.1.xsd
		http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.1.xsd"
       default-lazy-init="true">
    
    省略具体内容
    
</beans>

最关键的是有default-lazy-init="true",由于使用的是懒加载的方式,所以只有在第一次使用bean时才会实例化,所以,在SomeService在使用B时,会实例化B,而此时由于B没有直接去依赖AA,AA不会进行实例化,那么就不会将自己注册到B的listenersMap中,导致实际上没有调用到AA的doProcess。

这里的实现其实就是将B和AA接口的实现类通过反向注册实现解耦,在懒加载的机制下就会出现问题。

总结

懒加载的好处是服务在启动是会节省初始化bean的时间,启动速度更快,同时可以节省内存,在后续第一次用到时才会进行实例化。

但是,使用懒加载可能会掩盖住问题,比如上面案例中,还有可能有某些bean在实例化时会出错的情况,不符合fast-fail原则。所以,慎用懒加载。