【Spring编程常见错误50例】04. Spring Bean 生命周期常见错误-上
案例 1 构造器内空指针异常
在实际的开发中,会对用户操作,但是如果使用下面的方式,发现出现了异常。
@Repository
public class UserDao {
public void saveData() {
System.out.println("保存数据");
}
}
@Service
public class UserService {
@Autowired
private UserDao userDao;
public UserService() {
userDao.saveData();
}
}
问题
Caused by: java.lang.NullPointerException: Cannot invoke "com.qxlx.beanlifecycle.UserDao.saveData()" because "this.userDao" is null
异常调用栈,其实异常调用栈是一个非常好的排查问题的方式,可以定位到框架执行的关键调用链路,然后分析最终异常的点,除了这种方式,还有就是如果不知道一个地方在什么时候调用的,可以通过debug的方式,直接看用调用栈。
Exception in thread "main" org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'userService' defined in file [/Users/qxlx/work/source/spring-code/target/classes/com/qxlx/beanlifecycle/UserService.class]: Instantiation of bean failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.qxlx.beanlifecycle.UserService]: Constructor threw exception; nested exception is java.lang.NullPointerException: Cannot invoke "com.qxlx.beanlifecycle.UserDao.saveData()" because "this.userDao" is null
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateBean(AbstractAutowireCapableBeanFactory.java:1302)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1196)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:555)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:515)
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:320)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:318)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:847)
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:877)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:549)
at org.springframework.context.annotation.AnnotationConfigApplicationContext.<init>(AnnotationConfigApplicationContext.java:89)
at com.qxlx.beanlifecycle.SpringApplication.main(SpringApplication.java:12)
原因分析
上面的整体流程,注册配置类,以及加载公共的资源类,并初始化成对象。然后在初始化用户类对象。
public AnnotationConfigApplicationContext(Class<?>... componentClasses) {
this();
register(componentClasses);
refresh();
}
创建bean核心代码,可以看到 整体的流程,就是先创建对象实例,然后 填充实例需要的属性,然后执行初始化方法。
protected Object doCreateBean() {
// 其他代码
if (instanceWrapper == null) {
instanceWrapper = createBeanInstance(beanName, mbd, args);
}
populateBean(beanName, mbd, instanceWrapper);
exposedObject = initializeBean(beanName, exposedObject, mbd);
// 其他代码
}
实例化Bean,可以看到 因为没有设置构造参数,使用默认参数。但是这里构造方法执行userDao的方法,显然userDao 还没有注入。
public static <T> T instantiateClass(Constructor<T> ctor, Object... args) {
return (KotlinDetector.isKotlinReflectPresent() && KotlinDetector.isKotlinType(ctor.getDeclaringClass()) ?
KotlinDelegate.instantiateClass(ctor, args) : ctor.newInstance(args));
}
解决方案
方式1
直接在构造函数内 引入,这样当实例话bean的实例,发现需要参数UserDao,就会先依赖查找userDao。
@Autowired
private UserService (UserDao userDao) {
userDao.saveData();
}
方式2 @PostConstruct注解
在实例化之后,Spring还提供了默认的一些可拓展方法,
exposedObject = initializeBean(beanName, exposedObject, mbd);
wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
InitDestroyAnnotationBeanPostProcessor#postProcessBeforeInitialization 具体可以对应流程图和代码梳理逻辑。
@Autowired
private UserDao userDao;
@PostConstruct
public void init () {
userDao.saveData();
}
private UserService () {
}
方式3 实现 InitializingBean
通过提供高度封装的接口,用户如果实现了该接口,会回调用户的afterPropertiesSet方法。或者用户提供自定义的初始化方法,Spring通过解析获取,然后通过反射调用。
protected void invokeInitMethods(String beanName, final Object bean, @Nullable RootBeanDefinition mbd)
throws Throwable {
boolean isInitializingBean = (bean instanceof InitializingBean);
if (isInitializingBean && (mbd == null || !mbd.isExternallyManagedInitMethod("afterPropertiesSet"))) {
((InitializingBean) bean).afterPropertiesSet();
}
}
// 判断bd不为空 并且这个bean不是一个空Bean
if (mbd != null && bean.getClass() != NullBean.class) {
// 从bd中获取初始化方法
String initMethodName = mbd.getInitMethodName();
// 该方法不为空 & 不是 afterPropertiesSet方法
if (StringUtils.hasLength(initMethodName) &&
!(isInitializingBean && "afterPropertiesSet".equals(initMethodName)) &&
!mbd.isExternallyManagedInitMethod(initMethodName)) {
// 调用用户自定义的方法-反射调用
invokeCustomInitMethod(beanName, bean, mbd);
}
}
Method methodToInvoke = ClassUtils.getInterfaceMethodIfPossible(initMethod);
ReflectionUtils.makeAccessible(methodToInvoke);
methodToInvoke.invoke(bean);
public class UserService implements InitializingBean {
@Autowired
private UserDao userDao;
@Override
public void afterPropertiesSet() throws Exception {
userDao.saveData();
}
}