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
框架的理解又更深入了一步。