Spring对象初始化顺序引发的问题——理解bean的初始化
前言
最近在打代码的时候,遇到一个比较奇怪的问题,一个很简单的变更所引发的bug,但是它产生的原因却比较有意思,这里分享一下这个问题。
问题现象
先说一下问题现象,最近对某个项目的后台鉴权部分做了一些变更,忽略掉其它的不相关代码,简化之后我们的鉴权代码大概是这样的:
鉴权AOP,通过拦截对应的方法,然后调用AuthService
的鉴权方法进行鉴权操作
@Component
public class AuthPointcutAdvisor implements PointcutAdvisor {
@Resource
private AuthService authService;
//other code
}
然后就是引发问题的代码,这个变动很简单,只是在鉴权的实现类中增加了某个mapper的引用(下面以userMapper来说明):
/**
* 鉴权类,主要负责接口的鉴权
*/
@Service
class AuthServiceImpl implement AuthService {
/**
* 这行即是增加的代码,增加了某个mapper的注入
*/
@Resource
private UserMapper userMapper;
}
就这么一个简单的操作,导致的现象则是我们页面上的用户列表直接无法显示,后台接口返回空列表。这就很神奇,只是增加了一个mapper对象的注入,怎么就会导致查不到数据了?
问题分析
排查过程
首先肯定是排查数据到底是哪里丢的,打断点发现,是在mapper中就已经未返回数据,但是实际上DB中是存在数据的。并且比较特殊的是,我们使用的mybatis之前是公司内部封装的一个框架,这个列表的查询接口是使用的一个动态SQL,长成这样:
@Repository
public interface UserMapper extends Mapper {
@Select
default Page<User> select(Params params) {
String sql = "some sql";
Sql.compile(sql);
return new Page<>(Collections.emptyList(), 0, 0);
}
}
这段代码一看就知道肯定是要通过动态代理来增强的,通过代码类来生成SQL,返回SQL查询到的数据。通过查看框架代码,发现的确是,所有的mapper都会通过实现BeanPostProcessor来实现代理,如下:
public class SqlBeanProcessor implements BeanPostProcessor {
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
//在这里实现代理,然后返回代理类
return proxyBean;
}
}
那实际上SQL有没有执行呢? 尝试打开SQL日志打印,发现这条SQL的确没有被执行,直接返回了方法中的默认返回值(return new Page<>(Collections.emptyList(), 0, 0)
)。那么问题很明显,SQL没有被执行意味着这个方法并没有Spring代理。这就奇怪了,为什么? 我们只是在一个AuthService
中依赖了UserMapper
,怎么就会影响到代理类的创建?
根因分析
既然是代码没生效,那么也就意味着,在我们的UserMapper
初始化完成的时候,BeanPostProcessor
的postProcessAfterInitialization
还没有执行,所以才会导致拿到的是原始类,而不是代理类。
但是我们的代码在修改之前是正常运行的,修改之后才出现问题,而我们的变动只有一个地方,在AuthServiceImpl
中依赖了UserMapper
。也就是说,这个依赖会让UserMapper
的初始化时间提前,并且提前到了SqlBeanProcessor
这个BeanProcessor初始化之前,要不然没办法解释为什么引入这个依赖后代理就失效了。
AuthServiceImpl
本身并没有什么问题,使用@Service
注解,很常规的方式,那我们只能看再它的上层调用,AuthPointcutAdvisor
这个就有点意思了,因为它本身也是会产生一个代理类,那是它们的创建顺序有问题?
Spring代理类创建顺序
我们如果有看过Spring源码,那么应该知道,advisor
都是在BeanFactoryAdvisorRetrievalHelper
类中被创初始化的:
简化后的代码
public List<Advisor> findAdvisorBeans() {
List<Advisor> advisors = new ArrayList<>();
for (String name : advisorNames) {
try {
//初始化所有Advisor
advisors.add(this.beanFactory.getBean(name, Advisor.class));
}
catch (BeanCreationException ex) {
throw ex;
}
}
return advisors;
}
而findAdvisorBeans
这个方法,最早会在AbstractAutoProxyCreator.postProcessBeforeInstantiation
中被触发,而postProcessBeforeInstantiation
方法的触发时机,是必然会早于postProcessAfterInitialization
的。
所以实际上的执行流程应该是:
- ApplicationContext执行refresh()方法。
- 通过
registerBeanPostProcessors
方法开始初始化BeanPostProcessor
。 getBean
过程中,在调用postProcessBeforeInstantiation
扩展点的时候,其中会寻找所有可用的advisor
,并且初始化。- 此时进入了鉴权切面
AuthPointcutAdvisor
的初始化,同时开始循环初始化它的子类及依赖,发现它依赖到了userMapper,此时对mapper进行初始化时,由于对应的SqlBeanProcessor
还没有初始化完成,因此无法对它进行代理,因此代理失效。
解决
那么原因找到了,如何解决?本质上就是一个初始化顺序的问题,userMapper
初始化得太早了,比BeanPostProcessor
还早,导致它的代理失效,那么把它延后就行了,给它加上@Lazy
注解,问题解决。
转载自:https://juejin.cn/post/7270395137824505890