SpringBoot-Lazy-Initialization-PartII

SpringBoot-Lazy-Initialization提到,懒加载可以延迟容器中单实例的创建时机,从容器启动时延迟到实例被使用时。

在实际设置懒加载时,遇到了一些问题,在此记录并分析。

静态方法访问

如果试图调用一个类的静态方法,而此方法依赖的静态变量需要通过实例去进行设置,启用全局懒加载就会导致NPE(NullPointerException)报错。

常见的场景是,编写一个SpringContextUtil工具类,用于从容器中根据条件(名称、类型)获取beanSpringContextUtil通过实现ApplicationContextAware接口,来设置静态成员变量,并通过公共静态方法来向外暴露。一个典型的SpringContextUtil代码如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Component
public class SpringContextUtil implements ApplicationContextAware {

private static ApplicationContext applicationContext;

@Override
public void setApplicationContext(ApplicationContext context) throws BeansException {
applicationContext = context;
}

public static Object getBean(String beanName) {
return applicationContext.getBean(beanName);
}
}

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扫描懒加载

开启懒加载的另一种方式是通过设置@ComponentScanlazyInit属性,将通过组件扫描载入的bean设置为懒加载。这么做是合理的。对于大型项目来说,通过组件扫描载入的bean数量远大于其他方式,而且是些影响范围较小的bean(相对于框架层)。这么设置可以较大程度加快程序启动,又尽可能避免了bug延迟暴露的问题。

但如此设置后,发现项目中的Mybatis Mapper并不会开启懒加载。研究后发现,Mapper扫描和组件扫描使用的并不是同一个扫描器。

查询Mybatis懒加载相关资料后,发现存在歧义。在MyBatis中,懒加载表示按需进行关联查询,这显然与SpringBoot中懒加载的含义不同。因此无法通过在Mybatis配置文件中,开启我们需要的bean懒加载功能。

1
<setting name="lazyLoadingEnabled" value="true"/>

Mapper扫描使用的是ClassPathMapperScanner。在Mybatis中,可以通过@MapperScanlazyInitialization属性,设置Mapper Bean的懒加载。

但公司框架中使用的是通用mappermaven坐标如下:

1
2
3
4
<dependency>
<groupId>tk.mybatis</groupId>
<artifactId>mapper</artifactId>
</dependency>

由于公司使用的通用mapper版本较早,仍未添加懒加载功能。新增懒加载的PR

因此无法开启懒加载,只能使用全局懒加载。

总结

在实际开发过程中,总会遇到预料之外的情况。调试、分析、学习的过程中积累的经验,远重要于解决问题本身。经过这次问题解决,对SpringMybatis框架的理解又更深入了一步。


SpringBoot-Lazy-Initialization-PartII
http://dracoyus.github.io/2023/05/26/SpringBoot-Lazy-Initialization-PartII/
作者
DracoYu
发布于
2023年5月26日
许可协议