当前位置: 首页 > article >正文

【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();
    }
}

http://www.kler.cn/a/407933.html

相关文章:

  • 基于AXI PCIE IP的FPGA PCIE卡示意图
  • 3D Gaussian Splatting在鱼眼相机中的应用与投影变换
  • 利用Python爬虫获取商品评论:技术与实践
  • Vue3+SpringBoot3+Sa-Token+Redis+mysql8通用权限系统
  • JAVA中的Lamda表达式
  • Spring cloud 一.Consul服务注册与发现(4)
  • 软件工程导论 选填题知识点总结
  • ArcGIS Pro 3.4新功能1:唯一值符号化增加复选框,可在内容窗格和布局视图中控制类别的可见性。
  • 实现一个string的indexof方法,给出时空复杂度估计
  • HarmonyOS Next原创项目
  • 乐理的学习(调式)
  • 通过socket设置版本更新提示
  • 鸿蒙HarmonyOS学习笔记(1)
  • 工程师 - 智能家居方案介绍
  • 符号有向图(Signed Directed Graph, SDG)的前世今生
  • 基于YOLOv8深度学习的扰乱公共秩序打架异常行为检测系统研究与实现(PyQt5界面+数据集+训练代码)
  • Android开发实战班 -应用架构 之依赖注入(Hilt)
  • PHP8解析php技术10个新特性
  • 蓝桥杯嵌入式再学习(2)基础框架的构建
  • 首次公开用系统审查与评估大语言模型安全性的数据集
  • Go语言链接Redis数据库
  • 小鹏汽车大数据面试题及参考答案
  • C# 中的异步流:高效处理序列数据
  • kvm-dmesg:从宿主机窥探虚拟机内核dmesg日志
  • TCP vs UDP:如何选择适合的网络传输协议?
  • python sqlalchemy 操作数据库