案例 1 构造器内空指针异常
在实际的开发中,会对用户操作,但是如果使用下面的方式,发现出现了异常。
@Repository
public class UserDao {public void saveData() {System.out.println("保存数据");}}
@Service
public class UserService {@Autowiredprivate 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 nullat 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。
@Autowiredprivate UserService (UserDao userDao) {userDao.saveData();}
方式2 @PostConstruct注解
在实例化之后,Spring还提供了默认的一些可拓展方法,
exposedObject = initializeBean(beanName, exposedObject, mbd);wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
InitDestroyAnnotationBeanPostProcessor#postProcessBeforeInitialization 具体可以对应流程图和代码梳理逻辑。
@Autowiredprivate UserDao userDao;@PostConstructpublic 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不是一个空Beanif (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 {@Autowiredprivate UserDao userDao;@Overridepublic void afterPropertiesSet() throws Exception {userDao.saveData();}
}