SpringBoot-Lazy-Initialization-PartII
在SpringBoot-Lazy-Initialization提到,懒加载可以延迟容器中单实例的创建时机,从容器启动时延迟到实例被使用时。
在实际设置懒加载时,遇到了一些问题,在此记录并分析。
静态方法访问
如果试图调用一个类的静态方法,而此方法依赖的静态变量需要通过实例去进行设置,启用全局懒加载就会导致NPE(NullPointerException)报错。
常见的场景是,编写一个SpringContextUtil工具类,用于从容器中根据条件(名称、类型)获取bean。SpringContextUtil通过实现ApplicationContextAware接口,来设置静态成员变量,并通过公共静态方法来向外暴露。一个典型的SpringContextUtil代码如下。
1 | |
在SpringContextUtil实例化时,通过ApplicationContextAwareProcessor后置处理器调用setApplicationContext方法,完成属性注入。具体的注入时机引用源代码注释。
Invoked after population of normal bean properties but before an init callback.
问题在于,静态方法属于类,在懒加载的情况下,调用静态方法并不会引起类实例化,也因此不会对静态成员变量applicationContext进行设置,其值为引用类型的默认值null。此时调用静态方法getBean就会返回NPE。
解决的办法也很简单,就是将SpringContextUtil单独设置为非懒加载。由于SpringContextUtil是通过@Component注解,被组件扫描载入容器中,一种方法便是在@ComponentScan中设置excludeFilters属性。另一种更为简单优雅的做法是直接在SpringContextUtil类定义上添加@lazy(false)的注解。根据就近原则,会覆盖掉全局设置。
实际上@lazy有着很灵活的用法,可以添加在很多地方,在此不过多赘述。
Mybatis扫描懒加载
开启懒加载的另一种方式是通过设置@ComponentScan的lazyInit属性,将通过组件扫描载入的bean设置为懒加载。这么做是合理的。对于大型项目来说,通过组件扫描载入的bean数量远大于其他方式,而且是些影响范围较小的bean(相对于框架层)。这么设置可以较大程度加快程序启动,又尽可能避免了bug延迟暴露的问题。
但如此设置后,发现项目中的Mybatis Mapper并不会开启懒加载。研究后发现,Mapper扫描和组件扫描使用的并不是同一个扫描器。
查询Mybatis懒加载相关资料后,发现存在歧义。在MyBatis中,懒加载表示按需进行关联查询,这显然与SpringBoot中懒加载的含义不同。因此无法通过在Mybatis配置文件中,开启我们需要的bean懒加载功能。
1 | |
而Mapper扫描使用的是ClassPathMapperScanner。在Mybatis中,可以通过@MapperScan的lazyInitialization属性,设置Mapper Bean的懒加载。
但公司框架中使用的是通用mapper,maven坐标如下:
1 | |
由于公司使用的通用mapper版本较早,仍未添加懒加载功能。新增懒加载的PR。
因此无法开启懒加载,只能使用全局懒加载。
总结
在实际开发过程中,总会遇到预料之外的情况。调试、分析、学习的过程中积累的经验,远重要于解决问题本身。经过这次问题解决,对Spring、Mybatis框架的理解又更深入了一步。