likes
comments
collection
share

Spring对象初始化顺序引发的问题——理解bean的初始化

作者站长头像
站长
· 阅读数 37

前言

最近在打代码的时候,遇到一个比较奇怪的问题,一个很简单的变更所引发的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初始化完成的时候,BeanPostProcessorpostProcessAfterInitialization还没有执行,所以才会导致拿到的是原始类,而不是代理类。

但是我们的代码在修改之前是正常运行的,修改之后才出现问题,而我们的变动只有一个地方,在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的。

所以实际上的执行流程应该是:

  1. ApplicationContext执行refresh()方法。
  2. 通过registerBeanPostProcessors方法开始初始化BeanPostProcessor
  3. getBean过程中,在调用postProcessBeforeInstantiation扩展点的时候,其中会寻找所有可用的advisor,并且初始化。
  4. 此时进入了鉴权切面AuthPointcutAdvisor的初始化,同时开始循环初始化它的子类及依赖,发现它依赖到了userMapper,此时对mapper进行初始化时,由于对应的SqlBeanProcessor还没有初始化完成,因此无法对它进行代理,因此代理失效。

解决

那么原因找到了,如何解决?本质上就是一个初始化顺序的问题,userMapper初始化得太早了,比BeanPostProcessor还早,导致它的代理失效,那么把它延后就行了,给它加上@Lazy注解,问题解决。

转载自:https://juejin.cn/post/7270395137824505890
评论
请登录