Spring的Bean对象生命周期初探——依赖注入的时机
代码语言:javascript复制@Service
public class UserServiceImpl implements UserService {
@Resource
private UserCacheService userCacheService;
private Map<String,String> userInfoMap = userCacheService.getUserInfoMap();
@Override
public String queryAllUser() {
return "{all}" userInfoMap;
}
}
这段代码有什么问题呢?
能一眼看出来的同学,说明你不是受众,点击退出就可以了
像我一样马虎没看出来的,基础薄弱或者没有把概念和实践结合起来的同学继续往下看
这段代码会导致应用无法启动
究其原因是犯了一个很经典的低级错误,没有理解spring对bean的初始化和加载
当UserServiceImpl这个类被初始化的时候,会同时创建类中的对象userInfoMap
而userInfoMap是通过userCacheService这个对象获取的
那么问题来了,类还在初始化,还没有被spring注入进去,即userCacheService还没有被实际赋值
所以userCacheService对象必定为空,我使用userCacheService.getUserInfoMap()获取对象必定会报nep异常,导致应用无法启动
那么要怎么修改呢,如下所示即可
代码语言:javascript复制@Service
public class UserServiceImpl implements UserService {
@Resource
private UserCacheService userCacheService;
private Map<String,String> userInfoMap = null;
@PostConstruct
public void init(){
userInfoMap = userCacheService.getUserInfoMap();
}
@Override
public String queryAllUser() {
return "{all}" userInfoMap;
}
}
@PostConstruct该注解被用来修饰一个非静态的void()方法。
被@PostConstruct修饰的方法会在服务器加载Servlet的时候运行,并且只会被服务器执行一次。
PostConstruct在构造函数之后执行,init()方法之前执行。
通常我们会是在Spring框架中使用到@PostConstruct注解 该注解的方法在整个Bean初始化中的执行顺序:
Constructor(构造方法) -> @Autowired(依赖注入) -> @PostConstruct(注释的方法) -> InitializingBean -> init-method
用以上任意方法只要在初始化使用对象前,对userCacheService初始化即可。
问题现在是解决了,但是不求甚解肯定不是一个合格的搬砖人应有的态度,@PostConstruct注解的方法为什么就能在类对属性初始化之前被执行呢
@PostConstruct的使用
Spring容器的每个受管Bean在调用初始化方法之前,都会获得BeanPostProcessor接口实现类的一个回调。
在BeanPostProcessor的方法中有一段逻辑就是会判断当前被回调的bean的方法中有没有被initAnnotationType/destroyAnnotationType注释,如果有,则添加到init/destroy队列中,后续一一执行。initAnnotationType/destroyAnnotationType注解就是@PostConstruct/@PreDestroy。
加载顺序的探究
代码语言:javascript复制@Service
public class UserServiceImpl implements UserService {
private Map<String,String> userInfoMap = null;
static {
System.out.println("UserServiceImpl静态代码块");
}
public UserServiceImpl(){
System.out.println("UserServiceImpl构造方法");
}
@PostConstruct
public void init() {
System.out.println("UserServiceImpl PostConstruct init");
}
@Override
public String queryAllUser() {
return "{all}" userInfoMap;
}
}
结果如下:
UserServiceImpl静态代码块
UserServiceImpl构造方法
UserServiceImpl PostConstruct init
上面是单个Bean中添加了@PostConstruct的加载顺序,
现在放两个互不干涉的Bean
代码语言:javascript复制UserServiceImpl静态代码块
UserServiceImpl构造方法
UserServiceImpl PostConstruct init
UserCacheServiceImpl静态代码块
UserCacheServiceImpl构造方法
UserCacheService PostConstruct init
分别是独立加载的,那么在UserServiceImpl调用UserCacheService的结果是怎么样的呢
代码语言:javascript复制@Service
public class UserServiceImpl implements UserService {
@Resource
private UserCacheService userCacheService;
private Map<String,String> userInfoMap = null;
static {
System.out.println("UserServiceImpl静态代码块");
}
public UserServiceImpl(){
System.out.println("UserServiceImpl构造方法");
}
@PostConstruct
public void init() {
System.out.println("UserServiceImpl PostConstruct init");
userInfoMap = userCacheService.getUserInfoMap();
}
@Override
public String queryAllUser() {
return "{all}" userInfoMap;
}
}
UserServiceImpl静态代码块
UserServiceImpl构造方法
UserCacheServiceImpl静态代码块
UserCacheServiceImpl构造方法
UserCacheService PostConstruct init
UserServiceImpl PostConstruct init
可以观察到,在各自独立加载的时候是先UserServiceImpl,后UserCacheServiceImpl,但是在执行PostConstruct inti方法的时候
情况发生了逆转,顺序如下
1.UserServiceImpl实际初始化,
2.执行UserServiceImpl静态代码块
3.然后初始化UserServiceImpl属性
4.然后执行UserServiceImpl构造方法
5.初始化spring注入的对象UserCacheService
6.初始化UserCacheService
6.1UserCacheService 静态代码块
6.2UserCacheService 属性
6.3UserCacheService 构造方法
6.4UserCacheService的 PostConstruct init方法
6.然后执行UserServiceImpl的PostConstruct init方法给UserServiceImpl的userInfoMap属性实际赋值
这样就避免了在使用对象的时候,springbean没有注入而造成的NEP。
Bean的加载
顺着图片可以很清晰的理解方法的执行顺序
Constructor(构造方法) -> @Autowired(依赖注入) -> @PostConstruct(注释的方法) -> InitializingBean -> init-method
有时间debug看一下实际应用的启动过程,真正在机器上走一遍jvm如何加载字节码到类创建实体对象,然后把实体对象交给java程序管理,
然后实体对象创建的生命周期和如何托管给spring,然后运行的流程,立flag今年十一假期完成
封面图片提供 b站up主 Dara_达拉不崩巴
https://space.bilibili.com/455710953
里面有好康的